问题
我在类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_USERSOFTWAREPODIA", "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;
}
正如您可能知道的那样,我正试图触发从ViewModel
到View
的事件,但每次我从ViewModel
触发事件时,PasswordSet
都为空,并且出现错误。
当事件没有监听器时,该事件为null。
private void RaisePasswordSet(String pass) {
YourEventArgs args = new YourEventArgs(pass);
if(PasswordSet != null) PasswordSet(this, args);
}
你的问题是,当你试图提出事件时,没有人会听,。
在LoginVM
的构造函数中初始化密码是个好主意。这时应该进行初始化。通常,您会设置一个属性,XAML中的绑定将负责更新控件。不需要在虚拟机上发生事件。但这是一个密码框,所以你不能绑定它,而你写的事件是正确的。
但在您的实现中,这会给您留下以下一系列事件:
- 创建VM
- VM在其构造函数--中引发PasswordSet,而不检查是否有任何处理程序
- 视图将VM分配给DataContext
- 视图将处理程序添加到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_USERSOFTWAREPODIA", "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; }
}