防止在WPF代码中使用Dispatcher.Invoke

Pat*_*ick 2 c# wpf

我本质上是一个网络和后端程序员.通常我尝试avaoid制作Windows程序.现在我必须创建一个WPF客户端.

我有一个后台任务,每隔一段时间就会举办一次活动.(它像轮询器一样工作,当满足标准时,会引发一个事件).Noob和我一样,我写了这个附加到事件的代码来更新UI.

    private void IsDisconnectedEvent()
    {
            UserWindow.Visibility = Visibility.Hidden;
            DisconnectWindow.Visibility = Visibility.Visible;
    }
Run Code Online (Sandbox Code Playgroud)

这给出了一个例外,因为我不在同一个线程上.经过一些谷歌搜索后,我发现我应该改变代码:

    private void IsDisconnectedEvent()
    {
        Dispatcher.Invoke(() =>
                          {
                              UserWindow.Visibility = Visibility.Hidden;
                              DisconnectWindow.Visibility = Visibility.Visible;
                          });
    }
Run Code Online (Sandbox Code Playgroud)

这是有效的,但这不是唯一的事件,因此使我的代码变得丑陋丑陋.有没有更好的方法来做到这一点?

Fed*_*gui 15

关于这个:

这是有效的,但这不是唯一的事件,因此使我的代码变得丑陋丑陋

是的,除非你理解并接受WPF心态,否则基于WPF的代码肯定会非常可怕.

基本上,您的自定义逻辑(AKA业务逻辑或应用程序逻辑)与WPF UI之间的所有交互都应Declarative DataBinding的形式显示,而不是传统的命令式方法.

这意味着应该没有这样的东西:

UserWindow.Visibility = Visibility.Hidden;
Run Code Online (Sandbox Code Playgroud)

代码中的任何地方,只是因为引入类似的东西会使您的代码依赖于UI,因此只能在UI线程上执行.

相反,WPF的方法是声明Visibility性地将UI元素(IN XAML)的属性DataBind转换为可以从外部操作的相关bool属性,如下所示:

<UserWindow Visibility="{Binding ShowUserWindow, Converter={my:BoolToVisibilityConverter}}">
   <!-- ... -->
</UserWindow>
Run Code Online (Sandbox Code Playgroud)

然后,您需要创建一个包含UI期望绑定到的属性的相关类.这称为ViewModel.

请注意,为了正确支持双向WPF数据绑定,您的ViewModel必须实现该INotifyPropertyChanged接口.

这样做时,将PropertyChanged来自该接口的事件编组到UI线程也很方便,这样您就不必再担心使用设置ViewModel的属性了Dispatcher.

因此,我们的第一步是让所有的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));
        }));
    }
}
Run Code Online (Sandbox Code Playgroud)

一旦我们对UI线程进行了属性更改通知调度,我们就可以继续创建一个适合的ViewModel,在这种情况下,UserWindow它适用于它和DataBinding的期望:

public class UserViewModel: PropertyChangedBase
{
    private bool _showUserWindow;
    public bool ShowUserWindow
    {
        get {return _showUserWindow; }
        set
        {
            _showUserWindow = value;
            OnPropertyChanged("ShowUserWindow"); //This is important!!!
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,您需要将Window的DataContext设置为其对应的ViewModel的实例.一种简单的方法是在Window的构造函数中:

public UserWindow() //Window's Constructor
{
    InitializeComponent();  //this is required.

    DataContext = new UserViewModel(); //here we set the DataContext
}
Run Code Online (Sandbox Code Playgroud)

正如您在此示例中所看到的,实际上不需要在过程代码中操纵UI元素的属性.这很好,不仅因为它解决了Thread Affinity问题(因为现在你可以ShowUserWindow从任何线程设置属性),而且因为它使你的ViewModel和逻辑与UI完全分离,因此可测试且更具可伸缩性.

同样的概念适用于WPF中的所有内容.

有一个细节,我需要提及的是,我利用的技术的结合MarkupExtension,并IValueConverter以减少涉及到使用转换器的XAML样板.

您可以在链接以及上面链接的MSDN DataBinding页面中阅读有关该内容的更多信息.

如果您需要更多详细信息,请告诉我.

  • 你描述它的方式听起来像我正在寻找的.使用Web应用程序,我可以构建明确分离关注点的MVC应用程序.我会去看看MVVM,我想它会让我更进一步...... (3认同)