Как передать Диспетчер пользовательского интерфейса в ViewModel - PullRequest
68 голосов
/ 01 марта 2010

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

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

Ответы [ 15 ]

43 голосов
/ 01 марта 2010

Я абстрагировал Диспетчер, используя интерфейс IContext :

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

Это имеет то преимущество, что вы можете проще тестировать свои модели ViewModel.
Я внедряю интерфейс в мои 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);
    }
}
34 голосов
/ 29 мая 2012

почему бы вам не использовать

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

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

18 голосов
/ 01 марта 2010

Возможно, вам не нужен диспетчер. Если вы привязываете свойства вашей модели представления к элементам графического интерфейса в вашем представлении, механизм привязки 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 автоматически сериализует вызов в потоке пользовательского интерфейса.

14 голосов
/ 01 марта 2010

Я получаю ViewModel для сохранения текущего диспетчера в качестве члена.

Если ViewModel создается представлением, вы знаете, что текущий диспетчер во время создания будет диспетчером представления.

class MyViewModel
{
    readonly Dispatcher _dispatcher;
    public MyViewModel()
    {
        _dispatcher = Dispatcher.CurrentDispatcher;
    }
}
7 голосов
/ 08 января 2016

Начиная с MVVM Light 5.2, библиотека теперь включает класс DispatcherHelper в пространстве имен GalaSoft.MvvmLight.Threading, который предоставляет функцию CheckBeginInvokeOnUI(), которая принимает делегата и запускает его в потоке пользовательского интерфейса. Это очень удобно, если в вашей ViewModel запущены некоторые рабочие потоки, которые влияют на свойства ВМ, к которым привязаны ваши элементы пользовательского интерфейса.

DispatcherHelper необходимо инициализировать, позвонив по номеру DispatcherHelper.Initialize() на ранней стадии жизни вашего приложения (например, App_Startup). Затем вы можете запустить любой делегат (или лямбду), используя следующий вызов:

DispatcherHelper.CheckBeginInvokeOnUI(
        () =>
        {
           //Your code here
        });

Обратите внимание, что класс определен в библиотеке GalaSoft.MvvmLight.Platform, на которую нет ссылок по умолчанию при добавлении его через NuGet. Вы должны вручную добавить ссылку на эту библиотеку.

4 голосов
/ 25 июня 2011

Другим распространенным шаблоном (который сейчас широко используется в рамках) является SynchronizationContext .

Позволяет отправлять синхронно и асинхронно. Вы также можете установить текущий SynchronizationContext в текущем потоке, то есть его легко смоделировать. DispatcherSynchronizationContext используется приложениями WPF. Другие реализации SynchronizationContext используются WCF и WF4.

3 голосов
/ 17 мая 2016

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

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

}, DispatcherPriority.Normal); 
1 голос
/ 30 апреля 2014

для приложений магазинов WPF и Windows: -

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

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

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

       Deployment.Current.Dispatcher
1 голос
/ 26 октября 2011

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

App.Current.MainWindow.Dispatcher

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

1 голос
/ 27 ноября 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...