Delegates, Event Handler and Controls.OfType - c#

i have 2 questions about this code.
1st:
What is the difference between 1st way and 2nd way in the code? i mean there syntax not what they do. which one is prefered?
2nd:
in Controls.OfType how can we get multiple result for example for TextBox and Button,here i have written only for TextBox.(Windows form)
private void Form2_Load(object sender, EventArgs e)
{
foreach (Control item in Controls.OfType<TextBox>())
{
item.MouseEnter += item_MouseEnter; // 1st way
item.MouseLeave += new System.EventHandler(item_MouseLeave); //2nd way
}
}
void item_MouseLeave(object sender, EventArgs e)
{
var senderButton = (Control)sender;
senderButton.Height -= 5;
senderButton.Width -= 5;
}
void item_MouseEnter(object sender, EventArgs e)
{
var senderButton = (Control)sender;
senderButton.Height += 5;
senderButton.Width += 5;
}

1st: There's no difference. The first line will implicitly use EventHandler. This is prefered.
2nd: OfType takes only 1 type argument, so it will return only objects of that Type. But you can do the filtering yourself. Controls.Where(ctrl => ctrl is TextBox || ctrl is Button)
(This is what OfType does internally anyway.)
Edit: It appears that ControlCollection implements the non-generic IEnumerable, so it does not have an extension method called Where.
So you have to use:
Controls.OfType<Control>().Where(ctrl => ctrl is TextBox || ctrl is Button)
(OfType will convert IEnumerable to IEnumerable<Control>)
Or you could use a simple if statement inside the loop.

Related

C# assign an event to a "group" of controls?

I have a big numbers of labels custom controls let's say 100.
I would like to give them an MouseHover event.
I could do something like:
private void label_custom1_MouseHover(object sender, EventArgs e)
{
TextBox.Text = label_custom1.backcolor.ToString();
}
But then I would need to do that 100 times. Since I have 100 of them.
Is there a way to do that only once?
I guess I should probably declare the function in my custom_label class but so far I couldn't make it work.
Any Idea how to proceed?
Create only one event handler method like this:
private void Label_MouseHover(object sender, EventArgs e)
{
TextBox.Text = (sender as Label).BackColor.ToString();
}
And subscribe all Events to that method:
this.label_custom1.MouseHover += Label_MouseHover;
this.label_custom2.MouseHover += Label_MouseHover;
Thank you, this was very helpful.
This part didnt work though, it couldnt recognize the controls:
private void SetEventAllLabels()
{
var labels = Controls.OfType<Label>().Where(x => x.Name.StartsWith("label"));
foreach (var label in labels)
{
label.MouseHover += Common_MouseHover;
}
}
So I create a list containing all the label names and Then it works using this:
foreach (string i in liste)
{
CustomLabel x = (CustomLabel)(this.Controls.Find(i, true).FirstOrDefault() as Label);
x.MouseEnter += Common_MouseHover;
}
As a side note, using MouseEnter instead of MouseHover makes it much more responsive (faster reaction!)
Firstly, create a common event, we receive the Label posted here.
private void Common_MouseHover(object sender, EventArgs e)
{
TextBox.Text = (sender as Label).BackColor.ToString();
}
Give the common event to all Labels on the form. Here I assume Label names start with label, for example label1, label2, label3 ...
private void SetEventAllLabels()
{
var labels = Controls.OfType<Label>().Where(x => x.Name.StartsWith("label"));
foreach (var label in labels)
{
label.MouseHover += Common_MouseHover;
}
}
Call the SetEventAllLabels () method in the Load method of the form.
private void Form1_Load(object sender, EventArgs e)
{
SetEventAllLabels();
}

Cannot implicitly convert type 'void' to System.Windows.Forms.MouseEventHandler

In my program it is possible to draw a LinkLabel with the text which is defined before. When the LinkLabel is visible, I want to open a new form when I double click the LinkLabel
this is the code I have so far:
private Graphics g;
private int punt1 = 0;
private int punt2 = 0;
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
int x = e.X;
int y = e.Y;
g = this.CreateGraphics();
if (e.X > 20 && e.Y > 100)
{
...
if (rbTekst.Checked && MousePosition.X > 1000)
{
string naam = tbUseCaseName.Text;
LinkLabel lb = new LinkLabel();
UseCase uc = new UseCase();
lb.MouseDoubleClick += uc.Show();
lb.Location = e.Location;
lb.Text = naam;
this.Controls.Add(lb);
}
Everything works great except the DoubleClick event. I get the following error message:
Cannot implicitly convert type 'void' to
'System.Windows.Forms.MouseEventHandler'
Does this error occurs because Form1_MouseUp has MouseEventsArgs e?
Thanks in advance!
You are trying to assign the result of calling uc.Show(); as the event handler for the MouseDoubleClick event. That method returns void which results in the error message you see.
If the signature** of UseCase.Show matches the expected signature of the event handler you can just assign the method itself:
lb.MouseDoubleClick += uc.Show;
Given your comments though it would appear that the signature doesn't match. In that case you could create an anonymous event handler that simply calls the method you want to call:
lb.MouseDoubleClick += (sender, e) => uc.Show();
Personally I prefer to enclose the method body in curly braces but it's not required:
lb.MouseDoubleClick += (sender, e) => { uc.Show(); };
You could also create a method to handle the click and call show from within there:
lb.MouseDoubleClick += lb_MouseDoubleClick;
...
void lb_MouseDoubleClick(object sender, MouseEventArgs e)
{
uc.Show();
}
** The signature of a method is what the method looks like in terms of access modifiers, the return value and any parameters it accepts. I guess that uc is a Form in which case the Show method returns void and accepts no parameters. The MouseDoubleClick event expects a method that takes an object and a MouseEventArgs.

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).

How can I create only one event handler method for multiple controls?

I have 15 comboBox'es, and I do not want to create an event handler for each. How do I make just one procedure and tie all Combobox'es to it?
private void cbSlots0_SelectedIndexChanged(object sender, EventArgs e)
{
var item = ConfigClass.Slots["0"][cbSlots0.SelectedIndex];
ConfigClass.Slots["0"].Insert(0, item);
ConfigClass.Slots["0"].RemoveAt(cbSlots0.SelectedIndex + 1);
}
private void cbSlots1_SelectedIndexChanged(object sender, EventArgs e)
{
var item = ConfigClass.Slots["1"][cbSlots1.SelectedIndex];
ConfigClass.Slots["1"].Insert(1, item);
ConfigClass.Slots["1"].RemoveAt(cbSlots1.SelectedIndex + 1);
}
Correct answer:
var cb = ((ComboBox)sender);
var tag = int.Parse(cb.Tag.ToString());
var item = ConfigClass.Slots[tag.ToString()][cb.SelectedIndex];
ConfigClass.Slots[tag.ToString()].Insert(tag, item);
ConfigClass.Slots[tag.ToString()].RemoveAt(cb.SelectedIndex + 1);
You can give each ComboBox a distinct Tag, which contains the number of the entry in the ConfigClass, and then use that like so:
private void cbSlots0_SelectedIndexChanged(object sender, EventArgs e)
{
int tag = (int)((ComboBox)sender).Tag;
var item = ConfigClass.Slots[tag.ToString()][cbSlots0.SelectedIndex];
ConfigClass.Slots[tag.ToString()].Insert(tag, item);
ConfigClass.Slots[tag.ToString()].RemoveAt(cbSlots0.SelectedIndex + 1);
}
The tag can contain any data you want, so if you need something more complex stored in there, that's also a possibility.
I would recommend one event handler for all ComboBoxes. Afterwards, within your event handler, use the sender reference to decide which slot to use:
private void allComboBoxesSelectedIndesChanged(object sender, EventArgs e)
{
int index = 0; // Or string as you have shown in your example.
if (sender == cbSlots0)
index = 0;
else if (sender == cbSlots1)
index = 1;
/// And so on for any other comboBox
var item = ConfigClass.Slots[index][((ComboBox) sender).SelectedIndex];
ConfigClass.Slots[index].Insert(index, item);
ConfigClass.Slots[index].RemoveAt(((ComboBox) sender).SelectedIndex +1);
}
This is relatively simple. You create a single SelectedIndexChanged event handler method, and then wire that up to all of the combo box controls.
The way you distinguish between the controls inside of the method at run-time is by checking the value of the sender parameter. You'll have to cast it to a ComboBox control, but that's safe because you know that you didn't wire up any non-combobox controls to that event handler. Then you'll be able to access all the properties of the combobox that raised the event you're handling.
Tie each item in your markup to the same SelectedIndexChangedEvent and cast the sender as your item. So, in your code, look for all of the unique event names (ie. cbSlots0_SelectedIndexChanged, cbSlots1_SelectedIndexChanged, etc) and rename them to the single event name (eg. cbSlotsSelectedIndexChanged).
I think this is right. Verify.
CODE:
private void cbSlotsSelectedIndexChanged(object sender, EventArgs e)
{
ComboBox cBox = (ComboBox) sender;
int tag = (int)cBox.Tag;
var item = ConfigClass.Slots[tag.ToString()][cBox.SelectedIndex];
ConfigClass.Slots[tag.ToString()].Insert(tag, item);
ConfigClass.Slots[tag.ToString()].RemoveAt(item.SelectedIndex + 1);
}
UPDATE:
I revised my post as requested
private void cbSlotsSelectedIndexChanged(object sender, EventArgs e)
{
var cb = ((ComboBox)sender);
var tag = int.Parse(cb.Tag.ToString());
var item = ConfigClass.Slots[tag.ToString()][cb.SelectedIndex];
ConfigClass.Slots[tag.ToString()].Insert(tag, item);
ConfigClass.Slots[tag.ToString()].RemoveAt(cb.SelectedIndex + 1);
}

Preventing multiple repeat selection of synchronized Controls?

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.

Categories