WPF笔记
一、XAML代码界面
创建项目后,系统自动生成窗口元素其内容为网格元素。
格式:<元素名 属性=“值”>内容</元素名>
或 <元素名 属性=“值”/>
或 <元素名/>
窗口元素
管理、配置、创建、显示用户与独立应用程序交互的窗口,可将控件元素或布局元素添加于<Window 属性=“值”>内容</ Window >的内容位置
自动生成属性 | 含义 |
x:Class | 对应cs文件中partial关键字继承Window父类的同名类 |
xmlns | 默认的命名空间 |
xmlns:x | 映射为 x: 前缀的命名空间 |
xmlns:d | 映射为 d: 前缀的命名空间 |
xmlns:mc | 映射为 mc: 前缀的命名空间 |
xmlns:local | 映射为 local: 前缀、含 x:Class 同名类和所有 XAML 文件的代码的命名空间 |
mc:Ignorable | 忽略参数前缀中的设计特性 一般为d前缀名 |
Title | 运行时窗口显示标题 |
Height | 窗口高度 |
Width | 窗口宽度 |
可选填属性 | 参数 |
WindowState | 窗口状态:Maximized(全屏)、Minimized(最小化)、Normal(还原) |
WindowStyle | 边框类型:None (不显示标题栏和边框)、SingleBorderWindow(单个边框的窗口)、ThreeDBorderWindow(三维边框)、ToolWindow(固定工具窗口) |
默认命名空间 | 含义 |
整个Windows Presentation Foundation (WPF) | |
单独的可扩展应用程序标记语言 (XAML) | |
设计器支持 | |
表示并支持读取 XAML 的标记兼容性模式 |
二、布局元素
项目自动生成的网格元素Grid是常用布局元素之一。布局元素的内容一般可放置多个控件或嵌套其它布局。
布局元素名 | 解释 | 特点 |
Grid | 网格 | 行列摆放子元素,效果类似表格 |
StackPanel | 栈式面板 | 水平或竖直排列子元素,移除一个元素自动前移补齐 |
Canvas | 画布 | 坐标放置子元素 |
DockPanel | 停靠面板 | 按上下左右中的区域放置子元素,最后一个子元素默认填充布局剩余空间 |
WrapPanel | 自动折行面板 | 按布局大小自动子元素换行排列 |
1、Grid
定义Grid的行与列
Grid类具有ColumnDefinitions和RowDefinitions两个属性,它们分别是ColumnDefinition和RowDefinition的集合,表示Grid定义了多少列、多少行。例如:
以上定义了3行3列。
设定行的高度和宽度
列有宽度,行有高度
- 绝对值:double数值加单位后缀;
- 比例值:double数值后加一个星号(*);
- 自动值:字符串AUTO。
英文名称 | 中文名称 | 简写 | 换算 |
Pixel | 像素 | px(默认,可省略) | 图形基本单位 |
Inch | 英寸 | in | 1 inch = 96 pixel |
Centimeter | 厘米 | cm | 1 cm = (96/2.54) pixel |
Point | 点 | pt | 1 pt = (96/72) pixel |
以上为绝对值,即double数值加单位后缀。
比例值则是把所有比例值的数值加起来作为分母、把每个比例值的数值作为分子,再把这个分数值乘以未被占用控件的像素数,得到的结果就是分配给这个比例值的最终像素数。比如,一个总高度为150px的Grid,有5行,其中两行采用绝对值25px,其他三行分别是2*、1*、2*,计算后,这三行分配的像素数应该是40px、20px和40px。
如果使用自动值"AUTO",则有行列内的控件的高度和宽度决定,即控件会把行列撑到合适的宽度和高度。若行列中没有控件,则行高和列宽均为0。
示例:
2、StackPanel
可以把内部元素在纵向或横向上紧凑排列、形成栈式布局,通俗的讲就是像搭积木一样。基于这个特点,StackPanel适合的场景有:
- 同类元素需要紧凑排列(如制作菜单或列表);
- 移除其中的元素后能够自动补缺的布局或动画。
StackPanel使用三个属性来控制内部元素的布局:
属性名称 | 数据类型 | 可取值 | 描述 |
Orientation | Orientation枚举 |
Horizontal Vertical |
决定内部元素是横向累积还是纵向累积 |
HorizontalAlignment | HorizontalAlignment枚举 |
Left Center Right Stretch |
决定内部元素水平方向上的对齐方式 |
VerticalAlignment | VerticalAlignment枚举 |
Top Center Bottom Stretch |
决定内部元素竖直方向上的对齐方式 |
例如:
3、Canvas
当控件放在Canvas里就会被附加上Canvas.X和Canvas.Y属性。适用的场合包括:
- 一经设计基本上不会再有改动的小型布局(如图标);
- 艺术性比较强的布局;
- 需要大量使用横纵坐标进行绝对定位的布局;
- 依赖于横纵坐标的动画。
示例:
以上是用Canvas代替Grid设计的登录窗口,除非确定这个窗口的布局以后不会改变且窗体尺寸固定,不然还是用Grid进行布局弹性会更好。
4、DockPanel
DockPanel内的元素会被附加上DockPanel.Dock这个属性,这个属性的数据类型为Dock枚举,可取Left、Top、Right和Bottom四个值。根据Dock属性值,DockPanel内的元素会向指定方向累积、切分DockPanel内部的剩余可用空间,就像船舶靠岸一样。
DockPanel还有一个重要属性——bool类型的LastChildFill,它的默认值是True。当它为True时,DockPanel内最后一个元素的DockPanel.Dock属性值会被忽略,这个元素会把DockPanel内部剩余空间撑满。
可以看到最顶上和最左边的设定了固定大小,右边这个作为最后一个元素则会把剩余空间撑满。
5、WrapPanel
WarpPanel内部采用的是流式布局,使用Orientation属性来控制流延伸的方向,使用HorizontalAlignment和VerticalAlignment两个属性控制内部控件的对齐。在流延伸的方向上,WrapPanel会排列尽可能多的控件,排不下的控件将会新起一行或一列继续排列。
更改窗体的尺寸。WrapPanel会调整内部控件的排列。
三、Binding
1、两种写法:
XMAL代码
路径(Path)就是用来设置binding要关联的那个属性,ElementName为绑定源对象的元素的名称。
示例:
CS代码
SetBinding (DependencyObject target, DependencyProperty dp, BindingBase binding);
target DependencyObject:绑定的绑定目标
dp DependencyProperty:绑定的目标属性
binding BindingBase:描述绑定的BindingBase对象
XMAL代码:
等价于C#代码:
2、控制Binding的方向及数据更新
有时数据只需要展示给用户、不允许用户修改,这时可以把Binding模式改为从源向目标的单向沟通,同时支持从目标向源的单向沟通和只在Binding关系确立时读取一次数据。
控制Binding数据流向的属性是Mode,类型为BindingMode,默认值为Default,指的是根据目标的实际情况确定,比如若是可编辑的(如TextBox.Text属性),就采取双向模式TwoWay;若是只读的(如TextBlock.Text)则采用单项模式OneWay。
Mode属性 | 作用 |
TwoWay | 只要目标属性或源属性发生更改,就很更新目标属性或源属性 |
OneWay | 仅当源属性发生更改时更新目标属性 |
OneTime | 仅当应用程序启动时或DataContext进行更改时目标属性 |
OneWayToSource | 在目标属性更改时更新源属性 |
Default | 默认值 |
Binding还有一个属性——UpdateSourceTrigger,类型为UpdateSourceTrigger枚举,默认值为LostFocus,可取值PropertyChanged、LostFocus、Explicit和Default。将属性改为PropertyChanged,则上文中的Slider的手柄位置会即时地随TextBoxd里的值而改变。
参数值 | 作用 |
Explicit | 仅在调用UpdateSource()方法更新源 |
LostFocus | 默认值,一旦目标控件失去焦点,源就会被更新 |
PropertyChanged | 一旦绑定的属性值改变,源会立即更新 |
3、Binding的路径(Path)
Binding的Path属性来指定关注对象的哪个属性,简单例子:
上面这个C#代码也可使用Binding的构造器简写为:
与上面第一点重复了
4、没有“Path”的Binding
若Binding源本身就是数据且不用Path来指明,典型的string,int等基本类型就是这样,无法指出通过它的哪个属性来访问这个数据,这时只需将Path的值设置为“.”或省略就可以了,在XAML代码里这个“.”可以省略不写,但在C#代码中不能省略。
等效的C#代码如下:
5、使用DataContext作为Binding源
WPF的UI布局是树形结构,每个结点都是控件,在UI元素树的每个结点都有DataContext。当一个Binding只知道自己的Path而不知道Source时,他会沿着UI元素树一路向树的根部找,每路过一个结点就看看这个结点的DataContext是否具有Path所指定的属性。若有,则把这个对象作为自己的Source;若无,则继续找如果到了树的根部仍没找到,那这个Binding就没有Source,因而也不会得到数据。示例:
先定义一个类:
然后再XAML创建程序UI:
使用xmlns:local="clr-namespace:TestWPF",就可以在XAML代码中使用在C#代码中定义的Student类,使用代码:
就为外层StackPanel的DataContext进行了赋值,三个TextBlock的Text通过Binding获取值,但为Binding指定了Path、没有指定Source。这样,这三个TextBlock的Binding就会自动向元素树的上层去寻找可用的DataContext对象。最后它们在最外层的StackPanel上找到了可用的DataContext对象。结果如下:
当Binding的Source本身就是数据,不需要属性时,Path和Source可省略不写,如:
6、使用集合对象作为列表控件的ItemsSource
WPF中的列表式控件们派生自ItemsControl类,自然也就继承了ItemsSource这个属性。ItemsSource属性可以接收一个IEnumerable接口派生类(子类)的实例作为自己的值(所有可被迭代遍历的集合都实现了这个接口,包括数组、List<T>等)。每个ItemControl都具有自己的条目容器(Item Container),例如,ListBox的条目容器是ListBoxItem、Combox的条目容器是ComboxItem。ItemSource里面保存的是一条一条的数据,想要把数据显示出来就要为数据穿上外衣,条目容器就起到了数据外衣的作用。这样将数据外衣和它所对应的条目容器关联起来呢?当然时依靠Binding!只要我们为一个ItemControl设置了ItemSource属性值,ItemControl会自动迭代其中的数据元素,为每个数据元素准备一个条目容器,并使用Binding元素在条目容器和数据元素之间建立起关联。示例:
效果图如下:
我们要实现的效果是把一个List<Student>集合的实例作为ListBox的ItemsSource,让ListBox显示Student的Name并使用TextBox显示ListBox当前选中条目的Id。需在窗体的构造器中写代码:
运行效果图如下:
ListBoxStudents.DisplayMemberPath = "Name",注意到 它包含“Path”这个单词,说明它是个路径。当DisplayMemberPath属性被赋值后,ListBox在获得ItemSource的时候就会创建等量的ListBoxItem并以DisplayMemberPath属性值为Path创建Binding。
再看一个显式为数据设置DataTemplate的例子。先把C#代码中ListBoxStudents.DisplayMemberPath = "Name"删除,再在XAML中添加代码,ListBox的ItemTemplate属性的类型是DataTemplate,以下代码是为Student类型实例定做的:
结果如下:
另外有一点:在使用集合类型作为列表控件的ItemsSource时一般会考虑使用ObservableCollection<T>代替List<T>,因为ObservableCollection<T>实现了INotifyCollectionChanged和INotifyPropertyChanged接口,能把集合的变化立刻通知显示它的的列表控件,改变会立刻显现出来。
7、使用XML数据作为Binding的源
以下为基于DOM标准的XML类库。
需要注意的是,当使用XML数据作为Binding的Source时我们将使用XPath属性而不是Path来指定数据的来源。
以下示例:
以下是一组学生信息的XML文本,存放于D:\Students.xml文件中。要把它显示在ListView中。
XAML部分如下:
Butto的Click事件处理代码:
XAML代码中最关键的两句是“DisplayMemberBinding="{Binding XPath=@Id}";”,和“DisplayMemberBinding={Binding XPath=Name}";”,它们分别为GridView的两列指明了关注的XML路径——明显,使用@符号加字符串表示的是XML元素的Attribute,不加@符号的字符串表示的是子级元素。
8、使用LINQ检索结果作为Binding的源
LINQ查询的结果是一个IEnumerable<T>类型对象,而IEnumerable<T>又派生于IEnumerable,所以它可以作为列表控件的ItemsSource来使用。示例:
UI界面:
要从一个已经填充好的List<Student>对象中检索出所有女性,如下:
如果数据存储在XML文件中,如下:
四、Binding对数据的转换与校验
1、Binding的数据校验
Binding的ValidationRules属性的类型是Collection<ValidationRule>,从它的名称可以看见,每个 Binding可以接收多个校验条件,每个条件都是ValidationRule类型对象。示例:检验TextBox的里输入的值是不是在0到100的范围内。
首先准备放置一个TextBox和Slider,XAML代码:
为了进行校验,要准备一个ValidationRule派生类:
ValidationRule类是抽象类,在该派生类中要实现Validate方法,该方法的返回值类型为ValidationResult类型,如果校验成功,ValidationResult的IsVaild的属性会被置为True;如果校验失败,ValidationResult的IsVaild的属性会被置为False,并为其ErrorContent属性设置一个合适的消息的内容(一般是个字符串)。
然后再窗体的构造函数内建立Binding:
这样当输入不在[0,100]范围内的数字时,TextBox的边框变红,表示校验错误:
检验Source的数据:
将Slider的调到(-∞,0)U(100,+∞),发现TextBox没有进行校验,这时因为Binding只对Binding目标输入时进行校验,对于Source(Slider)传来的数据不进行校验。如果想要设置同时校验Source数据,我们需要将校验规则的ValidatesOnTargetUpdated设置为True。
校验失败的信息显示
当校验失败时,Validate 返回的ValidationResult携带了一条错误信息,我们怎么在界面上显示这个错误信息呢?
首先,需要把Binding对象的NotifyOnValidationError置为true。这样当数据校验失败时,Binding就会发出一个信息,以Binding对象的Target为起点,沿着UI元素树向上传播。信号每到达一个节点,如果节点设置了对这个信号的侦听器(事件处理器),那么这个侦听器就会被触发处理这个信号。这个传递过程称为路由(Route)。
2、Binding的数据转换
实际的开发中,我们经常会遇到Binding的Source和Target是不同的类型,如下面的例子,我们需要将一个Button的IsEnable属性绑定到一个TextBox的Text属性,实现的效果是当TextBox的输入为空时,Button不可用。
Bingding中有一个叫做Converter的属性,顾名思义,就是转换器的意思,就可以帮助我们实现这种效果。
首先,我们需要自己写一个Converter,方法是创建一个类,继承IValueConverter接口:
当数据从Binding的Source流向Target时,Convert方法将被调用;反之,ConvertBack将被调用。
在接口函数Convert中,我们进行从Binding的Source到Target类型的转换,在这里判断value是否为空,如果为空返回false,否则返回true;在ConvertBack中我们可以实现Binding的Target到Source类型的转换,同Convert的转换方法类似,在这里并没有实现,而是抛出了一个不用处理的NotImplementedException异常。
然后在XAML中实现Binding,如下:
我们先在Window.Resources中创建了一个StringToBoolConverter对象,键为s2b,然后将Button的IsEnabled绑定到TextBox的Text属性上,Converter设置静态资源s2b,这样就实现了Binding的数据转换。
3、MultiBinding(多路Binding)
有时候,UI需要的信息不止一个数据来源,这时候就需要使用多路绑定MultiBinding。下面我们实现通过两个TextBox来控制Button的可用性:两个TextBox的不为空且内容一致时,Button才可用;否则不可用。
首先,搭建界面,XAML代码:
然后,创建一个Convert,此时需要继承自IMultiValueConverter,而不是之前的IValueConverter。
在构造函数中实现多路绑定:
结果如下: