天天看點

【WPF】運用MEF實作視窗的動态擴充

若幹年前,老周寫了幾篇有關MEF的爛文,簡單地說,MEF是一種動态擴充技術,比如可以指定以某個程式集或某個目錄為搜尋範圍,應用程式在運作時會自動搜尋符合條件的類型,并自動完成導入,這樣做的好處是,主程式的代碼不用改來改去,隻需要把擴充的程式集放到對應的目錄下就可以了。

MEF不僅可以用于“看不見”的類型擴充上,對于“看得見”的類型照樣适用,比如視窗、控件之屬,你要是夠牛逼的話,甚至可以把它用到ASP.NET上,不過這個玩意兒估計要配合重寫路由規則才能實作,根據URL傳的參數來跳轉到具體的頁面。

較為簡單的,像Windows Forms中的視窗,WPF中的視窗或控件,就可以直接運用MEF來完成擴充,主應用程式界面可以動态生成菜單項或按鈕來打開視窗就可以了。而各個視窗的實作代碼可以寫在一個類庫項目中。

下面,咱們用一個實實在在的例子來說明一下。

建立一個類庫項目,然後在裡面做三個WPF視窗,XAML文檔如何與代碼類關聯,這個不要問我,問MSDN姐姐去。

【WPF】運用MEF實作視窗的動态擴充

因為這是做測試,視窗的UI布局你可以随便設計。

給大家一個提示吧,XAML檔案和視窗類的代碼檔案的關聯方法,和ASP.NET中.aspx檔案與代碼檔案的關聯方法一樣。例如XAML檔案名叫 test.xaml,那麼對應的代碼檔案名就是test.xaml.cs(VB語言的話,是test.xaml.vb)。

對視窗來說,一般是從Window類派生,是以,XAML文檔的根元素要寫Window,比如

XAML中有兩個必備的命名空間要引入:

.../xaml/presentation 表示的WPF中的UI類型,比如Button、Canvas等;而那個帶x字首的.../xaml表示的是XAML文法本身特有的東西,比如x:Class,這個特性就是聯合XAML檔案和代碼檔案的關鍵,用它來指定視窗類的名字,類名要包括命名空間名。

下面的步驟相當重要,不然就無法MEF了。

打開視窗的代碼檔案,在視窗類聲明上添加導出聲明。如下

 聲明導出需要一個協定,因為類型是可以動态擴充的,是以這些擴充的類型必須要向運作時表明它們有一個共同點,以便讓MEF能夠找到它,這就是類型協定。我們知道,所有視窗類都有一個共同點——從Window類派生,故而在聲明ExportAttribute時,用Window類的Type來标注協定。

ExportMetadataAttribute表示的是中繼資料,它是可選的,指定方式和字典的key - value形式差不多,name是字元串,value是Object類型,雖然可以指任何類型的value,但最好是可序列化的類型或者基礎類型(byte,string,int等),這樣友善傳遞。在接收擴充的代碼中,可以用IDictionary<string, object>類型來接收中繼資料,也可以自定義一個類型(接口、類)來接收,隻要屬性/字段的名字和ExportMetadataAttribute中的name相等就行了,這樣中繼資料就會自動填充到類型的屬性/字段成員中。

比如,如果你指定中繼資料的name為“Age”,value為25,那麼你自定義的類型隻要公開一個名為Age的屬性或字段即可,擷取時會自動填充資料。

這裡我一口氣做了三個視窗,最後,可以定義一個類,把上面的N個視窗批量導入這個類的一個屬性中,随後導出這個類的這個屬性。

 ImportMany可以一次性導入多個類型,因為擴充的視窗有N個,是以要使用這個特性來批量導入,還記得吧,前面的視窗都是以Window的Type作為協定來導出的,是以在導入時,一定指定比對的協定,不然無法導入。

因為類型有多個,是以要用IEnumerable<T>(協變)來存放,而其中的T為ExportFactory<T, TMetadata>,本來用ExportFactory<T>就可以了,但由于我為每個視窗的導出定義了中繼資料,是以要使用支援擷取中繼資料的工廠類型。

這個類可以不定義為public,因為導出的是它的屬性,而且對于MEF來說,非public的成員都可以導出,隻要你指定導出協定即可。

對于ExtWindows屬性,導出聲明就不必使用Type作為協定了,直接指定一個名字來做協定就可以了,本例是extWindows,注意這個協定名是區分大小寫的,ext和Ext被視為不同的協定。

通常,接收擴充類型用的是Lazy<T>,以達到延遲執行個體化,但是,這個項目比較特殊,不能用Lazy來承載類型。WPF的視窗類有個特點,就是每次顯示視窗必須使用新的執行個體,因為視窗一旦Close之後,就不能再次Show了,隻能重新new一個執行個體才能Show。基于這原因,用ExportFactory類最好,這個類每次通路都能重新建立執行個體,調用CreateExport方法能建立一個ExportLifetimeContext<T>執行個體,再通過這個ExportLifetimeContext<T>執行個體的Value屬性來得到視窗執行個體。

ExportLifetimeContext<T>實作了IDisposable接口,可以寫在using語句中,用完後釋放掉。

現在回到主應用程式項目,開始導入擴充視窗。

主視窗用一個菜單就行了,每個導入的視窗類型将作為菜單項。

下面代碼将擷取導出對象,由于剛才用IEnumable<T>來導入了視窗類型,是以此處隻需要擷取這個屬性的值即可。

 CompositionContainer是個容器,用它可以組合所有擷取到的擴充類型,執行個體化容器時,要指定一個搜尋範圍,這裡我指定它從剛才那個類庫項目中搜尋。因為我已經引用了這個類庫項目,是以調用Assembly.Load(程式集名)就可以直接加載了。

CompositionExtWindows方法負責從容器中擷取導出的IEnumrable<T>對象,代碼如下:

 直接調用GetExportedValue方法就可以擷取到導出的屬性值,參數是剛剛給ExtWindows屬性指定的協定名。

AddExtToMenuitems方法把擷取到的擴充視窗類型添加到子菜單項,這樣一來,有多少個擴充視窗,就有多少個菜單項。

讓菜單項的Tag屬性引用 ExportFactory執行個體,以便在Click事件處理方法中通路。

菜單項的Click事件處理如下:

 從Value屬性中擷取視窗執行個體,就可以調用Show方法來顯示視窗了。

來,運作一下,看看如何。運作後,會自動添加三個菜單項,因為我剛剛做了三個視窗。

【WPF】運用MEF實作視窗的動态擴充

點選對應的菜單,就能打開對應視窗。

【WPF】運用MEF實作視窗的動态擴充

現在,不妨往類庫項目中再添加一個視窗。

主應用程式的代碼不用做任何改動,然後直接運作。

【WPF】運用MEF實作視窗的動态擴充

此時,你會看到,第4個視窗也自動加進來了。

有沒有發現,這幾個菜單項的排序好像不太好看,要是能按一定順序排列多好。這個實作起來不難,老周就不實作了,你自己試着幹吧。

老周可以給個提示,還記得在ExportAttribute聲明導出類型時,可以指定中繼資料,例子中,老周指定了一個叫name的中繼資料,你可以指定一個叫order的中繼資料,值為數值,比如第一個視窗為1,第二個視窗為2……

然後,在主程式項目中擷取組合擴充時,可以用IEnumerable<T>的擴充方法進行排序,也可以用LinQ文法來排序。

好了,文章就寫到這裡吧,See you.

示例代碼下載下傳

===================================================================

有熱心朋友給老周留言,問老周,為什麼你的博文的右下角,老有人點“反對”,老周你是不是得罪人了。

謝謝朋友,你不說我還真沒注意,因為老周從來不在意那些虛的東西,故一直沒注意到這個。實話說,老周從來不得罪人,老周隻會得罪妖魔鬼怪,是以朋友多慮了。

至于說右下角那兩個按鈕,可能是一些沒文化的人,本來是想點選左邊的,由于不認識漢字,錯點了右邊的按鈕。

總之,大家不要在意這些無關緊要的東西,如果你覺得老周寫的爛文對你有用,那你就姑且當娛樂新聞看看吧,畢竟老周的寫作水準不高,老周已經在努力優化了,争取多讀點經典名著和大師著作,提升水準。

繼續閱讀