This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Unsubscribe anonymous method in C#
Single-shot event subscription
Is it possible to get a reference to an event handler from within the event handler itself so you can unhook it from the event that called it?
For instance, I'd love to make the control.Loaded event point to a Lambda, but if I did, I don't know what to pass to the unhook (-=) call.
Here's an excerpt of the code:
private static void IsEnabled_Changed(object sender, DependencyPropertyChangedEventArgs e)
{
var control = (Control)sender;
if(control.IsLoaded)
WireUpScrollViewerEvents(control);
else
control.Loaded += Control_Loaded;
}
private static void Control_Loaded(object sender, RoutedEventArgs e)
{
var control = (Control)sender;
control.Loaded -= Control_Loaded; // <-- How can I do this from a lambda in IsEnabled_Changed?
WireUpScrollViewerEvents(control);
}
private static void WireUpScrollViewerEvents(Control control, bool attach)
{
var scrollViewer = Utils.FindFirstVisualChild<ScrollViewer>(control);
if(scrollViewer == null) return;
var attachEvents = GetIsEnabled(control);
if(attachEvents)
{
scrollViewer.PreviewDragEnter += ScrollViewer_PreviewDragEnter;
scrollViewer.PreviewDragOver += PreviewDragOver;
}
else
{
scrollViewer.PreviewDragEnter -= ScrollViewer_PreviewDragEnter;
scrollViewer.PreviewDragOver -= PreviewDragOver;
}
}
The only reason Control_Loaded is an actual method is because of the 'control.Loaded -= Control_Loaded' line. I’m wondering if we can anonymous-lambda away it from within the IsEnabled_Changed call directly.
Ok, found it.
Ok, found it. The reason I couldn't get it to work was I was doing this...
RoutedEventHandler Control_Loaded = (s2, e2) =>
{
control.Loaded -= Control_Loaded;
WireUpScrollViewerEvents(control);
};
...when I should have been doing this...
RoutedEventHandler Control_Loaded = null; // <-- It's now declared before it's set, which is what we need
Control_Loaded = (s2, e2) =>
{
control.Loaded -= Control_Loaded;
WireUpScrollViewerEvents(control);
};
This is because you have to actually declare the variable before you can use it, and I was trying to use it as part of the declaration. This fixes that.
Related
This question already has answers here:
Do I need to unsubscribe events in my Form?
(4 answers)
Unsubscribing from an event
(3 answers)
C# .NET proper event subscribing and un-subscribing
(3 answers)
What best practices for cleaning up event handler references?
(6 answers)
Closed 3 years ago.
I've heard that you are supposed to unsubscribe from events on controls that get disposed.
TextBox textBox;
void Initialize()
{
textBox = new TextBox();
textBox.Click += ClickHandler;
this.Disposed += DisposeControl;
}
void ClickHandler(object sender, EventArgs e)
{
this.foo = bar;
}
void DisposeControl(object sender, EventArgs e)
{
textBox.Click -= ClickHandler;
this.Disposed -= DisposeControl; // DOES THIS LINE MAKE SENSE?
}
There's no reference to the control after it has been disposed so I would assume there is no need to unregister any event handlers. Yet, as I mentioned people are saying that you're supposed to. Does that include unregistering the dispose event itself?
To answer you question "DOES THIS LINE MAKE SENSE?", in your example it does not. That is because the event source and target are both the same. I am going to rewrite your code a bit.
class SOTest1 : Control
{
private bool bar = true;
private bool foo { get; set; }
TextBox textBox;
void Initialize()
{
textBox = new TextBox();
textBox.Click += ClickHandler;
this.Disposed += DisposeControl;
}
void ClickHandler(object sender, EventArgs e)
{
this.foo = bar;
}
void DisposeControl(object sender, EventArgs e)
{
textBox.Click -= ClickHandler;
this.Disposed -= DisposeControl; // DOES THIS LINE MAKE SENSE?
}
}
In this case, since Control implements IDisposable which calls the Disposed event at the end of the disposal, both the object with the event and the event handler are the same object. So, the C# garbage collector is going to see that the object and all references have been disposed and will clean it all up.
Also, since textBox and its event handlers are contained within that object, the textBox.Click -= ClickHandler is also redundant, as it will all be cleaned up by the GC when the SOTest1 object is disposed.
It's not a bad practice to clean up event subscribers, but, it does add code that is unnecessary much of the time. C# does a great job of cleaning up resources when your code has finished with them.
There are some cases where memory leaks can happen, like if the lifecycle of the object with the handler is shorter than the lifecycle of the object with the event. The main answer in this article has a good example of that case: What best practices for cleaning up event handler references?
I prefer use IDisposable to handle this case
List<IDisposable> eventsToDispose = new List<IDisposable>();
eventsToDispose.Add(Disposable.Create(() =>
{
textBox.Click += ClickHandler;
}));
Then you can dispose handler
eventsToDispose.ForEach(o => o.Dispose());
Someone mentioned to me that c# supports to use lambda expression as event handler, can anyone share with me some reference on this?
A code snippet is preferred.
You can use a lambda expression to build an anonymous method, which can be attached to an event.
For example, if you make a Windows Form with a Button and a Label, you could add, in the constructor (after InitializeComponent()):
this.button1.Click += (o,e) =>
{
this.label1.Text = "You clicked the button!";
};
This will cause the label to change as the button is clicked.
try this example
public Form1()
{
InitializeComponent();
this.button1.Click += new EventHandler(button1_Click);
}
void button1_Click(object sender, EventArgs e)
{
}
The above event handler can be rewritten using this lambda expression
public Form1()
{
InitializeComponent();
this.button1.Click += (object sender, EventArgs e) = >
{
MessageBox.Show(“Button clicked!”);
};
}
While debugging, can I look into textBox1.TextChanged to see the number of event subscriptions? If yes, then how do I drill to it? I need to know how many subscriptions there are at a given time for debugging because it looks like an event is triggered multiple times, but I suspect this bug is really because textBox1.TextChanged += handler is being mismanaged in the application, so there are too many subscribers.
Here is a simplified version of what I think is happening. If possible, I just want to set a breakpoint and count up the number of subscriptions to "textBox1.TextChanged":
private void textBox1_TextChanged(object sender, EventArgs e)
{
textBox1.TextChanged += textBox1_TextChanged;
MessageBox.Show("asdf");
textBox1.TextChanged -= textBox1_TextChanged;
textBox1.Text = DateTime.Now.ToString();
textBox1.TextChanged += textBox1_TextChanged;
}
Is that possible or is it more complicated?
If you're only concerned with doing it under the debugger, rather than programmatically, then this is perhaps a simpler, non-invasive way:
class _24003458
{
event EventHandler MyEvent;
public void Test()
{
MyEvent += Handler1;
MyEvent += Handler2;
MyEvent(this, EventArgs.Empty);
}
void Handler1(object sender, EventArgs e)
{
throw new NotImplementedException();
}
void Handler2(object sender, EventArgs e)
{
throw new NotImplementedException();
}
}
Put a breakpoint on either of the event handlers, and when it breaks, look at the Locals tab. The event, when expanded, will show the invocation count and event handlers:
You will have to use Reflection to get to the invocation list of the event delegate:
textBox1.TextChanged += textBox1_TextChanged;
MessageBox.Show("asdf");
textBox1.TextChanged -= textBox1_TextChanged;
textBox1.Text = DateTime.Now.ToString();
textBox1.TextChanged += textBox1_TextChanged;
var eventField = textBox1.GetType().GetField("TextChanged", BindingFlags.GetField
| BindingFlags.NonPublic
| BindingFlags.Instance);
var subscriberCount = ((EventHandler)eventField.GetValue(textBox1))
.GetInvocationList().Length;
It is not possible with an event like this (for good reason), however, it is possable via reflection as Selman22 says, above) if you are using an event directly you can do so:
private event EventHandler handler;
var delegates = handler.GetInvocationList();
You can create a member method which you add to the object which implements the INotifyPropertyChanged interface. It makes debugging very easy:
#if DEBUG
public System.Delegate[] GetInvocationList()
{
return PropertyChanged?.GetInvocationList();
}
#endif
This one may be hard to ask but I will do my best. Lots of stuff on SO and elsewhere on making an event trigger on change but I want the opposite (at least I think I do!).
I have several controls on a form (numericUpDowns). For each updown, I'm watching to see if a value has been changed and it will trigger a "ValueChanged" method.
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
//do some complicated code ...
}
It's all working well with the changes to up-down controls causing recalculation of the fomr BUT what if I want to set a few of the values in a method and NOT call my ValueChanged melthods until the end?
I'd almost like something like this
// start ignoring form events here
numericUpDown1.Value = 500;
numericUpDown2.Value = 50;
numericUpDown3.Value = 0;
// resume detecting form events here !
Is there a way to turn this on and off? is it something that I should worry about?
The best thing my limited C# knowledge leads me to a this point is to create a variable called "bIgnoreEvents" and use it in each of my ValueChanged Methods ...
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
if (!bIgmoreEvents)
{
//do some complicated code ...
}
}
this would skip the code but seems messy. Is there a simpler way?
You could also do this:
var eventHandler = new EventHandler((o, a) =>
{
//do stuff
});
numericUpDown1.ValueChanged += eventHandler;
//normal behaviour
//remove eventHandler
numericUpDown1.ValueChanged -= eventHandler;
numericUpDown1.Value = 500;
numericUpDown2.Value = 50;
numericUpDown3.Value = 0;
//add eventHandler
numericUpDown1.ValueChanged += eventHandler;
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.