2010-03-01 59 views
60

我應該能夠訪問屬於視圖的Dispatcher我需要將它傳遞給ViewModel。但視圖不應該瞭解任何有關ViewModel的內容,那麼你如何通過它?引入一個接口,或者不是將它傳遞給實例,而是創建一個將由View編寫的全局調度器單例?你如何在你的MVVM應用程序和框架中解決這個問題?如何將UI調度程序傳遞給ViewModel

編輯:請注意,因爲我的ViewModels可能創建在後臺線程中,我不能只是在ViewModel的構造函數中做Dispatcher.Current

回答

40

我一直在使用的接口IContext抽象的調度。
我使用MEF(託管擴展框架)將接口注入到ViewModels中。另一種可能性是構造參數。 但是,我更喜歡使用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); 
    } 
} 
+1

你能否提供一個wpf UserControl實現的例子? – Femaref 2010-06-10 09:37:38

+2

是的,可以提供有關實施的任何示例?謝謝。 – K2so 2010-11-01 13:46:35

+2

比從未更好地遲到;-)嘗試以下方法:http://pastebin.com/eXTQf9vm – Matthias 2011-07-10 10:03:57

12

我得到的ViewModel存儲當前的調度成員。

如果ViewModel是由視圖創建的,那麼您知道創建時的當前調度器將是View的調度器。

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

這樣做的好處是可以更輕鬆地單元測試你的ViewModels:

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

在我的場景中,ViewModels是在線程中創建的。這就是我首先要問的原因。 – bitbonk 2010-03-01 07:57:29

+4

但單元測試存在問題。你如何編寫這種代碼和單元測試你的虛擬機? – 2010-03-01 16:52:55

+0

@Roy:看到這個問題http://stackoverflow.com/questions/1106881/using-the-wpf-dispatcher-in-unit-tests/1303762#1303762 – 2010-03-01 21:54:53

18

您可能實際上並不需要的調度。如果將viewmodel上的屬性綁定到視圖中的GUI元素,那麼WPF綁定機制會自動使用調度程序將GUI更新整理到GUI線程。


編輯:

此編輯爲響應伊薩克薩沃的評論。

微軟內部的處理結合屬性,你會發現下面的代碼代碼:

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線程。

+9

但是有很多情況,您可能需要這樣做,想象一下綁定到UI的ObservableCollection,並且您試圖調用_collection .Add()來自工作線程 – 2010-03-01 08:58:13

+0

我知道。當然,通常的考慮因素仍然適用。 – 2010-03-01 09:01:46

+3

目前,我們只需將調度程序添加到ObservableCollection中即可。 – bitbonk 2010-03-01 09:25:02

-1

我的一些WPF項目面臨同樣的情況。在我的MainViewModel(Singleton實例)中,我得到了我的CreateInstance()靜態方法需要調度器。然後創建實例從視圖中調用,以便我可以從那裏傳遞分派器。 而ViewModel測試模塊調用CreateInstance()無參數。

但是在複雜的多線程場景中,在View端有一個接口實現總是好的,以便獲得當前窗口的正確Dispatcher。

0

如果你習慣uNhAddIns,你可以很容易做出的 - 異步行爲。看看here

,我覺得需要一些修改,以使其在溫莎城堡進行工作(無uNhAddIns)

1

喜也許我太晚了,因爲它已經因爲你的第一篇文章已有8個月〜 我在silverlight mvvm應用程序中遇到同樣的問題。我發現我的解決方案是這樣的。對於我有的每個模型和視圖模型,我也有一個叫做控制器的類。 像

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

我MainController分管指揮和模型視圖模型和之間的連接。在構造函數中,實例化視圖及其視圖模型,並將視圖的datacontext設置爲其視圖模型。

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

//(在我的命名約定我有成員變量的前綴米)

我也有我的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; 

幫我通過我的應用程序。也許它可以幫助你太...

4

另一種常見的模式(也就是現在看到的框架多大用處)爲SynchronizationContext

它使您可以同步和異步分派。您也可以在當前線程上設置當前的SynchronizationContext,這意味着它很容易被嘲弄。 DispatcherSynchronizationContext由WPF應用程序使用。 WCF和WF4使用SynchronizationContext的其他實現。

+2

這是我認爲最好也是最簡單的方法。您可以將ViewModel的SynchronizationContext默認爲創建ViewModel的線程當前上下文,並且如果UserControl需要更改它,則可以隨意更改它。 – ken 2012-04-16 16:49:18

0

我已經找到另一個(最簡單的)方式:

添加到瀏覽模式的行動,就是要在調度員可撥打:

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

現在你沒有測試問題,而且很容易實現。 我已將它添加到我的site

1

您不需要將UI調度程序傳遞給ViewModel。 UI調度程序可從當前應用程序單例中獲得。

App.Current.MainWindow.Dispatcher 

這將使您的ViewModel依賴於視圖。根據您的應用程序,可能會也可能不會。

+0

MainWindow可能爲null。 – Cologler 2015-06-02 01:42:54

26

你爲什麼不使用

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

,而不是保持參照GUI調度。

1

WPF和Windows應用商店的應用程序使用: -

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

保存參考GUI調度員是不是真的正確的方式。

如果不工作(例如在窗口電話8個應用的情況下),然後使用: -

 Deployment.Current.Dispatcher 
6

作爲MVVM燈5.2的,庫現在包括一個DispatcherHelperGalaSoft.MvvmLight.Threading命名空間暴露一個函數CheckBeginInvokeOnUI(),它接受一個委託並在UI線程上運行它。如果您的ViewModel正在運行一些影響您的UI元素所綁定的VM屬性的工作線程,則非常方便。

DispatcherHelper必須在應用程序生命週期的早期階段通過調用DispatcherHelper.Initialize()進行初始化(例如App_Startup)。然後,您可以使用下面的調用運行任何委託(或lambda):

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

注意類是當你通過的NuGet添加它默認不引用GalaSoft.MvvmLight.Platform庫中定義。您必須手動添加對此庫的引用。

0

從WPF版本4開始。5可以使用CurrentDispatcher

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

}, DispatcherPriority.Normal); 
+0

這個單例在多線程場景中不能很好地工作。 – bitbonk 2016-05-17 15:18:58

0

也許我有點晚了這次討論,但我發現1好文章https://msdn.microsoft.com/en-us/magazine/dn605875.aspx

有1款

此外,查看以外的所有代碼圖層(即ViewModel 和模型圖層,服務等)不應取決於綁定到特定UI平臺的任何類型 。任何直接使用Dispatcher (WPF/Xamarin/Windows Phone/Silverlight),CoreDispatcher(Windows 存儲)或ISynchronizeInvoke(Windows窗體)是一個壞主意。 (SynchronizationContext略微好一點,但幾乎沒有。)對於 示例,Internet上有很多代碼用於執行一些異步工作,然後使用Dispatcher更新UI;更多的 便攜式和不太麻煩的解決方案是使用等待異步 工作和更新UI而不使用分派器。

假設您可以正確使用異步/等待,這不是問題。