Как передать диспетчер пользовательского интерфейса в ViewModel


Я должен быть в состоянии получить доступ к диспетчер это относится к представлению, которое мне нужно передать в ViewModel. Но вид не должен ничего знать о ViewModel, так как вы его передаете? Ввести интерфейс или вместо того, чтобы передавать его экземплярам, создать глобальный одноэлементный диспетчер, который будет записан представлением? Как вы решаете эту проблему в своих приложениях и фреймворках MVVM?

EDIT: обратите внимание, что поскольку мои ViewModels могут быть созданы в фоновые темы я не могу просто сделать Dispatcher.Current в конструкторе ViewModel.

15 65

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

Если вы используете uNhAddIns вы можете сделать асинхронное поведение легко. взгляните здесь

и я думаю, нужно несколько модификаций, чтобы заставить его работать на замок Виндзор (без uNhAddIns)

Я нашел другой (самый простой) способ:

добавить для просмотра модели действие, которое должно быть вызов диспетчера:

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 () без параметров.

но в сложном многопоточном сценарии всегда хорошо иметь реализацию интерфейса на стороне представления, чтобы получить правильный диспетчер текущего окна.