我应该能够访问属于我需要将其传递给 ViewModel 的视图的调度程序。但是View应该对ViewModel一无所知,那怎么传呢?引入一个接口还是不将其传递给实例,而是创建一个将由视图编写的全局调度程序单例?您如何在 MVVM 应用程序和框架中解决这个问题?
编辑:请注意,由于我的 ViewModels 可能是在后台线程中创建的,所以我不能只Dispatcher.Current
在 ViewModel 的构造函数中执行此操作。
我应该能够访问属于我需要将其传递给 ViewModel 的视图的调度程序。但是View应该对ViewModel一无所知,那怎么传呢?引入一个接口还是不将其传递给实例,而是创建一个将由视图编写的全局调度程序单例?您如何在 MVVM 应用程序和框架中解决这个问题?
编辑:请注意,由于我的 ViewModels 可能是在后台线程中创建的,所以我不能只Dispatcher.Current
在 ViewModel 的构造函数中执行此操作。
我已经使用接口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);
}
}
你为什么不使用
System.Windows.Application.Current.Dispatcher.Invoke(
(Action)(() => {ObservableCollectionMemeberOfVM.Add("xx"); } ));
而不是保留对 GUI 调度程序的引用。
您可能实际上不需要调度程序。如果将视图模型上的属性绑定到视图中的 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 线程的调用。
我让 ViewModel 将当前调度程序存储为成员。
如果 ViewModel 是由视图创建的,则您知道创建时的当前调度程序将是视图的调度程序。
class MyViewModel
{
readonly Dispatcher _dispatcher;
public MyViewModel()
{
_dispatcher = Dispatcher.CurrentDispatcher;
}
}
从 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 添加时默认不引用的库中定义的。您必须手动添加对此库的引用。
另一个常见的模式(现在在框架中得到了广泛的应用)是SynchronizationContext。
它使您能够同步和异步调度。您还可以在当前线程上设置当前 SynchronizationContext,这意味着它很容易被模拟。WPF 应用程序使用 DispatcherSynchronizationContext。WCF 和 WF4 使用 SynchronizationContext 的其他实现。
从 WPF 4.5 版开始,可以使用CurrentDispatcher
Dispatcher.CurrentDispatcher.Invoke(() =>
{
// Do GUI related operations here
}, DispatcherPriority.Normal);
如果您只需要调度程序来修改另一个线程中的绑定集合,请查看这里的 SynchronizationContextCollection http://kentb.blogspot.com/2008/01/cross-thread-collection-binding-in-wpf.html
效果很好,我发现的唯一问题是当使用具有 ASP.NET 同步上下文的 SynchronizationContextCollection 属性的视图模型时,但很容易解决。
HTH 山姆
嗨,也许我来得太晚了,因为距离你的第一篇文章已经 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;
这对我的申请有所帮助。也许它也可以帮助你......
您不需要将 UI Dispatcher 传递给 ViewModel。UI Dispatcher 可从当前应用程序单例中获得。
App.Current.MainWindow.Dispatcher
这将使您的 ViewModel 依赖于 View。根据您的应用程序,这可能会也可能不会。
对于 WPF 和 Windows 商店应用程序,请使用:-
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() => {ObservableCollectionMemeberOfVM.Add("xx"); } ));
保持对 GUI 调度程序的引用并不是真正正确的方法。
如果这不起作用(例如在 windows phone 8 应用程序的情况下),则使用:-
Deployment.Current.Dispatcher
我找到了另一种(最简单的)方法:
添加到应在 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) ;
}
现在你对测试没有问题,而且很容易实现。我已将其添加到我的网站
当您可以使用访问应用程序调度程序时,无需传递调度程序
Dispatcher dis = Application.Current.Dispatcher
我的一些 WPF 项目遇到了同样的情况。在我的 MainViewModel(单例实例)中,我的 CreateInstance() 静态方法采用了调度程序。并且从视图调用创建实例,以便我可以从那里传递调度程序。ViewModel 测试模块无参数调用 CreateInstance()。
但是在复杂的多线程场景中,在 View 端有一个接口实现总是好的,以便获得当前 Window 的正确 Dispatcher。
也许我对这个讨论有点晚了,但我发现了一篇不错的文章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,这不是问题。