简单的自定义事件


75

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

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

这是我到目前为止所拥有的

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可以处理事件并更改标签。


您的TestClass必须提供一个事件,而Form必须订阅该事件。
Henk Holterman


是的,那很好,但是我不明白我该如何从另一个班级
Bill

1
@Bill,您不能直接这样做。这是设计使然。如果确实愿意,可以创建RaiseProgress()引发事件的公共方法,但是我不确定这是个好主意。
svick 2011年

Answers:


138

这是创建自定义事件并引发它们的简便方法。您将在抛出的类中创建一个委托和一个事件。然后从代码的另一部分订阅该事件。您已经有一个自定义事件参数类,因此可以在此基础上构建其他事件参数类。注意:我尚未编译此代码。

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

3
链接中讨论了事件的另一个重要点。要取消订阅事件,它是-=而不是+ =。例如_testClass.OnUpdateStatus-= UpdateStatus; 退订代码的放置位置是另一个问题,但是还有其他堆栈溢出问题可以解决。
themartinmcfly 2011年

1
名称选择不佳。这两个类都有一个UpdateStatus令人困惑的功能。为了清楚起见,我宁愿Form1使用其他名称来调用该函数ProcessUpdateStatusEvent。否则很好的解释。
Bogdan Alexandru

ProgressEventArgs上的readonly属性不会编译。删除此属性,效果很好。
洛伦·肖

此代码比必要的代码更加混乱,而且不是线程安全的。请查看此说明codeblog.jonskeet.uk/2015/01/30/…以及@Volomike的答案。
Xan-Kun Clark-Davis

20

您尚未创建活动。为此,请写:

public event EventHandler<Progress> Progress;

然后,您可以Progress像在普通函数或委托中一样在声明了该类的类中进行调用:

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

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

TestClass.Progress += SetStatus;

另外,您可能应该重命名ProgressProgressEventArgs,以便清楚地知道它是什么。


15

在C#中,事件非常容易,但是我认为MSDN文档使它们非常混乱。通常,您看到的大多数文档都讨论了使类从EventArgs基类继承,这是有原因的。但是,这不是进行事件的最简单方法,对于想要快速简便地进行某些事情的人来说,时间紧迫,使用这种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个字符串。但是您可以将其更改为任何可接受的类型,或使用更多或更少的类型,甚至删除<...>out并将任何内容都不传递给事件处理程序。

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

识别事件调用者

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

选项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;

取消订阅事件的注意事项也与此相同。如果您的自定义事件处理程序不再具有任何事件,然后再次触发它,则程序将引发异常。


1
作为正在寻找“快速而简单”的解决方案的人,我希望找到这个答案。
亚瑟·赫伯特

9

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

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;
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.