¿Es posible “robar” un controlador de eventos de un control y dárselos a otro?

Quiero hacer algo como esto:

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

Donde btn1_Click ya está definido en la clase:

 void btn1_Click(object sender, EventArgs e) { // } 

Esto no se comstackrá, por supuesto (“El evento ‘System.Windows.Forms.Control.Click’ solo puede aparecer en el lado izquierdo de + = o – =”). ¿Hay alguna manera de tomar el controlador de eventos de un control y asignarlo a otro en tiempo de ejecución? Si eso no es posible, ¿es posible duplicar el controlador de eventos y asignarlo a otro control durante el tiempo de ejecución?

Un par de puntos: he buscado en Google este por un tiempo y no he encontrado manera de hacerlo todavía. La mayoría de los enfoques intentados implican reflexión, por lo que si lee mi pregunta y cree que la respuesta es increíblemente obvia, primero intente comstackr el código en Visual Studio. O si la respuesta es realmente obvia, siéntete libre de abofetearme. Gracias, estoy deseando ver si esto es posible.

Sé que podría hacer esto:

 btn2.Click += new EventHandler(btn1_Click); 

Eso no es lo que estoy buscando aquí.

Esto tampoco es lo que estoy buscando:

 EventHandler handy = new EventHandler(btn1_Click); Button btn1 = new Button(); btn1.Click += handy; Button btn2 = new Button(); btn2.Click += handy; 

Sí, es técnicamente posible. Se requiere reflexión porque muchos de los miembros son privados e internos. Inicie un nuevo proyecto de Windows Forms y agregue dos botones. Entonces:

 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"); } } } 

Si esto lo convence de que Microsoft intentó realmente evitar que lo haga, usted entendió el código.

No, no puedes hacer esto. El motivo es la encapsulación: los eventos solo se suscriben / cancelan la suscripción, es decir, no te permiten “echar un vistazo dentro” para ver qué manejadores ya están suscritos.

Lo que podría hacer es derivar de Button y crear un método público que llame a OnClick . Entonces solo necesita hacer de btn1 una instancia de esa clase, y suscribir un controlador a btn2 que llama a btn1.RaiseClickEvent() o lo que sea que llame el método.

Aunque no estoy seguro de que realmente lo recomiende. ¿Qué estás tratando de hacer? ¿Cuál es la imagen más grande?

EDIT: veo que has aceptado la versión que recupera el conjunto actual de eventos con reflection, pero en caso de que estés interesado en la alternativa que llama al controlador OnXXX en el control original, tengo una muestra aquí. Originalmente copié todos los eventos, pero eso conduce a algunos efectos muy extraños de hecho. Tenga en cuenta que esta versión significa que si alguien se suscribe a un evento en el botón original después de llamar a CopyEvents, todavía está “conectado”, es decir, no importa cuando se asocian los dos.

 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 }); } } } 

Investigué un poco con la solución de @ nobugz y se me ocurrió esta versión genérica que podría usarse en la mayoría de los objetos de propósito general.

Lo que descubrí es que los eventos para, me atrevo a decir, los eventos automáticos en realidad se comstackn con un campo de delegado de respaldo del mismo nombre:

Así que aquí hay uno para robar controladores de eventos para objetos más simples:

 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()); } } 

Pensé que podría ser útil para algunos.

Pero tenga en cuenta que la forma en que los eventos están conectados en los componentes de Windows Forms es bastante diferente. Están optimizados para que los eventos múltiples no ocupen mucha memoria solo conteniendo nulos. Necesitará un poco más de investigación, pero @nobugz ya lo hizo 🙂

El artículo Delegados y eventos sobre delegates combinados pueden ayudar a aclarar muchos puntos en las respuestas.

Puede usar un controlador de eventos común para sus botones y cuadros de imagen (según los comentarios de una respuesta anterior) y luego usar el objeto ‘remitente’ para determinar cómo manejar el evento en tiempo de ejecución.