Как передать диспетчер пользовательского интерфейса в ViewModel
Я должен иметь доступ к Dispatcher, который принадлежит View Мне нужно передать его в ViewModel. Но View не должен ничего знать о ViewModel, так как вы его передаете? Ввести интерфейс или вместо передачи его экземплярам создать глобальный диспетчерский синглтон, который будет написан View? Как вы решаете это в своих приложениях и инфраструктурах MVVM?
EDIT: Обратите внимание: поскольку мои ViewModels могут быть созданы в фоновом потоке, я не могу просто сделать Dispatcher.Current
в конструкторе ViewModel.
Ответы
Ответ 1
Я отвлек Диспетчер с помощью интерфейса 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);
}
}
Ответ 2
почему бы вам не использовать
System.Windows.Application.Current.Dispatcher.Invoke(
(Action)(() => {ObservableCollectionMemeberOfVM.Add("xx"); } ));
вместо ссылки на диспетчер графического интерфейса.
Ответ 3
На самом деле вам не нужен диспетчер. Если вы привязываете свойства в своей структуре 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 автоматически сериализует вызов в поток пользовательского интерфейса.
Ответ 4
Я получаю ViewModel для хранения текущего диспетчера в качестве члена.
Если ViewModel создается представлением, вы знаете, что текущий диспетчер во время создания будет диспетчером View.
class MyViewModel
{
readonly Dispatcher _dispatcher;
public MyViewModel()
{
_dispatcher = Dispatcher.CurrentDispatcher;
}
}
Ответ 5
Как и MVVM Light 5.2, библиотека теперь включает класс DispatcherHelper
в пространстве имен GalaSoft.MvvmLight.Threading
, который предоставляет функцию CheckBeginInvokeOnUI()
, которая принимает делегат и запускает его в потоке пользовательского интерфейса. Приходит очень удобно, если в ViewModel запущены некоторые рабочие потоки, которые влияют на свойства VM, с которыми связаны ваши элементы пользовательского интерфейса.
DispatcherHelper
должен быть инициализирован путем вызова DispatcherHelper.Initialize()
на ранней стадии жизни вашего приложения (например, App_Startup
). Затем вы можете запустить любой делегат (или лямбда), используя следующий вызов:
DispatcherHelper.CheckBeginInvokeOnUI(
() =>
{
//Your code here
});
Обратите внимание, что класс определен в библиотеке GalaSoft.MvvmLight.Platform
, на которую по умолчанию не ссылаются, когда вы добавляете его через NuGet. Вы должны вручную добавить ссылку на эту библиотеку.
Ответ 6
Другим распространенным шаблоном (который сейчас широко используется в рамках) является SynchronizationContext.
Позволяет отправлять синхронно и асинхронно. Вы также можете установить текущий SynchronizationContext на текущий поток, что означает, что он легко высмеивается. DispatcherSynchronizationContext используется приложениями WPF. Другие реализации SynchronizationContext используются WCF и WF4.
Ответ 7
Если вам нужен диспетчер для изменения связанной коллекции в другом потоке, посмотрите здесь SynchronizationContextCollection http://kentb.blogspot.com/2008/01/cross-thread-collection-binding-in-wpf.html
Хорошо работает, только проблема, которую я обнаружил, - это использование моделей View Models с SynchronizationContextCollection с контекстом синхронизации ASP.NET, но с легкостью работало.
НТН
Сэм
Ответ 8
Привет, может быть, я слишком поздно, так как прошло 8 месяцев с момента вашего первого сообщения...
У меня был тот же самый прорыв в применении mvvm silverlight. и я нашел свое решение таким образом. для каждой модели и viewmodel, которые у меня есть, у меня также есть класс, называемый контроллером.
как это
public class MainView : UserControl // (because it is a silverlight user controll)
public class MainViewModel
public class MainController
my MainController отвечает за команду и связь между моделью и viewmodel. в конструкторе я индуцирует представление и его viewmodel и задает datacontext представления для его viewmodel.
mMainView = new MainView();
mMainViewModel = new MainViewModel();
mMainView.DataContext = mMainViewModel;
//(в моем соглашении об именах у меня есть префикс m для переменных-членов)
i также имеет общедоступную собственность в типе моего MainView. как это
public MainView View { get { return mMainView; } }
(этот mMainView является локальной переменной для публичного свойства)
и теперь я закончен. мне просто нужно использовать моего диспетчера для моего urad therad, как это...
mMainView.Dispatcher.BeginInvoke(
() => MessageBox.Show(mSpWeb.CurrentUser.LoginName));
(в этом примере я просил мой контроллер получить мою учетную запись sharepoint 2010, но вы можете делать то, что вам нужно)
мы почти закончили, вам также нужно определить свой корневой визуал в app.xaml, как это
var mainController = new MainController();
RootVisual = mainController.View;
это помогло мне по моей заявке. возможно, это тоже может вам помочь...
Ответ 9
Вам не нужно передавать диспетчер интерфейса пользователя в ViewModel.
Диспетчер пользовательского интерфейса доступен из текущего приложения singleton.
App.Current.MainWindow.Dispatcher
Это сделает вашу ViewModel зависимой от представления. В зависимости от вашего приложения это может быть или не быть нормально.
Ответ 10
для приложений приложений WPF и Windows: -
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() => {ObservableCollectionMemeberOfVM.Add("xx"); } ));
Сохранение ссылки на диспетчер графического интерфейса на самом деле не так.
если это не работает (например, в случае приложений Windows Phone 8), используйте: -
Deployment.Current.Dispatcher
Ответ 11
если вы используете uNhAddIns, вы можете легко сделать асинхронное поведение. посмотрите здесь
И я думаю, нужно несколько модификаций, чтобы заставить его работать над Castle Windsor (без uNhAddIns)
Ответ 12
Я нашел другой (самый простой) способ:
Добавить, чтобы просмотреть действие модели, которое должно быть вызвано в диспетчере:
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) ;
}
Теперь у вас нет проблем с тестированием, и его легко реализовать.
Я добавлю его на мой сайт
Ответ 13
В версии WPF версии 4.5 можно использовать CurrentDispatcher
Dispatcher.CurrentDispatcher.Invoke(() =>
{
// Do GUI related operations here
}, DispatcherPriority.Normal);
Ответ 14
Может быть, я немного опоздал на это обсуждение, но нашел 1 приятную статью https://msdn.microsoft.com/en-us/magazine/dn605875.aspx
Существует 1 абзац
Кроме того, весь код вне уровня просмотра (то есть ViewModel и уровни модели, сервисы и т.д.) не должны зависеть от какого-либо типа привязан к определенной платформе пользовательского интерфейса. Любое прямое использование Диспетчера (WPF/Xamarin/Windows Phone/Silverlight), CoreDispatcher (Windows Store), или ISynchronizeInvoke (Windows Forms) - плохая идея. (SynchronizationContext немного лучше, но едва.) Для Например, в Интернете много кода, который делает некоторые асинхронная работа, а затем использует диспетчер для обновления пользовательского интерфейса; более портативное и менее громоздкое решение - использовать ожидание асинхронного работать и обновлять пользовательский интерфейс без использования диспетчера.
Предположим, что вы можете правильно использовать async/wait, это не проблема.
Ответ 15
Некоторые из моих проектов WPF я столкнулся с такой же ситуацией. В моем экземпляре MainViewModel (Singleton) я получил свой статический метод CreateInstance(), который принимает диспетчер. И экземпляр create вызывается из представления, чтобы я мог передать Диспетчер оттуда.
И тестовый модуль ViewModel вызывает CreateInstance() без параметров.
Но в сложном многопоточном сценарии всегда хорошо иметь реализацию интерфейса на стороне View, чтобы получить правильный диспетчер текущего окна.