小编典典

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

c#

我天生就是一个网络和后端程序员。通常我会尝试使Windows程序无效。现在,我必须成为WPF客户。

我有一个后台任务,每一次都会引发一个事件。(它像轮询程序一样工作,并且当满足条件时会引发事件)。作为我的菜鸟,我编写了此事件附带的代码以更新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;
                          });
    }

这行得通,但这不是唯一的事件,因此使我的代码很难看。有更好的方法可以做到这一点吗?


阅读 354

收藏
2020-05-19

共1个答案

小编典典

关于此:

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

是的 ,除非您理解并接受 WPF的心态,
否则基于WPF的代码肯定会非常恐怖。

基本上,自定义逻辑(AKA业务逻辑或应用程序逻辑)与WPF UI之间的所有交互 都应声明性数据
绑定
的形式体现,而不是传统的命令式方法。

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

UserWindow.Visibility = Visibility.Hidden;

在代码中的任何位置,仅仅是因为引入诸如此类的操作使您的代码依赖于UI,因此只能在UI线程上执行。

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

<UserWindow Visibility="{Binding ShowUserWindow, Converter={my:BoolToVisibilityConverter}}">
   <!-- ... -->
</UserWindow>

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

请注意,为了正确地支持双向WPF数据绑定,您的ViewModels必须实现INotifyPropertyChangedinterface

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

因此,我们的第一步是让我们所有的ViewModels都从这样的类继承:

(摘自此答案):

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

一旦将 属性更改通知分派到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!!!
        }
    }
}

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

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

    DataContext = new UserViewModel(); //here we set the DataContext
}

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

同样的概念也适用于WPF中的一切。

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

您可以在链接以及上面链接的MSDN DataBinding页面中了解有关此内容的更多信息。

让我知道您是否需要更多详细信息。

2020-05-19