Как передать диспетчер пользовательского интерфейса в ViewModel
Я должен быть в состоянии получить доступ к диспетчер это относится к представлению, которое мне нужно передать в ViewModel. Но вид не должен ничего знать о ViewModel, так как вы его передаете? Ввести интерфейс или вместо того, чтобы передавать его экземплярам, создать глобальный одноэлементный диспетчер, который будет записан представлением? Как вы решаете эту проблему в своих приложениях и фреймворках MVVM?
EDIT: обратите внимание, что поскольку мои ViewModels могут быть созданы в фоновые темы я не могу просто сделать Dispatcher.Current
в конструкторе ViewModel.
15 ответов:
Я абстрагировал диспетчера с помощью интерфейса IContext:
public interface IContext { bool IsSynchronized { get; } void Invoke(Action action); void BeginInvoke(Action action); }
это имеет то преимущество, что вы можете модульное тестирование ваших ViewModels более легко.
Я внедряю интерфейс в свои ViewModels с помощью MEF (Managed Extensibility Framework). Другой возможностью был бы аргумент конструктора. Однако мне больше нравится инъекция с использованием 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"); } ));
вместо того, чтобы держать ссылку на графический интерфейс диспетчера.
возможно, на самом деле вам не нужен диспетчер. Если вы привязываете свойства в viewmodel к элементам GUI в своем представлении, механизм привязки WPF автоматически маршалирует обновления GUI к потоку GUI с помощью диспетчера.
EDIT:
это редактирование в ответ на комментарий Исака Саво.
внутри кода 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}); }
этот код маршалов любые обновления пользовательского интерфейса для потока пользовательского интерфейса потока, так что даже если вы обновите свойства, принимающие участие в привязке из другого потока, WPF автоматически сериализует вызов потока пользовательского интерфейса.
Я получаю ViewModel для хранения текущего диспетчера в качестве члена.
Если ViewModel создается представлением, вы знаете, что текущий диспетчер во время создания будет диспетчером представления.
class MyViewModel { readonly Dispatcher _dispatcher; public MyViewModel() { _dispatcher = Dispatcher.CurrentDispatcher; } }
начиная с MVVM Light 5.2, библиотека теперь включает в себя
DispatcherHelper
классGalaSoft.MvvmLight.Threading
пространство имен, которое предоставляет функциюCheckBeginInvokeOnUI()
, который принимает делегат и запускает его в потоке пользовательского интерфейса. Очень удобно, если ваш ViewModel запускает некоторые рабочие потоки, которые влияют на свойства виртуальной машины, к которым привязаны ваши элементы пользовательского интерфейса.
DispatcherHelper
должен быть инициализирован вызовомDispatcherHelper.Initialize()
на ранней стадии жизни вашего приложения (напримерApp_Startup
). Затем вы можете запустить любой делегат (или лямбда) с помощью следующий звонок:DispatcherHelper.CheckBeginInvokeOnUI( () => { //Your code here });
обратите внимание, что класс определен в
GalaSoft.MvvmLight.Platform
библиотека, на которую не ссылаются по умолчанию, когда вы добавляете ее через NuGet. Необходимо вручную добавить ссылку на эту библиотеку.
еще один общий шаблон (который видит много пользы сейчас в рамках) является SynchronizationContext.
Это позволяет отправлять синхронно и асинхронно. Вы также можете установить текущий SynchronizationContext в текущем потоке, что означает, что он легко высмеивается. DispatcherSynchronizationContext используется приложениями WPF. Другие реализации SynchronizationContext используются WCF и WF4.
начиная с версии 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
работает хорошо, только проблема, которую я нашел, это при использовании моделей представления с SynchronizationContextCollection свойства с ASP.NET синхронизировать контекст,но легко обойти.
HTH Сэм
привет, может быть, я слишком поздно, так как прошло 8 месяцев с момента вашего первого поста... у меня была такая же проблема в приложении silverlight mvvm. и я нашел свое решение вот так. для каждой модели и viewmodel, которые у меня есть, у меня также есть класс controller. вот так
public class MainView : UserControl // (because it is a silverlight user controll) public class MainViewModel public class MainController
мой MainController отвечает за командование и связь между моделью и viewmodel. в конструкторе я создаю экземпляр представления и его viewmodel и устанавливаю datacontext представления в его viewmodel.
mMainView = new MainView(); mMainViewModel = new MainViewModel(); mMainView.DataContext = mMainViewModel;
//(в моем соглашении об именах у меня есть префикс m для переменных-членов)
у меня также есть публичное свойство в типе моего MainView. вот так
public MainView View { get { return mMainView; } }
(это mMainView является локальной переменной для публичного свойства)
и теперь я сделал. мне просто нужно использовать мой диспетчер для моего пользовательского интерфейса therad, как это...
mMainView.Dispatcher.BeginInvoke( () => MessageBox.Show(mSpWeb.CurrentUser.LoginName));
(в этом примере я просил мой контроллер, чтобы получить мой sharepoint 2010 loginname но вы может делать то, что вам нужно)
мы почти закончили вам также нужно определить свой корень визуальный в приложении.xaml вот так
var mainController = new MainController(); RootVisual = mainController.View;
это помогло мне с помощью моего приложения. может быть, это и вам поможет...
вам не нужно передавать диспетчер пользовательского интерфейса в ViewModel. Диспетчер пользовательского интерфейса доступен из текущего синглтона приложения.
App.Current.MainWindow.Dispatcher
это сделает ваш ViewModel зависит от вида. В зависимости от вашего приложения, это может быть или не быть хорошо.
для приложений WPF и Windows store используйте: -
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() => {ObservableCollectionMemeberOfVM.Add("xx"); } ));
сохранение ссылки на GUI dispatcher-это не совсем правильный путь.
если это не работает (например, в случае windows phone 8 приложений) , то используйте: -
Deployment.Current.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) ; }
теперь у вас нет проблем с тестированием, и это легко реализовать. Я добавляю его к моему сайт
может быть, я немного опоздал к этому обсуждению, но я нашел 1 хорошую статью https://msdn.microsoft.com/en-us/magazine/dn605875.aspx
есть 1 пункт
кроме того, весь код вне слоя представления (то есть ViewModel и модели слоев, сервисов и так далее) не должны зависеть от какого-либо типа привязан к определенной платформе пользовательского интерфейса. Любое прямое использование диспетчера (В WPF и Xamarin/Windows телефоне или Silverlight), класса coredispatcher (окна Магазин), или ISynchronizeInvoke (Windows Forms) - это плохая идея. (SynchronizationContext немного лучше, но едва.) Для например, в интернете есть много кода, который делает некоторые асинхронная работа, а затем использует Dispatcher для обновления пользовательского интерфейса; подробнее портативное и менее громоздкое решение-использовать await для асинхронного работа и обновление пользовательского интерфейса без использования Диспетчера.
предположим, если вы можете использовать async/await правильно, это не проблема.
некоторые из моих проектов WPF я столкнулся с такой же ситуацией. В моем MainViewModel (одноэлементный экземпляр), я получил мой CreateInstance () статический метод принимает диспетчер. И экземпляр create вызывается из представления, чтобы я мог передать диспетчер оттуда. И тестовый модуль ViewModel вызывает CreateInstance () без параметров.
но в сложном многопоточном сценарии всегда хорошо иметь реализацию интерфейса на стороне представления, чтобы получить правильный диспетчер текущего окна.