我本质上是一名网络和后端程序员。通常我尝试avaoid制作Windows程序。现在我必须创建一个WPF客户端。
我有一个后台任务,每隔一段时间就会发一次事件。 (它像轮询器一样工作,当满足标准时,会引发一个事件)。 Noob和我一样,我写了这个附加到事件的代码来更新UI。
private void IsDisconnectedEvent()
{
UserWindow.Visibility = Visibility.Hidden;
DisconnectWindow.Visibility = Visibility.Visible;
}
这给出了一个例外,因为我不在同一个线程上。经过一些谷歌搜索后,我发现我应该用以下代码更改代码:
private void IsDisconnectedEvent()
{
Dispatcher.Invoke(() =>
{
UserWindow.Visibility = Visibility.Hidden;
DisconnectWindow.Visibility = Visibility.Visible;
});
}
这是有效的,但这不是唯一的事件,因此使我的代码变得丑陋丑陋。有更好的方法吗?
答案 0 :(得分:15)
关于这个:
这是有效的,但这不是唯一的事件,因此使我的代码 可怕的丑陋
是,除非您理解并接受The WPF Mentality,否则基于WPF的代码肯定会非常糟糕。
基本上,您的自定义逻辑(AKA业务逻辑或应用程序逻辑)与WPF UI 之间的所有交互都应以声明 DataBinding的形式显示为反对传统的命令式方法。
这意味着应该没有这样的东西:
UserWindow.Visibility = Visibility.Hidden;
代码中的任何地方,只是因为引入类似的东西会使您的代码依赖于UI,因此只能在UI线程上执行。
相反,WPF的方法是声明性地将UI元素的Visibility
属性( IN XAML )引用到可以从外部操作的相关bool属性,像这样:
<UserWindow Visibility="{Binding ShowUserWindow, Converter={my:BoolToVisibilityConverter}}">
<!-- ... -->
</UserWindow>
然后,您需要创建一个包含UI期望绑定到的属性的相关类。这称为ViewModel。
请注意,为了正确支持双向WPF数据绑定,您的ViewModel必须Implement the INotifyPropertyChanged
interface。
执行此操作时,将<{1}}事件从编组到UI线程也很方便,这样您就不必再担心设置ViewModel的属性了使用PropertyChanged
。
因此,我们的第一步是让所有的ViewModel继承自这样的类:
(取自this answer):
Dispatcher
一旦我们将属性更改通知发送到UI线程,我们就可以继续创建一个适合的ViewModel,在这种情况下,public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
//Raise the PropertyChanged event on the UI Thread, with the relevant propertyName parameter:
Application.Current.Dispatcher.BeginInvoke((Action) (() =>
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}));
}
}
和它的DataBinding期望:
UserWindow
最后,您需要将Window的DataContext设置为其对应的ViewModel的实例。一种简单的方法是在Window的构造函数中:
public class UserViewModel: PropertyChangedBase
{
private bool _showUserWindow;
public bool ShowUserWindow
{
get {return _showUserWindow; }
set
{
_showUserWindow = value;
OnPropertyChanged("ShowUserWindow"); //This is important!!!
}
}
}
正如您在本示例中所看到的,实际上无需来操作过程代码中的UI元素属性。这很好,不仅因为它解决了 Thread Affinity 问题(因为现在你可以从任何线程设置public UserWindow() //Window's Constructor
{
InitializeComponent(); //this is required.
DataContext = new UserViewModel(); //here we set the DataContext
}
属性),而且因为它使你的ViewModel和逻辑完全脱离了用户界面,因此可测试且更具可扩展性。
同样的概念适用于WPF中的所有内容。
我需要提到的一个细节是,我正在使用Combining MarkupExtension
and IValueConverter
技术,以减少使用转换器所涉及的XAML样板。
您可以在链接以及上面链接的MSDN DataBinding页面上阅读有关该内容的更多信息。
如果您需要更多详细信息,请与我们联系。