XAML UserControl的继承

前言

相信不少学习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中设计器的代码相差很大。

在生成文件夹中可以看到设计器生成的.g.i.cs文件,其中包含对应于XAML内命名成员的相应变量的定义,以及InitializeComponent方法实现。

可见于Winform设计器代码相比,其中不包含C#代码形式的控件初始化逻辑,所有界面表达均在xaml文件中,代码通过System.Windows.Application.LoadComponent方法从xaml文件实例化。

在.xaml文件中,可见<UserControl>标签及其属性x:class,此两者指明了XAML文档对应的类型信息,其中根元素是当前类型的基类(UserControl),x:class属性指定当前类型(UserControl1)。

注意到Application.LoadComponent方法包含两个重载:

  1. object (Uri) – 接受xaml资源的定位符,返回其根元素决定的实例;
  2. void (object, Uri) – 接受根元素类型实例和资源定位符。

自动生成代码采用了第2个重载,并传递当前类实例作为第一个参数,也就是说,XAML加载得到了拓展的UserControl1类型。

这种在xaml中指定类型信息的类(UserControl1)被称为是“由XAML定义的”。而XAML渲染器无法识别由XAML定义的根类型,也就是说当控件继承时,若在子类型控件(如UserControl2)设计器中指定其为根元素时,编译过程将失败。

但假如子类型不包含XAML代码,如新建Class1继承自UserControl1,则没有问题。

现在的问题是,在设计控件,尤其是结构较复杂时,我们往往需要借助设计器,这就要求必须使用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,程序运行效果如下:

其他注意事项

关于Silverlight

Silverlight中不包含 Application.LoadComponent的第一个重载,可事先创建Grid实例,之后将其作为参数调用 Application.LoadComponent第二重载,效果和WPF一样。

关于界面设计

若UserControl1界面中有交互内容,设计UserControl2时需要注意避让。

实际上,完全可以通过代码控制界面元素的布局,例如可以尝试将UserControl2构造函数的代码改成如下内容:

 示例代码

请移步百度网盘获取示例代码(基于VS2013)

  • 链接: http://pan.baidu.com/s/1ntG7AC5
  • 提取密码: k3k1

发表评论

电子邮件地址不会被公开。 必填项已用*标注


− 5 = 二