【问题标题】:Prevent using Dispatcher.Invoke in WPF code防止在 WPF 代码中使用 Dispatcher.Invoke
【发布时间】:2014-02-19 14:40:50
【问题描述】:

我天生就是一名网络和后端程序员。通常我会尽量避免制作 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;
                          });
    }

这行得通,但这不是唯一的事件,因此使我的代码丑陋得可怕。有没有更好的方法来做到这一点?

【问题讨论】:

  • 您可以使用BackgroundWorker。除此之外,Dispatcher.Invoke() 是要走的路。此外,您可以将 Dispatcher 调用包装在一个方法中,例如PropagateChangesToUI(UIState newState) { Dispatcher.Invoke([...]); }
  • 当然,有很多机制可以更轻松地将代码编组到 UI 线程。它们专门用于各种任务,因此您使用的将取决于您在做什么。如果您正在更新进度,请使用IProgress,如果您在一段时间后调用代码,请使用DispatcherTimer,如果您在后台线程中执行CPU 密集型工作,您可以使用BackgroundWorker。以此类推。

标签: c# wpf


【解决方案1】:

关于这个:

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

是的,除非您理解并接受The WPF Mentality,否则您基于 WPF 的代码肯定会非常糟糕。

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

这意味着不应该是这样的:

UserWindow.Visibility = Visibility.Hidden;

在您的代码中的任何位置,仅仅因为引入类似的东西会使您的代码依赖于 UI,因此只能在 UI 线程上执行。

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

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

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

请注意,为了正确支持双向 WPF 数据绑定,您的 ViewModel 必须Implement the INotifyPropertyChanged interface

这样做时,还可以方便地将来自该接口的PropertyChanged 事件编组到 UI 线程,这样您就不必再担心使用Dispatcher.

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

(取自this answer):

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 元素的属性。这很好,不仅因为它解决了 Thread Affinity 问题(因为现在您可以从任何线程设置ShowUserWindow 属性),而且因为它使您的 ViewModels 和逻辑与 UI 和因此可测试且更具可扩展性。

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

我需要提及的一个细节是,我正在使用Combining MarkupExtension and IValueConverter 的技术来减少使用转换器所涉及的 XAML 样板。

您可以在链接以及上面链接的 MSDN DataBinding 页面中阅读更多相关信息。

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

【讨论】:

  • -1 来自我,这是一个很好的答案,但我不同意 wpf 完全与 mvvm 模式有关。 mvvm 是一种模式,就是这样,只是一种模式。 wpf 也可以与 mvp 模式一起使用,这也只是一种模式。当然 wpf 和 mvvm 比 wpf 和 mvp 更喜欢对方,但我仍然不同意你如此强迫 mvvm 模式。这就像说所有在winforms中开发或开发ui应用程序的人都是傻瓜和马:)呵呵
  • 你不知道什么是mvp?伙计...没有评论:)
  • @devhedgehog 我是在讽刺老兄......我知道 MVP 是什么,但我完全忽略了它,因为它与我完全无关......你现在明白了吗?
  • 你描述它的方式听起来像我正在寻找的东西。使用 Web 应用程序,我构建了具有明确关注点分离的 MVC 应用程序。我会去阅读有关 MVVM 的内容,我认为它会让我更进一步......
  • WPF 自动将所有 PropertyChanged 事件编组到 UI 线程,在 Dispatcher.BeginInvoke 中没有意义。但对于第二个主要 WPF 事件 - CollectionChanged 而言,情况并非如此。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-25
  • 1970-01-01
相关资源
最近更新 更多