是否有可能从一个控件“偷”一个事件处理程序,并将其交给另一个?

我想要做这样的事情:

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

在类中已经定义了btn1_Click:

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

这当然不会编译(“事件”System.Windows.Forms.Control.Click“只能出现在+ =或 – =”的左侧)。 有没有办法从一个控件采取事件处理程序,并将其分配给另一个在运行时? 如果这是不可能的,重复的事件处理程序,并分配给另一个控制在运行时可以吗?

有几点:我已经search了一段时间,发现没有办法。 大多数尝试的方法都涉及到reflection,所以如果您阅读我的问题,并认为答案是非常明显的,请尝试先在Visual Studio中编译代码。 或者,如果答案真的非常明显,请随时给我打电话。 谢谢,我真的很期待看到这是否可能。

我知道我可以这样做:

 btn2.Click += new EventHandler(btn1_Click); 

这不是我在这里找的。

这也不是我要找的东西:

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

是的,这在技术上是可行的。 反思是必要的,因为许多成员是私人的和内部的。 启动一个新的Windows窗体项目并添加两个button。 然后:

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

如果这说服了你,微软努力阻止你这样做,你就明白了代码。

不,你不能这样做。 原因是封装 – 事件只是订阅/取消订阅,即他们不让你“偷看”内部看看处理程序已经订阅。

可以做的是从Button派生,并创build一个调用OnClick的公共方法。 然后你只需要让btn1成为btn1一个实例,然后订阅一个处理程序给btn2 ,它调用btn1.RaiseClickEvent()或者任何你调用的方法。

我不确定我是否真的推荐它。 你究竟在做什么? 什么是更大的图片?

编辑:我看到你已经接受了reflection的当前设置的事件的版本,但万一你有兴趣在原始控件中调用OnXXX处理程序的替代品,我在这里有一个示例。 我原本复制了所有的事件,但这确实导致了一些非常奇怪的效果。 请注意,这个版本意味着如果有人调用CopyEvents 之后订阅了原始button中的一个事件,它仍然是“挂钩”的 – 也就是说,当你把这两个关联起来的时候并不重要。

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

我用@ nobugz的解决scheme进行了一些挖掘,并提出了可用于大多数通用对象的通用版本。

我发现,事实上,我敢说, 自动事件实际上是用一个同名的后备代理字段编译的:

所以这里是一个窃取更简单的对象的事件处理程序:

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

认为它可能对一些有用的。

但是请注意,事件在Windows窗体组件中的连接方式相当不同。 他们被优化,以便多个事件不占用大量的内存只是持有空值。 所以它需要更多的挖掘,但@nobugz已经做到了:-)

文章代表和有关联合代表的事件可能有助于澄清很多答案。

您可以使用常用的事件处理程序来处理button和图片框(根据先前的回答中的注释),然后使用“发件人”对象来确定如何在运行时处理事件。