Как передать диспетчер пользовательского интерфейса в 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, чтобы получить правильный диспетчер текущего окна.