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

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

РЕДАКТИРОВАТЬ: Обратите внимание, что так как мои ViewModels могут быть созданы в фоновых потоках, я не могу просто сделать Dispatcher.Current в конструкторе ViewModel.

15 ответов

Решение

Я абстрагировал Dispatcher с помощью интерфейса IContext:

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

Это дает преимущество в том, что вы можете проще тестировать свои модели ViewModels.
Я внедряю интерфейс в мои ViewModels, используя MEF (Managed Extensibility Framework). Другой возможностью будет аргумент конструктора. Однако мне больше нравится инъекция с использованием MEF.

Обновление (пример по ссылке в комментариях):

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.

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


РЕДАКТИРОВАТЬ:

Это изменение является ответом на комментарий Исака Саво.

Внутри кода 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.

Начиная с версии 4.5 WPF можно использовать CurrentDispatcher

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

}, DispatcherPriority.Normal); 

Привет, может быть, я опоздал, так как прошло 8 месяцев с момента вашего первого поста... У меня была такая же проблема в приложении Silverlight Mvvm. и я нашел свое решение, как это. для каждой модели и модели представления у меня также есть класс с именем controller. как это

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;

это помогло мне моим заявлением. может быть, это может помочь вам тоже...

Для приложений магазина WPF и Windows использовать:-

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

хранить ссылку на диспетчер GUI не совсем правильно.

если это не работает (например, в случае приложений для Windows Phone 8), используйте:-

       Deployment.Current.Dispatcher

Вам не нужно передавать Диспетчер пользовательского интерфейса в ViewModel. Диспетчер пользовательского интерфейса доступен из единого приложения текущего приложения.

App.Current.MainWindow.Dispatcher

Это сделает вашу ViewModel зависимой от View. В зависимости от вашего приложения, это может или не может быть хорошо.

Если вам нужен только диспетчер для изменения связанной коллекции в другом потоке, взгляните на SynchronizationContextCollection здесь http://kentb.blogspot.com/2008/01/cross-thread-collection-binding-in-wpf.html

Работает хорошо, единственная проблема, которую я обнаружил, - это использование моделей представления со свойствами SynchronizationContextCollection с контекстом синхронизации ASP.NET, но их легко обойти.

HTH Сэм

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

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

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

Добавьте для просмотра действия модели, которые следует вызывать в 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 

Может быть, я немного опоздал к этому обсуждению, но я нашел 1 хорошую статью https://msdn.microsoft.com/en-us/magazine/dn605875.aspx

Есть 1 параграф

Кроме того, весь код вне слоя View (то есть слои ViewModel и Model, сервисы и т. Д.) Не должен зависеть от какого-либо типа, привязанного к конкретной платформе пользовательского интерфейса. Любое прямое использование Dispatcher (WPF/Xamarin/Windows Phone/Silverlight), CoreDispatcher (Windows Store) или ISynchronizeInvoke (Windows Forms) является плохой идеей. (SynchronizationContext немного лучше, но едва ли.) Например, в Интернете много кода, который выполняет некоторую асинхронную работу, а затем использует Dispatcher для обновления пользовательского интерфейса; более переносимым и менее громоздким решением является использование await для асинхронной работы и обновление пользовательского интерфейса без использования Dispatcher.

Предположим, что вы можете правильно использовать async / await, это не проблема.

С некоторыми из моих проектов WPF я столкнулся с такой же ситуацией. В моем MainViewModel (экземпляр Singleton) я получил статический метод CreateInstance(), принимающий диспетчер. И экземпляр create вызывается из View, чтобы я мог передать оттуда Dispatcher. И тестовый модуль ViewModel вызывает CreateInstance() без параметров.

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

Другие вопросы по тегам