相当于C#中的PostMessage与主线程与MVVM同步?

时间:2010-03-09 17:34:17

标签: c# wpf user-interface mvvm synchronization

我必须坚持不懈地寻找,因为这是我无法解决的另一个看似常见的问题。

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

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

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

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

混淆。如果我错过了一些关于互联网上发布的非常明显的内容,请继续给我一个口头抨击,我可能应该得到它。

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

3 个答案:

答案 0 :(得分:8)

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

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

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

答案 1 :(得分:4)

我认为你的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
}

答案 2 :(得分:1)

我有另一种似乎有效的方法,我只是想把它扔出去得到一些评论(如果有人甚至再读这个问题了!)。

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