XAML UserControl的继承
欢迎访问Heroius博客:崩溃的脑壳查看文章原文!
前言
相信不少学习WPF和Silverlight的同学们都出于Winform的习惯,希望能够在新展示层框架中实现控件的继承。本文就是说明如何实现这一点。
但是在正文开始之前,必须要指明,一般情况下,在WPF/SL中并不推荐使用自定义控件或控件继承(当然,使用模板生成的Window, UserControl, Page不在此限),因为基于XAML的前台设计语言本身具有丰富的表现能力,且框架支持样式(Style)、模板(Template)等控制外观和行为的方式—-这些特性足以构建出任何形式的界面UI。反过来说,创建自定义控件或实现控件继承在WPF/SL中不仅不受推崇,其实现难度也要比Winform中大。
那么当什么时候才不得不使用自定义控件呢?就是这个控件需要在多处实例化使用,而本身内容较为复杂时。相比较而言,控件的继承使用的情况就更少了,它使用的必要性一般要满足如下几条:
- 只在原控件不满足新需求,但同时又有可资利用的价值;
- 新控件需要直接访问原控件成员(否则在新控件中包含原控件即可,不必继承);
- 新控件同样需要在多处使用。
如果你面临的情况满足这几个条件,阅读本文可能会提供帮助。
问题分析
在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方法包含两个重载:
- object (Uri) – 接受xaml资源的定位符,返回其根元素决定的实例;
- void (object, Uri) – 接受根元素类型实例和资源定位符。
自动生成代码采用了第2个重载,并传递当前类实例作为第一个参数,也就是说,XAML加载得到了拓展的UserControl1类型。
这种在xaml中指定类型信息的类(UserControl1)被称为是“由XAML定义的”。而XAML渲染器无法识别由XAML定义的根类型,也就是说当控件继承时,若在子类型控件(如UserControl2)设计器中指定其为根元素时,编译过程将失败。
但假如子类型不包含XAML代码,如新建Class1继承自UserControl1,则没有问题。
现在的问题是,在设计控件,尤其是结构较复杂时,我们往往需要借助设计器,这就要求必须使用XAML代码,这种情况应该如何应对呢?
XAML UserControl的继承
命题:两个带有设计界面的类型UserControl1和UserControl2,其中后者继承于前者。
思路:
- 对于xaml代码,利用Application.LoadComponent方法可获取根元素决定的实例;
- UserControl属于Content Control,其显示内容由Content决定;
- 基于以上两点,分离控件xaml部分,并修改为以某FrameworkElement为根,如此可得到用于设置Content属性的可视化内容。
UserControl2继承代码修改
修改UserControl2的代码,使其继承自UserControl1
1
2
3
4
|
public partial class UserControl2 : UserControl1
{
//...
}
|
修改xaml代码,将根元素设置为基类,注意引用本地程序集命名空间
1
2
3
4
5
6
7
8
9
10
11
12
|
<ci:UserControl1 x:Class="MyNamespace.UserControl2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:ci="clr-namespace:ControlInherit"
d:DesignHeight="300" d:DesignWidth="300">
<Grid x:Name="layoutRoot2">
<Label Content="UC2 xaml" Foreground="LightGreen" HorizontalAlignment="Left" VerticalAlignment="Top"/>
</Grid>
</ci:UserControl1>
|
UserControl1 xaml和代码分离
UserControl1控件包含的.xaml和.xaml.cs文件由VS管理,为了使两者分开,需要重命名。先将项从项目中移除,分别重命名:
- UserControl1.xaml -> UserControl1_skin.xaml
- UserControl1.xaml.cs -> UserControl1.cs
重新加载到项目中,修改xaml文件根元素,使用Grid代替UserControl
1
2
3
4
5
6
7
8
9
|
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid.Resources>
<ResourceDictionary Source="/MyAssembly;component/Dictionary1.xaml"/>
</Grid.Resources>
<Grid>
<Button x:Name="BtnMe" Content="Button" Style="{StaticResource ResourceKey=BtnStyle}"/>
</Grid>
</Grid>
|
移除UserControl.cs中类定义前的partial修饰符,并手动添加InitializeComponent函数,在其中利用Application.LoadComponent的第一个重载获取如上修改之后xaml编译得到的Grid实例,将其设置给Content:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public void InitializeComponent()
{
if (_contentLoaded)
{
return;
}
_contentLoaded = true;
System.Uri resourceLocater = new System.Uri("/MyAssembly;component/usercontrol1_skin.xaml", System.UriKind.Relative);
var root = Application.LoadComponent(resourceLocater);
this.Content = root;
//使用FindName方法获取实例
this.BtnMe = ((System.Windows.Controls.Button)(((Grid)root).FindName("BtnMe")));
}
private bool _contentLoaded;
internal System.Windows.Controls.Button BtnMe;
|
UserControl2的代码调整
为避免UserControl2自动生成代码覆盖基类的Content内容,在调用UserControl2的InitializeComponent函数之前需要获取基类Content,即上文中的Grid实例,并将其插入到当前UserControl2的最下层。
1
2
3
4
5
6
|
public UserControl2()
{
var root = (Grid)base.Content;
InitializeComponent();
layoutRoot2.Children.Insert(0, root);
}
|
此时在主窗体中拖放UserControl2,程序运行效果如下:
其他注意事项
关于Silverlight
Silverlight中不包含 Application.LoadComponent的第一个重载,可事先创建Grid实例,之后将其作为参数调用 Application.LoadComponent第二重载,效果和WPF一样。
关于界面设计
若UserControl1界面中有交互内容,设计UserControl2时需要注意避让。
实际上,完全可以通过代码控制界面元素的布局,例如可以尝试将UserControl2构造函数的代码改成如下内容:
1
2
3
4
5
6
7
8
9
10
|
public UserControl2()
{
//InitializeComponent();
var root = (Grid)base.Content;
root.RowDefinitions.Add(new RowDefinition());
root.RowDefinitions.Add(new RowDefinition());
var lb = new TextBlock() { Text = "this is uc2" };
Grid.SetRow(lb, 4);
root.Children.Add(lb);
}
|
示例代码
访问崩溃的脑壳文章页面底部以获取百度网盘地址和提取密码
XAML UserControl的继承的更多相关文章
- 背水一战 Windows 10 (6) - 控件 UI: 字体的自动继承的特性, Style, ControlTemplate
[源码下载] 背水一战 Windows 10 (6) - 控件 UI: 字体的自动继承的特性, Style, ControlTemplate 作者:webabcd 介绍背水一战 Windows 10 ...
- 控件 UI: 字体的自动继承的特性, Style, ControlTemplate
字体的自动继承的特性 Style 样式 ControlTemplate 控件模板 示例1.演示字体的自动继承的特性Controls/UI/FontInherit.xaml <Page x:Cla ...
- 重新想象 Windows 8 Store Apps (15) - 控件 UI: 字体继承, Style, ControlTemplate, SystemResource, VisualState, VisualStateManager
原文:重新想象 Windows 8 Store Apps (15) - 控件 UI: 字体继承, Style, ControlTemplate, SystemResource, VisualState ...
- [UWP]了解模板化控件(5.2):UserControl vs. TemplatedControl
1. UserControl vs. TemplatedControl 在UWP中自定义控件常常会遇到这个问题:使用UserControl还是TemplatedControl来自定义控件. 1.1 使 ...
- 背水一战 Windows 10 (77) - 控件(控件基类): ContentControl, UserControl, Page
[源码下载] 背水一战 Windows 10 (77) - 控件(控件基类): ContentControl, UserControl, Page 作者:webabcd 介绍背水一战 Windows ...
- [UWP 自定义控件]了解模板化控件(5.2):UserControl vs. TemplatedControl
1. UserControl vs. TemplatedControl 在UWP中自定义控件常常会遇到这个问题:使用UserControl还是TemplatedControl来自定义控件. 1.1 使 ...
- XAML实例教程系列 - 事件(Event) 五
Kevin Fan分享开发经验,记录开发点滴 XAML实例教程系列 - 事件(Event) 2012-06-19 01:36 by jv9, 1727 阅读, 7 评论, 收藏, 编辑 Events, ...
- wpf的UserControl用户控件怎么添加到Window窗体中
转载自 http://www.cnblogs.com/shuang121/archive/2013/01/09/2853591.html 我们来新建一个用户控件UserControl1.xaml &l ...
- wpf中UserControl的几种绑定方式
我们经常会抽取一些可重用的控件,某个属性是否需要重用,直接决定了这个属性的绑定方式. 1.完全不可重用的控件 有一些与业务强相关的控件,它们的属性完全来自ViewModel,越是相对复杂的控件,越容易 ...
随机推荐
- C#-WebForm-★★★ 分页展示 ★★★
什么是"分页展示"? 分页展示就是将庞大的数据分成若干页,每页展示若干条数据,向用户展示数据 流程:客户端浏览器向服务器发送查询请求 → 服务器从数据库查询数据 → 服务器转换成代 ...
- 让PDF.NET支持最新的SQLite数据库
最近项目中用到了SQLite,之前项目中用的是PDF.NET+MySQL的组合,已经写了不少代码,如果能把写好的代码直接用在SQLite上就好了,PDF.NET支持大部分主流的数据库,这个当然可以,只 ...
- 启动Maven项目启动报错:java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener
tomcat在发布项目的时候没有同时发布maven依赖所添加的jar包,你需要设置一下eclipse:项目 -> 属性 -> Deployment Assembly -> Add - ...
- Linux下如何查找.sh后缀的文件
find / -name *.sh或locate *.shfind 与locate的区别:locate 配合数据库查看文件位置 find 实际搜寻硬盘查询文件名称
- 解决Linux下启动Tomcat遇到Neither the JAVA_HOME nor the JRE_HOME environment variable is defined
找到启动路径所在的目录: cd bin/ vi catalina.sh 加入以下信息: export JAVA_HOME=/home/gongzi/http/jdk1.6.0_26 export JR ...
- HDU3394:Railway
传送门 点双练习. 对于一张图,询问有多少条边不属于任意一个点双和多少条边至少属于两个点双. 显然,一张图里有多少个桥就是第一问的答案. 对于第二问,考虑对于一个点双,如果其中的边数等于点数,那么这个 ...
- CSS3 Animation 帧动画 steps()
@keyframes fn{ 0%{} 100%{} } CSS3的Animation有八个属性 animation-name :动画名 fn animation-duration:时间 1s ani ...
- java ---- 面试题
1.java 语言如何进行异常处理,关键字:throws.throw.try.catch.finally分别代表什么意义?finally代码是在return之后还是之前执行? throws是获取异常, ...
- SQL分页语句三方案
方法一: SELECT TOP 页大小 * FROM table1 WHERE id NOT IN ( SELECT TOP 页大小*(页数-1) id FROM table1 ORDER BY id ...
- 387. First Unique Character in a String
Given a string, find the first non-repeating character in it and return it's index. If it doesn't ex ...