C# WPF MVVM开发框架Caliburn.Micro IResult和协同程序⑥
“?引言部分,總領全篇文章的中心內容。”
01
—
IResult and Coroutines
在前面,我提到了Actions概念的另一個引人注目的特性,稱為協同程序。如果你以前沒聽說過這個詞,下面是維基百科要說的:
在計算機科學中,協同程序是一種程序組件,它泛化子例程以允許多個入口點在某些位置暫停和恢復執行。協同程序非常適合實現更熟悉的程序組件,如協作任務、迭代器、無限列表和管道。
在計算機科學中,協同程序是一種程序組件,它泛化子例程以允許多個入口點在某些位置暫停和恢復執行。協同程序非常適合于實現更熟悉的程序組件,如協作任務、迭代器、無限列表和管道。這里有一種方法可以解決這個問題:想象能夠執行一個方法,然后在某個語句上暫停它的執行,去做其他事情,然后返回并在您停止的地方繼續執行。這種技術在基于任務的編程中非常強大,特別是當這些任務需要異步運行時。例如,假設我們有一個ViewModel,它需要異步調用一個web服務,然后它需要獲取該結果,對其進行一些處理,并異步調用另一個web服務。最后,它必須在模式對話框中顯示結果,并用另一個異步任務響應用戶的對話框選擇。使用標準的事件驅動異步模型實現這一點并不是一種愉快的體驗。然而,這是一個使用協同程序來完成的簡單任務。問題是……C#沒有在本地實現協同路由。幸運的是,我們可以(某種程度上)在迭代器之上構建它們。
利用Caliburn.Micro中的這一特性需要兩件事:首先,在某個類上實現IResult接口,表示您希望執行的任務;其次,從Action2生成IResult實例。讓我們更具體一些。假設我們有一個Silverlight應用程序,我們希望動態下載并顯示屏幕,而不是主包的一部分。首先,我們可能希望顯示一個“加載”指示器,然后異步下載外部包,接下來隱藏“加載”指示器,最后導航到動態模塊內的特定屏幕。如果您的第一個屏幕希望使用協同程序導航到動態加載的第二個屏幕,則代碼如下所示:
using System.Collections.Generic; using System.ComponentModel.Composition;[Export(typeof(ScreenOneViewModel))] public class ScreenOneViewModel {public IEnumerable<IResult> GoForward(){yield return Loader.Show("Downloading...");yield return new LoadCatalog("Caliburn.Micro.Coroutines.External.xap");yield return Loader.Hide();yield return new ShowScreen("ExternalScreen");} }首先,請注意動作“GoForward”的返回類型為IEnumerable。這對于使用協同程序是至關重要的。該方法的主體有四個收益率語句。每個收益都返回一個IResult實例。第一個是顯示“下載”指示器的結果,第二個是異步下載xap,第三個是隱藏“下載”消息,第四個是顯示下載的xap的新屏幕。在每個yield語句之后,編譯器將“暫停”此方法的執行,直到特定任務完成。第一個、第三個和第四個任務是同步的,而第二個是異步的。但是yield語法允許您以順序方式編寫所有代碼,將原始工作流保留為可讀性和聲明性更強的結構。要進一步了解其工作原理,請查看IResult接口:
這是一個實現起來相當簡單的接口。只需在“Execute”方法中編寫代碼,并確保在完成時引發“Completed”事件,無論是同步任務還是異步任務。因為協同路由發生在動作內部,所以我們為您提供了一個ActionExecutionContext,它在構建與UI相關的IResult實現時非常有用。這允許ViewModel以聲明的方式聲明其控制視圖的意圖,而無需對視圖進行任何引用,也無需進行基于交互的單元測試。以下是ActionExecutionContext的外觀:
public class ActionExecutionContext {public ActionMessage Message;public FrameworkElement Source;public object EventArgs;public object Target;public DependencyObject View;public MethodInfo Method;public Func<bool> CanExecute;public object this[string key]; }下面是對所有這些屬性含義的解釋:
Message
導致調用此IResult的原始ActionMessage。
Source
觸發操作執行的框架元素。
EventArgs
與操作觸發器關聯的任何事件參數。
Target
存在實際操作方法的類實例。
View
與目標關聯的視圖。
Method
MethodInfo指定要在目標實例上調用的方法。
CanExecute
如果可以調用操作,則返回true,否則返回false的函數。
Key Index
存儲/檢索框架擴展可能使用的任何附加元數據的位置。
考慮到這一點,我編寫了一個naive Loader IResult,它搜索VisualTree,查找用于顯示加載消息的BusyIndicator的第一個實例。以下是實現:
using System; using System.Windows; using System.Windows.Controls;public class Loader : IResult {readonly string message;readonly bool hide;public Loader(string message){this.message = message;}public Loader(bool hide){this.hide = hide;}public void Execute(CoroutineExecutionContext context){var view = context.View as FrameworkElement;while(view != null){var busyIndicator = view as BusyIndicator;if(busyIndicator != null){if(!string.IsNullOrEmpty(message))busyIndicator.BusyContent = message;busyIndicator.IsBusy = !hide;break;}view = view.Parent as FrameworkElement;}Completed(this, new ResultCompletionEventArgs());}public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };public static IResult Show(string message = null){return new Loader(message);}public static IResult Hide(){return new Loader(true);} }看看我是如何利用上下文的。查看?這在保持視圖和視圖模型之間的分離的同時打開了許多可能性。僅列出使用IResult實現可以做的一些有趣的事情:顯示消息框、顯示基于VM的模式對話框、在用戶的鼠標位置顯示基于VM的彈出窗口、播放動畫、顯示文件保存/加載對話框、基于VM屬性而非控件將焦點放在特定的UI元素上等。當然,最大的機會之一是調用web服務。讓我們看看您可能如何做到這一點,但通過使用稍微不同的場景,動態下載xap:
using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.ComponentModel.Composition.ReflectionModel; using System.Linq;public class LoadCatalog : IResult {static readonly Dictionary<string, DeploymentCatalog> Catalogs = new Dictionary<string, DeploymentCatalog>();readonly string uri;[Import]public AggregateCatalog Catalog { get; set; }public LoadCatalog(string relativeUri){uri = relativeUri;}public void Execute(CoroutineExecutionContext context){DeploymentCatalog catalog;if(Catalogs.TryGetValue(uri, out catalog))Completed(this, new ResultCompletionEventArgs());else{catalog = new DeploymentCatalog(uri);catalog.DownloadCompleted += (s, e) =>{if(e.Error == null){Catalogs[uri] = catalog;Catalog.Catalogs.Add(catalog);catalog.Parts.Select(part => ReflectionModelServices.GetPartType(part).Value.Assembly).Where(assembly => !AssemblySource.Instance.Contains(assembly)).Apply(x => AssemblySource.Instance.Add(x));}else Loader.Hide().Execute(context);Completed(this, new ResultCompletionEventArgs {Error = e.Error,WasCancelled = false});};catalog.DownloadAsync();}}public event EventHandler<ResultCompletionEventArgs> Completed = delegate { }; }如果不清楚,此樣本使用的是MEF。此外,我們正在利用為Silverlight 4創建的DeploymentCatalog。您不需要了解很多關于MEF或DeploymentCatalog的知識就可以獲得外賣。請注意,我們連接DownloadCompleted事件,并確保在其處理程序中觸發IResult.Completed事件。這就是使異步模式能夠工作的原因。我們還確保檢查錯誤并在ResultCompletionEventArgs中傳遞該錯誤。說到這里,這門課看起來是這樣的:
public class ResultCompletionEventArgs : EventArgs {public Exception Error;public bool WasCancelled; }Caliburn.Micro的枚舉器在從每個IResult回調后檢查這些屬性。如果出現錯誤或WASCELLENCEL設置為true,則停止執行。你可以利用這個優勢。假設您為OpenFileDialog創建了一個IResult。您可以檢查該對話框的結果,如果用戶取消了該對話框,請在事件參數上設置wascelected。通過執行此操作,您可以編寫一個動作,該動作假定如果執行Dialog.Show后面的代碼,則用戶必須選擇了一個文件。這種技術可以簡化這種情況下的邏輯。顯然,如果需要,可以對SaveFileDialog或任何確認樣式的消息框使用相同的技術。上面顯示的LoadCatalog實現中我最喜歡的部分是,最初的實現是由CM用戶編寫的!感謝janoveh提交的這篇精彩文章!作為旁注,我們添加到CM項目站點的內容之一是“配方”部分。在未來幾個月內,我們將在該領域添加更多類似的通用解決方案。因此,它將是一個檢查酷插件和框架定制的好地方。
您可以做的另一件事是創建一系列圍繞應用程序外殼構建的IResult實現。這就是上面使用的ShowScreen結果所做的。以下是它的實施:
using System; using System.ComponentModel.Composition;public class ShowScreen : IResult {readonly Type screenType;readonly string name;[Import]public IShell Shell { get; set; }public ShowScreen(string name){this.name = name;}public ShowScreen(Type screenType){this.screenType = screenType;}public void Execute(CoroutineExecutionContext context){var screen = !string.IsNullOrEmpty(name)? IoC.Get<object>(name): IoC.GetInstance(screenType, null);Shell.ActivateItem(screen);Completed(this, new ResultCompletionEventArgs());}public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };public static ShowScreen Of<T>(){return new ShowScreen(typeof(T));} }這帶來了IResult的另一個重要特性。在CM執行結果之前,它會將結果傳遞給IoC.build方法,從而使容器有機會通過屬性將依賴項推入。這允許您在視圖模型中正常創建它們,同時仍然允許它們依賴于應用程序服務。在這種情況下,我們依賴于IShell。您還可以注入容器,但在本例中,我選擇在內部使用IoC靜態類。一般來說,你應該避免直接從容器中取出東西。但是,我認為在基礎架構代碼(如ShowScreen IResult)內部執行時,這是可以接受的。
其他用途
現成的Caliburn.Micro可以為通過ActionMessage調用的任何操作自動執行協同路由。但是,有時您可能希望直接利用協同程序特性。要執行協同程序,可以使用靜態的coroutine.BeginExecute方法。
我希望這能為IResult提供一些解釋和創造性的想法。請務必查看隨附的示例應用程序。還有其他一些有趣的事情。
04
—
最后
原文標題:Caliburn.Micro Xaml made easy
原文鏈接:https://caliburnmicro.com/documentation/coroutines
翻譯:dotnet編程大全
C#技術群?:?添加小編微信mm1552923,備注:進群!
總結
以上是生活随笔為你收集整理的C# WPF MVVM开发框架Caliburn.Micro IResult和协同程序⑥的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: WPF 四种不同效果呼吸灯
- 下一篇: 如何在 Entity Framework