控件的本质是“数据+算法”——用户输入原始数据,算法处理原始数据并获得结果数据。问题就在于程序如何将结果数据展现给用户。一样一组数据,你可使用LED阵列显示出来,或者是以命令行模式借助各类控制字符(如Tab)对其并输出,但这些都不如图形化用户界面(Graphics User Interface ,GUI)来的友好和方便。GUI是程序界的优胜者,但在Windows上实现图形化界面有不少中方法。每种方法又拥有本身的一套开发理念和工具。每种GUI开发与它的里理念和工具共同组成一种方法论。常见的有:程序员
咱们能够对以上方法论分为四代:算法
WPF之因此可以称上最新的一代在于两点:第一,以前几代GUI方法论只能使用编程语言进行UI设计,而WPF具备专门的UI设计的XAML;第二,前几代在UI与数据交互方面是由Windows消息到控件事件一脉相承,始终是把UI控件放在主导地位而把数据放在被动地位,用UI来驱动数据的改变,WPF在事件驱动的基础上引入了数据驱动界面的理念,让数据重回核心地位而让UI回归数据表达者的位置编程
在从Winform转到WPF的学习过程当中,心中必定要树立起这样一个概念——WPF中是数据驱动UI,数据是核心、是主动的。UI从属于数据并表达数据、是被动的。也能够这么理解,当咱们想改变控件上的显示内容时,只须要改变该控件绑定的数据源内容便可。app
UI的功能始让用户观察和操做数据,为了让用户观察数据,咱们须要用UI元素来显示数据;为了让用户操做数据,咱们须要用UI元素响应用户的操做。WPF把那些可以展现数据、响应用户操做的UI元素称为控件(Control)。控件所展现的数据,咱们称之为控件的“数据内容”,控件在响应用户的操做后会执行本身的一些方法以事件(Event)的形式通知应用程序(开发人员就能够决定如何处理这些事件),咱们称之为控件的“行为”或“算法”内容。可见,WPF中的控件扮演者双重角色、是个很是抽象的概念——Control是数据和行为的载体,而无需具备固定的形象。换句话说,Button之因此是Button不是由于它长得方方正正、显示一串文字而且可以响应用户单击,而是应该倒过来想——凡是符合“能显示一些提示文字(能够是文字、也能够是图片、动画甚至是视频)并能响应用户单击”这一抽象概念的UI元素均可以是Button,至于Button具体长成什么样子(是方是圆、是显示文字仍是显示动画)彻底由它的风格(Style)和模板(Template)来决定。编程语言
在平常的开发工做中咱们打交道的控件大体分为6类,即:函数
WPF能够分为以下几类工具
名称 | 注释 |
ContentControl | 单一内容控件 |
HeaderedContentControl | 带标题的单一内容控件 |
ItemsControl | 以条目集合为内容的控件 |
HeaderedItemsControl | 带标题的以条目集合为内容的控件 |
Decorator | 控件装饰的元素 |
Panel | 面板类元素 |
Adorner | 文字点缀元素 |
Flow Text | 流式文本元素 |
TextBox | 文本输入框 |
TextBlock | 静态文字 |
Shape | 图形元素 |
下面咱们逐一剖析这些元素的内部结构,了解内容与内容属性。组件化
咱们能够把控件想象成一个容器,容器里面装的东西就是它的内容,控件的内容能够直接是数据,也能够是控件。当控件的内容仍是控件的时候就造成了控件的嵌套,因此WPF的UI会造成一个树形结构。若是不考虑控件内部的组成结构,只观察由控件组成的“树”,那么这棵树称为“逻辑树(Logicol Tree)”;WPF控件每每是由更基本的控件构成的。即控件自己就是一棵树,若是连控件自己的树也考虑在内,则这颗比逻辑树更“繁茂”的树称为可视元素树(Visual Tree)。布局
控件时内存中的对象,控件的内容也是内存中的对象。控件经过本身的某个属性引用着做为其控件的对象,这个属性称为内容属性(Content Property)。“内容属性”是个统称,具体到每种控件上,内容属性都有本身确切的名字——有的直接叫Content,有的叫Child;有些控件的内容能够是集合,其内容属性叫Items或Children的。学习
咱们把符合某类内容模型的UI元素称为一个族,每一个族用它们共同的基类来命名
本类元素的特色以下:
怎么理解“只能由单一元素充当其内容”这句话呢?看下例子。
Button控件属于ContentControl族,因此下面两个Button代码都是正确的——第一个Button的内容是一个静态文字,第二个Button的内容是一张图片。
<StackPanel> <Button> <TextBlock>Hello World</TextBlock> </Button> <Button> <Image Source=".\1.jpg" Height="30" Width="30"></Image> </Button> </StackPanel>
但若是你想让Button的内容即包含文字又包含图片是不行的:
<StackPanel> <Button> <TextBlock>Hello World</TextBlock> <Image Source=".\1.jpg" Height="30" Width="30"></Image> </Button> </StackPanel>
编译器会报错“对象“Button”已经具备子级且没法添加“Image”。“Button”只能接受一个子级。”但是若是咱们真的须要一个带图标的Button那怎么办呢?咱们只须要先用一个能够包含多个元素的布局控件把图片和文字包装起来,再把这个布局控件做为Button的内容就行了
ContentControl族包含的控件以下
Button | ButtonBase | CheckBox | ComboboxItem |
ContentControl | Frame | GridViewColumnHeader | GroupItem |
Label | ListBoxItem | ListViewItem | NavigationWindow |
RadioButton | RepeatButton | ScrollViewer | StatusBarItem |
ToggleButton | ToolTip | UserControl | Window |
本族元素的特色以下:
HeaderedContentControl族包含的控件以下
Expander | GroupBox | HeaderedContentControl | TabItem |
下面这个例子是一个以图标为Header、以文字为内容主体的GroupBox
<StackPanel> <GroupBox Margin="10"> <GroupBox.Header> <Image Source=".\1.jpg" Height="30"></Image> </GroupBox.Header> <TextBlock TextWrapping="WrapWithOverflow" Margin="10"> 愿你慢慢长大,愿你有好运,若是没有,但愿你在不幸中学会慈悲;愿你被不少人爱,若是没有,但愿你在寂寞中学会宽容。 </TextBlock> </GroupBox> </StackPanel>
本族元素特色以下:
本族的包含控件以下所示
Menu | MenuBase | ContextMenu | Combobox |
ItemsControl | ListBox | ListView | TabControl |
TreeView | Selector | StatusBar |
本族控件具备特点的一点就是会自动使用条目容器对提交给它的内容进行包装。合法的ItemsControl内容必定是个集合,当咱们把这个集合做为内容提交给ItemsControl时,ItemsControl不会把这个集合直接拿来用,而是使用本身对应的条目容器把集合中的条目逐个包装,而后再把包装好的条目序列看成本身的内容。这种自动包装的好处就是容许程序员向ItemsControl提交各类数据类型的集合,程序员在思考问题时会天然而然的感受到ItemsControl控件直接装载着数据,若是须要进行增长、删除、更新或者排序,那么直接去操做数据集合就能够,UI会自动将改变展示出来,这正体现了在WPF开发时数据直接驱动UI再进行显示。
ListBox是典型的ItemsControl,下面将以它为例,研究一下ItemsControl。
首先,咱们看看ListBox的自动包装。WPF的ListBox在显示功能上比Windows Form或者ASP.NET的ListBox要强大的多。传统的ListBox只能将条目以字符串的形式显示,而WPF的ListBox除了能够显示中规中矩的字符串条目还能显示更多的元素,如CheckBox、RadioButton、TextBox等,这样一来,咱们就能制做出更加丰富的UI,代码以下
<ListBox> <CheckBox x:Name="ckBoxTim" Content="Tim"/> <CheckBox x:Name="ckBoxTom" Content="Tom"/> <CheckBox x:Name="ckBoxSimple" Content="Simple"/> <Button x:Name="Mess" Content="Mess"/> <Button x:Name="Ownen" Content="Ownen"/> <Button x:Name="Victor" Content="Victor"/> </ListBox>
运行效果以下
表面看上去是ListBox直接包含了一些CheckBox和Button,实际并不是这样。咱们为Ownen这个按钮添加一个Click事件,看看它的父容器是什么。
private void Ownen_Click(object sender, RoutedEventArgs e) { var invoker = sender as Button; var parent = VisualTreeHelper.GetParent( VisualTreeHelper.GetParent(VisualTreeHelper.GetParent(invoker))); MessageBox.Show(parent.GetType().ToString()); }
VisualTreeHelper类是帮助咱们在这颗由可视化元素构成的树上进行导航的辅助类。咱们沿着被单击的Button一层一层向上找,找到第三层发现它是一个ListBoxItem。ListBoxItem就是ListBox对应的Container,也就是说,不管你把什么样的数据集合交给ListBox,他都会以这种方式进行自动包装。
上面这个例子就是单纯的为了说明ItemsControl可以使用对应的Item Container自动包装数据。实际工做中,除非列表里的元素自始至终都是固定的咱们才使用这种直接把UI元素做为ItemControl内容的方法,好比一年由十二个月、一周有七天等。大多数状况下,UI上的列表会用于显示动态的后台数据,这时候咱们交给ItemsControl的就是程序逻辑中的数据而非控件了。
假设程序中定义有Person类:
public class Person { public int ID { get; set; } public string Name { get; set; } public int Age { get; set; } // ... }
而且有一个Person类型的集合:
var lstPerson = new List<Person>() { new Person() { Age = 20, Name = "Simple", ID = 1 }, new Person() { Age = 22, Name = "Tim", ID = 2 }, new Person() { Age = 22, Name = "Tom", ID = 3 }, new Person() { Age = 22, Name = "jrrey", ID = 3 } };
在主程序中有一个名为lsbPerson的ListBox,咱们只须要这样写:
var lstPerson = new List<Person>() { new Person() { Age = 20, Name = "Simple", ID = 1 }, new Person() { Age = 22, Name = "Tim", ID = 2 }, new Person() { Age = 22, Name = "Tom", ID = 3 }, new Person() { Age = 22, Name = "jrrey", ID = 3 } }; lsbPerson.DisplayMemberPath = "Name"; lsbPerson.SelectedValuePath = "ID"; lsbPerson.ItemsSource = lstPerson;
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列在下面
Items名称 | 对应的Item Container |
ComboBox | ComboBoxItem |
ContextMenu | MenuItem |
ListBox | ListBoxItem |
ListView | ListViewItem |
Menu | MenuItem |
StatusBar | StatusBarItem |
TabControl | TabItem |
TreeView | TreeViewItem |
顾名思义,本族控件除了具备ItemsControl的特性外,还具显示标题的能力。
本族元素的特色以下
在本族中的元素,在UI上是其装饰做用的。如可使用Border元素为一些组织在一块儿的内容加个边框。若是须要组织在一块儿的内容可以自由缩放,则可使用ViewBox元素。
本元素的特色以下:
本族元素以下
ButtonChrome | ClassicBorderDecorator | ListBoxChrome | SystemDropShadowChrome |
Border | InkPresenter | BulletDecorator | ViewBox |
AdornerDectorator |
这两个控件最主要的功能就是显示文本。TextBlock只能显示文本,不能编辑,因此又称静态文本。TextBox则容许用户编辑其中的内容。TextBlock虽然不能编辑内容,但可使用丰富的印刷级的格式控制标记显示专业的排版效果。
TextBox不须要太多的显示格式,因此它的内容是简单的字符串,内容属性为Text。
TextBlock因为须要操纵格式,因此内容属性是InLines(印刷中的“行”),同时TextBlock也保留一个名为Text的属性,当简单的显示一个字符串时,可使用这个属性。
友好的界面离不开各类图形的搭配,Shape族元素(它们只是简单的视觉元素,不是控件)就是专门用来在UI上绘制图形的一类元素。这类元素没有本身的内容,咱们可使用Fill属性为它们设置填充效果,还可使用Stroke属性为它们设置边线效果。
本族的元素特色以下:
之因此把Panel元素放在最后是由于这一族控件实在是过重要了——全部用于UI布局的元素都属于这一族。
本族元素的特色以下:
对比ItemsControl和Panel元素,虽然内容均可以是多个元素,但ItemsControl强调以列表的形式来展示数据而Panel则强调对包含元素进行布局。因此ItemsControl的内容属性是Items和ItemsSource而Panel的内容属性名为Children。
本族元素以下所示
Canvas | DockPanel | Grid | TabPanel |
ToolBarOverflowPanel | StackPanel | ToolBarPanel | UniformGrid |
VirtualizingPanel | VirtualizingStackPanel | WrapPanel |