76

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

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

4

16 回答 16

47

我已经使用接口IContext抽象了 Dispatcher :

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

这样做的好处是您可以更轻松地对 ViewModel 进行单元测试。
我使用 MEF(托管可扩展性框架)将接口注入到我的 ViewModel 中。另一种可能性是构造函数参数。但是,我更喜欢使用 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);
    }
}
于 2010-03-01T08:47:06.520 回答
46

你为什么不使用

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

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

于 2012-05-28T20:26:40.957 回答
19

您可能实际上不需要调度程序。如果将视图模型上的属性绑定到视图中的 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 线程的调用。

于 2010-03-01T08:53:01.983 回答
15

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

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

class MyViewModel
{
    readonly Dispatcher _dispatcher;
    public MyViewModel()
    {
        _dispatcher = Dispatcher.CurrentDispatcher;
    }
}
于 2010-03-01T07:54:09.770 回答
7

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

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

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

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

于 2016-01-08T13:25:26.803 回答
5

另一个常见的模式(现在在框架中得到了广泛的应用)是SynchronizationContext

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

于 2011-06-24T20:15:01.847 回答
3

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

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

}, DispatcherPriority.Normal); 
于 2016-05-17T15:16:50.537 回答
1

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

效果很好,我发现的唯一问题是当使用具有 ASP.NET 同步上下文的 SynchronizationContextCollection 属性的视图模型时,但很容易解决。

HTH 山姆

于 2010-10-04T09:01:22.927 回答
1

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

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

我的 MainController 负责模型和视图模型之间的指挥和连接。在构造函数中,我实例化了视图及其视图模型,并将视图的数据上下文设置为其视图模型。

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 中定义您的根视觉对象

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

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

于 2010-11-27T15:59:19.777 回答
1

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

App.Current.MainWindow.Dispatcher

这将使您的 ViewModel 依赖于 View。根据您的应用程序,这可能会也可能不会。

于 2011-10-26T17:53:33.917 回答
1

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

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

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

如果这不起作用(例如在 windows phone 8 应用程序的情况下),则使用:-

       Deployment.Current.Dispatcher
于 2014-04-30T06:47:38.077 回答
0

如果您使用uNhAddIns,您可以轻松地做出异步行为。看看这里

而且我认为需要进行一些修改才能使其在温莎城堡上运行(没有 uNhAddIns)

于 2010-10-23T04:56:44.853 回答
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) ;
    }

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

于 2011-09-14T11:29:06.283 回答
0

当您可以使用访问应用程序调度程序时,无需传递调度程序

Dispatcher dis = Application.Current.Dispatcher 
于 2020-05-31T02:39:13.033 回答
-1

我的一些 WPF 项目遇到了同样的情况。在我的 MainViewModel(单例实例)中,我的 CreateInstance() 静态方法采用了调度程序。并且从视图调用创建实例,以便我可以从那里传递调度程序。ViewModel 测试模块无参数调用 CreateInstance()。

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

于 2010-03-01T09:03:07.443 回答
-1

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

有1段

此外,View 层之外的所有代码(即 ViewModel 和 Model 层、服务等)不应依赖于与特定 UI 平台绑定的任何类型。任何直接使用 Dispatcher (WPF/Xamarin/Windows Phone/Silverlight)、CoreDispatcher (Windows Store) 或 ISynchronizeInvoke (Windows Forms) 都是一个坏主意。(SynchronizationContext 稍微好一点,但勉强。)例如,互联网上有很多代码做一些异步工作,然后使用 Dispatcher 更新 UI;一个更便携且不那么繁琐的解决方案是使用 await 进行异步工作并在不使用 Dispatcher 的情况下更新 UI。

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

于 2017-10-05T00:57:09.827 回答