日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > C# >内容正文

C#

C# WPF MVVM开发框架Caliburn.Micro Screens, Conductors 和 Composition⑦

發(fā)布時(shí)間:2023/12/4 C# 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C# WPF MVVM开发框架Caliburn.Micro Screens, Conductors 和 Composition⑦ 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

01

Screens, Conductors and Composition

Actions, Coroutines and Conventions往往最能吸引Caliburn.Micro的注意力,但如果你想讓你的UI設(shè)計(jì)得更好,那么了解屏幕和導(dǎo)體可能是最重要的。如果您想利用合成,這一點(diǎn)尤其重要。杰里米·米勒最近在為艾迪生·韋斯利撰寫《呈現(xiàn)模式》一書時(shí),將屏幕、屏幕指揮和屏幕收藏這三個(gè)術(shù)語編成了法典。雖然這些模式主要通過從特定基類繼承ViewModels來在CM中使用,但將它們視為角色而不是視圖模型是很重要的。事實(shí)上,根據(jù)您的體系結(jié)構(gòu),屏幕可以是用戶控件、演示者或視圖模型。不過這有點(diǎn)超前了。首先,讓我們談?wù)勥@些東西的一般含義。

Theory

Screen

這是最容易理解的結(jié)構(gòu)。您可能認(rèn)為它是應(yīng)用程序表示層中存在的一個(gè)有狀態(tài)的工作單元。它獨(dú)立于應(yīng)用程序外殼。外殼可能會(huì)顯示許多不同的屏幕,有些甚至同時(shí)顯示。shell可能也會(huì)顯示很多小部件,但它們不是任何屏幕的一部分。一些屏幕示例可能是應(yīng)用程序設(shè)置的模式對(duì)話框、Visual Studio中的代碼編輯器窗口或?yàn)g覽器中的頁面。你可能對(duì)此有很好的直覺。

通常情況下,屏幕具有與其相關(guān)聯(lián)的生命周期,允許屏幕執(zhí)行自定義激活和停用邏輯。這就是杰里米所說的屏幕激活器。例如,以VisualStudio代碼編輯器窗口為例。如果在一個(gè)選項(xiàng)卡中編輯C#代碼文件,然后切換到包含XML文檔的選項(xiàng)卡,您會(huì)注意到工具欄圖標(biāo)會(huì)發(fā)生變化。這些屏幕中的每一個(gè)都有自定義的激活/停用邏輯,使其能夠設(shè)置/拆除應(yīng)用程序工具欄,以便它們根據(jù)活動(dòng)屏幕提供適當(dāng)?shù)膱D標(biāo)。在簡單的場景中,ScreenActivator通常與Screen是同一個(gè)類。但是,您應(yīng)該記住,這是兩個(gè)獨(dú)立的角色。如果特定屏幕具有復(fù)雜的激活邏輯,則可能需要將ScreenActivator考慮到其自己的類中,以降低屏幕的復(fù)雜性。如果您的應(yīng)用程序具有許多不同的屏幕,但都具有相同的激活/停用邏輯,則這一點(diǎn)尤為重要。

Screen Conductor

一旦將屏幕激活生命周期的概念引入到應(yīng)用程序中,就需要某種方法來實(shí)施它。這是屏幕指揮的角色。當(dāng)您顯示屏幕時(shí),導(dǎo)線會(huì)確保屏幕已正確激活。如果您正在從屏幕過渡,它會(huì)確保屏幕被停用。還有另一個(gè)場景也很重要。假設(shè)您有一個(gè)包含未保存數(shù)據(jù)的屏幕,并且有人試圖關(guān)閉該屏幕甚至應(yīng)用程序。ScreenConductor已經(jīng)在強(qiáng)制停用,它可以通過實(shí)現(xiàn)正常關(guān)機(jī)來提供幫助。與您的屏幕可能實(shí)現(xiàn)激活/停用界面的方式相同,它也可能實(shí)現(xiàn)一些界面,允許售票員詢問“您可以關(guān)閉嗎?”這引出了一個(gè)重要的問題:在某些情況下,停用屏幕與關(guān)閉屏幕相同,而在其他情況下,停用屏幕與關(guān)閉屏幕不同。例如,在VisualStudio中,當(dāng)您從一個(gè)選項(xiàng)卡切換到另一個(gè)選項(xiàng)卡時(shí),它不會(huì)關(guān)閉文檔。它只是激活/停用它們。必須顯式關(guān)閉選項(xiàng)卡。這就是觸發(fā)正常關(guān)機(jī)邏輯的原因。然而,在基于導(dǎo)航的應(yīng)用程序中,離開頁面導(dǎo)航肯定會(huì)導(dǎo)致停用,但也可能導(dǎo)致該頁面關(guān)閉。這完全取決于您的特定應(yīng)用程序的體系結(jié)構(gòu),您應(yīng)該仔細(xì)考慮這一點(diǎn)。

Screen Collection

在像VisualStudio這樣的應(yīng)用程序中,您不僅有一個(gè)ScreenConductor來管理激活、停用等,而且還有一個(gè)ScreenCollection來維護(hù)當(dāng)前打開的屏幕或文檔列表。通過添加這一難題,我們還可以解決停用與關(guān)閉的問題。屏幕集合中的任何內(nèi)容都保持打開狀態(tài),但一次只有其中一項(xiàng)處于活動(dòng)狀態(tài)。在像VS這樣的MDI風(fēng)格的應(yīng)用程序中,導(dǎo)體將管理在ScreenCollection成員之間切換活動(dòng)屏幕。打開一個(gè)新文檔會(huì)將其添加到屏幕集合并切換到活動(dòng)屏幕。關(guān)閉文檔不僅會(huì)停用文檔,還會(huì)將其從屏幕集合中刪除。所有這一切都取決于它是否正面回答了“你能關(guān)門嗎?”。當(dāng)然,文檔關(guān)閉后,指揮需要決定ScreenCollection中的哪些其他項(xiàng)目應(yīng)該成為下一個(gè)活動(dòng)文檔。

Implementations

有很多不同的方法來實(shí)現(xiàn)這些想法。您可以從TabControl繼承并實(shí)現(xiàn)IScreenConductor接口,并直接在控件中構(gòu)建所有邏輯。把它添加到你的IoC容器中,你就可以開始跑步了。您可以在自定義UserControl上實(shí)現(xiàn)IScreen接口,也可以將其實(shí)現(xiàn)為POCO,用作監(jiān)控控制器的基礎(chǔ)。ScreenCollection可以是一個(gè)自定義集合,具有維護(hù)活動(dòng)屏幕的特殊邏輯,也可以只是一個(gè)簡單的IList。

Caliburn.Micro實(shí)現(xiàn)

這些概念通過各種接口和基類在CM中實(shí)現(xiàn),這些接口和基類主要用于構(gòu)建ViewModels。讓我們來看看它們:

Screens

在Caliburn.Micro中,我們將屏幕激活的概念分解為幾個(gè)界面:

IActivate–表示實(shí)現(xiàn)者需要激活。此接口提供激活方法、IsActive屬性和激活事件,激活時(shí)應(yīng)引發(fā)這些事件。

IDeactivate–表示實(shí)現(xiàn)者需要停用。此接口有一個(gè)Deactivate方法,該方法采用bool屬性,指示除禁用屏幕外是否關(guān)閉屏幕。它還有兩個(gè)事件:AttemptingDeactivation(應(yīng)在停用前引發(fā))和Deactivate(應(yīng)在停用后引發(fā))。

IGuardClose–表示實(shí)現(xiàn)者可能需要取消關(guān)閉操作。它有一種方法:CanClose。該方法是使用異步模式設(shè)計(jì)的,允許在做出密切決策時(shí)發(fā)生復(fù)雜的邏輯,如異步用戶交互。調(diào)用方將向CanClose方法傳遞一個(gè)操作。實(shí)現(xiàn)者應(yīng)該在保護(hù)邏輯完成時(shí)調(diào)用該操作。Pass true表示實(shí)現(xiàn)者可以關(guān)閉,否則為false。

除了這些核心生命周期接口之外,我們還有一些其他接口可以幫助創(chuàng)建表示層類之間的一致性:

IHaveDisplayName–有一個(gè)名為DisplayName的屬性

INotifyPropertyChangedEx–此接口繼承標(biāo)準(zhǔn)INotifyPropertyChanged,并使用其他行為對(duì)其進(jìn)行擴(kuò)展。它添加了一個(gè)IsNotifying屬性(可用于關(guān)閉/打開所有更改通知)、一個(gè)NotifyOfPropertyChange方法(可調(diào)用該方法引發(fā)屬性更改)和一個(gè)Refresh方法(可用于刷新對(duì)象上的所有綁定)。

IObservableCollection–由以下接口組成:IList、INotifyPropertyChangedEx和INotifyCollectionChanged

IChild–由作為層次結(jié)構(gòu)一部分或需要引用所有者的元素實(shí)現(xiàn)。它有一個(gè)名為Parent的屬性。

IViewAware–由需要了解其綁定到的視圖的類實(shí)現(xiàn)。它有一個(gè)AttachView方法,框架在將視圖綁定到實(shí)例時(shí)調(diào)用該方法。它有一個(gè)GetView方法,框架在為實(shí)例創(chuàng)建視圖之前調(diào)用該方法。這允許緩存復(fù)雜視圖,甚至復(fù)雜視圖解析邏輯。最后,當(dāng)視圖附加到名為ViewAttached的實(shí)例時(shí),應(yīng)該引發(fā)一個(gè)事件。

由于某些組合非常常見,我們有一些方便的接口和基類:

PropertyChangedBase–實(shí)現(xiàn)INotifyPropertyChangedEx(從而實(shí)現(xiàn)INotifyPropertyChanged)。除了標(biāo)準(zhǔn)字符串機(jī)制之外,它還提供了一個(gè)基于lambda的NotifyOfPropertyChange方法,支持強(qiáng)類型更改通知。此外,所有屬性更改事件都會(huì)自動(dòng)封送到UI線程。

BindableCollection–通過繼承標(biāo)準(zhǔn)ObservableCollection并添加INotifyPropertyChangedEx指定的其他行為來實(shí)現(xiàn)IObservableCollection。此外,此類確保所有屬性更改和集合更改事件都發(fā)生在UI線程上。

IScreen–此接口由其他幾個(gè)接口組成:IHaveDisplayName、IActivate、IDeactivate、IGuardClose和INotifyPropertyChangedEx。

Screen–繼承自PropertyChangedBase并實(shí)現(xiàn)IScreen接口。此外,還實(shí)現(xiàn)了IChild和iViewWare。

這意味著您可能會(huì)從PropertyChangedBase或Screen繼承大多數(shù)視圖模型。一般來說,如果您需要任何激活功能和PropertyChangedBase來完成其他一切,您將使用Screen。CM的默認(rèn)屏幕實(shí)現(xiàn)還具有一些附加功能,可以輕松地連接到生命周期的適當(dāng)部分:

OnInitialize–重寫此方法以添加僅在屏幕第一次激活時(shí)執(zhí)行的邏輯。初始化完成后,IsInitialized將為true。

OnActivate–覆蓋此方法以添加每次激活屏幕時(shí)應(yīng)執(zhí)行的邏輯。激活完成后,IsActive將為true。

OnDeactivate–覆蓋此方法以添加自定義邏輯,該邏輯應(yīng)在屏幕停用或關(guān)閉時(shí)執(zhí)行。bool屬性將指示停用是否實(shí)際結(jié)束。停用完成后,IsActive將為false。

CanClose–默認(rèn)實(shí)現(xiàn)始終允許關(guān)閉。重寫此方法以添加自定義保護(hù)邏輯。

OnViewLoaded–由于Screen實(shí)現(xiàn)了IViewAware,它借此機(jī)會(huì)讓您知道何時(shí)觸發(fā)視圖的Loaded事件。如果您遵循SupervisingController或被動(dòng)查看樣式,并且需要使用視圖,請(qǐng)使用此選項(xiàng)。這也是放置視圖模型邏輯的地方,視圖模型邏輯可能依賴于視圖的存在,即使您可能沒有直接使用視圖。

TryClose–調(diào)用此方法關(guān)閉屏幕。如果屏幕由導(dǎo)體控制,它會(huì)要求導(dǎo)體啟動(dòng)屏幕的關(guān)閉過程。如果屏幕不是由導(dǎo)體控制的,而是獨(dú)立存在的(可能是因?yàn)樗鞘褂肳indowManager顯示的),此方法將嘗試關(guān)閉視圖。在這兩種情況下,將調(diào)用CanClose邏輯,如果允許,將使用true值調(diào)用OnDeactivate。

所以,再重復(fù)一次:若你們需要一個(gè)生命周期,從屏幕繼承;否則從PropertyChangedBase繼承。

Conductors

正如我前面提到的,一旦引入生命周期,就需要一些東西來實(shí)施它。在Caliburn.Micro中,此角色由IConductor接口表示,該接口具有以下成員:

ActivateItem–調(diào)用此方法以激活特定項(xiàng)。如果導(dǎo)體使用“屏幕采集”,它也會(huì)將其添加到當(dāng)前進(jìn)行的項(xiàng)目中

DeactivateItem–調(diào)用此方法以停用特定項(xiàng)。第二個(gè)參數(shù)指示是否也應(yīng)關(guān)閉該項(xiàng)。如果是這樣,如果導(dǎo)體使用“屏幕采集”,它也會(huì)將其從當(dāng)前進(jìn)行的項(xiàng)目中刪除

ActivationProcessed–在指揮處理項(xiàng)目激活時(shí)引發(fā)。它指示激活是否成功。

GetChildren–調(diào)用此方法返回導(dǎo)體正在跟蹤的所有項(xiàng)目的列表。如果導(dǎo)體使用“屏幕集合”,則返回所有“屏幕”,否則僅返回ActiveItem。(從iPart界面)

INotifyPropertyChangedEx–此接口由IConductor組成。

我們還有一個(gè)名為IConductActivieItem的接口,它由IConductor和IHaveActiveItem組成,用于添加以下成員:

ActiveItem–一個(gè)屬性,用于指示導(dǎo)體當(dāng)前跟蹤的活動(dòng)項(xiàng)目。

您可能已經(jīng)注意到,CM的IConductor接口使用術(shù)語“項(xiàng)”而不是“屏幕”,我在引號(hào)中加了術(shù)語“屏幕集合”。原因是CM的導(dǎo)體實(shí)現(xiàn)不需要執(zhí)行的項(xiàng)目來實(shí)現(xiàn)IScreen或任何特定接口。執(zhí)行的項(xiàng)目可以是POCO。每個(gè)導(dǎo)體實(shí)現(xiàn)都是泛型的,對(duì)類型沒有約束,而不是強(qiáng)制使用IScreen。當(dāng)要求導(dǎo)體激活/停用/關(guān)閉/等其正在執(zhí)行的每個(gè)項(xiàng)目時(shí),它會(huì)分別檢查它們是否存在以下細(xì)粒度接口:IActivate、IDeactivate、IGuardClose和IChild。實(shí)際上,我通常從Screen繼承已執(zhí)行的項(xiàng)目,但這使您可以靈活地使用自己的基類,或者僅在每個(gè)類的基礎(chǔ)上實(shí)現(xiàn)所關(guān)心的生命周期事件的接口。您甚至可以讓一個(gè)導(dǎo)體跟蹤異構(gòu)項(xiàng),其中一些項(xiàng)繼承自屏幕,另一些項(xiàng)實(shí)現(xiàn)特定接口,或者根本沒有。

開箱即用的CM有三種IConductor實(shí)現(xiàn),兩種與“屏幕集合”配合使用,另一種不配合使用。我們先來看看沒有收藏的售票員。

Conductor

這個(gè)簡單的導(dǎo)體通過顯式接口機(jī)制實(shí)現(xiàn)IConductor的大多數(shù)成員,并添加公開可用的相同方法的強(qiáng)類型版本。這允許通過接口以強(qiáng)類型方式(基于導(dǎo)體所執(zhí)行的項(xiàng)目)處理導(dǎo)體。導(dǎo)體將停用和關(guān)閉視為同義詞。由于導(dǎo)線不保持“屏幕收集”,每個(gè)新項(xiàng)目的激活都會(huì)導(dǎo)致先前激活項(xiàng)目的停用和關(guān)閉。由于IGuardClose的異步性質(zhì)以及傳導(dǎo)項(xiàng)可能實(shí)現(xiàn)或可能不實(shí)現(xiàn)此接口的事實(shí),用于確定傳導(dǎo)項(xiàng)是否可以關(guān)閉的實(shí)際邏輯可能很復(fù)雜。因此,列車長將此委托給ICloseStrategy,ICloseStrategy負(fù)責(zé)處理此問題,并將查詢結(jié)果告知列車長。大多數(shù)情況下,您可以使用自動(dòng)提供的DefaultCloseStrategy,但如果需要更改內(nèi)容(可能IGuardClose不足以滿足您的需要),您可以將導(dǎo)體上的CloseStrategy屬性設(shè)置為您自己的自定義策略。

Conductor.Collection.OneActive

此實(shí)現(xiàn)具有導(dǎo)體的所有功能,但也添加了“屏幕集合”的概念。由于CM中的導(dǎo)體可以執(zhí)行任何類型的類,因此此集合通過稱為Items而不是Screens的IObservableCollection公開。由于存在項(xiàng)目收集,已執(zhí)行項(xiàng)目的停用和關(guān)閉不會(huì)被視為同義詞。激活新項(xiàng)目時(shí),前一個(gè)激活項(xiàng)目僅被停用,并保留在“項(xiàng)目”集合中。要使用此導(dǎo)體關(guān)閉項(xiàng),必須顯式調(diào)用其CloseItem方法。當(dāng)項(xiàng)目關(guān)閉且該項(xiàng)目為激活項(xiàng)目時(shí),指揮必須確定下一步應(yīng)激活的項(xiàng)目。默認(rèn)情況下,這是列表中上一個(gè)活動(dòng)項(xiàng)之前的項(xiàng)。如果需要更改此行為,可以覆蓋DetermineExtItemToActivate。

Conductor.Collection.AllActive

類似地,此實(shí)現(xiàn)還具有Conductor的功能,并添加了“屏幕集合”的概念。主要區(qū)別在于,與單個(gè)項(xiàng)目同時(shí)處于活動(dòng)狀態(tài)不同,許多項(xiàng)目可以處于活動(dòng)狀態(tài)。關(guān)閉項(xiàng)目將停用該項(xiàng)目并將其從集合中移除。

關(guān)于CMs IConductor實(shí)現(xiàn),我還沒有提到兩個(gè)非常重要的細(xì)節(jié)。首先,它們都繼承自屏幕。這是這些實(shí)現(xiàn)的一個(gè)關(guān)鍵特性,因?yàn)樗谄聊缓蛯?dǎo)體之間創(chuàng)建了一個(gè)復(fù)合模式。假設(shè)您正在構(gòu)建一個(gè)基本的導(dǎo)航樣式應(yīng)用程序。您的shell將是導(dǎo)體的一個(gè)實(shí)例,因?yàn)樗淮物@示一個(gè)屏幕,并且不維護(hù)集合。但是,假設(shè)其中一個(gè)屏幕非常復(fù)雜,需要一個(gè)多選項(xiàng)卡界面,每個(gè)選項(xiàng)卡都需要生命周期事件。嗯,這個(gè)特定的屏幕可能繼承自Conductor.Collection.OneActive。shell不需要考慮單個(gè)屏幕的復(fù)雜性。如果需要的話,其中一個(gè)屏幕甚至可以是實(shí)現(xiàn)IScreen而不是ViewModel的UserControl。第二個(gè)重要細(xì)節(jié)是第一個(gè)細(xì)節(jié)的結(jié)果。由于IConductor的所有OOTB實(shí)現(xiàn)都繼承自Screen,這意味著它們也有一個(gè)生命周期,生命周期級(jí)聯(lián)到它們正在執(zhí)行的任何項(xiàng)目。因此,如果導(dǎo)體被停用,其活動(dòng)項(xiàng)也將被停用。如果你試圖關(guān)閉一個(gè)導(dǎo)體,它將只能在它所執(zhí)行的所有項(xiàng)目都可以關(guān)閉的情況下才能關(guān)閉。這是一個(gè)非常強(qiáng)大的功能。關(guān)于這一點(diǎn),我注意到有一個(gè)方面經(jīng)常絆倒開發(fā)人員**如果您在導(dǎo)體中激活了一個(gè)本身未激活的項(xiàng)目,則該項(xiàng)目在導(dǎo)體被激活之前不會(huì)被激活。**這一點(diǎn)在您思考時(shí)是有意義的,但偶爾會(huì)導(dǎo)致頭發(fā)拉扯。

Quasi-Conductors

在CM中,并不是所有可以成為屏幕的東西都植根于導(dǎo)體內(nèi)部。例如,您的根視圖模型是什么?如果是指揮員,誰在激活它?這是引導(dǎo)程序執(zhí)行的工作之一。引導(dǎo)程序本身不是引導(dǎo)者,但它理解上面討論的細(xì)粒度生命周期接口,并確保根視圖模型得到應(yīng)有的尊重。WindowManager的工作方式與此類似,它的作用有點(diǎn)像一個(gè)指揮者,目的是強(qiáng)制執(zhí)行模態(tài)(僅限非模態(tài)WPF)窗口的生命周期。所以,生命周期并不神奇。所有屏幕/導(dǎo)體必須植根于導(dǎo)體,或由引導(dǎo)程序或WindowManager管理,才能正常工作;否則,您將需要自己管理生命周期。

View-First

如果您正在使用WP7或Silverlight導(dǎo)航框架,您可能想知道是否/如何利用屏幕和導(dǎo)體。到目前為止,我一直在假設(shè)外殼工程主要采用ViewModel優(yōu)先的方法。但是WP7平臺(tái)通過控制頁面導(dǎo)航來實(shí)施視圖優(yōu)先的方法。SL Nav框架也是如此。在這些情況下,電話/導(dǎo)航框架就像一個(gè)導(dǎo)體。為了更好地使用ViewModels,WP7版本的CM有一個(gè)FrameAdapter,它與NavigationService掛鉤。這個(gè)適配器是由PhoneBootstrapper設(shè)置的,它理解導(dǎo)體所做的相同的細(xì)粒度生命周期接口,并確保在導(dǎo)航過程中在適當(dāng)?shù)臅r(shí)候在ViewModels上調(diào)用它們。您甚至可以通過在ViewModel上實(shí)現(xiàn)IGuardClose來取消手機(jī)的頁面導(dǎo)航。雖然FrameAdapter只是WP7版本的CM的一部分,但如果您希望將其與Silverlight導(dǎo)航框架結(jié)合使用,它應(yīng)該可以方便地移植到Silverlight。

之前,我們?cè)贑aliburn.Micro中討論了屏幕和導(dǎo)體的理論和基本API。現(xiàn)在,我將介紹幾個(gè)示例中的第一個(gè)。此特定示例演示如何使用導(dǎo)體和兩個(gè)“頁面”視圖模型設(shè)置一個(gè)簡單的導(dǎo)航樣式shell。正如您從項(xiàng)目結(jié)構(gòu)中看到的,我們有典型的Bootstrapper和ShellViewModel模式。為了使這個(gè)示例盡可能簡單,我甚至沒有使用帶引導(dǎo)程序的IoC容器。讓我們先看看ShellViewModel。它繼承自導(dǎo)體,實(shí)現(xiàn)如下:

以下是相應(yīng)的ShellView:

請(qǐng)注意,ShellViewModel有兩個(gè)方法,每個(gè)方法都將視圖模型實(shí)例傳遞給ActivateItem方法。回想一下我們之前的討論,ActivateItem是導(dǎo)體上的一種方法,它將導(dǎo)體的ActiveItem屬性切換到此實(shí)例,并將實(shí)例推過屏幕生命周期的激活階段(如果它通過實(shí)現(xiàn)IActivate支持它)。還記得,如果ActiveItem已設(shè)置為實(shí)例,則在設(shè)置新實(shí)例之前,將檢查前一個(gè)實(shí)例是否實(shí)現(xiàn)了IGuardClose,這可能會(huì)取消ActiveItem的切換,也可能不會(huì)取消。假設(shè)當(dāng)前ActiveItem可以關(guān)閉,那么導(dǎo)體將推動(dòng)它通過生命周期的停用階段,將true傳遞給Deactivate方法以指示視圖模型也應(yīng)該關(guān)閉。這就是在Caliburn.Micro中創(chuàng)建導(dǎo)航應(yīng)用程序所需的全部內(nèi)容。導(dǎo)體的ActiveItem表示“當(dāng)前頁面”,導(dǎo)體管理從一個(gè)頁面到另一個(gè)頁面的轉(zhuǎn)換。這一切都是以ViewModel優(yōu)先的方式完成的,因?yàn)轵?qū)動(dòng)導(dǎo)航而不是“視圖”的是指揮家和子視圖模型

一旦基本導(dǎo)體結(jié)構(gòu)就位,就很容易獲得它。ShellView演示了這一點(diǎn)。我們所要做的就是在視圖中放置ContentControl。通過將其命名為“ActiveItem”,我們的數(shù)據(jù)綁定約定開始生效。ContentControl的約定有點(diǎn)有趣。如果綁定到的項(xiàng)不是值類型,也不是字符串,那么我們假設(shè)內(nèi)容是ViewModel。因此,我們沒有像在其他情況下那樣綁定到Content屬性,而是使用CM的自定義附加屬性:View.Model設(shè)置綁定。此屬性使CM的ViewLocator為視圖模型查找適當(dāng)?shù)囊晥D,并使CM的ViewModelBinder將兩者綁定在一起。完成后,我們將視圖彈出到ContentControl的Content屬性中。這個(gè)單一的約定使得框架中功能強(qiáng)大但簡單的ViewModel優(yōu)先組合成為可能。

為了完整起見,讓我們看看PageOneViewModel和PageTwoViewModel:

Along with their views:

<UserControl x:Class="Caliburn.Micro.SimpleNavigation.PageOneView"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"><TextBlock FontSize="32">Page One</TextBlock> </UserControl><UserControl x:Class="Caliburn.Micro.SimpleNavigation.PageTwoView"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"><TextBlock FontSize="32">Page Two</TextBlock> </UserControl>

我想指出最后幾點(diǎn)。請(qǐng)注意,PageOneViewModel只是一個(gè)POCO,但PageTwoViewModel繼承自Screen。請(qǐng)記住,CM中的導(dǎo)線不會(huì)對(duì)可以進(jìn)行的操作施加任何限制。相反,他們會(huì)在必要的時(shí)候檢查每個(gè)實(shí)例是否支持各種細(xì)粒度生命周期實(shí)例。因此,當(dāng)為PageTwoViewModel調(diào)用ActivateItem時(shí),它將首先檢查PageOneViewModel以查看是否實(shí)現(xiàn)了IGuardClose。由于它沒有,它將嘗試關(guān)閉它。然后,它將檢查是否實(shí)現(xiàn)了IDeactivate。由于沒有,它將繼續(xù)激活新項(xiàng)目。首先,它檢查新項(xiàng)是否實(shí)現(xiàn)了IChild。因?yàn)镾creen是這樣做的,所以它連接了層次關(guān)系。接下來,它將檢查PageTwoViewModel以查看是否實(shí)現(xiàn)了IActivate。因?yàn)镾creen會(huì)這樣做,所以O(shè)nActivate方法中的代碼將運(yùn)行。最后,它將在導(dǎo)體上設(shè)置ActiveItem屬性并引發(fā)適當(dāng)?shù)氖录_@里有一個(gè)重要的結(jié)果應(yīng)該記住:激活是一個(gè)特定于ViewModel的生命周期過程,不能保證任何有關(guān)視圖狀態(tài)的信息。很多時(shí)候,即使您的ViewModel已激活,其視圖也可能不可見。運(yùn)行示例時(shí),您將看到這一點(diǎn)。消息框?qū)⒃诩せ畎l(fā)生時(shí)顯示,但第二頁的視圖仍不可見。請(qǐng)記住,如果您有任何依賴于已加載視圖的激活邏輯,則應(yīng)覆蓋Screen.OnViewLoaded,而不是與OnActivate結(jié)合使用。

Simple MDI

讓我們看另一個(gè)例子:這一次是一個(gè)使用“屏幕集合”的簡單MDI shell。正如您再次看到的,我讓事情變得非常小和簡單:

下面是應(yīng)用程序運(yùn)行時(shí)的屏幕截圖:

這里我們有一個(gè)簡單的WPF應(yīng)用程序,其中包含一系列選項(xiàng)卡。單擊“打開選項(xiàng)卡”按鈕會(huì)產(chǎn)生明顯的效果。單擊選項(xiàng)卡內(nèi)的“X”將關(guān)閉該特定選項(xiàng)卡(也可能是顯而易見的)。讓我們通過查看ShellViewModel深入了解代碼:

public class ShellViewModel : Conductor<IScreen>.Collection.OneActive {int count = 1;public void OpenTab() {ActivateItem(new TabViewModel {DisplayName = "Tab " + count++});} }

由于我們希望維護(hù)一個(gè)打開項(xiàng)目的列表,但一次只保持一個(gè)項(xiàng)目處于活動(dòng)狀態(tài),因此我們使用Conductor.Collection.OneActive作為基類。注意,與前面的示例不同,我實(shí)際上是將已執(zhí)行項(xiàng)的類型限制為IScreen。在這個(gè)示例中并沒有真正的技術(shù)原因,但這更接近于我在實(shí)際應(yīng)用程序中的實(shí)際操作。OpenTab方法只需創(chuàng)建TabViewModel的一個(gè)實(shí)例,并設(shè)置其DisplayName屬性(來自IScreen),使其具有人類可讀的唯一名稱。讓我們思考幾個(gè)關(guān)鍵場景中導(dǎo)體與其屏幕之間的交互邏輯:

打開第一項(xiàng)

將項(xiàng)目添加到“項(xiàng)目”集合。

檢查項(xiàng)目是否存在IActivate,如果存在則調(diào)用它。

將項(xiàng)目設(shè)置為ActiveItem。

關(guān)閉現(xiàn)有項(xiàng)目

將該項(xiàng)傳遞給CloseStrategy,以確定是否可以關(guān)閉該項(xiàng)(默認(rèn)情況下,它查找IGuardClose)。否則,操作將被取消。

檢查結(jié)束項(xiàng)是否為當(dāng)前活動(dòng)項(xiàng)。如果是,請(qǐng)確定下一步要激活的項(xiàng)目,并按照“打開其他項(xiàng)目”中的步驟進(jìn)行操作

檢查結(jié)賬項(xiàng)目是否已激活。如果是這樣,則使用true調(diào)用以指示應(yīng)該停用和關(guān)閉它。

從Items集合中刪除該項(xiàng)。

這些是主要的情況。希望你能看到一些不同的指揮家沒有收集,并理解為什么這些差異存在。讓我們看看ShellView如何渲染:

<Window x:Class="Caliburn.Micro.SimpleMDI.ShellView"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:cal="http://www.caliburnproject.org"Width="640"Height="480"><DockPanel><Button x:Name="OpenTab"Content="Open Tab" DockPanel.Dock="Top" /><TabControl x:Name="Items"><TabControl.ItemTemplate><DataTemplate><StackPanel Orientation="Horizontal"><TextBlock Text="{Binding DisplayName}" /><Button Content="X"cal:Message.Attach="DeactivateItem($dataContext, 'true')" /></StackPanel></DataTemplate></TabControl.ItemTemplate></TabControl></DockPanel> </Window>

如您所見,我們使用的是WPF選項(xiàng)卡控件。CM的約定將其ItemsSource綁定到Items集合,將其SelectedItem綁定到ActiveItem。它還將添加一個(gè)默認(rèn)ContentTemplate,用于在ActiveItem的ViewModel/View對(duì)中進(jìn)行組合。約定還可以提供ItemTemplate,因?yàn)槲覀兊倪x項(xiàng)卡都實(shí)現(xiàn)IHaveDisplayName(通過屏幕),但我選擇通過提供我自己的來啟用關(guān)閉選項(xiàng)卡來覆蓋它。我們將在后面的文章中更深入地討論約定。為完整起見,以下是TabViewModel及其視圖的簡單實(shí)現(xiàn):

namespace Caliburn.Micro.SimpleMDI {public class TabViewModel : Screen {} }<UserControl x:Class="Caliburn.Micro.SimpleMDI.TabView"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"><StackPanel Orientation="Horizontal"><TextBlock Text="This is the view for "/><TextBlock x:Name="DisplayName" /><TextBlock Text="." /></StackPanel> </UserControl>

到目前為止,我一直試圖保持簡單,但我們的下一個(gè)樣本并非如此。在準(zhǔn)備過程中,您可能希望至少仔細(xì)考慮或嘗試做以下事情:

擺脫常規(guī)的TabViewModel。在真正的應(yīng)用程序中,您不會(huì)真的做這樣的事情。創(chuàng)建兩個(gè)自定義視圖模型和視圖。將對(duì)象連接起來,以便可以在導(dǎo)體中打開不同的視圖模型。當(dāng)激活每個(gè)視圖模型時(shí),確認(rèn)在選項(xiàng)卡控件中看到正確的視圖。

在Silverlight中重建此示例。不幸的是,Silverlight的TabControl完全崩潰,無法充分利用數(shù)據(jù)綁定。相反,嘗試使用水平列表框作為選項(xiàng)卡,使用ContentControl作為選項(xiàng)卡內(nèi)容。將它們放在DockPanel中,并使用一些命名約定,您將獲得與TabControl相同的效果。

創(chuàng)建工具欄視圖模型。添加IoC容器并將ToolBarViewModel注冊(cè)為singleton。將其添加到ShellViewModel,并確保在ShellView中呈現(xiàn)(請(qǐng)記住,您可以為此使用命名ContentControl)。接下來,將工具欄ViewModel插入到每個(gè)選項(xiàng)卡ViewModels中。在選項(xiàng)卡ViewModel OnActivate和OnActivate中編寫代碼,以便在激活特定選項(xiàng)卡ViewModel時(shí)從工具欄中添加/刪除上下文項(xiàng)。額外好處:創(chuàng)建一個(gè)DSL來完成這項(xiàng)工作,它不需要在激活覆蓋中使用顯式代碼。提示:使用事件。

取SimpleMDI樣本和SimpleNavigation樣本,并將它們組合在一起。在導(dǎo)航示例中將MDI外殼添加為PageViewModel,或在MDI示例中將導(dǎo)航外殼添加為選項(xiàng)卡。

Hybrid

此示例大致基于Billy Hollis在這部著名的DNR電視劇中展示的想法。與其花時(shí)間解釋UI的功能,不如看一下這個(gè)簡短的視頻,以獲得一個(gè)簡短的視覺解釋。

好的,現(xiàn)在您已經(jīng)看到了它的功能,讓我們看看它是如何組合在一起的。正如您從屏幕截圖中看到的,我選擇按功能組織項(xiàng)目:客戶、訂單、設(shè)置等。在大多數(shù)項(xiàng)目中,我更喜歡這樣做,而不是按“技術(shù)”分組組織,如視圖和視圖模型。如果我有一個(gè)復(fù)雜的特性,那么我可能會(huì)將其分解為這些區(qū)域。

我不打算逐行檢查這個(gè)樣本。如果你花點(diǎn)時(shí)間仔細(xì)看看,自己弄清楚事情是如何運(yùn)作的,那就更好了。但是,我想指出一些有趣的實(shí)現(xiàn)細(xì)節(jié)。

ViewModel Composition

Caliburn.Micro的屏幕和導(dǎo)體最重要的特征之一是,它們是復(fù)合模式的實(shí)現(xiàn),使它們易于以不同的配置組合在一起。一般來說,組合是面向?qū)ο缶幊套钪匾姆矫嬷?#xff0c;學(xué)習(xí)如何在表示層中使用它可以帶來很大的好處。為了了解構(gòu)圖在這個(gè)特定示例中的作用,讓我們看兩個(gè)屏幕截圖。第一個(gè)顯示視圖中包含CustomerWorkspace的應(yīng)用程序,編輯特定客戶的地址。第二個(gè)屏幕是相同的,但其視圖/視圖模型對(duì)是三維旋轉(zhuǎn)的,因此您可以看到UI是如何組成的。

編輯客戶地址

編輯客戶地址(3D分支)

在此應(yīng)用程序中,ShellViewModel是一個(gè)Conductor.Collection.OneActive。它在視覺上由窗口鍍鉻、標(biāo)題和底部底座表示。碼頭有按鈕,每個(gè)正在進(jìn)行的IWorkspace都有一個(gè)按鈕。單擊特定按鈕可使Shell激活該特定工作區(qū)。由于ShellView有一個(gè)綁定到ActiveItem的TransitionContentControl,激活的工作區(qū)被注入,其視圖顯示在該位置。在本例中,激活的是CustomerWorkspace視圖模型。CustomerWorkspaceViewModel恰好繼承了Conductor.Collection.OneActive。此ViewModel有兩個(gè)上下文視圖(請(qǐng)參見下文)。在上面的屏幕截圖中,我們顯示了詳細(xì)信息視圖。details視圖還有一個(gè)TransitionContentControl綁定到CustomerWorkspaceViewModel的ActiveItem,從而導(dǎo)致當(dāng)前CustomerServiceWModel與其視圖一起組成。CustomerViewModel能夠顯示本地模式對(duì)話框(它們只是特定自定義記錄的模式對(duì)話框,而不是其他任何對(duì)話框)。這是由DialogConductor的實(shí)例管理的,DialogConductor是CustomServiceWModel上的一個(gè)屬性。DialogConductor的視圖覆蓋CustomerView,但僅當(dāng)DialogConductor的ActiveItem不為null時(shí)才可見(通過值轉(zhuǎn)換器)。在上面描述的狀態(tài)中,DialogConductor的ActiveItem被設(shè)置為AddressViewModel的實(shí)例,因此模態(tài)對(duì)話框與AddressView一起顯示,并且基礎(chǔ)CustomerView被禁用。本示例中使用的整個(gè)shell框架就是以這種方式工作的,只需實(shí)現(xiàn)IWorkspace即可完全擴(kuò)展。CustomerViewModel和SettingsViewModel是此接口的兩種不同實(shí)現(xiàn),您可以深入研究。

同一ViewModel上的多個(gè)視圖

您可能不知道這一點(diǎn),但是Caliburn.Micro可以在同一個(gè)ViewModel上顯示多個(gè)視圖。在View/ViewModel的注入站點(diǎn)上設(shè)置View.Context attached屬性可以支持這一點(diǎn)。以下是默認(rèn)CustomerWorkspace視圖中的一個(gè)示例:

<clt:TransitioningContentControl cal:View.Context="{Binding State, Mode=TwoWay}"cal:View.Model="{Binding}" Style="{StaticResource specialTransition}"/>

圍繞它還有許多其他Xaml,以形成CustomerWorkSpace視圖的chrome,但內(nèi)容區(qū)域是視圖中最值得注意的部分。請(qǐng)注意,我們正在將View.Context附加屬性綁定到CustomerWorkspaceViewModel的State屬性。這允許我們根據(jù)該屬性的值動(dòng)態(tài)更改視圖。因?yàn)檫@些都托管在TransitioningContentControl中,所以每當(dāng)視圖發(fā)生更改時(shí),我們都會(huì)得到一個(gè)很好的轉(zhuǎn)換。此技術(shù)用于將CustomerWorkSpace視圖模型從“主”視圖(其中顯示所有打開的CustomerViewModel)、搜索UI和新按鈕切換到“詳細(xì)”視圖,其中顯示當(dāng)前激活的CustomerViewModel及其特定視圖(由中組成)。為了讓CM找到這些上下文視圖,您需要一個(gè)基于ViewModel名稱的名稱空間,減去單詞“View”和“Model”,其中一些視圖的名稱與上下文對(duì)應(yīng)。例如,當(dāng)框架查找Caliburn.Micro.HelloScreens.Customers.CustomersWorkspaceViewModel的詳細(xì)視圖時(shí),它將查找Caliburn.Micro.HelloScreens.Customers.CustomersWorkspace.Detail,這是現(xiàn)成的命名約定。如果這不適用于您,只需自定義ViewLocator.LocateForModelType函數(shù)。

自定義IConductor實(shí)現(xiàn)

盡管Caliburn.Micro為開發(fā)人員提供了IScreen和IConductor的默認(rèn)實(shí)現(xiàn)。很容易實(shí)現(xiàn)您自己的。在這個(gè)示例中,我需要一個(gè)對(duì)話框管理器,它可以是應(yīng)用程序特定部分的模態(tài),而不會(huì)影響其他部分。正常情況下,默認(rèn)導(dǎo)體可以工作,但我發(fā)現(xiàn)我需要微調(diào)關(guān)機(jī)順序,所以我實(shí)現(xiàn)了自己的。讓我們看一看:

[Export(typeof(IDialogManager)), PartCreationPolicy(CreationPolicy.NonShared)] public class DialogConductorViewModel : PropertyChangedBase, IDialogManager, IConductActiveItem {readonly Func<IMessageBox> createMessageBox;[ImportingConstructor]public DialogConductorViewModel(Func<IMessageBox> messageBoxFactory) {createMessageBox = messageBoxFactory;}public IScreen ActiveItem { get; private set; }public IEnumerable GetChildren() {return ActiveItem != null ? new[] { ActiveItem } : new object[0];}public void ActivateItem(object item) {ActiveItem = item as IScreen;var child = ActiveItem as IChild;if(child != null)child.Parent = this;if(ActiveItem != null)ActiveItem.Activate();NotifyOfPropertyChange(() => ActiveItem);ActivationProcessed(this, new ActivationProcessedEventArgs { Item = ActiveItem, Success = true });}public void DeactivateItem(object item, bool close) {var guard = item as IGuardClose;if(guard != null) {guard.CanClose(result => {if(result)CloseActiveItemCore();});}else CloseActiveItemCore();}object IHaveActiveItem.ActiveItem{get { return ActiveItem; }set { ActivateItem(value); }}public event EventHandler<ActivationProcessedEventArgs> ActivationProcessed = delegate { };public void ShowDialog(IScreen dialogModel) {ActivateItem(dialogModel);}public void ShowMessageBox(string message, string title = "Hello Screens", MessageBoxOptions options = MessageBoxOptions.Ok, Action<IMessageBox> callback = null) {var box = createMessageBox();box.DisplayName = title;box.Options = options;box.Message = message;if(callback != null)box.Deactivated += delegate { callback(box); };ActivateItem(box);}void CloseActiveItemCore() {var oldItem = ActiveItem;ActivateItem(null);oldItem.Deactivate(true);} }

嚴(yán)格地說,我實(shí)際上不需要實(shí)現(xiàn)IConductor來完成這項(xiàng)工作(因?yàn)槲覜]有將它組合成任何東西)。但我選擇這樣做是為了表示這個(gè)類在系統(tǒng)中扮演的角色,并盡可能保持體系結(jié)構(gòu)上的一致性。實(shí)現(xiàn)本身非常簡單。導(dǎo)體主要需要確保正確激活/停用其項(xiàng)目,并正確更新ActiveItem屬性。我還創(chuàng)建了兩個(gè)簡單的方法來顯示對(duì)話框和消息框,這些對(duì)話框和消息框通過IDialogManager界面公開。該類在MEF中注冊(cè)為非共享,以便希望顯示本地模態(tài)的應(yīng)用程序的每個(gè)部分都將獲得自己的實(shí)例,并能夠維護(hù)自己的狀態(tài),如上面討論的CustomServiceWModel所示。

自定義策略

本示例最酷的特性之一可能是如何控制應(yīng)用程序關(guān)閉。由于IShell繼承了IGuardClose,因此在引導(dǎo)程序中,我們只需覆蓋啟動(dòng)并連接Silverlight的主窗口。關(guān)閉事件以調(diào)用IShell.CanClose:

protected override void OnStartup(object sender, StartupEventArgs e) {base.OnStartup(sender, e);if(Application.IsRunningOutOfBrowser) {mainWindow = Application.MainWindow;mainWindow.Closing += MainWindowClosing;} }void MainWindowClosing(object sender, ClosingEventArgs e) {if (actuallyClosing)return;e.Cancel = true;Execute.OnUIThread(() => {var shell = IoC.Get<IShell>();shell.CanClose(result => {if(result) {actuallyClosing = true;mainWindow.Close();}});}); }

ShellViewModel通過其基類Conductor.Collection.OneActive繼承此功能。由于所有內(nèi)置導(dǎo)體都有閉合策略,因此我們可以創(chuàng)建特定于導(dǎo)體的關(guān)機(jī)機(jī)制,并輕松地將其插入。以下是我們?nèi)绾尾迦胱远x策略:

[Export(typeof(IShell))] public class ShellViewModel : Conductor<IWorkspace>.Collection.OneActive, IShell {readonly IDialogManager dialogs;[ImportingConstructor]public ShellViewModel(IDialogManager dialogs, [ImportMany]IEnumerable<IWorkspace> workspaces) {this.dialogs = dialogs;Items.AddRange(workspaces);CloseStrategy = new ApplicationCloseStrategy();}public IDialogManager Dialogs {get { return dialogs; }} }

以下是該戰(zhàn)略的實(shí)施情況:

public class ApplicationCloseStrategy : ICloseStrategy<IWorkspace> {IEnumerator<IWorkspace> enumerator;bool finalResult;Action<bool, IEnumerable<IWorkspace>> callback;public void Execute(IEnumerable<IWorkspace> toClose, Action<bool, IEnumerable<IWorkspace>> callback) {enumerator = toClose.GetEnumerator();this.callback = callback;finalResult = true;Evaluate(finalResult);}void Evaluate(bool result){finalResult = finalResult && result;if (!enumerator.MoveNext() || !result)callback(finalResult, new List<IWorkspace>());else{var current = enumerator.Current;var conductor = current as IConductor;if (conductor != null){var tasks = conductor.GetChildren().OfType<IHaveShutdownTask>().Select(x => x.GetShutdownTask()).Where(x => x != null);var sequential = new SequentialResult(tasks.GetEnumerator());sequential.Completed += (s, e) => {if(!e.WasCancelled)Evaluate(!e.WasCancelled);};sequential.Execute(new ActionExecutionContext());}else Evaluate(true);}} }

我在這里做的有趣的事情是重用IResult功能來異步關(guān)閉應(yīng)用程序。以下是自定義策略如何使用它:

檢查每個(gè)IWorkspace以查看它是否是IConductor。

如果為true,則獲取實(shí)現(xiàn)應(yīng)用程序特定接口IHaveShutdownTask的所有已執(zhí)行項(xiàng)。

通過調(diào)用GetShutdownTask檢索關(guān)機(jī)任務(wù)。如果沒有任務(wù),它將返回null,所以將其過濾掉。

由于關(guān)機(jī)任務(wù)是IResult,因此將所有這些傳遞給SequentialResult并開始枚舉。

IResult可以將ResultCompletionEventArgs.wasCancelled設(shè)置為true以取消應(yīng)用程序關(guān)閉。

繼續(xù)執(zhí)行所有工作區(qū),直到完成或取消。

如果所有IResults成功完成,將允許關(guān)閉應(yīng)用程序。

如果存在臟數(shù)據(jù),CustomerViewModel和OrderViewModel將使用此機(jī)制顯示模式對(duì)話框。但是,您也可以將其用于任意數(shù)量的異步任務(wù)。例如,假設(shè)您有一個(gè)長時(shí)間運(yùn)行的進(jìn)程,希望防止應(yīng)用程序關(guān)閉。這也會(huì)很好地解決這個(gè)問題。

02

最后

原文標(biāo)題:Caliburn.Micro Xaml made easy

原文鏈接:https://caliburnmicro.com/documentation/coroutines

翻譯:dotnet編程大全

C#技術(shù)群?:?添加小編微信mm1552923,備注:進(jìn)群!

總結(jié)

以上是生活随笔為你收集整理的C# WPF MVVM开发框架Caliburn.Micro Screens, Conductors 和 Composition⑦的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。