Is it possible to "steal" an event handler from one control and give it to another?
Solution 1:
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.
Solution 2:
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 });
}
}
}