XAML UserControl的繼承


歡迎訪問Heroius博客:崩潰的腦殼查看文章原文!

前言

相信不少學習WPF和Silverlight的同學們都出於Winform的習慣,希望能夠在新展示層框架中實現控件的繼承。本文就是說明如何實現這一點。

但是在正文開始之前,必須要指明,一般情況下,在WPF/SL中並不推薦使用自定義控件或控件繼承(當然,使用模板生成的Window, UserControl, Page不在此限),因為基於XAML的前台設計語言本身具有豐富的表現能力,且框架支持樣式(Style)、模板(Template)等控制外觀和行為的方式—-這些特性足以構建出任何形式的界面UI。反過來說,創建自定義控件或實現控件繼承在WPF/SL中不僅不受推崇,其實現難度也要比Winform中大。

那么當什么時候才不得不使用自定義控件呢?就是這個控件需要在多處實例化使用,而本身內容較為復雜時。相比較而言,控件的繼承使用的情況就更少了,它使用的必要性一般要滿足如下幾條:

  1. 只在原控件不滿足新需求,但同時又有可資利用的價值;
  2. 新控件需要直接訪問原控件成員(否則在新控件中包含原控件即可,不必繼承);
  3. 新控件同樣需要在多處使用。

如果你面臨的情況滿足這幾個條件,閱讀本文可能會提供幫助。

問題分析

在WPF/SL中實現控件繼承之所以會比Winform困難,是因為在底層框架設計上WPF/SL將表示層徹底從邏輯中分離了出去,控件外觀幾乎均有XAML標記定義,而在對控件進行繼承時,XAML部分對應的類型成員是無法被繼承的。

以WPF為例,使用UserControl模板新建用戶控件,得到一個.cs文件和一個.xaml文件。注意在.cs文件中類定義有partial修飾,說明是分部類,代碼中的InitializeComponent函數即是在另外一部分代碼(設計器生成代碼)中定義的。這部分由設計器生成的代碼和Winform中設計器的代碼相差很大。

partial-300x172 XAML UserControl的繼承

在生成文件夾中可以看到設計器生成的.g.i.cs文件,其中包含對應於XAML內命名成員的相應變量的定義,以及InitializeComponent方法實現。

gics-300x263 XAML UserControl的繼承

可見於Winform設計器代碼相比,其中不包含C#代碼形式的控件初始化邏輯,所有界面表達均在xaml文件中,代碼通過System.Windows.Application.LoadComponent方法從xaml文件實例化。

在.xaml文件中,可見<UserControl>標簽及其屬性x:class,此兩者指明了XAML文檔對應的類型信息,其中根元素是當前類型的基類(UserControl),x:class屬性指定當前類型(UserControl1)。

uc1-300x19 XAML UserControl的繼承

注意到Application.LoadComponent方法包含兩個重載:

  1. object (Uri) – 接受xaml資源的定位符,返回其根元素決定的實例;
  2. void (object, Uri) – 接受根元素類型實例和資源定位符。

自動生成代碼采用了第2個重載,並傳遞當前類實例作為第一個參數,也就是說,XAML加載得到了拓展的UserControl1類型。

這種在xaml中指定類型信息的類(UserControl1)被稱為是“由XAML定義的”。而XAML渲染器無法識別由XAML定義的根類型,也就是說當控件繼承時,若在子類型控件(如UserControl2)設計器中指定其為根元素時,編譯過程將失敗。

但假如子類型不包含XAML代碼,如新建Class1繼承自UserControl1,則沒有問題。

cl1 XAML UserControl的繼承

現在的問題是,在設計控件,尤其是結構較復雜時,我們往往需要借助設計器,這就要求必須使用XAML代碼,這種情況應該如何應對呢?

XAML UserControl的繼承

命題:兩個帶有設計界面的類型UserControl1和UserControl2,其中后者繼承於前者。

思路:

  1. 對於xaml代碼,利用Application.LoadComponent方法可獲取根元素決定的實例;
  2. UserControl屬於Content Control,其顯示內容由Content決定;
  3. 基於以上兩點,分離控件xaml部分,並修改為以某FrameworkElement為根,如此可得到用於設置Content屬性的可視化內容。

UserControl2繼承代碼修改

修改UserControl2的代碼,使其繼承自UserControl1

修改xaml代碼,將根元素設置為基類,注意引用本地程序集命名空間

 

 UserControl1 xaml和代碼分離

UserControl1控件包含的.xaml和.xaml.cs文件由VS管理,為了使兩者分開,需要重命名。先將項從項目中移除,分別重命名:

  1. UserControl1.xaml -> UserControl1_skin.xaml
  2. UserControl1.xaml.cs -> UserControl1.cs

重新加載到項目中,修改xaml文件根元素,使用Grid代替UserControl

移除UserControl.cs中類定義前的partial修飾符,並手動添加InitializeComponent函數,在其中利用Application.LoadComponent的第一個重載獲取如上修改之后xaml編譯得到的Grid實例,將其設置給Content:

 

 UserControl2的代碼調整

為避免UserControl2自動生成代碼覆蓋基類的Content內容,在調用UserControl2的InitializeComponent函數之前需要獲取基類Content,即上文中的Grid實例,並將其插入到當前UserControl2的最下層。

此時在主窗體中拖放UserControl2,程序運行效果如下:

run-300x200 XAML UserControl的繼承

其他注意事項

關於Silverlight

Silverlight中不包含 Application.LoadComponent的第一個重載,可事先創建Grid實例,之后將其作為參數調用 Application.LoadComponent第二重載,效果和WPF一樣。

關於界面設計

若UserControl1界面中有交互內容,設計UserControl2時需要注意避讓。

實際上,完全可以通過代碼控制界面元素的布局,例如可以嘗試將UserControl2構造函數的代碼改成如下內容:

 

 示例代碼

訪問 崩潰的腦殼 文章頁面底部以獲取百度網盤地址和提取密碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM