Preventing multiple repeat selection of synchronized Controls? - c#

The working code sample here synchronizes (single) selection in a TreeView, ListView, and ComboBox via the use of lambda expressions in a dictionary where the Key in the dictionary is a Control, and the Value of each Key is an Action<int>.
Where I am stuck is that I am getting multiple repetitions of execution of the code that sets the selection in the various controls in a way that's unexpected : it's not recursing : there's no StackOverFlow error happening; but, I would like to figure out why the current strategy for preventing multiple selection of the same controls is not working.
Perhaps the real problem here is distinguishing between a selection update triggered by the end-user and a selection update triggered by the code that synchronizes the other controls ?
Note: I've been experimenting with using Delegates, and forms of Delegates like Action<T>, to insert executable code in Dictionaries : I "learn best" by posing programming "challenges" to myself, and implementing them, as well as studying, at the same time, the "golden words" of luminaries like Skeet, McDonald, Liberty, Troelsen, Sells, Richter.
Note: Appended to this question/code, for "deep background," is a statement of how I used to do things in pre C#3.0 days where it seemed like I did need to use explicit measures to prevent recursion when synchronizing selection.
Code : Assume a WinForms standard TreeView, ListView, ComboBox, all with the same identical set of entries (i.e., the TreeView has only root nodes; the ListView, in Details View, has one Column).
private Dictionary<Control, Action<int>> ControlToAction = new Dictionary<Control, Action<int>>();
private void Form1_Load(object sender, EventArgs e)
{
// add the Controls to be synchronized to the Dictionary
// with appropriate Action<int> lambda expressions
ControlToAction.Add(treeView1, (i => { treeView1.SelectedNode = treeView1.Nodes[i]; }));
ControlToAction.Add(listView1, (i => { listView1.Items[i].Selected = true; }));
ControlToAction.Add(comboBox1, (i => { comboBox1.SelectedIndex = i; }));
// optionally install event handlers at run-time like so :
// treeView1.AfterSelect += (object obj, TreeViewEventArgs evt)
// => { synchronizeSelection(evt.Node.Index, treeView1); };
// listView1.SelectedIndexChanged += (object obj, EventArgs evt)
// => { if (listView1.SelectedIndices.Count > 0)
// { synchronizeSelection(listView1.SelectedIndices[0], listView1);} };
// comboBox1.SelectedValueChanged += (object obj, EventArgs evt)
// => { synchronizeSelection(comboBox1.SelectedIndex, comboBox1); };
}
private void synchronizeSelection(int i, Control currentControl)
{
foreach(Control theControl in ControlToAction.Keys)
{
// skip the 'current control'
if (theControl == currentControl) continue;
// for debugging only
Console.WriteLine(theControl.Name + " synchronized");
// execute the Action<int> associated with the Control
ControlToAction[theControl](i);
}
}
private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
synchronizeSelection(e.Node.Index, treeView1);
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
// weed out ListView SelectedIndexChanged firing
// with SelectedIndices having a Count of #0
if (listView1.SelectedIndices.Count > 0)
{
synchronizeSelection(listView1.SelectedIndices[0], listView1);
}
}
private void comboBox1_SelectedValueChanged(object sender, EventArgs e)
{
if (comboBox1.SelectedIndex > -1)
{
synchronizeSelection(comboBox1.SelectedIndex, comboBox1);
}
}
background : pre C# 3.0
Seems like, back in pre C# 3.0 days, I was always using a boolean flag to prevent recursion when multiple controls were updated. For example, I'd typically have code like this for synchronizing a TreeView and ListView : assuming each Item in the ListView was synchronized with a root-level node of the TreeView via a common index :
// assume ListView is in 'Details View,' has a single column,
// MultiSelect = false
// FullRowSelect = true
// HideSelection = false;
// assume TreeView
// HideSelection = false
// FullRowSelect = true
// form scoped variable
private bool dontRecurse = false;
private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
if(dontRecurse) return;
dontRecurse = true;
listView1.Items[e.Node.Index].Selected = true;
dontRecurse = false;
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
if(dontRecurse) return
// weed out ListView SelectedIndexChanged firing
// with SelectedIndices having a Count of #0
if (listView1.SelectedIndices.Count > 0)
{
dontRecurse = true;
treeView1.SelectedNode = treeView1.Nodes[listView1.SelectedIndices[0]];
dontRecurse = false;
}
}
Then it seems, somewhere around FrameWork 3~3.5, I could get rid of the code to suppress recursion, and there was was no recursion (at least not when synchronizing a TreeView and a ListView). By that time it had become a "habit" to use a boolean flag to prevent recursion, and that may have had to do with using a certain third party control.

I believe your approach is totally fine. If you want something a little more advanced, see Rein in runaway events with the "Latch", which allows for
void TabControl_TabSelected(object sender, TabEventArgs args)
{
_latch.RunLatchedOperation(
delegate
{
ContentTab tab = (ContentTab)TabControl.SelectedTab;
activatePresenter(tab.Presenter, tab);
});
}

Note: I always assumed an SO user should never answer their own question. But, after reading-up on SO-Meta on this issue, I find it's actually encouraged. Personally, I would never vote on my own answer as "accepted."
This "new solution" uses a strategy based on distinguishing between a control being updated as a result of end-user action, and a control being updated by synchronizing code: this issue was mentioned, as a kind of "rhetorical question," in the original question.
I consider this an improvement: it works; it prevents multiple update calls; but, I also "suspect" it's still "not optimal": appended to this code example is a list of "suspicions."
// VS Studio 2010 RC 1, tested under Framework 4.0, 3.5
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace SynchronizationTest_3
{
public partial class Form1 : Form
{
private readonly Dictionary<Control, Action<int>> ControlToAction = new Dictionary<Control, Action<int>>();
// new code : keep a reference to the control the end-user clicked
private Control ClickedControl;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
ControlToAction.Add(treeView1, (i => { treeView1.SelectedNode = treeView1.Nodes[i]; }));
ControlToAction.Add(listView1, (i => { listView1.Items[i].Selected = true; }));
ControlToAction.Add(comboBox1, (i => { comboBox1.SelectedIndex = i; }));
// new code : screen out redundant calls generated by other controls
// being updated
treeView1.AfterSelect += (obj, evt)
=>
{
if (treeView1 == ClickedControl) SynchronizeSelection(evt.Node.Index);
};
listView1.SelectedIndexChanged += (obj, evt)
=>
{
if (listView1.SelectedIndices.Count > 0 && listView1 == ClickedControl)
{
SynchronizeSelection(listView1.SelectedIndices[0]);
}
};
comboBox1.SelectedValueChanged += (obj, evt)
=>
{
if (comboBox1 == ClickedControl) SynchronizeSelection(comboBox1.SelectedIndex);
};
// new code here : all three controls share a common MouseDownHandler
treeView1.MouseDown += SynchronizationMouseDown;
listView1.MouseDown += SynchronizationMouseDown;
comboBox1.MouseDown += SynchronizationMouseDown;
// trigger the first synchronization
ClickedControl = treeView1;
SynchronizeSelection(0);
}
// get a reference to the control the end-user moused down on
private void SynchronizationMouseDown(object sender, MouseEventArgs e)
{
ClickedControl = sender as Control;
}
// revised code using state of ClickedControl as a filter
private void SynchronizeSelection(int i)
{
// we're done if the reference to the clicked control is null
if (ClickedControl == null) return;
foreach (Control theControl in ControlToAction.Keys)
{
if (theControl == ClickedControl) continue;
// for debugging only
Console.WriteLine(theControl.Name + " synchronized");
ControlToAction[theControl](i);
}
// set the clicked control to null
ClickedControl = null;
}
}
}
Why I "suspect" this is not optimal:
the idiosyncratic behavior of WinForms controls has to be taken into account: for example, the ListView Control fires its Selected### Events before it fires a Click Event: ComboBox and TreeView fire their Click Events before their SelectedValueChanged and AfterSelect Events respectively: so had to experiment to find that using 'MouseDown would work the same across all three controls.
a "gut level" feeling that I've gone "too far" out on "some kind of limb" here: a sense a much simpler solution might be possible.

Related

C# communication between forms bug

I'm working on a GUI for an admin interface for management of a student complex. Currently the GUI has a listbox with predefined 6 rules for the students. In the beginning of the code, I add them to a list
private void Form1_Load(object sender, EventArgs e)
{
foreach (string rule in lbRules.Items)
ruleList.Add(rule);
}
Then, the GUI provides the admin with an option to modify the rules. To do so he selects a rule from the listbox and clicks a "Modify" button, which opens another form:
private void BtnModify_Click(object sender, EventArgs e)
{
if (lbRules.SelectedItems.Count > 0)
{
selectedRule = lbRules.SelectedItem.ToString();
selectedIndex = lbRules.SelectedIndex;
selectedRuleNumber = selectedRule.Substring(0, 3);
selectedRule = selectedRule.Substring(6);
var rulesForm = new Rules();
rulesForm.Show();
}
}
On the second form load event I get the rule's text and number:
private void Rules_Load(object sender, EventArgs e)
{
tbRule.Text = Form1.selectedRuleNumber;
tbModifyRule.Text = Form1.selectedRule;
}
The text gets added to a RichTextBox, from where the rule can be edited.
Then the admin clicks a "Save" button, which gets the edited text from the RichTextBox(tbModifyRule) and adds it to a static ruleList in form1, sets a static boolean from form1 to true. Afterwards the second form gets closed:
private void BtnSave_Click(object sender, EventArgs e)
{
saveRule = Form1.selectedRuleNumber + " - " + tbModifyRule.Text;
Form1.ruleList.Insert(Form1.selectedIndex, saveRule);
Form1.ruleList.RemoveAt(Form1.selectedIndex+1);
Form1.formOpen = true;
this.Dispose();
}
At this point we are back to form1, in which we have a timer with timer_tick event. In there we check whether the boolean formOpen is true (which it is set before closing form2). Inside the if statement we clear the listbox and add each rule from the ruleList (previously edited in form2) to the listbox, then sets the formOpen back to false so it doesn't get executed all the time:
if (formOpen)
{
lbRules.Items.Clear();
foreach (string item in ruleList)
lbRules.Items.Add(item);
}
formOpen = false;
Now this is really weird, and at this point makes absolutely no sense to me, since I tried debugging it for over an hour, trying different ways, which also led me to mysterious wonders of WHY TF IT WORKS WHENEVER IT WANTS...
So this works randomly, like it would work the first time, the second and third times it won't. Or vice versa. It's all random.
Strangely, I tried adding a breakpoint on the
lbRules.Items.Add(item);
in the foreach loop, so it stops on each item. And I actually saw the changed rule getting added from the ruleList into the listBox, however in the end it was not there.
And weirdly enough, I also tried adding the text from form2 in the listBox in form1, without using a list, but for whatever odd reason, I use the int selectedIndex, which gets the index of the selected item from the BtnModify_Click event to insert the text in that particular index, but this very index gets RANDOMLY set to bloody 0 after form2 closes.
hence, it again works from time to time, because at some tries it doesn't get set to 0 and it works.
if (formOpen)
{
selectedRule = Rules.saveRule;
lbRules.Items.Insert(selectedIndex, selectedRule);
lbRules.Items.RemoveAt(selectedIndex+1);
}
formOpen = false;
I don't assign value to this integer ANYWHERE else in the code.
I really tried digging some sense, but I hit a solid hard rock.
Any help appreciated!
And thanks for the time!
edit1:
as requested - rest of the timer method
private void Timer1_Tick(object sender, EventArgs e)
{
foreach (string text in ws.messages)
message = text;
if (ws.messages.Count > 0)
{
if (message.Contains("comp"))
{
Complaints();
message = String.Empty;
ws.messages.Clear();
}
}
if (formOpen)
{
lbRules.Items.Clear();
foreach (string item in ruleList)
lbRules.Items.Add(item);
}
formOpen = false;
}
I would change your code to the following:
if (formOpen)
{
formOpen = false;
lbRules.Items.Clear();
foreach (string item in ruleList)
lbRules.Items.Add(item);
}
The issue with having the formOpen = false; outside the if statement is that there is a chance that once the user clicks the Save button the timer could be about to execute the formOpen = false instruction setting it to false making the code inside the If statement to never be executed.
I truly believe this is not random but just a timing issue due to complicated logic.
If I were you, I'd do a couple things:
Use a separate class for data exchange between forms, avoid using public static (I assume) form members for this.
Instead of a timer, subscribe to the Form.Closed event of RulesForm
This might make code flow a bit more predictable and allow you to find errors more easily.
Better yet, use the following pattern:
class Form1
{
private void BtnModify_Click(object sender, EventArgs e)
{
var ruleData = ..... //get current rule data
var rulesForm = new Rules();
rulesForm.SetData(ruleData); //pass initial state to the form
rulesForm.SaveChanges = this.ApplyRules; //pass a method which will be called on save
rulesForm.Show();
}
private bool ApplyRules(RuleData ruleData)
{
//do whatever you like with the rules here
return true;
}
}
class RuleForm
{
public void SetData(RuleData ruleData)
{
//initialize fields, etc
}
public Func<RuleData, bool> SaveChanges { get; set; }
private void BtnSave_Click(object sender, EventArgs e)
{
var ruleData = .... //get data from form fields
if(this.SaveChanges(ruleData))
this.Close();
}
}
class RuleData
{
//whatever data you need
}

Programmatically created CheckBox does not fire Checked/Unchecked events

I have a simple WPF application that uses a "frame" for multi-page navigation. One of that pages creates a series of CheckBoxes and adds a couple of handlers (Checked/Unchecked) for each checkbox created. The CheckBoxes work as intended and are programmatically accessible, they can be checked or unchecked by click but none of the two events is ever fired if I click.
Here is the creation of the CheckBoxes:
ModuleStackpanels[i].Children.Add(ModuleCheckBoxes[i]);
StackPanel.SetZIndex(ModuleCheckBoxes[i], 2);
ModuleCheckBoxes[i].Checked += new RoutedEventHandler(ModuleCheckBoxClick);
ModuleCheckBoxes[i].Unchecked += new RoutedEventHandler(ModuleCheckBoxClick);
Where I go from 0 to 30. Then I have the handler:
private void ModuleCheckBoxClick(object sender, RoutedEventArgs e)
{
int CheckBoxCounter = 0;
for(int i=0;i<30;i++)
{
if (ModuleCheckBoxes[i].IsChecked == true) CheckBoxCounter++;
}
if(CheckBoxCounter > 1)
{
Button_QueryStatus.IsEnabled = false;
}
}
But nothing is fired.
Someone has got an idea?
Try to use CheckedChanged instead of Checked and Unchecked.
For example:
public bool checkedthecheckbox { get; set; }
CheckBox testchbox = new CheckBox();
private void Form1_Load(object sender, EventArgs e)
{
testchbox.CheckedChanged += new EventHandler(testchbox_CheckedChanged);
}
void testchbox_CheckedChanged(object sender, EventArgs e)
{
if (testchbox.Checked)
checkedthecheckbox = true;
else
checkedthecheckbox = false;
}
In your case:
ModuleStackpanels[i].Children.Add(ModuleCheckBoxes[i]);
StackPanel.SetZIndex(ModuleCheckBoxes[i], 2);
ModuleCheckBoxes[i].CheckedChanged += new EventHandler(ModuleCheckBoxClick);
private void ModuleCheckBoxClick(object sender, RoutedEventArgs e)
{
int CheckBoxCounter = 0;
for(int i=0;i<30;i++)
{
if (ModuleCheckBoxes[i].IsChecked == true) CheckBoxCounter++;
}
if(CheckBoxCounter > 1)
{
Button_QueryStatus.IsEnabled = false;
}
}
Good news! Not all the checkboxes had the event handler because of a fault in the creation of the buttons. 3 on 30 had it, and these buttons represented a different kind of item.
I just dealt with a similar issue on a WPF app that I did not write the front end for and here it came down to: Checked and similar event handlers will fire if the checkbox is triggered manually or programmatically. Other types are not guaranteed in the same way if you are changing the checkbox by setting to IsChecked.
ex. in my case, they attempted to use a Clicked event handler which would only fire from actual user interaction and not programmatic change, which makes perfect sense because setting the IsChecked status is truly not a click event (even though both can potentially check or uncheck the checkbox)

SelectedIndex is unchanging in tabcontrol - Dispatcher issue

Why, after I set SelectedIndex=0, do I subsequently (not in response) get the event handler invoked with SelectedIndex=4? I traced it down to an invocation of a Dispatcher, but I do not understand why.
Here's the plan: I have a page containting a tab control with assorted tabs. When I click on certain kinds of tabs, I want to switch out of this page to another page. The trouble is, when I return to this page, I'm getting looped back out to the other page again, and I can't figure out why.
Specifically, I set SelectedIndex=0, and then [External Code] changes SelectedIndex to 4, causing the page to be switched out again.
// In JobTreePage.xaml
<TabControl x:Name="mainTab"
ItemsSource="{Binding}"
SelectionChanged="mainTab_SelectionChanged">
// in JobTreePage
private void mainTab_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
e.Handled = true;
if (!ready) return;
var ee = this.TryFindResource("Title");
TabItem x = mainTab.SelectedItem as TabItem;
if (x == null) return;
if (IsJobTab(x)) // index 4 is a job tab
{
if (ready ) // assuming the job selected is 1273.
{
ResetTab();
//mainTab.SelectedItem = AddNew;
mw.moldDataButton_Click(sender, e);
}
ready = true;
}
}
public void ResetTab()
{
ready = false;
Application.Current.Dispatcher.BeginInvoke
((Action)delegate { mainTab.SelectedIndex = 0; }, DispatcherPriority.Send, null);
//mainTab.SelectedIndex = 0;
ready = true;
}
And in the MainWindow I have two buttons:
public void moldDataButton_Click(object sender, RoutedEventArgs e)
{
CurrentPage = this.moldDataPage;
moldDataPage.ActivateTab("Project");
// the problem line. problem arises after I click on the Customers button.
Dispatcher.Invoke(new System.Action(() => { }), DispatcherPriority.ContextIdle, null);
Slow_COM_Operation();
}
private void CustomersButton_Click(object sender, RoutedEventArgs e)
{
this.jobTreePage.ResetTab();
CurrentPage = this.jobTreePage;
}
So tracing the program from CustomersButton_Click(), in ResetTab, SelectedIndex == 0, but after that finishes we jump to mainTab_SelectionChanged and see that SelectedIndex == 4. I have no idea what is forcing SelectedIndex to 4.
Aside: Why do I have this dispatcher line? Because I'm attempting to send an asynchronous request via COM interop to get some other stuff happening without the dialog freezing. I don't know how to achieve the goal, but putting in the line at least gives me a page refresh.

Allow DragDrop anywhere in a form

Is there a way to allow Drag and Drop anywhere in a form full of controls?
The idea is to allow user to drag a file anywhere in a form in order to "load" it. I will not need any other DragDrop behavior but this.
By setting AllowDrop=True to the form only, I get DragEnter events but not DragDrop ones.
An idea would be to make a topmost panel visible on DragEnter and handle DragDrop events there, but I wonder if I miss something obvious here since I have little experience in the field.
Another Idea would be to iterate through all controls and subscribe to Drag-related events. I really don't like this approach, though.
Sure, iterating the controls will work, it doesn't take much code:
public Form1() {
InitializeComponent();
WireDragDrop(this.Controls);
}
private void WireDragDrop(Control.ControlCollection ctls) {
foreach (Control ctl in ctls) {
ctl.AllowDrop = true;
ctl.DragEnter += ctl_DragEnter;
ctl.DragDrop += ctl_DragDrop;
WireDragDrop(ctl.Controls);
}
}
void ctl_DragDrop(object sender, DragEventArgs e) {
// etc..
}
void ctl_DragEnter(object sender, DragEventArgs e) {
// etc..
}
If you still don't like the approach then use a recognizable single drop target that the user will always hit. Could be as simple as a label that says "Drop here".
I'm not sure what kinds of control you have on your form. But I've tested with a Button, a GroupBox, a PictureBox and a TextBox. All these controls have AllowDrop = false by default. And I can drag-n-drop something from outside onto the form OK. The DragDrop is fired OK. Everything is OK. What is actually your problem? I guess your controls have AllowDrop = true.
In the case the DragDrop event is not fired (which I think only happens if the target is one of your Control with AllowDrop = true). I think the following may work. But if the target is one of your Control with AllowDrop = true, the effect icon is gone away.
public Form1(){
InitializeComponents();
t.Interval = 1;
t.Tick += Tick;
}
IDataObject data;
Timer t = new Timer();
int i = 0;
private void Tick(object sender, EventArgs e)
{
Text = (i++).ToString();
if (ClientRectangle.Contains(PointToClient(new Point(MousePosition.X, MousePosition.Y))) && MouseButtons == MouseButtons.None)
{
t.Stop();
if (data != null)
{
//Process data here
//-----------------
data = null;
}
}
else if (MouseButtons == MouseButtons.None)
{
data = null;
t.Stop();
}
}
private void Form1_DragEnter(object sender, DragEventArgs e)
{
e.Effect = e.AllowedEffect;
if (data == null)
{
data = e.Data;
t.Start();
}
}
And I think you may have to use the loop through all the Controls to add appropriate event handlers. There is no other better way.
In the Drop event.
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
foreach (string file in files) Console.WriteLine(file);
In the DragEnter event.
if (e.Data.GetDataPresent(DataFormats.FileDrop)) e.Effects = DragDropEffects.Copy;

Winforms controls and "generic" events handlers. How can I do this?

In the demo of the ObjectListView control there is this code (in the "Complex Example" tab page) to allow for a custom editor (a ComboBox) (Adapted to my case and edited for clarity):
EventHandler CurrentEH;
private void ObjectListView_CellEditStarting(object sender,
CellEditEventArgs e)
{
if (e.Column == SomeCol)
{
ISomeInterface M = (e.RowObject as ObjectListView1Row).SomeObject; //(1)
ComboBox cb = new ComboBox();
cb.Bounds = e.CellBounds;
cb.DropDownStyle = ComboBoxStyle.DropDownList;
cb.DataSource = ISomeOtherObjectCollection;
cb.DisplayMember = "propertyName";
cb.DataBindings.Add("SelectedItem",
M, "ISomeOtherObject", false,
DataSourceUpdateMode.Never);
e.Control = cb;
cb.SelectedIndexChanged +=
CurrentEH = (object sender2, EventArgs e2) =>
M.ISomeOtherObject =
(ISomeOtherObject)((ComboBox)sender2).SelectedValue; //(2)
}
}
private void ObjectListView_CellEditFinishing(object sender,
CellEditEventArgs e)
{
if (e.Column == SomeCol)
{
// Stop listening for change events
((ComboBox)e.Control).SelectedIndexChanged -= CurrentEH;
// Any updating will have been down in the SelectedIndexChanged
// event handler.
// Here we simply make the list redraw the involved ListViewItem
((ObjectListView)sender).RefreshItem(e.ListViewItem);
// We have updated the model object, so we cancel the auto update
e.Cancel = true;
}
}
I have too many other columns with combo editors inside objectlistviews to use a copy& paste strategy (besides, copy&paste is a serious source of bugs), so I tried to parameterize the code to keep the code duplication to a minimum. ObjectListView_CellEditFinishing is a piece of cake:
HashSet<OLVColumn> cbColumns = new HashSet<OLVColumn> (new OLVColumn[] { SomeCol, SomeCol2, ...};
private void ObjectListView_CellEditFinishing(object sender,
CellEditEventArgs e)
{
if (cbColumns.Contains(e.Column)) ...
but ObjectListView_CellEditStarting is the problematic.
I guess in CellEditStarting I will have to discriminate each case separately:
private void ObjectListView_CellEditStarting(object sender,
CellEditEventArgs e)
{
if (e.Column == SomeCol)
// code to create the combo, put the correct list as the datasource, etc.
else if (e.Column == SomeOtherCol)
// code to create the combo, put the correct list as the datasource, etc.
And so on.
But how can I parameterize the "code to create the combo, put the correct list as the datasource, etc."? Problem lines are
(1) Get SomeObject. the property NAME varies.
(2) Set ISomeOtherObject, the property name varies too.
The types vary too, but I can cover those cases with a generic method combined with a not so "typesafe" API (for instance, the cb.DataBindings.Add and cb.DataSource both use an object)
Reflection? more lambdas? Any ideas? Any other way to do the same?
PS: I want to be able to do something like this:
private void ObjectListView_CellEditStarting(object sender,
CellEditEventArgs e)
{
if (e.Column == SomeCol)
SetUpCombo<ISomeInterface>(ISomeOtherObjectCollection,
"propertyName",
SomeObject,
ISomeOtherObject);
else if (e.Column == SomeOtherCol)
SetUpCombo<ISomeInterface2>(ISomeOtherObject2Collection,
"propertyName2",
SomeObject2
ISomeOtherObject2);
and so on. Or something like that.
I know, parameters SomeObject and ISomeOtherObject are not real parameters per see, but you get the idea of what I want. I want not to repeat the same code skeleton again and again and again.
One solution would be "preprocessor generics" like C's DEFINE, but I don't thing c# has something like that.
So, does anyone have some alternate ideas to solve this?
Found it. Kudos to Tejs!
private void SetUpCombo(CellEditEventArgs e,
object ComboItems, string DisplayMember,
object DataSource, string PropertyName,
EventHandler evt)
{
ComboBox cb = new ComboBox();
cb.Bounds = e.CellBounds;
cb.DropDownStyle = ComboBoxStyle.DropDownList;
cb.DataSource = ComboItems;
cb.DisplayMember = DisplayMember;
cb.DataBindings.Add("SelectedItem", DataSource, PropertyName,
false, DataSourceUpdateMode.Never);
e.Control = cb;
cb.SelectedIndexChanged += CurrentEH = evt;
}
and the rewritten CellEditStarting:
private void ObjectListView_CellEditStarting(object sender,
CellEditEventArgs e)
{
if (e.Column == SomeCol)
{
ISomeInterface M = (e.RowObject as ObjectListView1Row).SomeObject;
SetUpCombo(e,
ISomeOtherObjectCollection,"propertyName",
M, "ISomeOtherObject",
(sender2, e2) =>
M.ISomeOtherObject =
(ISomeOtherObject)((ComboBox)sender2).SelectedValue);
}
else if (e.Column == SomeOtherCol)
{
ISomeInterface2 M = (e.RowObject as ObjectListView1Row).SomeObject2;
SetUpCombo(e,
ISomeOtherObjectCollection2,"propertyName2",
M, "ISomeOtherObject2",
(sender2, e2) =>
M.ISomeOtherObject2 =
(ISomeOtherObject)((ComboBox)sender2).SelectedValue);
}
and so on... There are some things I don't like yet. For instance: the disconnection between M.ISomeOtherObject (outside the method call), "ISomeOtherObject" (the param) and the setting of M.ISomeOtherObject (inside the lambda).
But, all things considered, it is much much better than copying & pasting the same code over and over again).

Categories