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

时间:2016-08-17 11:50:32

标签: c# wpf event-handling

问题

我在班级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都是null和错误。

2 个答案:

答案 0 :(得分:1)

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

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

您的问题是,当您尝试举办此活动时,没有人会听取

答案 1 :(得分:1)

最好像LoginVM一样在构造函数中初始化密码。这是初始化应该发生的时候。通常,您设置了一个属性,XAML中的绑定将负责更新控件。无需VM上的事件。但这是一个密码框,所以你不能绑定它,你写的事件就是The Right Thing。

但是在你的实现中,会留下这一系列事件:

  1. 创建VM
  2. VM在其构造函数中引发PasswordSet,而不检查是否有任何处理程序
  3. 视图将VM分配给DataContext
  4. View将处理程序添加到PasswordSet事件
  5. 您在步骤2中遇到异常,因为您没有检查处理程序。

    这是你做的。

    在VM或任何地方,始终使用此模式来引发事件:

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