I'm writing an application in winForm. I have a panel in from1 and it has many event handler.
When I dispose panel1 and create new panel , the previous events exist & they fire. For removing panel1 events I have tryed code below.
panel1.Click -= clickHandle_1 ;
But it doesn't work every where in program code. for example in another method.
How can I remove all previous events of panel1 when I create new panel1 ?
According to this,
for cancelling all Click Events of panel1 do this:
FieldInfo f1 = typeof(Control).GetField("EventClick", BindingFlags.Static| BindingFlags.NonPublic);
object obj = f1.GetValue(panel1);
PropertyInfo pi = panel1.GetType().GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Instance);
EventHandlerList list = (EventHandlerList)pi.GetValue(panel1, null);
list.RemoveHandler(obj, list[obj]);
And for cancelling other events of panel1 change EventClick to event name that you want to remove.
One can get all event names using this code:
EventInfo[] info = type.GetEvents();
for (int i = 0; i < info.Length; i++)
{
Console.WriteLine(info[i].Name + "\n");
}
Normally when you Dispose an object/control, all the members of that object/control including Event handler list should be disposed too and unusable. So when you say your panel1 is disposed, it's impossible to fire any event on that disposed control (such as Resize). You should check if you surely dispose your panel. Here is a way you can use to remove all the Event handlers of a control using Reflection to Dispose the Events (type of EventHandlerList):
public void RemoveHandlerList(Control c){
EventHandlerList list = (EventHandlerList) typeof(Control).GetProperty("Events", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(c, null);
typeof(EventHandlerList).GetMethod("Dispose").Invoke(list, null);
}
Remove them one at a time:
panel1.Click -= clickHandle_1;
There is no clean way of unsubscribing all event handlers at once. Keep track of what you're subscribing and unsubscribe them accordingly.
Not sure if this will work but this is closest I came with:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private Dictionary<string, EventHandler> _handlers = new Dictionary<string, EventHandler>();
TextBox _txt = new TextBox();
void WireHandlers()
{
// get references of your handlers
EventHandler _hnd1 = delegate { return; }; // here will be your named method. This is only for example
EventHandler _hnd2 = delegate { return; }; // here will be your named method. This is only for example
// wire handlers
_txt.Click += _hnd1;
_txt.TextChanged += _hnd2;
// save wired handler collection
_handlers.Add("Click", _hnd1);
_handlers.Add("TextChanged", _hnd2);
}
void UnwireHandlers()
{
// lets unwire all handlers
foreach (var kvp in _handlers)
{
// inspect keyValuePair - each key corresponds to the name of some event
switch (kvp.Key)
{
case "Click":
_txt.Click -= kvp.Value;
break;
case "TextChanged":
_txt.TextChanged -= kvp.Value;
break;
default:
throw new Exception("no such handler found");
}
}
_handlers.Clear();
}
}
Related
I want do something like this:
Button btn1 = new Button();
btn1.Click += new EventHandler(btn1_Click);
Button btn2 = new Button();
// Take whatever event got assigned to btn1 and assign it to btn2.
btn2.Click += btn1.Click; // The compiler says no...
Where btn1_Click is already defined in the class:
void btn1_Click(object sender, EventArgs e)
{
//
}
This won't compile, of course ("The event 'System.Windows.Forms.Control.Click' can only appear on the left hand side of += or -="). Is there a way to take the event handler from one control and assign it to another at runtime? If that's not possible, is duplicating the event handler and assigning it to another control at runtime doable?
A couple of points: I have googled the heck out of this one for awhile and found no way of doing it yet. Most of the attempted approaches involve reflection, so if you read my question and think the answer is incredibly obvious, please try to compile the code in Visual Studio first. Or if the answer really is incredibly obvious, please feel free to slap me with it. Thanks, I'm really looking forward to seeing if this is possible.
I know I could just do this:
btn2.Click += new EventHandler(btn1_Click);
That's not what I'm looking for here.
This is also not what I'm looking for:
EventHandler handy = new EventHandler(btn1_Click);
Button btn1 = new Button();
btn1.Click += handy;
Button btn2 = new Button();
btn2.Click += handy;
Yeah, it's technically possible. Reflection is required because many of the members are private and internal. Start a new Windows Forms project and add two buttons. Then:
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Reflection;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
button1.Click += new EventHandler(button1_Click);
// Get secret click event key
FieldInfo eventClick = typeof(Control).GetField("EventClick", BindingFlags.NonPublic | BindingFlags.Static);
object secret = eventClick.GetValue(null);
// Retrieve the click event
PropertyInfo eventsProp = typeof(Component).GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Instance);
EventHandlerList events = (EventHandlerList)eventsProp.GetValue(button1, null);
Delegate click = events[secret];
// Remove it from button1, add it to button2
events.RemoveHandler(secret, click);
events = (EventHandlerList)eventsProp.GetValue(button2, null);
events.AddHandler(secret, click);
}
void button1_Click(object sender, EventArgs e) {
MessageBox.Show("Yada");
}
}
}
If this convinces you that Microsoft tried really hard to prevent your from doing this, you understood the code.
No, you can't do this. The reason is encapsulation - events are just subscribe/unsubscribe, i.e. they don't let you "peek inside" to see what handlers are already subscribed.
What you could do is derive from Button, and create a public method which calls OnClick. Then you just need to make btn1 an instance of that class, and subscribe a handler to btn2 which calls btn1.RaiseClickEvent() or whatever you call the method.
I'm not sure I'd really recommend it though. What are you actually trying to do? What's the bigger picture?
EDIT: I see you've accepted the version which fetches the current set of events with reflection, but in case you're interested in the alternative which calls the OnXXX handler in the original control, I've got a sample here. I originally copied all events, but that leads to some very odd effects indeed. Note that this version means that if anyone subscribes to an event in the original button after calling CopyEvents, it's still "hooked up" - i.e. it doesn't really matter when you associate the two.
using System;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
class Test
{
static void Main()
{
TextBox output = new TextBox
{
Multiline = true,
Height = 350,
Width = 200,
Location = new Point (5, 15)
};
Button original = new Button
{
Text = "Original",
Location = new Point (210, 15)
};
original.Click += Log(output, "Click!");
original.MouseEnter += Log(output, "MouseEnter");
original.MouseLeave += Log(output, "MouseLeave");
Button copyCat = new Button
{
Text = "CopyCat",
Location = new Point (210, 50)
};
CopyEvents(original, copyCat, "Click", "MouseEnter", "MouseLeave");
Form form = new Form
{
Width = 400,
Height = 420,
Controls = { output, original, copyCat }
};
Application.Run(form);
}
private static void CopyEvents(object source, object target, params string[] events)
{
Type sourceType = source.GetType();
Type targetType = target.GetType();
MethodInfo invoker = typeof(MethodAndSource).GetMethod("Invoke");
foreach (String eventName in events)
{
EventInfo sourceEvent = sourceType.GetEvent(eventName);
if (sourceEvent == null)
{
Console.WriteLine("Can't find {0}.{1}", sourceType.Name, eventName);
continue;
}
// Note: we currently assume that all events are compatible with
// EventHandler. This method could do with more error checks...
MethodInfo raiseMethod = sourceType.GetMethod("On"+sourceEvent.Name,
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic);
if (raiseMethod == null)
{
Console.WriteLine("Can't find {0}.On{1}", sourceType.Name, sourceEvent.Name);
continue;
}
EventInfo targetEvent = targetType.GetEvent(sourceEvent.Name);
if (targetEvent == null)
{
Console.WriteLine("Can't find {0}.{1}", targetType.Name, sourceEvent.Name);
continue;
}
MethodAndSource methodAndSource = new MethodAndSource(raiseMethod, source);
Delegate handler = Delegate.CreateDelegate(sourceEvent.EventHandlerType,
methodAndSource,
invoker);
targetEvent.AddEventHandler(target, handler);
}
}
private static EventHandler Log(TextBox output, string text)
{
return (sender, args) => output.Text += text + "\r\n";
}
private class MethodAndSource
{
private readonly MethodInfo method;
private readonly object source;
internal MethodAndSource(MethodInfo method, object source)
{
this.method = method;
this.source = source;
}
public void Invoke(object sender, EventArgs args)
{
method.Invoke(source, new object[] { args });
}
}
}
I did some digging around with #nobugz's solution and came up with this generic version which could be used on most general-purpose objects.
What I found out is that events for, dare I say, automatic events actually are compiled with a backing delegate field of the same name:
So here's one for stealing event handlers for simpler objects:
class Program
{
static void Main(string[] args)
{
var d = new Dummy();
var d2 = new Dummy();
// Use anonymous methods without saving any references
d.MyEvents += (sender, e) => { Console.WriteLine("One!"); };
d.MyEvents += (sender, e) => { Console.WriteLine("Two!"); };
// Find the backing field and get its value
var theType = d.GetType();
var bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;
var backingField = theType.GetField("MyEvents", bindingFlags);
var backingDelegate = backingField.GetValue(d) as Delegate;
var handlers = backingDelegate.GetInvocationList();
// Bind the handlers to the second instance
foreach (var handler in handlers)
d2.MyEvents += handler as EventHandler;
// See if the handlers are fired
d2.DoRaiseEvent();
Console.ReadKey();
}
}
class Dummy
{
public event EventHandler MyEvents;
public void DoRaiseEvent() { MyEvents(this, new EventArgs()); }
}
Thought it might be useful to some.
But do note that the way events are wired in Windows Forms components is rather different. They are optimized so that multiple events doesn't take up a lot of memory just holding nulls. So it'll need a little more digging around, but #nobugz has already done that :-)
The article Delegates and events about combined delegates might help clarify a lot of points in answers.
You could use a common event handler for your buttons and your picture boxes (as per the comments on an earlier answer) and then use the 'sender' object to determine how to handle the event at runtime.
I am adding a custom event to a DataGridView to handle cell clicks. This
needs a parameter passed to it.
I first attempt to delete the previous event handler, as I am sending the same DataGridView in and populating it each time.
//To delete the old handle
DataGridViewToPopulate.CellClick -= (sender, e) => DataGridView_CellClick(sender, e, SourceObject);
//To add new handle
DataGridViewToPopulate.CellClick += (sender, e) => DataGridView_CellClick(sender, e, SourceObject);
My problem is, when this runs the second time, and the SourceObject has changed, the event still has the original SourceObject being sent to it (I believe the original handle is never removed).
I need to dynamically remove all CellClick events (or even all, there aren't that many).
Maybe "this", when you are calling the second time the -= operator, is a different object than the first time you executed the +=. So that -= will not detach that first object, and you would be attaching a second object while not removing the first one.
If that's the case, you should try to find the way to detach that first object.
I was able to find a solution with reflection,
private static void UnsubscribeOne(object target)
{
Delegate[] subscribers;
Type targetType;
string EventName = "EVENT_DATAGRIDVIEWCELLCLICK";
targetType = target.GetType();
do
{
FieldInfo[] fields = targetType.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic);
foreach (FieldInfo field in fields)
{
if (field.Name == EventName)
{
EventHandlerList eventHandlers = ((EventHandlerList)(target.GetType().GetProperty("Events", (BindingFlags.FlattenHierarchy | (BindingFlags.NonPublic | BindingFlags.Instance))).GetValue(target, null)));
Delegate d = eventHandlers[field.GetValue(target)];
if ((!(d == null)))
{
subscribers = d.GetInvocationList();
foreach (Delegate d1 in subscribers)
{
targetType.GetEvent("CellClick").RemoveEventHandler(target, d1);
}
return;
}
}
}
targetType = targetType.BaseType;
} while (targetType != null);
}
My constructor parses its instance for events with a certain attribute:
_EventAttributes = from EventDescriptor a in TypeDescriptor.GetEvents(this)
let attribute = a.Attributes[typeof(MyAttribute)] as MyAttribute
where attribute != null
select new EventAttributeTuple { Event = a, Attribute = attribute };
Later in the code i want to check whether the corresponding event is null (nobody is interested in the event) to decide whether or not i have to write it in my XML output:
//write events
foreach (var attr in _EventAttributes)
{
if (check corresponding attr.Event object is null)
{
writer.WriteAttributeString(attr.Attribute.Name, GetRPCAdress(attr.Event.Name));
}
}
EDIT:
Getting the EventInfo is also quite easy:
var evtInfo = this.GetType().GetEvent(attr.Event.Name);
But still, i don't know how to check whether the event has subscribers or not.
EDIT2:
After looking at the generated code with dotPeek, I think there is no chance to access any field here:
//original code
[MyAttribute("onclick")]
public event EventHandler<MouseArg> Click;
//generated code
[MyAttribute("onclick")]
public event EventHandler<MouseArg> Click
{
add
{
EventHandler<MouseArg> eventHandler = this.Click;
EventHandler<MouseArg> comparand;
do
{
comparand = eventHandler;
eventHandler = Interlocked.CompareExchange<EventHandler<MouseArg>>(ref this.Click, comparand + value, comparand);
}
while (eventHandler != comparand);
}
remove
{
EventHandler<MouseArg> eventHandler = this.Click;
EventHandler<MouseArg> comparand;
do
{
comparand = eventHandler;
eventHandler = Interlocked.CompareExchange<EventHandler<MouseArg>>(ref this.Click, comparand - value, comparand);
}
while (eventHandler != comparand);
}
}
There may be a chance, if i handle all of the event add/remove operations myself, but that seems very tedious. Anyone has a better idea?
EDIT3:
I got it, i was using this.GetType() which does not give me the declaring type but was a subclass of the class which declares the event, hence i was not able to retrieve the field.
It now works like this:
var evtValue = attr.Event.ComponentType.GetField(attr.Event.Name,
BindingFlags.Instance | BindingFlags.NonPublic).GetValue(this);
Sorry, this is impossible in general case, for instance:
class CounterExample {
public event EventHandler MyEvent {
add {
// You can't control me here: whether I truly add an event or not
// and even if I always add the event - where I store it
}
remove {
// You can't control me here: whether I truly remove an event or not
// and even if I always remove the event - from where I delete it
}
}
}
But in many cases you can do a hack (be careful though):
class SimpleCase {
// Usual event realization (default add and remove accessors)
public event EventHandler MyEvent;
}
...
SimpleCase cs = new SimpleCase();
cs.MyEvent += ...
...
// Since MyEvent has default accessors, lets check the default storage:
FieldInfo fi = cs.GetType().GetField("MyEvent", BindingFlags.NonPublic | BindingFlags.Instance);
Boolean myEventAssigned = !Object.ReferenceEquals(fi.GetValue(cs), null);
In WPF I have created a control that dynamically creates buttons for me. On some occasions the buttons may change and need be recreated. I am currently using the following:
public void GenerateButtons()
{
WrapPanel_Main.Children.Clear();
foreach (ActivatedItem thisItem in Controller.ItemList.Where(sl => sl.IsTypeCompatible(typeof(ActivatedItem))))
{
Button newButton = new Button() { Content = thisItem, ToolTip = thisItem.Desc, Width = 50, Height = 25 };
newButton.Click += new System.Windows.RoutedEventHandler(this.DynamicButtonClick);
WrapPanel_Main.Children.Add(newButton);
}
}
I am wondering if the WrapPanel_Main.Children.Clear(); section of my code is enough to remove the buttons and the events from memory or if I am leaving stuff (like the event handlers) floating around out there?
As always I am open to suggestions on bettering the code shown above as well.
In short, you don't need to worry about this.
When you attach an event handler to the button that event handler isn't keeping the button alive, the button is keeping whatever is referenced by the event handler alive. The event handler is referencing your window, so essentially you can't clear the window from memory until the button leaves memory. Since it doesn't really make sense for the button to live longer than the window this won't happen.
In other words, the situation you need to watch out for is where the items used in the event handler need to have a shorter life than the class that owns the event.
As #Servy mentioned its probably not a needed to detach these handlers, but if you really want to the only way I could think to do this is with Reflection.
Here is a small example based on your question (Button click event for buttons in WrapPanel)
public void RemoveButtonClickHandlers(UIElementCollection elements)
{
foreach (var button in elements.OfType<Button>())
{
try
{
var handlers = typeof(UIElement).GetProperty("EventHandlersStore", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(button, null);
if (handlers != null)
{
var clickEvents = (RoutedEventHandlerInfo[])handlers.GetType()
.GetMethod("GetRoutedEventHandlers", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Invoke(handlers, new object[] { ButtonBase.ClickEvent });
foreach (var clickEvent in clickEvents)
{
button.Click -= (RoutedEventHandler)clickEvent.Handler;
}
}
}
catch (Exception ex)
{
// :(
}
}
}
Usage:
RemoveButtonClickHandlers(WrapPanel_Main.Children);
WrapPanel_Main.Children.Clear();
I have 2 handlers using the same form. How do I remove the handlers before adding the new one (C#)?
If you are working in the form itself, you should be able to do something like:
PseudoCode:
Delegate[] events = Form1.SomeEvent.GetInvokationList();
foreach (Delegate d in events)
{
Form1.SomeEvent -= d;
}
From outside of the form, your SOL.
If you know what those handlers are, just remove them in the same way that you subscribed to them, except with -= instead of +=.
If you don't know what the handlers are, you can't remove them - the idea being that the event encapsulation prevents one interested party from clobbering the interests of another class in observing an event.
EDIT: I've been assuming that you're talking about an event implemented by a different class, e.g. a control. If your class "owns" the event, then just set the relevant variable to null.
I realize this question is rather old, but hopefully it will help someone out. You can unregister all the event handlers for any class with a little reflection.
public static void UnregisterAllEvents(object objectWithEvents)
{
Type theType = objectWithEvents.GetType();
//Even though the events are public, the FieldInfo associated with them is private
foreach (System.Reflection.FieldInfo field in theType.GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance))
{
//eventInfo will be null if this is a normal field and not an event.
System.Reflection.EventInfo eventInfo = theType.GetEvent(field.Name);
if (eventInfo != null)
{
MulticastDelegate multicastDelegate = field.GetValue(objectWithEvents) as MulticastDelegate;
if (multicastDelegate != null)
{
foreach (Delegate _delegate in multicastDelegate.GetInvocationList())
{
eventInfo.RemoveEventHandler(objectWithEvents, _delegate);
}
}
}
}
}