1、控件
官网中文版:控件示例网址
httpsdocs.microsoft.comzh-cnprevious-versionsdotnetnetframework3.5ms771645(v=vs.90)
分类
控件是咱们的门面,控件有很多,但是如果仔细去分析,也是有规律可循的,根据
其作用,我们可以把控件分类,日常工作中我们打交道最多的控件无外乎6类:
布局控件:是可以容纳多个控件或者嵌套其他布局的控件,用于在UI上组织和
排列控件。Grid、StackPanel、DockPanel等控件都属此类,它们拥有共同的父
类为Panel。
内容控件:只能容纳一个控件或者布局控件作为他的内容。Window、Button等
控件属于此类,因为只能容纳一个控件作为其内容,所以经常借助布局控件来
规划其内容。它们的共同父类是ContentControl。
带标题内容控件:相当于一个内容控件,但是可以加一个标题(Header),标
题部分亦可容纳一个控件或者布局,GroupBox、TabItem等是这类控件的典型
代表。它们的共同父类是HeaderedContentControl。
条目控件:可以显示一列数据,一般情况下这列数据的类型是相同的。此类控
件包括ListBox、ComboBox等。它们额共同基类是ItemsControl。此类控件在
显示集合类型数据方面功能比较强大。
带标题条目控件:相当于一个条目控件加上一个标题显示区。TreeViewItem、
MenuItem都属于此类控件。这类控件往往用于显示层级关系数据,结点显示在
其Header区域,子级结点则显示在其条目控件区域。此类控件的共同基类是
HeaderdeItemsControl。
特殊内容控件:比如TextBox容纳的是字符串、TextBlock可以容纳可自由控制格
式的文本、Image容纳图片类型数据等。这类控件相对比较独立,但也比较常
用。
6类控件的派生关系如图所示
2、WPF的内容模型
根据是否可以装载内容、能够装载什么样的内容,WPF的UI元素可以分为如表所示的这
些类型
WPF的UI元素的类型
下面我们逐一剖析这些元素的内部结构,了解内容与内容属性。
你可以把控件想象成一个容器,容器里装的东西就是它的内容。控件的内容可以直接
是数据,也可以是控件。当控件的内容还是控件的时候就形成了控件的嵌套。我们把被
嵌套的控件称为子级控件,这种控件嵌套在UI布局时尤为常见。因为允许控件嵌套,所
以WPF的UI会形成一个树形结构。如果不考虑控件内部的组成结构,只观察由控件组成
的“树”,那么这棵树为逻辑树(LoicalTree);WPF控件往往是由更基本的控件构成的,即
控件本身就是一颗树,如果连控件本身的树也考虑在内,则这棵比逻辑树更“繁茂”的树称
为可视元素树(Visual Tree)。
控件是内存中的对象,控件的内容也是内存中的对象。控件通过自己的某个属性引
用着作为其内容的对象,这个属性称为内容属性(Content Property)。“内容属性”是个
统称,具体到每种控件上,内容属性都有自己确切的名字——有的直接就叫Content,有的
叫Child;有些控件的内容可以是集合,其内容属性有叫Items或Children的。
控件的内容属性与XAML标签的内容存在一定的对应关系,下面稍作解析。
所谓“于理”,就是说我们严格按照语法来行事。控件不是有内容属性吗?那在XAML
里我们就应该能够使用Attribute=Value或者属性标签的形式来为内容赋值。比如想把字符
串“OK”作为内容赋值给一个Button,下面两种写法都是正确的:
或者:
所谓“于情”,是指如果说得通就不必要按照冗长的语法一板一眼来行事。控件对应到
XAML文档里就是标签,按照大家对标签语言的理解,控件的内容就应该是标签的内容、
子级控件就应该是标签的子级元素(简称标签的元素)。标签的内容是夹在起始标签和
结束标签间的代码,因此,上面的代码也可以写成这样:
换句话说,XAML标签的内容区域专门映射了控件的内容属性。
Button Content=OK
Button
Button.Content
OK
Button.Content
Button
ButtonOKButton
有些控件的内容是一个集合,如StackPanel的内容属性是Children、ListBox的内容
属性是Items,为这类控件添加内容时一样可以省略内容属性的标签。以StackPanel为例,
当为一个StackPanel添加三个TextBox和一个Button时,完整的语法应该是这样:
简化后的代码是:
3、各类内容模型详解
我们把符合某类内容模型的UI元素称为一个族,每个族用它们共同基类来命名。
3.1 ContentControl族
本族元素的特点如下:
均派生自ContentControl类。
它们都是控件(Control)。
内容属性的名称为Content。
只能由单一元素充当其内容。
怎样理解“只能由单一元素充当其内容”这句话呢?让我们看一个例子。
Button控件属于这一族,所以,下面两个Button的代码都是正确的——第一个Button的
内容是一个静态文本,第二个Button的内容是一张图片。
StackPanel Background=Gray
StackPanel.Children
TextBox Margin=5
TextBox Margin=5
Button Content=OK Margin=5
StackPanel.Children
StackPanel
StackPanel Background=Gray
TextBox Margin=5
TextBox Margin=5
Button Content=OK Margin=5
StackPanel
StackPanel Background=Gray
Button Margin=5
TextBlock Text=Hello
但如果你想让Button的内容既包含文字又包含图片是不行的:Button只能接受一个元
素作为它的Content。
如果真的需要一个带图标的Button,控件的内容也可以是控件,我们只需要先用一个可
以包含多个元素的布局控件把图片和文字包装起来,再把这个布局控件作为Button的内
容就好了。
例子如下
ContentControl族包含的控件
Button
Button Margin=5
Image Source=Imagespng-0190.ico Width=30 Height=30
Button
StackPanel
StackPanel Background=Gray
Button Margin=5
TextBlock Text=Hello
Image Source=Imagespng-0190.ico Width=30 Height=30
Button
StackPanel
StackPanel Background=Gray
Button Margin=5
!–Orientation左右布局–
StackPanel Orientation=Horizontal
TextBlock Text=Hello
Image Source=Imagespng-0190.ico Width=30
Height=30
StackPanel
Button
StackPanel
3.2 HeaderedContentControl族
本族元素的特点如下:
它们都派生自HeaderedContentControl类,HeaderedContentControl是
ContentControl类的派生类。
它们都是控件,用于显示带标题的数据。
除了用于显示主体内容的区域外,控件还具有一个显示标题(Header)的区域。
内容属性为Content和Header。
无论是Content还是Header都只是容纳一个元素作为其内容。
HeaderedContentControl族包含的控件如表所示。
下面这个例子是一个以图标为Header、以文字为主体内容的GroupBox。
Grid
GroupBox Margin=10 BorderBrush=Gray
!–标题–
GroupBox.Header
Image Source=Imagespng-0177.icoImage
GroupBox.Header
!–TextWrappingNoWrap内容不允许换行; Wrap内容换行;
WrapWithOverflow根据宽度流式显示 –
TextBlock TextWrapping=WrapWithOverflow Margin=10 Text=三
种方式显示文字,分别以WrapWithOverflow方式、Wrap方式、NoWrap方式
TextBlock
GroupBox
Grid
例子2
3.3 ItemsControl族
本族元素的特点如下:
!–网格布局–
Grid
GroupBox
!–1、标题(图片+文字)–
GroupBox.Header
!–WrapPanel(环绕面板)–
WrapPanel
Image Source=1.jpg Width=30 Height=30
Image
TextBlock员工信息TextBlock
WrapPanel
GroupBox.Header
!–2、内容–
GroupBox.Content
GroupBox Header=员工基本信息
!–StackPanel(栈式面板)Orientation (排布方向)–
StackPanel Orientation=Vertical
TextBoxTextBox
TextBoxTextBox
TextBoxTextBox
TextBoxTextBox
StackPanel
GroupBox
GroupBox.Content
GroupBox
Grid
均派生自ItemsControl类。
它们都是控件,用于显示列表化的数据。
内容属性为Items或ItemsSource。
每种ItemsControl都对应有自己的条目容器(Item Container)。
本族的包含控件如表所示:
ListBox是个典型的ItemsControl,下面将以它为例,研究一下ItemsControl。
首先,我们看看ListBox的自动包装。WPF的ListBox在显示功能上比Winform Form或
者ASP.NET的ListBox要强大很多。传统的ListBox只能将条目以字符串的形式显示,而
WPF的ListBox除了可以显示中规中矩的字符串条目还能够显示更多的元素,如
CheckBox、Button、RadioButton、TextBox等,这样一来,我们就能制作出更加丰富的
UI。代码例子如下:
Grid
ListBox Margin=5
CheckBox xName=checkBoxTim Content=Tim
CheckBox xName=checkBoxTom Content=Tim
CheckBox xName=checkBoxBruce Content=Bruce
Button xName=buttonMess Content=Mess
Button xName=buttonOwen Content=Owen
Button xName=buttonVictor Content=Victor
Image Source=Imagespng-0176.ico
ListBox
Grid
表面看上去是ListBox直接包含了一些CheckBox和Button,实际上并非这样。我们为
Victor这个按钮添加Click事件的响应,看看它的父级容器是什么。
单击按钮,弹出消息框如图:
前面我们知道,WPF的UI是树形结构,VisualTreeHelper类就是帮助我们在这棵由可
视化元素构成的树上进行导航的辅助类。我们沿着被单击的Button一层一层向上找,找
到第三层发现他是一个ListBoxItem。ListBoxItem就是ListBox对应的Item Container,也就
是说,无论你把什么样的数据集合交给ListBox,它都会以这种方式进行自动包装。所以我
们完全没必要这样写:
private void buttonVictor_Click(object sender, RoutedEventArgs e)
{
Button btn = sender as Button;
DependencyObject level1 = VisualTreeHelper.GetParent(btn);
DependencyObject level2 = VisualTreeHelper.GetParent(level1);
DependencyObject level3 = VisualTreeHelper.GetParent(level2);
MessageBox.Show(level3.GetType().ToString());
}
Grid
ListBox Margin=5
Image Source=Imagespng-0176.ico
ListBoxItem
Button xName=buttonMess Content=Mess
ListBoxItem
ListBoxItem
Button xName=buttonOwen Content=Owen
ListBoxItem
ListBoxItem
Button xName=buttonVictor Content=Victor
Click=buttonVictor_Click
ListBoxItem
ListBox
Grid
上面这个例子是单纯地为了说明ItemsControl能够使用对应的Item Container自动包装数
据。实际工作中,除非列表里的元素自始至终都是固定的我们才使用直接把UI元素作为
ItemsControl内容的方法,比如一年有十二个月、一周有七天等。大多数情况 下,UI上
的列表会用于显示动态的后台数据,这时候我们交给ItemsControl的就是程序逻辑中的数
据而非控件了。
假设程序中自定义有Employee类:
并且有一个Employee类型的集合:
在程序的主界面上有一个名为listBoxEmplyee的ListBox。我们只需要这样写:
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
ListEmployee empList = new ListEmployee()
{
new Employee () { Id=1,Name=Tim, Age=18},
new Employee () { Id=2,Name=Tom, Age=19},
new Employee () { Id=3,Name=Guo, Age=20},
new Employee () { Id=4,Name=Yan, Age=21},
new Employee () { Id=5,Name=Owen, Age=22},
new Employee () { Id=6,Name=Victor, Age=23},
};
private void Window_Loaded(object sender, RoutedEventArgs e)
{
ListEmployee empList = new ListEmployee()
{
new Employee () { Id=1,Name=Tim, Age=18},
new Employee () { Id=2,Name=Tom, Age=19},
new Employee () { Id=3,Name=Guo, Age=20},
new Employee () { Id=4,Name=Yan, Age=21},
new Employee () { Id=5,Name=Owen, Age=22},
DisplayMemberPath这个属性告诉ListBox显示每条数据的那个属性,换句话说,ListBox
会去调用这个属性值的Tostring()方法,把得到的字符串放入一个TextBlock(最简单的文本
控件),然后再按照前面说的办法把TextBlock包装进一个ListBoxItem里。
ListBox的SelectedValuePath属性将与其SelectedValue属性配合使用。当你调用
SelectedValue属性时,ListBox先找到选中的Item所对应的数据对象,然后把
SelectedValuePath的值当作数据对象的属性名称并把这个属性的值取出来。
DisplayMemberPath和SelectedValuePath是两个相当简化的属性。
DisplayMemberPath只能显示简单的字符串,想用更加复杂的形式显示数据需要使用
DataTemplate。SelectedValuePath也只能返回单一的值,如果想进行一些复杂的操作,
不妨直接使用ListBox的SelectedItem和SelectedItems属性,这两个属性返回的就是数据
集合中对象,得到原始的数据对象后就任由程序员操作了。
理解了ListBox的自动包装机制之后,我们把全部ItemsControl对应的Item Container列在
下面,如表所示:
new Employee () { Id=6,Name=Victor, Age=23},
};
this.listBoxEmplyee.DisplayMemberPath = Name;
this.listBoxEmplyee.SelectedValue = Id;
this.listBoxEmplyee.ItemsSource = empList;
}
3.4 HeaderedItemsControl族
本族控件除了具有ItemsControl的特性外,还具显示标题的能力。元素特点如下:
均派生自HeaderedItemsControl类。
它们都是控件,用于显示列表化的数据,同时可以显示一个标题。
内容属性为Items、ItemsSource和Header。
因为与ItemsControl非常类似,在此就不浪费时间了。本族控件只有3个:MenuItem、
TreeViewItem、ToolBar。
3.5 Decorator族
本族中的元素是在UI上起装饰效果的。如可以使用Border元素为一些组织在一起的内
容加个边框。如果需要组织在一起的内容能够自由缩放,则可使用ViewBox元素。
本族元素的特点如下:
均派生自Decorator类。
起UI装饰作用。
内容属性为Child。
只能由单一元素充当内容。
本族元素如表所示:
3.6 TextBlock和TextBox
这两个控件最主要的功能是显示文本。TextBlock只能显示文本,不能编辑,所以又
称静态文本。TextBox则运行用户编辑其中的内容。TextBlock虽然不能编辑内容,但可以
使用丰富的印刷级的格式控制标记显示专业的排版效果。
TextBox不需要太多的格式显示,所以它的内容是简单的字符串,内容属性为Text。
TextBlock由于需要操纵格式,所以内容属性是Inlines(印刷中的“行”),同时,
TextBlock也保留一个名为Text的属性,当简单地显示一个字符串时,可以使用这个属
性。
3.7 Shape族元素
友好的用户界面离不开各种图形的搭配,Shape族元素(它们只是简单的视觉元素,
不是控件)就是专门用来在UI上绘制图形的一类元素。这类元素没有自己的内容,我们
可以使用Fill属性为它们设置填充效果,还可以使用Stroke属性为它们设置边线的效果。
本族元素的特点如下:
均派生自Shape类。
用于2D图形绘制。
无内容属性。
使用Fill属性设置填充,使用Stroke属性设置边线。
3.8 Panel族元素
之所以把Panel族元素放在最后是因为这一族控件实在是太重要了——所有用于UI布
局的元素都属于这一族。
本族元素的特点如下:
均派生自Panel抽象类。
主要功能是控制UI布局。
内容属性为Children。
内容可以是多个元素,Panel元素将控制它们的布局。
对比ItemsControl和Panel元素,虽然内容都可以是多个元素,但ItemsControl强调以列
表的形式来展现数据而Panel则强调对包含的元素进行布局,所以ItemsControl的内容属
性是Items和ItemsSourse而Panel的内容属性名为Children。WPF框架中这种良好的命名
习惯非常值得我们学习。