如何将UI Dispatcher传递给ViewModel

bit*_*onk 69 .net wpf dispatcher mvvm

我应该能够访问属于View 的Dispatcher,我需要将它传递给ViewModel.但是View应该对ViewModel一无所知,那么你如何传递它呢?引入一个接口或者不是将它传递给实例创建一个将由View编写的全局调度程序单例?您如何在MVVM应用程序和框架中解决这个问题?

编辑:请注意,因为我的ViewModel可能是在后台线程中创建的,所以我不能只Dispatcher.Current在ViewModel的构造函数中创建.

Mat*_*ias 45

我使用接口IContext抽象了Dispatcher :

public interface IContext
{
   bool IsSynchronized { get; }
   void Invoke(Action action);
   void BeginInvoke(Action action);
}
Run Code Online (Sandbox Code Playgroud)

这样做的好处是可以更轻松地对ViewModel进行单元测试.
我使用MEF(Managed Extensibility Framework)将接口注入我的ViewModels.另一种可能性是构造函数参数.但是,我更喜欢使用MEF注射.

更新(例如来自评论中的pastebin链接):

public sealed class WpfContext : IContext
{
    private readonly Dispatcher _dispatcher;

    public bool IsSynchronized
    {
        get
        {
            return this._dispatcher.Thread == Thread.CurrentThread;
        }
    }

    public WpfContext() : this(Dispatcher.CurrentDispatcher)
    {
    }

    public WpfContext(Dispatcher dispatcher)
    {
        Debug.Assert(dispatcher != null);

        this._dispatcher = dispatcher;
    }

    public void Invoke(Action action)
    {
        Debug.Assert(action != null);

        this._dispatcher.Invoke(action);
    }

    public void BeginInvoke(Action action)
    {
        Debug.Assert(action != null);

        this._dispatcher.BeginInvoke(action);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 是的,可以在实施中提供任何示例吗?谢谢. (3认同)
  • 您能否提供一个 wpf UserControl 中的实现示例? (2认同)
  • 迟到总比没有好;-)尝试以下方法:http://pastebin.com/eXTQf9vm (2认同)

Vit*_*nov 38

你为什么不用

 System.Windows.Application.Current.Dispatcher.Invoke(
         (Action)(() => {ObservableCollectionMemeberOfVM.Add("xx"); } ));
Run Code Online (Sandbox Code Playgroud)

而不是保持对GUI调度程序的引用.


Jak*_*sen 18

您实际上可能不需要调度员.如果将viewmodel上的属性绑定到视图中的GUI元素,则WPF绑定机制会使用调度程序自动将GUI更新封送到GUI线程.


编辑:

此编辑是对Isak Savo评论的回应.

在Microsoft的处理绑定到属性的代码中,您将找到以下代码:

if (Dispatcher.Thread == Thread.CurrentThread)
{ 
    PW.OnPropertyChangedAtLevel(level);
} 
else 
{
    // otherwise invoke an operation to do the work on the right context 
    SetTransferIsPending(true);
    Dispatcher.BeginInvoke(
        DispatcherPriority.DataBind,
        new DispatcherOperationCallback(ScheduleTransferOperation), 
        new object[]{o, propName});
} 
Run Code Online (Sandbox Code Playgroud)

此代码将对线程UI线程的任何UI更新进行编组,以便即使您从另一个线程更新参与绑定的属性,WPF也会自动将调用序列化到UI线程.

  • 但是有很多情况,你可能需要这样做,想象一下绑定到UI的ObservableCollection,你试图从工作线程调用_collection.Add() (9认同)
  • 目前,我们需要调度程序的唯一目的是向ObservableCollection添加项目. (3认同)
  • 嗨Isak.你理解正确但你错了.如果您通过Microsoft的WPF绑定代码进行调试(或使用Reflector查看它),您将看到代码检查您是否在GUI线程上,如果不是,它将使用Dispatcher在GUI线程上更新.我不知道它是否适用于ObservableCollection,但它适用于"普通"属性.我写了一篇博客文章(丹麦语).在博客条目的底部显示Microsoft的代码:http://www.dotninjas.dk/post/Flere-trade-og-binding-i-WPF.aspx (3认同)

And*_*erd 15

我得到ViewModel将当前的调度程序存储为成员.

如果ViewModel是由视图创建的,则您知道创建时的当前调度程序将是View的调度程序.

class MyViewModel
{
    readonly Dispatcher _dispatcher;
    public MyViewModel()
    {
        _dispatcher = Dispatcher.CurrentDispatcher;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 但单元测试存在问题.你如何编写这种代码并对你的虚拟机进行单元测试? (4认同)

dot*_*NET 7

从MVVM Light 5.2开始,该库现在DispatcherHelperGalaSoft.MvvmLight.Threading命名空间中包含一个类,该类公开一个CheckBeginInvokeOnUI()接受委托并在UI线程上运行它的函数.如果ViewModel正在运行一些影响UI元素绑定的VM属性的工作线程,则非常方便.

DispatcherHelper必须通过DispatcherHelper.Initialize()在申请生命的早期阶段致电来初始化(例如App_Startup).然后,您可以使用以下调用运行任何委托(或lambda):

DispatcherHelper.CheckBeginInvokeOnUI(
        () =>
        {
           //Your code here
        });
Run Code Online (Sandbox Code Playgroud)

请注意,该类在GalaSoft.MvvmLight.Platform库中定义,当您通过NuGet添加时,默认情况下不会引用该类.您必须手动添加对此lib的引用.


小智 5

另一种常见模式(现在在框架中非常有用)是SynchronizationContext

它使您能够同步和异步调度。您还可以在当前线程上设置当前 SynchronizationContext,这意味着它很容易被模拟。WPF 应用程序使用 DispatcherSynchronizationContext。WCF 和 WF4 使用 SynchronizationContext 的其他实现。

  • 我认为这是最好和最简单的方法。您可以将 ViewModel 的 SynchronizationContext 默认为创建 ViewModel 的线程的当前上下文,并且,如果 UserControl 需要更改它,他们可以随意更改它。 (2认同)