【问题标题】:simple custom event简单的自定义事件
【发布时间】:2011-10-02 10:07:48
【问题描述】:

我正在尝试学习自定义事件,并尝试创建一个,但似乎我遇到了问题

我创建了一个表单、静态类和自定义事件。我想要实现的是当我按下按钮时 Form 会调用静态类函数,然后 func 会不时引发一个事件来报告当前状态。 Form1 将监听事件是否引发,如果是,它将更改 label1 的文本

这是我目前所拥有的

public partial class Form1 : Form
{
    public EventHandler<Progress> progress; 

    public Form1()
    {
        InitializeComponent();
        progress += SetStatus;
    }

    private void SetStatus(object sender, Progress e)
    {
        label1.Text = e.Status;
    }

    private void button1_Click_1(object sender, EventArgs e)
    {
         TestClass.Func();
    }

 }

文件 2

class TestClass
{
    public static void Func()
    {
        //time consuming code
        Report status 
        // time consuming code
        report status
    }
}

public class Progress : EventArgs
{
    public string Status { get; private set; }

    private Progress() {}

    public Progress(string status)
    {
        Status = status;
    }
}

现在我不明白的是,我怎样才能从 TestClass 中引发一个事件,以便 Form1 可以处理事件并更改 label.Text

【问题讨论】:

  • 您的 TestClass 必须提供一个事件并且表单必须订阅它。
  • 是的,没关系,但我不明白如何从另一个班级发起活动
  • @Bill,你不能直接这样做。这是设计使然。如果你真的想要,你可以创建一个公共方法RaiseProgress() 来引发事件,但我不确定这是一个好主意。

标签: c# events


【解决方案1】:

您尚未创建活动。要做到这一点:

public event EventHandler<Progress> Progress;

然后,您可以在声明为普通函数或委托的类中调用Progress

Progress(this, new Progress("some status"));

因此,如果您想在TestClass 中报告进度,则该事件也应该在那里并且它也应该是静态的。您可以像这样从您的表单中订阅它:

TestClass.Progress += SetStatus;

另外,您可能应该将Progress 重命名为ProgressEventArgs,以便清楚它是什么。

【讨论】:

    【解决方案2】:

    这是一种创建自定义事件并引发它们的简单方法。您在要抛出的类中创建一个委托和一个事件。然后从代码的另一部分订阅事件。您已经有了一个自定义事件参数类,因此您可以在此基础上构建其他事件参数类。 N.B:我没有编译这段代码。

    public partial class Form1 : Form
    {
        private TestClass _testClass;
        public Form1()
        {
            InitializeComponent();
            _testClass = new TestClass();
            _testClass.OnUpdateStatus += new TestClass.StatusUpdateHandler(UpdateStatus);
        }
    
        private void UpdateStatus(object sender, ProgressEventArgs e)
        {
            SetStatus(e.Status);
        }
    
        private void SetStatus(string status)
        {
            label1.Text = status;
        }
    
        private void button1_Click_1(object sender, EventArgs e)
        {
             TestClass.Func();
        }
    
    }
    
    public class TestClass
    {
        public delegate void StatusUpdateHandler(object sender, ProgressEventArgs e);
        public event StatusUpdateHandler OnUpdateStatus;
    
        public static void Func()
        {
            //time consuming code
            UpdateStatus(status);
            // time consuming code
            UpdateStatus(status);
        }
    
        private void UpdateStatus(string status)
        {
            // Make sure someone is listening to event
            if (OnUpdateStatus == null) return;
    
            ProgressEventArgs args = new ProgressEventArgs(status);
            OnUpdateStatus(this, args);
        }
    }
    
    public class ProgressEventArgs : EventArgs
    {
        public string Status { get; private set; }
    
        public ProgressEventArgs(string status)
        {
            Status = status;
        }
    }
    

    【讨论】:

    • link 中谈到了事件的另一个重要点。要取消订阅事件,它是 -= 而不是 +=。例如 _testClass.OnUpdateStatus -= UpdateStatus;在哪里放置退订代码是另一个问题,但还有其他堆栈溢出问题可以解决这个问题。
    • 名称选择不当。这两个类都有一个UpdateStatus 函数,这可能会造成混淆。为了清楚起见,我宁愿在Form1 中使用不同的名称(如ProcessUpdateStatusEvent)调用该函数。否则很好的解释。
    • ProgressEventArgs 上的只读属性无法编译。删除此属性,效果很好。
    • 这段代码比必要的更令人困惑,而且不是线程安全的。查看codeblog.jonskeet.uk/2015/01/30/… 的解释和@Volomik 的答案。
    【解决方案3】:

    就像已经提到的进度字段需要关键字事件

    public event EventHandler<Progress> progress;
    

    但我认为这并不是您真正想要举办活动的地方。我认为您实际上想要TestClass 中的事件。下面看起来如何? (我实际上从未尝试过设置静态事件,所以我不确定以下是否会编译,但我认为这让您了解了您应该瞄准的模式。)

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            TestClass.progress += SetStatus;
        }
    
        private void SetStatus(object sender, Progress e)
        {
            label1.Text = e.Status;
        }
    
        private void button1_Click_1(object sender, EventArgs e)
        {
             TestClass.Func();
        }
    
     }
    
    public class TestClass
    {
        public static event EventHandler<Progress> progress; 
    
        public static void Func()
        {
            //time consuming code
            OnProgress(new Progress("current status"));
            // time consuming code
            OnProgress(new Progress("some new status"));            
        }
    
        private static void OnProgress(EventArgs e) 
        {
           if (progress != null)
              progress(this, e);
        }
    }
    
    
    public class Progress : EventArgs
    {
        public string Status { get; private set; }
    
        private Progress() {}
    
        public Progress(string status)
        {
            Status = status;
        }
    }
    

    【讨论】:

      【解决方案4】:

      事件在 C# 中非常简单,但我认为 MSDN 文档使它们非常混乱。通常,您看到的大多数文档都讨论了使类继承自 EventArgs 基类,并且有 a reason 。然而,这并不是最简单的活动方式,对于想要快速简单的事情的人来说,在时间紧迫的情况下,使用Action 类型是您的选择。

      创建事件并订阅它们

      1。在您的class 声明之后立即在您的班级上创建您的活动。

      public event Action<string,string,string,string>MyEvent;
      

      2。在您的类中创建您的事件处理程序类方法。

      private void MyEventHandler(string s1,string s2,string s3,string s4)
      {
        Console.WriteLine("{0} {1} {2} {3}",s1,s2,s3,s4);
      }
      

      3。现在,当您的类被调用时,告诉它将事件连接到您的新事件处理程序。使用 += 运算符的原因是因为您将特定的事件处理程序附加到事件中。您实际上可以使用多个单独的事件处理程序来执行此操作,并且当引发事件时,每个事件处理程序将按照您添加它们的顺序进行操作。

      class Example
      {
        public Example() // I'm a C# style class constructor
        {
          MyEvent += new Action<string,string,string,string>(MyEventHandler);
        }
      }
      

      4。现在,当您准备好时,在您的类代码中的某处触发(也称为引发)事件,如下所示:

      MyEvent("wow","this","is","cool");
      

      运行它的最终结果是控制台会发出“哇,这太酷了”。如果您用日期或序列更改“酷”,并多次运行此事件触发器,您会看到结果以 FIFO 序列出现,就像事件正常运行一样。

      在这个例子中,我传递了 4 个字符串。但是您可以将它们更改为任何可接受的类型,或使用更多或更少的类型,甚至删除 &lt;...&gt; 并且不将任何内容传递给您的事件处理程序。

      同样,如果您有多个自定义事件处理程序,并使用 += 运算符将它们全部订阅到您的事件,那么您的事件触发器将按顺序调用它们。

      识别事件调用者

      但是,如果您想在事件处理程序中识别此事件的调用者怎么办?如果您想要一个事件处理程序根据引发/触发事件的人对条件做出反应,这很有用。有几种方法可以做到这一点。以下是按运行速度按顺序显示的示例:

      选项 1。(最快)如果您已经知道它,则在触发它时将名称作为文字字符串传递给事件处理程序。

      选项 2.(有点快)将其添加到您的类中并从调用方法中调用它,然后在触发它时将该字符串传递给事件处理程序:

      private static string GetCaller([System.Runtime.CompilerServices.CallerMemberName] string s = null) => s;
      

      选项 3.(最慢但仍然很快)在您的事件处理程序中,当您触发它时,使用以下命令获取调用方法名称字符串:

      string callingMethod = new System.Diagnostics.StackTrace().GetFrame(1).GetMethod().ReflectedType.Name.Split('<', '>')[1];
      

      退订事件

      您可能会遇到这样一种情况,即您的自定义事件有多个事件处理程序,但您想从事件处理程序列表中删除一个特殊事件。为此,请像这样使用-= 运算符:

      MyEvent -= MyEventHandler;
      

      不过,请注意这一点。如果您这样做并且该事件不再具有任何事件处理程序,并且您再次触发该事件,它将引发异常。 (当然,例外情况,您可以使用 try/catch 块进行捕获。)

      清除所有事件

      好的,假设您已经完成了事件,并且您不想再处理任何事情。只需将其设置为 null,如下所示:

      MyEvent = null;
      

      退订事件的注意事项也在这里。如果您的自定义事件处理程序不再有任何事件,而您再次触发它,您的程序将抛出异​​常。

      【讨论】:

      • 作为一个正在寻找“快速简便”解决方案的人,我希望找到这个答案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-04
      • 2010-10-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多