如何将UI Dispatcher传递给ViewModel

时间:2010-03-01 07:48:15

标签: .net wpf mvvm dispatcher

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

编辑:请注意,由于我的ViewModel可能是在后台线程中创建的,因此我不能在ViewModel的构造函数中执行Dispatcher.Current

16 个答案:

答案 0 :(得分:43)

我使用接口 IContext

抽象了Dispatcher
public interface IContext
{
   bool IsSynchronized { get; }
   void Invoke(Action action);
   void BeginInvoke(Action action);
}

这样做的好处是可以更轻松地对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);
    }
}

答案 1 :(得分:33)

为什么不使用

 System.Windows.Application.Current.Dispatcher.Invoke(
         (Action)(() => {ObservableCollectionMemeberOfVM.Add("xx"); } ));

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

答案 2 :(得分: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});
} 

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

答案 3 :(得分:14)

我让ViewModel将当前的调度程序存储为成员。

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

class MyViewModel
{
    readonly Dispatcher _dispatcher;
    public MyViewModel()
    {
        _dispatcher = Dispatcher.CurrentDispatcher;
    }
}

答案 4 :(得分:7)

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

必须在应用程序生命周期的早期阶段调用DispatcherHelper来初始化

DispatcherHelper.Initialize()(例如App_Startup)。然后,您可以使用以下调用运行任何委托(或lambda):

DispatcherHelper.CheckBeginInvokeOnUI(
        () =>
        {
           //Your code here
        });

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

答案 5 :(得分:4)

另一种常见模式(现在在框架中看到很多用途)是SynchronizationContext

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

答案 6 :(得分:3)

从WPF版本4.5开始,可以使用CurrentDispatcher

Dispatcher.CurrentDispatcher.Invoke(() =>
{
    // Do GUI related operations here

}, DispatcherPriority.Normal); 

答案 7 :(得分:1)

如果您只需要调度程序来修改另一个线程中的绑定集合,请在此处查看SynchronizationContextCollection http://kentb.blogspot.com/2008/01/cross-thread-collection-binding-in-wpf.html

运行良好,我发现的唯一问题是将View Models与SynchronizationContextCollection属性一起使用与ASP.NET同步上下文,但很容易解决。

HTH 萨姆

答案 8 :(得分:1)

嗨,也许我已经太晚了,因为你的第一篇帖子已经有8个月... 我在silverlight mvvm应用程序中遇到了同样的问题。我找到了这样的解决方案。对于我拥有的每个模型和视图模型,我还有一个名为controller的类。 像那样

public class MainView : UserControl  // (because it is a silverlight user controll)
public class MainViewModel
public class MainController

我的MainController负责模型和视图模型之间的命令和连接。在构造函数中,我实例化视图及其viewmodel,并将视图的datacontext设置为其viewmodel。

mMainView = new MainView();
mMainViewModel = new MainViewModel();
mMainView.DataContext = mMainViewModel; 

//(在我的命名约定中,我有成员变量的前缀m)

我的MainView类型也有一个公共属性。像那样

public MainView View { get { return mMainView; } }

(此mMainView是公共财产的局部变量)

现在我完成了。我只需要使用我的调度员为我的ui therad这样......

mMainView.Dispatcher.BeginInvoke(
    () => MessageBox.Show(mSpWeb.CurrentUser.LoginName));

(在这个例子中,我要求我的控制器获取我的sharepoint 2010登录名,但你可以做你需要的事情)

我们差不多完成你还需要在app.xaml中像这样定义你的root视觉

var mainController = new MainController();
RootVisual = mainController.View;

这对我的申请帮助了我。也许它也可以帮到你......

答案 9 :(得分:1)

您无需将UI Dispatcher传递给ViewModel。 UI Dispatcher可从当前应用程序单例中获得。

App.Current.MainWindow.Dispatcher

这将使ViewModel依赖于View。根据您的申请,可能会或可能不会。

答案 10 :(得分:1)

对于WPF和Windows商店应用程序使用: -

       System.Windows.Application.Current.Dispatcher.Invoke((Action)(() => {ObservableCollectionMemeberOfVM.Add("xx"); } ));

保持对GUI调度程序的引用并不是正确的方法。

如果不起作用(例如windows phone 8应用程序),则使用: -

       Deployment.Current.Dispatcher

答案 11 :(得分:0)

如果您使用uNhAddIns,则可以轻松实现异步行为。看看here

我认为需要一些修改才能使它适用于温莎城堡(没有uNhAddIns)

答案 12 :(得分:0)

我找到了另一种(最简单的)方式:

添加以查看应在Dispatcher中调用的模型操作:

public class MyViewModel
{
    public Action<Action> CallWithDispatcher;

    public void SomeMultithreadMethod()
    {
        if(CallWithDispatcher != null)
            CallWithDispatcher(() => DoSomethingMetod(SomeParameters));
    }
}

在视图构造函数中添加此动作处理程序:

    public View()
    {
        var model = new MyViewModel();

        DataContext = model;
        InitializeComponent();

        // Here 
        model.CallWithDispatcher += act => _taskbarIcon.Dispatcher
            .BeginInvoke(DispatcherPriority.Normal, act) ;
    }

现在您没有测试问题,而且很容易实现。 我已将其添加到我的site

答案 13 :(得分:0)

也许我对这次讨论有点迟,但我发现了一篇不错的文章https://msdn.microsoft.com/en-us/magazine/dn605875.aspx

有1段

  

此外,View层外的所有代码(即ViewModel   和模型层,服务等)不应该依赖任何类型   绑定到特定的UI平台。任何直接使用Dispatcher   (WPF / Xamarin / Windows Phone / Silverlight),CoreDispatcher(Windows   Store)或ISynchronizeInvoke(Windows窗体)是一个坏主意。   (SynchronizationContext稍微好一些,但几乎没有。)For   例如,互联网上有很多代码可以做一些   异步工作,然后使用Dispatcher更新UI;更多   便携式和不太麻烦的解决方案是使用等待异步   在不使用Dispatcher的情况下工作和更新UI。

假设您可以正确使用async / await,这不是问题。

答案 14 :(得分:0)

使用

访问应用程序调度程序时,无需通过调度程序
Dispatcher dis = Application.Current.Dispatcher 

答案 15 :(得分:-1)

我的一些WPF项目遇到了同样的情况。在我的MainViewModel(Singleton实例)中,我得到了我的CreateInstance()静态方法接受调度程序。并且从View中调用create实例,以便我可以从那里传递Dispatcher。 ViewModel测试模块调用CreateInstance()无参数。

但是在一个复杂的多线程场景中,在View端有一个接口实现总是很好的,以便获得当前Window的正确Dispatcher。