尝试在WPF中激发事件时,EventHandler为null

本文关键字:EventHandler null 事件 WPF | 更新日期: 2023-09-27 18:02:57

问题


我在类LoginVM中创建了一个事件,如下所示:

public class LoginVM : INotifyPropertyChanged
{
    public event EventHandler<string> PasswordSet;
}

同样在这个类中,我有一段代码触发了这个事件:

public class LoginVM : INotifyPropertyChanged
{
    public event EventHandler<string> PasswordSet;
    private void PopulateLatestServer()
    {
        try
        {
            string SERVER_ID = Registry.GetValue(@"HKEY_CURRENT_USER'SOFTWARE'PODIA", "LATESTSERVER", null).ToString();
            BDO_SERVERS latestserver = SavedServers.Where(a => a.Server_ID == SERVER_ID).FirstOrDefault();
            setServerURL(latestserver.ServerURL, false);
            Username = latestserver.Username;
            PasswordSet(this, latestserver.Password);
        }
        catch (Exception)
        {
            Global.WriteLog("Could not find last logged in server.", EventLogEntryType.Warning);
        } 
    }
}

我有另一个名为LoginV的类,在那里我创建了该类的一个实例并订阅事件:

public partial class LoginV : MetroWindow
{
    public LoginV()
    {
        InitializeComponent();
        LoginVM _loginVM = new LoginVM();
        this.DataContext = _loginVM;
        _loginVM.PasswordSet += new EventHandler<string> (_loginVM_PasswordSet);
    }
    private void _loginVM_PasswordSet(object sender, string e)
    {
        passwordBox.Password = e;
    }

正如您可能知道的那样,我正试图触发从ViewModelView的事件,但每次我从ViewModel触发事件时,PasswordSet都为空,并且出现错误。

尝试在WPF中激发事件时,EventHandler为null

当事件没有监听器时,该事件为null。

private void RaisePasswordSet(String pass) {
    YourEventArgs args = new YourEventArgs(pass);
    if(PasswordSet != null) PasswordSet(this, args);
}

你的问题是,当你试图提出事件时,没有人会听

LoginVM的构造函数中初始化密码是个好主意。这时应该进行初始化。通常,您会设置一个属性,XAML中的绑定将负责更新控件。不需要在虚拟机上发生事件。但这是一个密码框,所以你不能绑定它,而你写的事件是正确的。

但在您的实现中,这会给您留下以下一系列事件:

  1. 创建VM
  2. VM在其构造函数--中引发PasswordSet,而不检查是否有任何处理程序
  3. 视图将VM分配给DataContext
  4. 视图将处理程序添加到PasswordSet事件

在第2步您会得到一个异常,因为您没有检查处理程序。

这是你要做的。

在虚拟机或任何地方,始终使用以下模式引发事件:

C#<= 5:

protected void OnPasswordSet(String e)
{
    var handler = PasswordSet;
    if (handler != null)
    {
        handler(this, e);
    }
}

C#6

protected void OnPasswordSet(String e) => PasswordSet?.Invoke(this, e);

任一:

private void PopulateLatestServer()
{
    try
    {
        string SERVER_ID = Registry.GetValue(@"HKEY_CURRENT_USER'SOFTWARE'PODIA", "LATESTSERVER", null).ToString();
        BDO_SERVERS latestserver = SavedServers.Where(a => a.Server_ID == SERVER_ID).FirstOrDefault();
        setServerURL(latestserver.ServerURL, false);
        Username = latestserver.Username;
        OnPasswordSet(latestserver.Password);
    }
    catch (Exception)
    {
        Global.WriteLog("Could not find last logged in server.", EventLogEntryType.Warning);
    } 
}

现在不能崩溃。或者至少与上次不同。

问题二:最初如何更新视图?

简单:取视图的PasswordSet处理程序中的任何内容,将其移动到受保护的方法中,并在两个位置调用它。这看起来有点冗长,因为它只是一行代码,但把东西卷成标签整齐的单元是很好的。如果代码更复杂,你绝对不想复制和粘贴它。如果一年后它变得更复杂,那么你就不必浪费任何时间重新解析旧代码。

public partial class LoginV : MetroWindow
{
    public LoginV()
    {
        InitializeComponent();
        LoginVM _loginVM = new LoginVM();
        this.DataContext = _loginVM;
        _loginVM.PasswordSet += new EventHandler<string> (_loginVM_PasswordSet);
        UpdatePassword();
    }
    protected void UpdatePassword()
    {
        passwordBox.Password = e;
    }
    private void _loginVM_PasswordSet(object sender, string e)
    {
        UpdatePassword();
    }

选项二:保留如上所示的OnPasswordSet(),但不要让视图在构造函数中手动更新密码,而是让LoginVM需要一个PasswordSet处理程序作为参数。我不会这样做;像这样的构造函数参数让我很紧张。但对我来说,这可能只是一种非理性的偏见。这种方式更清楚地表明了这样一个事实,即所有者需要处理该事件才能使用该类,并且"提供合适的事件处理程序"成为消费者使用该类所需要做的唯一的事情。出于显而易见的原因,使用者需要了解的类内部信息越少越好。柏拉图式的理想设计是,那些根本不思考的程序员可以对你的类做出随意的油嘴滑舌的假设,而不会在Stack Overflow上乞求别人大声给他们读文档。不过,我们永远也到不了那里。

public class LoginVM : INotifyPropertyChanged
{
    public LoginVM(EventHandler<string> passwordSetHandler)
    {
        if (passwordSetHandler != null)
        {
            PasswordSet += passwordSetHandler;
        }
        PopulateLatestServer();
    }
    //  If the consumer doesn't want to handle it right way, don't force the issue.
    public LoginVM()
    {
        PopulateLatestServer();
    }

第三个选项是为事件设置显式添加/删除,并在处理程序进入时引发事件:

public class LoginVM : INotifyPropertyChanged
{
    private event EventHandler<string> _passwordSet;
    public event EventHandler<string> PasswordSet
    {
        add
        {
            _passwordSet += value;
            //  ...or else save latestServer in a private field, so here you can call 
            //  OnPasswordSet(_latestServer.Password) -- but since it's a password, 
            //  best not to keep it hanging around. 
            PopulateLatestServer();
        }
        remove { _passwordSet -= value; }
    }