Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/334.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/24.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 有没有可能;偷;从一个控件创建事件处理程序并将其交给另一个控件?_C#_.net - Fatal编程技术网

C# 有没有可能;偷;从一个控件创建事件处理程序并将其交给另一个控件?

C# 有没有可能;偷;从一个控件创建事件处理程序并将其交给另一个控件?,c#,.net,C#,.net,我想这样做: 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已在类中定义: vo

我想这样做:

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“只能出现在+=或-=”的左侧”)。是否有一种方法可以从一个控件获取事件处理程序,并在运行时将其分配给另一个控件?如果不可能,那么在运行时复制事件处理程序并将其分配给另一个控件是否可行

有两点:我已经在谷歌上搜索了一段时间,但还没有找到方法。大多数尝试的方法都涉及反射,因此,如果您阅读了我的问题并认为答案非常明显,请首先尝试在VisualStudio中编译代码。或者如果答案真的非常明显,请随便打我一巴掌。谢谢,我真的很期待看到这是否可行

我知道我可以这么做:

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;

不,你不能这样做。原因是封装-事件只是订阅/取消订阅,也就是说,它们不允许您“查看内部”以查看已订阅的处理程序

您可以做的是从按钮派生,并创建一个调用
OnClick
的公共方法。然后您只需要将
btn1
作为该类的一个实例,并向
btn2
订阅一个处理程序,该处理程序调用
btn1.RaiseClickEvent()
或您调用该方法的任何内容

我不确定我是否真的会推荐它。你到底想做什么?更大的图景是什么

编辑:我看到您已经接受了获取当前事件集的反射版本,但是如果您对调用原始控件中的OnXXX处理程序的替代方案感兴趣,我这里有一个示例。我最初复制了所有事件,但这确实导致了一些非常奇怪的效果。请注意,此版本意味着,如果有人在调用CopyEvents后订阅了原始按钮中的某个事件,它仍然是“连接”的,即,当您将这两个事件关联起来时,这并不重要

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

您可以为按钮和图片框使用通用事件处理程序(根据前面答案中的注释),然后使用“sender”对象确定如何在运行时处理事件。

是的,这在技术上是可行的。反射是必需的,因为许多成员是私有的和内部的。启动一个新项目并添加两个按钮。然后:

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

如果这让你确信微软真的很努力地阻止你这么做,那么你就理解了代码。

我对@nobugz的解决方案进行了深入研究,并提出了这个通用版本,它可以用于大多数通用对象

我发现,我敢说,自动事件的事件实际上是使用同名的backing delegate字段编译的:

因此,这里有一个用于为更简单的对象窃取事件处理程序的方法:

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已经做到了:-)


关于组合代理的文章可能有助于澄清答案中的许多要点。

EventHandler btnClick=new EventHandler(btn1\u单击);btn1.点击-=BTN点击;btn2.点击+=BTN点击;如果这没有帮助,我不知道我是否理解你想要什么。请注意,从C#2开始,你不需要“new EventHandler(…)”-只需“EventHandler btnClick=btn1_Click”即可。。。从C#2开始,很少需要“new WhateverDelegateType(…)”。对不起,我经常在代码片段中使用new,这样我就可以制表了。谢谢Jon。我复制了上面示例中的代码:)@shahkalpesh:对不起,我不清楚,但在我的例子中,EventHandler实际上是在不同的范围内分配给btn1的,因此它不再是分配给btn2的对象。有关更多详细信息,请参见Jon答案中的注释。我希望在运行时生成一个表单,遍历其控件集合,并用图片框替换所有按钮。这样做很容易,但是你的替换图片框没有任何与原始按钮相关的事件。我知道没有合理的理由这样做。有时候,作为一个程序员,你会被外力所束缚,以至于精神错乱成为最简单的出路。我不认为你能不经思考就做到这一点。基本上,您正在尝试打破封装。这可能是一个很好的理由,但它仍然是一个破碎。但是,您可以通过反射调用Button.OnClick,而不是通过反射获取处理程序。我可以通过这种方式调用Button.OnClick,但实际上我不知道该调用什么,因为我不知道为按钮分配了哪些事件(可能有getfocus或textchanged等)。我假设需要反射,但这超出了我的能力。在调用typeof(Control).GetField(“EventClick”)时,您正在传递“EventClick”以获取名为“EventClick”的字段,应该为ParentChanged控件/按钮的事件传递什么字符串?获取了“EventParent”,之前我尝试了“EventParentChanged”获取空异常。运行了所有字段,得到“EventParent”,谢谢。@HansPassant如何获得textbox控件的TextChanged事件?我没有找到任何事件,或者找不到其他一些事件:(