如何将 UI Dispatcher 传递给 ViewModel

发布于 2024-08-23 11:30:35 字数 244 浏览 9 评论 0原文

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

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

I'm supposed to be able to access the Dispatcher that belongs to the View I need to pass it to the ViewModel. But the View should not know anything about the ViewModel, so how do you pass it? Introduce an interface or instead of passing it to the instances create a global dispatcher singleton that will be written by the View? How do you solve this in your MVVM applications and frameworks?

EDIT: Note that since my ViewModels might be created in background threads I can't just do Dispatcher.Current in the constructor of the ViewModel.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

岛歌少女 2024-08-30 11:30:35

我使用接口 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);
    }
}

I have abstracted the Dispatcher using an interface IContext:

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

This has the advantage that you can unit-test your ViewModels more easily.
I inject the interface into my ViewModels using the MEF (Managed Extensibility Framework). Another possibility would be a constructor argument.
However, I like the injection using MEF more.

Update (example from pastebin link in comments):

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);
    }
}
初雪 2024-08-30 11:30:35

为什么不使用

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

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

why would not you use

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

instead of keeping reference to GUI dispatcher.

东京女 2024-08-30 11:30:35

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

You may not actually need the dispatcher. If you bind properties on your viewmodel to GUI elements in your view, the WPF binding mechanism automatically marshals the GUI updates to the GUI thread using the dispatcher.


EDIT:

This edit is in response to Isak Savo's comment.

Inside Microsoft's code for handling binding to properties you will find the following code:

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});
} 

This code marshals any UI updates to the thread UI thread so that even if you update the properties taking part of the binding from a different thread, WPF will automatically serialize the call to the UI thread.

想你只要分分秒秒 2024-08-30 11:30:35

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

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

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

I get the ViewModel to store the current dispatcher as a member.

If the ViewModel is created by the view, you know that the current dispatcher at creation time will be the View's dispatcher.

class MyViewModel
{
    readonly Dispatcher _dispatcher;
    public MyViewModel()
    {
        _dispatcher = Dispatcher.CurrentDispatcher;
    }
}
毁梦 2024-08-30 11:30:35

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

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

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

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

As of MVVM Light 5.2, the library now includes a DispatcherHelper class in GalaSoft.MvvmLight.Threading namespace that exposes a function CheckBeginInvokeOnUI() that accepts a delegate and runs it on the UI thread. Comes in very handy if your ViewModel is running some worker threads which affect VM properties to which your UI elements are bound.

DispatcherHelper must be initialized by calling DispatcherHelper.Initialize() at an early stage in the life of your application (e.g. App_Startup). You can then run any delegate (or lambda) using the following call:

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

Note that the class is defined in GalaSoft.MvvmLight.Platform library which is not referenced by default when you add it through NuGet. You must manually add a reference to this lib.

甜是你 2024-08-30 11:30:35

另一个常见的模式(现在在框架中被广泛使用)是 同步上下文

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

Another common pattern (which is seeing much use now in the framework) is the SynchronizationContext.

It enables you to dispatch synchronously and asynchronously. You can also set the current SynchronizationContext on the current thread, meaning it is easily mocked. The DispatcherSynchronizationContext is used by WPF apps. Other implementations of the SynchronizationContext are used by WCF and WF4.

要走就滚别墨迹 2024-08-30 11:30:35

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

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

}, DispatcherPriority.Normal); 

As of WPF version 4.5 one can use CurrentDispatcher

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

}, DispatcherPriority.Normal); 
四叶草在未来唯美盛开 2024-08-30 11:30:35

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

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

华泰
山姆

If you're only needing the dispatcher for modifying a bound collection in another thread take a look at the SynchronizationContextCollection here http://kentb.blogspot.com/2008/01/cross-thread-collection-binding-in-wpf.html

Works well, only issue I found is when using View Models with SynchronizationContextCollection properties with ASP.NET synch context, but easily worked around.

HTH
Sam

罪歌 2024-08-30 11:30:35

嗨,也许我来得太晚了,因为距离你第一篇帖子已经过去了 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;

这对我的应用程序有帮助。也许它也可以帮助你......

hi maybe i am too late since it has been 8 months since your first post...
i had the same proble in a silverlight mvvm applicatioin. and i found my solution like this. for each model and viewmodel that i have, i have also a class called controller.
like that

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

my MainController is in charge of the commanding and the connection between the model and viewmodel. in the constructor i instanciate the view and its viewmodel and set the datacontext of the view to its viewmodel.

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

//(in my naming convention i have a prefix m for member variables)

i also have a public property in the type of my MainView. like that

public MainView View { get { return mMainView; } }

(this mMainView is a local variable for the public property)

and now i am done. i just need to use my dispatcher for my ui therad like this...

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

(in this example i was asking my controller to get my sharepoint 2010 loginname but you can do what your need)

we are almost done you also need to define your root visual in the app.xaml like this

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

this helped me by my application. maybe it can help you too...

命比纸薄 2024-08-30 11:30:35

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

App.Current.MainWindow.Dispatcher

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

You don't need to pass the UI Dispatcher to the ViewModel.
The UI Dispatcher is available from the current application singleton.

App.Current.MainWindow.Dispatcher

This will make your ViewModel dependent on the View. Depending on your application, that may or may not be fine.

め七分饶幸 2024-08-30 11:30:35

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

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

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

如果这不起作用(例如 Windows Phone 8 应用程序),则使用:-

       Deployment.Current.Dispatcher

for WPF and Windows store apps use:-

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

keeping reference to GUI dispatcher is not really the right way.

if that doesn't work (such as in case of windows phone 8 apps) then use:-

       Deployment.Current.Dispatcher
ヅ她的身影、若隐若现 2024-08-30 11:30:35

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

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

if you are used uNhAddIns you can make an asynchrounous behavior easily. take a look here

And i think need a few modification to make it work on Castle Windsor (without uNhAddIns)

虚拟世界 2024-08-30 11:30:35

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

添加到应该在调度程序中调用的视图模型操作:

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) ;
    }

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

I've find another (most simplest) way:

Add to view model action that's should be call in Dispatcher:

public class MyViewModel
{
    public Action<Action> CallWithDispatcher;

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

And add this action handler in view constructor:

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

        DataContext = model;
        InitializeComponent();

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

Now you haven't problem with testing, and it's easy to implement.
I've add it to my site

北斗星光 2024-08-30 11:30:35

当您可以使用以下方式访问应用程序调度程序时,无需通过调度程序

Dispatcher dis = Application.Current.Dispatcher 

No need to pass dispatcher when you can access application dispatcher using

Dispatcher dis = Application.Current.Dispatcher 
静若繁花 2024-08-30 11:30:35

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

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

Some of my WPF projects I have faced the same situation. In my MainViewModel (Singleton instance), I got my CreateInstance() static method takes the dispatcher. And the create instance gets called from the View so that I can pass the Dispatcher from there.
And the ViewModel test module calls CreateInstance() parameterless.

But in a complex multithread scenario it is always good to have an interface implementation on the View side so as to get the proper Dispatcher of the current Window.

当梦初醒 2024-08-30 11:30:35

也许我对这个讨论有点晚了,但我发现了一篇不错的文章 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 稍微好一点,但也勉强好一点。)
例如,互联网上有很多代码可以完成一些工作
异步工作,然后使用Dispatcher更新UI;一个更多
可移植且不太麻烦的解决方案是使用await进行异步
无需使用 Dispatcher 即可工作并更新 UI。

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

Maybe I am a bit late to this discussion, but I found 1 nice article https://msdn.microsoft.com/en-us/magazine/dn605875.aspx

There is 1 paragraph

Furthermore, all code outside the View layer (that is, the ViewModel
and Model layers, services, and so on) should not depend on any type
tied to a specific UI platform. Any direct use of Dispatcher
(WPF/Xamarin/Windows Phone/Silverlight), CoreDispatcher (Windows
Store), or ISynchronizeInvoke (Windows Forms) is a bad idea.
(SynchronizationContext is marginally better, but barely.) For
example, there’s a lot of code on the Internet that does some
asynchronous work and then uses Dispatcher to update the UI; a more
portable and less cumbersome solution is to use await for asynchronous
work and update the UI without using Dispatcher.

Assume if you can use async/await properly, this is not an issue.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文