Jak przekazać Ui Dispatcher do ViewModel

Powinienem mieć dostęp do Dispatcher , który należy do widoku, który muszę przekazać do ViewModel. Ale widok nie powinien nic wiedzieć o ViewModel, więc jak go przekazać? Wprowadzić interfejs lub zamiast przekazywać go do instancji utworzyć globalny singleton dyspozytora, który będzie zapisywany przez Widok? Jak rozwiązać ten problem w aplikacjach i frameworkach MVVM?

EDIT: zauważ, że ponieważ moje Viewmodele mogą być tworzone w wątkach tła I nie można po prostu zrobić Dispatcher.Current w konstruktorze modelu widoku.

Author: Adi Lester, 2010-03-01

15 answers

Wyodrębniłem dyspozytora używając interfejsu IContext :

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

Ma to tę zaletę, że możesz łatwiej testować swoje modele widoku.
Wprowadzam interfejs do moich ViewModels za pomocą MEF (Managed Extensibility Framework). Inną możliwością byłby argument konstruktora. Jednak bardziej podoba mi się zastrzyk za pomocą MEF.

Aktualizacja (przykład z Pastebin link w komentarzach):

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);
    }
}
 43
Author: Matthias,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-28 06:08:35

Dlaczego nie używasz

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

Zamiast zachować odniesienie do dyspozytora GUI.

 29
Author: Vitaliy Markitanov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-17 12:50:47

Możesz nie potrzebować dyspozytora. Jeśli właściwości w modelu widoku zostaną powiązane z elementami GUI w widoku, mechanizm wiązania WPF automatycznie przeniesie aktualizacje GUI do wątku GUI za pomocą dyspozytora.


EDIT:

Ta edycja jest w odpowiedzi na komentarz Isaka Savo.

W kodzie Microsoftu do obsługi wiązania z właściwościami znajduje się następujący kod:

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});
} 

Ten kod powoduje wszelkie aktualizacje UI do wątku UI tak, że nawet jeśli zaktualizujesz właściwości biorące udział w wiązaniu z innego wątku, WPF automatycznie serializuje wywołanie do wątku interfejsu użytkownika.

 18
Author: Jakob Christensen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-21 14:09:12

I get the ViewModel to store the current dispatcher as a member.

Jeśli ViewModel jest tworzony przez Widok, wiesz, że bieżący dyspozytor w czasie tworzenia będzie dyspozytorem widoku.

class MyViewModel
{
    readonly Dispatcher _dispatcher;
    public MyViewModel()
    {
        _dispatcher = Dispatcher.CurrentDispatcher;
    }
}
 14
Author: Andrew Shepherd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-01 07:54:09

Począwszy od MVVM Light 5.2, biblioteka zawiera teraz klasę DispatcherHelper w przestrzeni nazw GalaSoft.MvvmLight.Threading, która eksponuje funkcję CheckBeginInvokeOnUI(), która akceptuje delegata i uruchamia go w wątku UI. Jest to bardzo przydatne, jeśli model ViewModel uruchamia wątki robocze, które wpływają na właściwości maszyny Wirtualnej, z którymi powiązane są elementy interfejsu użytkownika.

DispatcherHelper musi być zainicjalizowane przez wywołanie DispatcherHelper.Initialize() na wczesnym etapie życia aplikacji(np. App_Startup). Następnie można uruchomić dowolny delegat (lub lambda), używając następujących wywołanie:

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

Zauważ, że klasa jest zdefiniowana w bibliotece GalaSoft.MvvmLight.Platform, do której domyślnie nie odwołuje się po dodaniu jej przez NuGet. Musisz ręcznie dodać odniesienie do tej lib.

 7
Author: dotNET,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-08 13:25:26

Innym powszechnym wzorcem (który jest obecnie bardzo przydatny w frameworku) jest SynchronizationContext.

Umożliwia wysyłanie synchronicznie i asynchronicznie. Możesz również ustawić bieżący tekst Synchronizationkontekst w bieżącym wątku, co oznacza, że jest łatwo wyśmiewany. DispatcherSynchronizationContext jest używany przez aplikacje WPF. Inne implementacje SynchronizationContext są używane przez WCF i WF4.

 4
Author: Will,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-24 20:15:01

Od wersji WPF 4.5 można użyć CurrentDispatcher

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

}, DispatcherPriority.Normal); 
 2
Author: ΩmegaMan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-17 15:16:50

Jeśli potrzebujesz tylko dispatchera do modyfikacji zbioru powiązanego w innym wątku, spójrz na SynchronizationContextCollection tutaj http://kentb.blogspot.com/2008/01/cross-thread-collection-binding-in-wpf.html

Działa dobrze, jedyny problem jaki znalazłem to używanie modeli widoku z właściwościami SynchronizationContextCollection z ASP.NET Synchronizuj kontekst, ale łatwo obejść.

HTH Sam

 1
Author: sambomartin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-04 09:01:22

Cześć może jestem za późno, ponieważ minęło 8 miesięcy od pierwszego postu... miałem to samo proble w silverlight mvvm applicatioin. i znalazłem swoje rozwiązanie w ten sposób. dla każdego modelu i viewmodel, który mam, mam również klasę o nazwie controller. like that

public class MainView : UserControl  // (because it is a silverlight user controll)
public class MainViewModel
public class MainController

Mój MainController jest odpowiedzialny za dowodzenie i połączenie między modelem a viewmodel. w konstruktorze instancjuję widok i jego model viewmodel i ustawiam datacontext widoku na jego viewmodel.

mMainView = new MainView();
mMainViewModel = new MainViewModel();
mMainView.DataContext = mMainViewModel; 

/ / (w mojej konwencji nazewnictwa mam przedrostek m dla zmiennych członkowskich)

Mam również własność publiczną typu Mój widok główny. like that

public MainView View { get { return mMainView; } }

(this mMainView is a local variable for the public property)

A teraz skończyłem. muszę tylko użyć mojego dyspozytora do mojego ui therad w ten sposób...
mMainView.Dispatcher.BeginInvoke(
    () => MessageBox.Show(mSpWeb.CurrentUser.LoginName));

(w tym przykładzie prosiłem mojego kontrolera o pobranie nazwy loginu sharepoint 2010, ale możesz zrobić to, czego potrzebujesz)

Jesteśmy prawie gotowe musisz również zdefiniować wizualizację root w aplikacji.XAML like this

var mainController = new MainController();
RootVisual = mainController.View;
Pomogło mi to moje podanie. może tobie też pomoże...
 1
Author: arborinfelix,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-13 18:41:45

Nie musisz przekazywać Dispatchera interfejsu do ViewModel. Dyspozytor interfejsu użytkownika jest dostępny z bieżącej aplikacji singleton.

App.Current.MainWindow.Dispatcher

Spowoduje to, że Twój ViewModel będzie zależny od widoku. W zależności od aplikacji, może to być, ale nie musi być w porządku.

 1
Author: Rana Ian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-26 17:53:33

Dla aplikacji WPF i Windows Store użyj:-

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

Utrzymywanie referencji do GUI dispatcher nie jest tak naprawdę dobrym sposobem.

Jeśli to nie działa (jak w przypadku aplikacji windows phone 8), Użyj: -

       Deployment.Current.Dispatcher
 1
Author: Parth Sehgal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-30 06:47:38

Jeśli używasz uNhAddIns możesz łatwo wykonać asynchroniczne zachowanie. Zobacz Tutaj

I myślę, że potrzeba kilku modyfikacji, aby to działało na Castle Windsor (bez uNhAddIns)

 0
Author: ktutnik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-23 05:03:04

Znalazłem inny (najprostszy) sposób:

Dodaj do widoku akcję modelu, która powinna być wywołana w Dyspozytorze:

public class MyViewModel
{
    public Action<Action> CallWithDispatcher;

    public void SomeMultithreadMethod()
    {
        if(CallWithDispatcher != null)
            CallWithDispatcher(() => DoSomethingMetod(SomeParameters));
    }
}

I dodaj ten handler w view constructor:

    public View()
    {
        var model = new MyViewModel();

        DataContext = model;
        InitializeComponent();

        // Here 
        model.CallWithDispatcher += act => _taskbarIcon.Dispatcher
            .BeginInvoke(DispatcherPriority.Normal, act) ;
    }

Teraz nie masz problemu z testowaniem i jest to łatwe do wdrożenia. Dodałem go do mojej Strony

 0
Author: Alexander Molodih,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-14 12:25:20

Może jestem trochę spóźniony na tę dyskusję, ale znalazłem 1 fajny artykuł https://msdn.microsoft.com/en-us/magazine/dn605875.aspx

Jest 1 akapit

Ponadto cały kod poza warstwą widoku (czyli ViewModel i warstw modelowych, usług itp.) nie powinny zależeć od żadnego typu powiązane z określoną platformą interfejsu użytkownika. Dowolne bezpośrednie wykorzystanie dyspozytora (WPF / Xamarin / Windows Phone/Silverlight), CoreDispatcher( Windows Store), lub ISynchronizeInvoke (Windows Forms) to zły pomysł. (SynchronizationContext jest nieznacznie lepszy, ale ledwo.) Dla przykład, w Internecie jest dużo kodu, który robi pewne pracy asynchronicznej, a następnie używa Dispatchera do aktualizacji interfejsu użytkownika; a więcej przenośnym i mniej uciążliwym rozwiązaniem jest zastosowanie asynchronicznego pracuj i aktualizuj interfejs użytkownika bez użycia Dispatchera.

Załóżmy, że możesz poprawnie używać async/wait, to nie jest problem.

 0
Author: hardywang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-05 00:57:09

Niektóre z moich projektów WPF miałem do czynienia z tą samą sytuacją. W moim MainViewModel (instancja Singleton), mam mój CreateInstance () statyczna metoda bierze dispatcher. Instancja create jest wywoływana z widoku, dzięki czemu mogę przekazać stamtąd dyspozytora. Moduł testowy ViewModel wywołuje metodę CreateInstance() bez parametru.

Ale w złożonym scenariuszu wielowątkowym zawsze dobrze jest mieć implementację interfejsu po stronie widoku, aby uzyskać właściwego dyspozytora bieżące okno.

 -1
Author: Jobi Joy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-01 09:03:07