【问题标题】:Equivalent of PostMessage in C# to synchronize with the main thread with MVVM?相当于C#中的PostMessage用MVVM与主线程同步?
【发布时间】:2011-01-25 13:11:39
【问题描述】:

我一定是搜索迟缓,因为这是另一个我无法解决的看似常见的问题。

这是我的问题——我正在使用 WPF 和 MVVM,并且我有一个在模型中执行的状态机。如果发生错误,我需要将信息传递给 ViewModel 以显示错误。这部分似乎工作正常。当用户单击所需的行为时,模型中的代码会继续执行,并查看用户与之交互的对象以确定下一步要做什么。

问题在于模型需要重新加载一个文件,该文件会使用该文件的内容更新 GUI。因为模型是在一个线程中执行的,你可以想象我接下来要问的问题——你到底是如何与 GUI 正确同步的?在 MFC 中,我会使用 SendMessage 或 PostMessage 来完成 GUI 更新。

我有 read articles for WinForms 建议在必要时使用 InvokeRequired 自动调用 BeginInvoke。我实际上不知道 BeginInvoke 会完成我想要的,所以我被鼓励去学习这个。

我实际上如何从我的模型中调用 BeginInvoke?这个方法甚至适用于 WPF 吗?我继续实现了一个委托,然后调用了 Invoke,但我得到了同样的错误,告诉我不能从这个线程修改集合。我也尝试过 BeginInvoke,但我认为这也行不通,因为它无论如何都会从不同的线程启动。

困惑。如果我错过了在互联网上发布的非常明显的内容,请继续对我进行口头抨击,我可能应得的。

编辑 - 我可能还应该补充一点,我正在寻找除计时器或基于 BackgroundWorker 的解决方案之外的其他东西,除非这是在 WPF 中解决此问题的唯一方法/MVVM。另外,我想知道是否有任何 MVVM 工具包已经具备此类功能......

【问题讨论】:

  • 澄清一下:我假设您只是直接在委托上调用 Invoke() 而不是 Dispatcher.Invoke()?第一个将简单地在同一个线程上执行委托,第二个将编组对 Dispatcher 线程的调用(根据使用 ViewModel 的应用程序类型,可能存在也可能不存在)。
  • 韦恩,你是对的。我只是粗略地关注这篇文章,我想知道它是针对 WinForms 的事实是否就是他们的示例有效的原因?无论如何,我认为 Franci 是对的,我需要以任何必要的方式传递 Dispatcher。 :) 谢谢你的时间。希望我能尽快解决这个问题。
  • 您可以利用 MVVMLights Messenger 类向 GUI 发送消息而无需通过事件。如果背后的代码触及任何实际 UI,您仍然必须通过 Dispatcher。我有点困惑,因为数据绑定已经通过调度程序,为什么这是一个问题。如果您的 Thread 更新了 View 上的绑定属性,它应该会自动编组到 UI 中而不会出现问题。

标签: c# wpf user-interface mvvm synchronization


【解决方案1】:

如果您想在 WPF 中将一些工作从后台线程安排到 UI 线程,请使用DispatcherObject。这是一篇关于如何Build More Responsive Apps with the Dispatcher 的好文章。

更新:请注意,如果您使用事件从 Model 向 ViewModel 发送通知,您仍然需要在某处切换到 UI 线程。该开关应该在 Model 还是 ViewModel 中是一个很好的设计讨论,但它与您的问题正交。

该事件将在相应的 Dispatcher 线程上引发。由于您需要访问 UI 线程,因此您需要使用在 UI 线程上创建的 Dispatcher。最简单的方法是在其中一个 UI 元素上使用 DispatcherObject.Dispatcher 属性。另一种方法是在您的模型或视图模型中创建一个。如果您是设计纯粹主义者,我建议您在模型中创建 Dispatcher 并将调用分派回 UI 线程,然后再引发 ViewModel 正在侦听的事件。这样,所有线程切换和管理都包含在您的模型中,并且 ViewModel 仅在 UI 线程上作为单线程工作。

【讨论】:

  • 真有趣,我刚刚读了那篇文章,正在考虑 DispatcherObject。不过,Wayne 提出了一个很好的观点,我应该考虑使用事件来处理这个问题。
  • 我想我将不得不试一试 DispatcherObject。您的观点是有效的,无论 GUI 架构如何,我仍然需要一种与主线程同步的方法。你能解释一下为什么上面韦恩的例子适用于属性吗?我只是为了向自己证明它确实从不同的线程中完成所有事情,而且确实如此,但它仍然可以按照人们的意愿工作。
  • 我应该在评论之前刷新我的浏览器。不管怎样,谢谢你的建议。如果我理解正确的话,这是一个有趣的想法。建议不要从视图中获取 Dispatcher 属性并将其传递下来(这似乎违反了 MVVM),而是建议在我的模型中创建对 Dispatcher 的引用,然后基本上通过回调接口(它被传递给模型的构造函数),Model可以请求View的Dispatcher吗?我假设 View 在创建时也应该传递对 ViewModel 的引用?
  • 问题已解决。我只是让控制器获得对 View 调度程序的引用(从 View -> VM -> Model 传递),然后一切正常。谢谢,弗朗西,也感谢韦恩试图帮助我解决这个问题。
【解决方案2】:

我认为你的 ViewModel 真的不应该知道关于 View 的任何事情,包括它是否是 WPF UI,或者那个 UI 是否甚至有 Dispatcher 线程的概念,所以红旗应该尽快飘扬当您开始在 ViewModel 中编写代码时,它会尝试 CheckAccess() 或 InvokeRequired 以便将一些代码编组到 UI 线程。相反,我会让模型引发一个 View 可以监听并相应更新自身的事件,或者让 ViewModel 公开一个 View 简单绑定的属性(例如 bool FileIsLoading),以便检测和显示模型是什么异步执行,ViewModel 有责任确保该属性的值是准确的。

例如:

public partial class MainWindow : Window {
    private ViewModel _model = new ViewModel();

    public MainWindow() {
        InitializeComponent();
        DataContext = _model;
    }

    private void Button_Click(object sender, RoutedEventArgs e) {
        _model.Run();
    }
}


<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button Click="Button_Click"
                Content="Run"
                IsEnabled="{Binding IsIdle}" />
    </Grid>
</Window>



public class ViewModel : INotifyPropertyChanged {

    private bool _isIdle = true;

    public bool IsIdle {
        get { return _isIdle; }
        set {
            _isIdle = value;
            OnPropertyChanged("IsIdle");
        }
    }

    public void Run() {
        ThreadPool.QueueUserWorkItem((state) => {
            IsIdle = false;
            Thread.Sleep(10000);
            IsIdle = true;
        });
    }

    #region INotifyPropertyChanged Implementation

    protected void OnPropertyChanged(string propertyName) {
        PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
        if (propertyChanged != null) {
            propertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion
}

【讨论】:

  • 确实如此,低层的东西不应该知道高层的东西。当我在开发我的应用程序时,我会尽量记住这一点。在这种特殊情况下,我完全没有注意到我正在修改 ViewModel 中的内容,这可能是因为我正在处理的这个特定组件将 Model 和 ViewModel 放在一个类中(以节省 alpha 的时间)。我认为事件是要走的路,因为您关于让 VM 公开属性的建议正是我正在做的,但最终我无法在没有事件的情况下从线程更新该属性。
  • 好吧,从技术上讲,即使公开/更改属性也会引发事件——您需要确保您的 ViewModel 实现 INotifyPropertyChanged 以通知 WPF 数据绑定管道您的属性已更改。
  • 数据绑定很好,我已经有一段时间了。所以也许我应该确认一下——当我的 worker thread 引发事件并且我的 ViewModel 已注册事件处理程序时,您对基于事件的解决方案的建议将起作用?
  • 工作线程是从ViewModel发起的?在那种情况下,不涉及视图,对吗?您的问题是“我如何告诉 View 后台工作已完成?”为此,我不明白为什么 ViewModel 中的简单(数据绑定)属性是不够的。我错过了什么吗?
  • 工作线程确实是从 ViewModel 发起的。这是怎么回事->用户单击按钮,执行ICommand并调用VM,然后调用Model。该模型产生一个工作线程并做它的事情。该模型最终失败并需要恢复。然后,恢复操作需要刷新 GUI。我希望模型能够以某种方式异步通知 GUI 进行更新,目前通过让 ViewModel 更新数据绑定到 View 的 ObservableCollection 来尝试这样做。顺便说一句,该事件未与主 GUI 线程同步。
【解决方案3】:

我有另一种似乎可行的方法,我只是想把它扔出去以获得一些 cmets(如果有人甚至不再阅读这个问题!)。

我开始使用 MVVM Light Toolkit 的 Messenger 类,它似乎对我很有效。例如,我们以 ProgressBar 为例。我在 ViewModel 中注册了两条消息,用于设置进度值和最大进度。然后在我的模型中,当它设置任务和整个流程时,它会发送这些消息。当 VM 收到消息时,它只会更新数据绑定值,我的 GUI 会自动更新!这非常简单,但我想知道你们对这种方法的看法。有没有其他人在没有事故的情况下这样做?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-31
    • 1970-01-01
    • 2013-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多