WPF阶段总结

一、数据源就是对象,只不过是一个包含有数据的对象,如x:XData。而把这个对象中的数据提供给使用者的“中介对象”被成为数据提供者如XmlDataProvider。

 

二、margin属性就想象成一个带一圈空隙的控件。

 

三、使用Type类型创建一个窗口实例,Type对象是某个类型描述的一个包装

Window win=Activator.CreateInstance(Type类型的一个对象如button对象) as Window;

斯四、日常工作中打交道的控件无外乎6类

(1)布局控件:如Grid、StackPanel等,他们拥有共同的父类Panel。

(2)内容控件:只能容纳一个其他的控件作为他的内容,如Window、Button等控件,因为只能容纳一个控件作为其内容,所以经常需要借助布局控件来规划其内容,如Window中经常使用Grid,他们共同的父类是ContentControl。

(3)带标题的内容控件:相当于一个内容控件但可以加一个标题(Header),标题部分亦可容纳一个控件(包括布局控件),如GroupBox、TabItem等,他们共同的父类是HeadereContentControl。

(4)条目控件:可以显示一列数据,这列数据的类型可以不同但一般情况下相同,如ListBox、ComboBox等,此类控件在显示集合类型数据方面功能非常强大,共同基类是ItemsControl。

(5)带标题的条目控件:相当于一个条目控件加上一个标题,如TreeViewItem、Menutem,这类控件往往用于显示层级关系的数据,节点显示在Header区域,子级节点则显示在其条目控件区域,共同基类是HeaderedItemsControl。

(6)特殊内容控件:比如TextBox容纳的是字符串、TextBlock能容纳可自由控制格式的文本、Image容纳图片等,这里控件比较独立和Control类处于同一个继承级别。

上面六种控件继承关系如下图:

WPF阶段总结_第1张图片

 

四、对于条目控件ItemsControl,他的合法内容一定是个集合,当我们把这个集合作为内容提交给ItemsControl时,ItemsControl不会把这个集合直接拿来用,而是使用自己“对应的条目容器”(系统内部自动使用,比如ListBox中的内容包装器就是ListBoxItem,只不过默认可以省略不写出来而已)来把集合中的条目逐个包装,然后再把包装好的条目序列当作自己的内容。这种自动包装的好处就是允许程序员想ItemsControl提交各种数据类型的集合,程序员只需要关注这个集合数据就行,如果需要增、删、更新或者排序,那么直接去操作数据集合就行了,UI会自动将改变展现出来,这正体现了WPF是数据直接驱动UI进行显示的。

这里补充:对条目控件使用DisplayMemberPath属性一般只能显示简单的字符串,如果想要显示复杂的数据需要使用DataTemPlate。

 

五、布局的经验

最后尽量用比例

如下面例子:

如果是上面的界面要求,该怎么布局呢?

Grid布局预览如下:

WPF阶段总结_第2张图片

共5行5列,布局过程:首先考虑列,左上角文本占一列,因为文字块的宽度更具文字的字体大小具有确定的宽度,不会随着整个窗体的改变而改变。然后是右边的下来组合框应该紧靠着左边文字块,右边紧靠窗体右边沿,所以它的长度因随着窗体宽度而改变。中间的大文本框的宽度和高度按要求要随着窗体而改变,左右要紧挨着窗体的左右边沿。最后是连个按钮,两个按钮宽度固定且按钮间空隙也固定,所以可以优先考虑使用表格宽度固定,也就是优先考虑按钮的卡槽宽度固定(次好的方案是考虑设置按钮的width属性为固定值)。综上要画5列,而最左边的列里装着文字块(宽度随字体大小确定),所以应该设为此列宽度自动,而且为避免窗体太小容不下字体快了,要设置此列的最小值。

对行的布局,按图上要求就行了,比较简单。

最后是空出Grid的四个边沿各留10像素的空白即可。

总结一个原则:尽量用卡槽确定卡槽内部控件的宽度和高度,也就是让卡槽内部的控件尽量100%填充已经确定好的卡槽。

下面是设计的代码:

WPF阶段总结_第3张图片

WPF阶段总结_第4张图片

下面是最终效果图:

WPF阶段总结_第5张图片

 

六、附近属性的来历

为什么不使用Button.Row而使用Grid.Row呢?原因很简单,因为Button设计时并没有规定Button一定要放在Grid里面,所以就不应带有Row属性。只有它放在Grid里,Row属性才有意义,也就是说这类属性不是控件固有的而是被Grid附加上的,所以叫附加属性。

 

七、GridSplitter分割条会改变Grid先前设置好的行高或列宽。

 

八、Binding

(1)对"{Binding Path="abc"}"这个整体表达式的理解,不要理解成返回一个绑定类型的数据(很容易这样误解),应理解成像函数调用一样返回一个值,即这个整体表达式返回一个值,这不过这个值像函数一样是通过内部复杂运算最终产生的。但他不是固定的,产生绑定(关联)关系后,以后两个关联数据会同步更新。

另外,绑定Bnding还支持“多级路径”,即路径属性可以是多个“点”号。如:

《TextBox x:Name="textBox2" Text="{Binding Path=Text.Length,ElementName=textBox1}" /》

等价于后台代码:

this.textBox2.SetBinding(TextBox.TextProperty,new Binding("Text.Length"){Source=this.textBox1});

也就是说,Path可以赋予绑定对象的某个属性一路点下去的值。对于这种较复杂的情况,只要把Path翻译为等于“数据源对象.属性.的某个东西.某个东西”即可,最典型的是TextBox控件绑定到列表控件的某个选择的项目的ID,如{Binding SelectedItem.Id Source="ListBox"},甚至还可以如下使用:"{Binding Path=Text.[3],ElementName=textBox1}";这是因为c#中集合类型的索引器Indexer(用“[3]”代替调用)又称为代参属性,所以索引器也可以作为Path来使用(使用中就把“[3]”这个符合看成和Length等价的属性即可,后台代码就略了)。

 (2)另外Binding中还经常使用“/”表示使用绑定的对象的默认属性作为Path。还使用“.”点号代表Path就是使用数据源本身,尤其数据源就是一个string或int类型的数时,点号甚至在XAML中还可以省略。最最极端的还会碰到如"{Binding}",这样的即省略Path又省略Source的情况,是因为寻找的默认的DataContext的内容就是一个单数据。

 (3)对于ItemsControl的绑定比较特殊,比如在后台不用显示建立Binding对象,只需要指定DisplayMemberPath属性,他会自动帮我门建立好Binding对象并绑定到ItemsControl对象。所以对于ItemsControl只需要在后头设定Source和DisplayMemberPath两个属性即可建立绑定。

如果深究的话,实际上当DisplayMemberPath属性被赋值后,ListBox在获得ItemsSource(集合)的时候就会创建等量的ListBoxItem外衣包住数据源集合中的的项目,并以DisplayMemberPath属性值为Path创建Binding,Binding的目标是ListBoxItem的内容插件(就是在ListBoxItem中插入一个TextBox作为接收数据的目标并显示出来)。这个绑定的过程实在一个内部的函数中具体完成绑定的,而且这个函数返回一个DataTemplate类型的对象,这个DataTemplate类型是一个默认的(也是最简单的),实际上就是包了一个没边框的TextBox(记住默认只有一个,也就是显示一列,实际上也只指定了一个DisplayMemberPath属性),就好像给数据穿上了一件最简单的衣服一样。当然可以显示的设置DataTemplate属性代替默认的DataTemplate来显示数据,如下:

在后台只需加上设置ListBox的Source属性为一个集合即可,结果显示如下:

WPF阶段总结_第6张图片

可以看出,上面ListBox有点ListView效果了。一般说ListBox和ListView区别在于,Box是Window标准控件,而View需要更多库支持且多媒体内容丰富,外观上一般ListBox唱用来显示一列多行的东西,而ListView显示多列多行的东西,就是一个表格的东西,ListView的View属性就是一个GridView(而gridview就是表格)。

更深入的:ListView是ListBox的派生类,而GridView是ViewBase的派生类,ListView的View属性是一个ViewBase类型的对象,所以,GridView可以作为ListView的View来使用而不能当作独立的控件来使用。这里使用的是组合模式,即ListView中有一个View,至于这个View是GridView还是其他什么类型的View则又程序员自由选择。如下代码:

在后台,只需将ListView的数据源ItemsSource设为一个DataTable对象即可,如=dt.DefaultView。

这里有一个小细节要注意,如果直接把DataTable对象赋予ItemSource属性会得到一个编译错误,所以一般使用DataTable的DefaultView默认视图,但是如果把DataTable对象赋予DataContext属性,有控件自己去找数据源,折不会出现编译错误,实际上系统会自动使用那个DataTable对象的DefaultView当作自己的Source来使用。

当然还可以更偷懒的不设置ListView的GridView中的DiaplayMemberPath属性,直接在后台写如下代码:

this.listView.DataContext=dt;

this.listView.SetBinding(ListView.ItemsSourceProperty,new Binding());

这样显示的使用空的Binding对象(空绑定表示绑定所有属性)并不使用DisplayMemberPath属性,则会把dt的默认视图完全显示在ListView上。

 

九、控件模版和数据模版

控件模版是控件的外衣,数据模版是数据的外衣,他们两个分别对应Control的Template和ContentTemplate(或ItemTemplate,gridview中还有CellTemplate)两个属性。对于数据模版,ContentTemplate相当于给内容控件的内容部分穿上一件外衣,而ItemTemplate相当于给条目控件的每个条目穿上一件外衣。如果要说他们有什么关系的话,如下图:

 从图中可以看出,两个分别模版套用在控件的不同部分罢了,另外控件模版中使用绑定用{ControlBinding},而数据模版使用{Binding}。另外,如果要将两种类型的模版分别用在某种类型的控件或数据上自动套用,而不用显示的设置控件的Template或ContentTemplate属性,那么需要去除模版定义中的x:Key属性以及分别对应设置TargetType和DateType属性为{x:Type Text或自定义类名称},这样之后,所有的Text或所有绑定到自定义类的对象作为内容部分(Content或Items)或数据源(ItemsSource,因为这个源也是属于控件的内容部分)的控件都自动套用相应的模版。

其实,简单的理解控件模版和内容模版中定义的控件标签和特性就是】:

对于控件模版,把他里面定义的所有标签和特性想象成一个正常控件的外围(除了内容区域,虽然控件模版包括内容区域但它一般是用来定义非内容区域的外形的)对应的标签和标签的特性。

对于数据模版,把他里面定义的所有标签和特性想像成一个正常控件的内部(内容部分)对应的标签和标签的特性。

有了上面的简单理解,那么如果在内容模版中出现类似《TextBox Text="{Binding Name}"》,其中的绑定就好理解了,无非是想象成内容部分包含了一个TextBox标签,而这个TextBox的Text属性绑定到内容显示的数据对象的Name属性。而这里“内容显示的数据”该怎样理解呢?就理解成这个子控件往上找(包括自己)直到找到一个数据为止,而这个数据可能是一个数据源,也可能是一个DataContent等。最常见的是外围控件的ItemsSource属性对应的值。

【我理解就把数据模版想象成内容部分的子控件就行】

其实数据模版作用在的对象就是“ContentPresenter”,而ContentPresenter也是一个控件,Content属性就是这个内容ContentPresenter的属性。【为了方便使用控件也具有和内容呈现器一样的属性Content,给控件Content属性赋值就同时给了内容呈现器的Content属性赋值。实际上控件树上看到的是Content属性,而外观树上看到的是ContentPresenter子部件,只不过这个子部件中有一个Content属性且这个属性的值等于控件树上的Content属性的值】

 

十、样式

下图是样式style内部的结构:

注意:style内部有三大并列地位的子元素设定器Setters,触发器集合Triggers和资源集合Resources。其中设定器是默认子元素,所以经常看到style内部直接使用setter而不用setters包着。单个触发器Trigger和单个设定器Setter区别就在于前者是根据条件添加上各种属性值,而后者是无条件添加上各种属性值【只不过Trigger内部也使用Setter来设置属性罢了,容易误解为Setter是比Trigger第一个级别的元素,实际上他们是并列地位,至于使用在Trigger内部的Setter不用特别关注地位问题,记住就行】。

关于设定器,常常遗忘事件设定器EventSetter,用于处理当类似于“Event=MouseEnter”时处理事件程序【理解为给style加一个动态的属性,即增加一个事件属性,只不过这个属性有条件的,满足如MouseEnter条件时才执行那个改变状态的东西----事件处理程序】

关于触发器,一般说的触发器|Trigger都是指属性触发器,即依赖与属性,当属性为某个值时触发。多项触发器MutiTrigger就是依赖于多个属性的多属性触发器,这两类触发器本质都是属性触发器,一般用在样式Style内部。还有事件触发器EnentTrigger、数据触发器等,用在其它场合。这写触发器都是并列地位,只不过用在不同的地方而已,如属性触发器用在Style中,事件触发器用在动画中。

 

十一、样式、模版、触发器的关系

样式和模版是一个级别的东西,触发器比他们两个的级别低,常常用作样式和模版的子元素。

下面就比较样式和模版的区别:

首先,形式上比较,两者处于并列地位都是资源对象。只不过定义对象是使用Style、ControlTemplate和DataTemplate三种关键字罢了(即Style不是接口类是个具体类,而Template是个接口类,必须用具体的子类实例化)。

其次,功能或用途上比较,两者功能可能有重复,即有时style能实现的样式用Template也可以实现,反之亦然。但区别在于style常用于宏观,且只能改变控件可视的属性的值。而Template常常用于微观,甚至改变控件内部的结构(比如原来内部是由两个TextBox组成,现在改成由三个Rectangle组成)。可以说,Template功能更细致和强大,Style做到的基本上Template都能做到。当然Style写起代码来更方便。

第三、一般用在什么地方,由于可以认为Style只能改变样貌,而Template不仅可以改变样貌,还能改变内部的组成结构。所以style一般用在简单改变控件外貌上(Style定义是内部子元素都是用Setters包着的Setter之类的设定属性值的子元素或Triggers包着的Trigger之类的根据条件的设定属性值的子元素),而Template一般用在改变控件内部组成上(所以Template第一时内部子元素都是用Grid包起来的Ellipse、Rectangle等的形状元素,当然Template内部的子元素也可以是Triggers,此时Triggers和Style中的Triggers一模一样)

 

十二、关于Resources、Style、Template和Triggers的地位问题详细解释

其实上面解释的样式、模版和触发器的地位问题并不准确。关于地位问题要分两者情况:

(1)为了作为共享资源定义在资源集合中,即Resources中,此时Resources>Styles=Templates>Triggers(大于号是指包含的关系)

(2)直接用做控件的属性,如在Button,此时Button具有Resources(这个资源指Button的子资源,而上面的资源指全局共享的资源)、Style、Template和Triggers属性。即Resources=Styles=Templates=Triggers。

总之就是分为用在Resources和直接用在Control内部两者情况。

总结就是,作为共享资源时,考虑他们之间的包含关系。作为控件的属性时,同时具有这四个属性,具体定义时再考虑他们直接的包含关系(比如在Button中直接定义一个Style对象,此Style中再包含Triggers)

你可能感兴趣的:(WPF)