Programing

UI 디스패처를 ViewModel에 전달하는 방법

lottogame 2020. 11. 8. 09:11
반응형

UI 디스패처를 ViewModel에 전달하는 방법


ViewModel에 전달해야하는 View에 속한 Dispatcher 에 액세스 할 수 있어야 합니다. 그러나 View는 ViewModel에 대해 아무것도 알지 못하므로 어떻게 전달합니까? 인터페이스를 도입하거나 인스턴스에 전달하는 대신 View에 의해 작성 될 글로벌 디스패처 싱글 톤을 생성합니까? MVVM 애플리케이션 및 프레임 워크에서이 문제를 어떻게 해결합니까?

편집 : 내 ViewModels가 백그라운드 스레드에서 생성 될 수 있으므로 ViewModel Dispatcher.Current의 생성자에서 할 수 없습니다 .


IContext 인터페이스를 사용하여 Dispatcher를 추상화했습니다 .

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

이는 ViewModel을보다 쉽게 ​​단위 테스트 할 수 있다는 장점이 있습니다.
MEF (Managed Extensibility Framework)를 사용하여 ViewModel에 인터페이스를 삽입합니다. 또 다른 가능성은 생성자 인수입니다. 그러나 나는 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"); } ));

GUI 디스패처에 대한 참조를 유지하는 대신.


실제로 디스패처가 필요하지 않을 수 있습니다. 뷰 모델의 속성을 뷰의 GUI 요소에 바인딩하면 WPF 바인딩 메커니즘이 디스패처를 사용하여 GUI 업데이트를 GUI 스레드에 자동으로 마샬링합니다.


편집하다:

이 편집은 Isak Savo의 의견에 대한 응답입니다.

속성에 대한 바인딩을 처리하기위한 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});
} 

이 코드는 모든 UI 업데이트를 스레드 UI 스레드에 마샬링하므로 다른 스레드에서 바인딩에 포함되는 속성을 업데이트하더라도 WPF는 UI 스레드에 대한 호출을 자동으로 직렬화합니다.


현재 디스패처를 구성원으로 저장하는 ViewModel을 얻습니다.

ViewModel이 뷰에 의해 생성 된 경우 생성시 현재 디스패처가 View의 디스패처가됨을 알고 있습니다.

class MyViewModel
{
    readonly Dispatcher _dispatcher;
    public MyViewModel()
    {
        _dispatcher = Dispatcher.CurrentDispatcher;
    }
}

MVVM Light 5.2부터 라이브러리는 이제 대리자를 받아들이고 UI 스레드에서 실행 하는 함수 노출하는 네임 스페이스 DispatcherHelper클래스를 포함합니다 . ViewModel이 UI 요소가 바인딩 된 VM 속성에 영향을 미치는 일부 작업자 스레드를 실행하는 경우 매우 편리합니다.GalaSoft.MvvmLight.ThreadingCheckBeginInvokeOnUI()

DispatcherHelperDispatcherHelper.Initialize()애플리케이션 수명의 초기 단계 (예 :) 에서 호출 하여 초기화해야합니다 App_Startup. 그런 다음 다음 호출을 사용하여 모든 대리자 (또는 람다)를 실행할 수 있습니다.

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

클래스는 GalaSoft.MvvmLight.PlatformNuGet을 통해 추가 할 때 기본적으로 참조되지 않는 라이브러리에 정의되어 있습니다. 이 lib에 대한 참조를 수동으로 추가해야합니다.


또 다른 일반적인 패턴 (현재 프레임 워크에서 많이 사용되고 있음)은 SynchronizationContext 입니다.

이를 통해 동기식 및 비동기식으로 디스패치 할 수 있습니다. 현재 스레드에서 현재 SynchronizationContext를 설정할 수도 있습니다. 즉, 쉽게 모의 할 수 있습니다. DispatcherSynchronizationContext는 WPF 앱에서 사용됩니다. SynchronizationContext의 다른 구현은 WCF 및 WF4에서 사용됩니다.


As of WPF version 4.5 one can use CurrentDispatcher

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

}, DispatcherPriority.Normal); 

If you're only needing the dispatcher for modifying a bound collection in another thread take a look at the SynchronizationContextCollection here http://kentb.blogspot.com/2008/01/cross-thread-collection-binding-in-wpf.html

Works well, only issue I found is when using View Models with SynchronizationContextCollection properties with ASP.NET synch context, but easily worked around.

HTH Sam


hi maybe i am too late since it has been 8 months since your first post... i had the same proble in a silverlight mvvm applicatioin. and i found my solution like this. for each model and viewmodel that i have, i have also a class called controller. like that

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

my MainController is in charge of the commanding and the connection between the model and viewmodel. in the constructor i instanciate the view and its viewmodel and set the datacontext of the view to its viewmodel.

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

//(in my naming convention i have a prefix m for member variables)

i also have a public property in the type of my MainView. like that

public MainView View { get { return mMainView; } }

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

and now i am done. i just need to use my dispatcher for my ui therad like this...

mMainView.Dispatcher.BeginInvoke(
    () => MessageBox.Show(mSpWeb.CurrentUser.LoginName));

(in this example i was asking my controller to get my sharepoint 2010 loginname but you can do what your need)

we are almost done you also need to define your root visual in the app.xaml like this

var mainController = new MainController();
RootVisual = mainController.View;

this helped me by my application. maybe it can help you too...


You don't need to pass the UI Dispatcher to the ViewModel. The UI Dispatcher is available from the current application singleton.

App.Current.MainWindow.Dispatcher

This will make your ViewModel dependent on the View. Depending on your application, that may or may not be fine.


for WPF and Windows store apps use:-

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

keeping reference to GUI dispatcher is not really the right way.

if that doesn't work (such as in case of windows phone 8 apps) then use:-

       Deployment.Current.Dispatcher

if you are used uNhAddIns you can make an asynchrounous behavior easily. take a look here

And i think need a few modification to make it work on Castle Windsor (without uNhAddIns)


I've find another (most simplest) way:

Add to view model action that's should be call in Dispatcher:

public class MyViewModel
{
    public Action<Action> CallWithDispatcher;

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

And add this action handler in view constructor:

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

        DataContext = model;
        InitializeComponent();

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

Now you haven't problem with testing, and it's easy to implement. I've add it to my site


Some of my WPF projects I have faced the same situation. In my MainViewModel (Singleton instance), I got my CreateInstance() static method takes the dispatcher. And the create instance gets called from the View so that I can pass the Dispatcher from there. And the ViewModel test module calls CreateInstance() parameterless.

But in a complex multithread scenario it is always good to have an interface implementation on the View side so as to get the proper Dispatcher of the current Window.


Maybe I am a bit late to this discussion, but I found 1 nice article https://msdn.microsoft.com/en-us/magazine/dn605875.aspx

There is 1 paragraph

Furthermore, all code outside the View layer (that is, the ViewModel and Model layers, services, and so on) should not depend on any type tied to a specific UI platform. Any direct use of Dispatcher (WPF/Xamarin/Windows Phone/Silverlight), CoreDispatcher (Windows Store), or ISynchronizeInvoke (Windows Forms) is a bad idea. (SynchronizationContext is marginally better, but barely.) For example, there’s a lot of code on the Internet that does some asynchronous work and then uses Dispatcher to update the UI; a more portable and less cumbersome solution is to use await for asynchronous work and update the UI without using Dispatcher.

Assume if you can use async/await properly, this is not an issue.

참고URL : https://stackoverflow.com/questions/2354438/how-to-pass-the-ui-dispatcher-to-the-viewmodel

반응형