【问题标题】:How to call an event manually in C#?如何在 C# 中手动调用事件?
【发布时间】:2011-04-19 23:06:56
【问题描述】:

我有一个 USerControll,其中有一个文本框。我在表单中使用用户控件,当有人在文本框上按 Enter 时,我想做一些事情。我该怎么做? 如果你告诉我如何手动调用事件,我可以在 textbox.keydown 中调用 usercontrol.keydown。

【问题讨论】:

  • 所以您想从 TextBox.KeyDown 事件处理程序中引发 UserControl.KeyDown 事件,对吗?
  • 那么在用户实际按下按键时响应事件对于您的场景来说还不够吗?我想我在你的场景中遗漏了一些东西。
  • @saeed - 原则上解决方案是相同的,但这适用于 ASP.NET 还是 WinForms?
  • @RQDQ:我不明白你错过了什么。

标签: c# winforms visual-studio-2010


【解决方案1】:

首先,事件只能从声明事件的控件内的代码中引发。因此,您的用户控件必须声明自定义事件 KeyDown 才能引发它。例如,您不能在用户控件包含的 TextBox 上引发 KeyDown。但是,您可以声明自己的 KeyDown,并将处理程序附加到 TextBox 的 KeyDown 将引发您自己的 KeyDown。

鉴于此限制,引发事件很容易:

public delegate void MyEventHandler(object sender, MyEventArgs e)

public event MyEventHandler MyEvent;

public void RaisesMyEvent()
{
   ...

   if(MyEvent != null) //required in C# to ensure a handler is attached
      MyEvent(this, new MyEventArgs(/*any info you want handlers to have*/));
}

引发事件看起来很像一种方法,因为本质上这就是你正在做的事情;您在事件的幕后调用分配给 MultiCast 委托的一个或多个方法委托。将其视为将方法分配给普通的命名委托(例如,如果您从定义中省略了“event”关键字)并从代码内部调用它。真正的事件和事件之间的唯一区别是,一个事件可以附加多个处理程序委托,并且在引发时将调用所有这些委托。

【讨论】:

  • 不幸的是,这段代码不是线程安全的。您需要创建MyEvent 的本地副本,以防止在另一个线程同时删除您的if 和事件调用之间的所有事件处理程序时出现NullReferenceExceptions
  • ... 或 lock(MyEvent()) 在评估 if 之前,然后释放它。问题只是关于提出一个事件,这就是我的回答;如果他随意地附加和分离处理程序,这表明他对一般事件有更高级的了解,不需要问这个问题。
  • 原则上你是对的,但我的意思不是这样:在本地复制活动是一个牢固确立的最佳实践。 No 事件引发代码不应该在没有它的情况下编写,甚至不能作为示例(因为如果你不期望多线程问题,你不会想到它)。随意附加事件不是用户控件的代码可以控制的——这取决于用户。作为可重用的代码,用户控制代码应该尽可能的健壮。
  • Hmya,这是教条,但在这里几乎不相关。一个控件只有 4 个成员,这些成员被记录为可从另一个线程(InvokeRequired 等)使用。甚至 MSFT 也不会始终如一地这样做,例如 NumericUpDown。
  • 但是任何多播委托都不能附加多个委托吗?那么事件有什么不同呢?
【解决方案2】:

您所描述的称为事件冒泡。这是一个例子:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="MyUserControl.ascx.cs" Inherits="MyUserControl" %>

<asp:TextBox ID="TextBox1" runat="server" OnTextChanged="TextBox1_TextChanged" />

public partial class MyUserControl : UserControl
{
 public event EventHandler UserControlTextBoxChanged;

 protected void TextBox1_TextChanged(object sender, EventArgs e) {
  if (UserControlTextBoxChanged != null)
   UserControlTextBoxChanged(sender, e);
 }
}

<%@ Page Language="C#" AutoEventWireup="True" Inherits="Default" CodeBehind="Default.aspx.cs" %>
<%@ Register Src="~/MyUserControl.ascx" TagName="MyUserControl" TagPrefix="uc1" %>

<uc1:MyUserControl ID="ucMyUserControl" runat="server" OnUserControlTextBoxChanged="ucMyUserControl_UserControlTextBoxChanged" />

public partial class MyPage : Page {
 protected void ucMyUserControl_UserControlTextBoxChanged(object sender, EventArgs e) {
  // sender is ucMyUserControl.TextBox1
 }
}

【讨论】:

  • 只是为了澄清,因为我可能误解了这个问题。您是专门寻找“Enter”还是试图通过 TextBox 捕获提交?
【解决方案3】:

通常,事件调用被包装在一个名为“On[EventName]”之类的方法中,该方法验证了 delgate 是否有一个或多个目标(事件不为空),然后使用发送者和任何适用的参数调用它...所以这样的事情是典型的模式:

public event EventHandler SomethingHappened;
protected void OnSomethingHappend(EventArgs e)
{
    if (SomethingHappened != null)
        SomethingHappened(this, e);
}

任何需要引发该事件的东西都会调用该方法(假设它可以访问)。

如果您只是想传递事件,那么作为用户控件,您可能只调用基本的“On[Event]”方法,该方法可能已暴露。您也可以连接事件处理程序,直接将来自子控件的事件作为父控件的事件传递……这样 txtFoo.KeyPress 就可以简单地调用父控件的 OnKeyPress 方法。

【讨论】:

    【解决方案4】:

    如果您使用的是 WPF,您也许可以使用 RaiseEvent:http://msdn.microsoft.com/en-us/library/system.windows.uielement.raiseevent.aspx

    但这对于你想做的事情是错误的。

    你应该冒泡这个事件。

    class MyControl : UserControl {
        public KeyDownEventHandler KeyDown;
    
        private void OnTextBoxKeyDown(object sender, EventArgs e){ KeyDown.Invoke(sender, e); }
    }
    

    然后从您的表单中收听 KeyDown。请原谅在命名各种元素/事件时的错误。

    【讨论】:

      【解决方案5】:

      我一直在为我寻找这个问题的答案,

      就这样做

      例子:

      //this is the call to trigger the event:
      
       **lst_ListaDirectorios_SelectedIndexChanged(this, new EventArgs());**
      
      //do that if you have the method signature in the same class as I do. (something like this below)
      private void lst_ListaDirectorios_SelectedIndexChanged(object sender, EventArgs e)
              {
                //do something
               }
      

      我希望这对你有用。

      【讨论】:

        【解决方案6】:

        如果您真的需要手动调用事件,您可以获取支持委托,该委托通常是私有的。使用 .NET 反编译器(如 ILSPY)定位 Event 的支持字段,然后使用反射获取支持委托。

        示例:从BackgroundWorker 获取事件DoWork

        在ILSpy中反编译BackgroundWorker类,你会看到:

        public event DoWorkEventHandler DoWork
        {
            add
            {
                base.Events.AddHandler(doWorkKey, value);
            }
            remove
            {
                base.Events.RemoveHandler(doWorkKey, value);
            }
        }
        

        所以你需要找到Events 成员,以及doWorkKey 字段作为键。

        Events 是在 Component 类中声明的 EventHandlerList(公共类)。

        doWorkKey 是类BackgroundWorker 中声明的静态字段。

        然后使用反射获取委托:

        PropertyInfo property = backgroundWorker.GetType().GetProperty("Events", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
        EventHandlerList eventHandlerList = (EventHandlerList)property.GetValue(backgroundWorker, null);
        FieldInfo doWorkField = backgroundWorker.GetType().GetField("doWorkKey", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.FlattenHierarchy);
        object doWorkKey = doWorkField.GetValue(null);
        DoWorkEventHandler doWork = (DoWorkEventHandler)eventHandlerList[doWorkKey];
        

        现在您有了DoWork 事件的委托,并且可以调用它。

        同样的方法也适用于其他控件。

        请注意,只要有新版本的代码,使用反射获取私有字段可能会中断。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-01-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-10-19
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多