一、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(固定工具窗口) |
默认命名空间 | 含义 |
http://schemas.microsoft.com/winfx/2006/xaml/presentation | 整个Windows Presentation Foundation (WPF) |
http://schemas.microsoft.com/winfx/2006/xaml | 单独的可扩展应用程序标记语言 (XAML) |
http://schemas.microsoft.com/expression/blend/2008 | 设计器支持 |
http://schemas.openxmlformats.org/markup-compatibility/2006 | 表示并支持读取 XAML 的标记兼容性模式 |
二、布局元素
项目自动生成的网格元素Grid是常用布局元素之一。布局元素的内容一般可放置多个控件或嵌套其它布局。
布局元素名 | 解释 | 特点 |
Grid | 网格 | 行列摆放子元素,效果类似表格 |
StackPanel | 栈式面板 | 水平或竖直排列子元素,移除一个元素自动前移补齐 |
Canvas | 画布 | 坐标放置子元素 |
DockPanel | 停靠面板 | 按上下左右中的区域放置子元素,最后一个子元素默认填充布局剩余空间 |
WrapPanel | 自动折行面板 | 按布局大小自动子元素换行排列 |
1、Grid
定义Grid的行与列
Grid类具有ColumnDefinitions和RowDefinitions两个属性,它们分别是ColumnDefinition和RowDefinition的集合,表示Grid定义了多少列、多少行。例如:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
</Grid>
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
以上定义了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。
示例:
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="80" />
<ColumnDefinition Width="4" /><!-- 这是“提交”和“清除”两个按钮之间的空隙 -->
<ColumnDefinition Width="80" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="4" /><!-- 这是ComboBox与textbox之间的空隙 -->
<RowDefinition Height="*" />
<RowDefinition Height="4" /><!-- 这是textbox与按钮之间的空隙 -->
<RowDefinition Height="25" />
</Grid.RowDefinitions>
<TextBlock Text="选择部门并留言:" Grid.Column="0" Grid.Row="0" VerticalAlignment="Center" />
<ComboBox Grid.Column="1" Grid.Row="0" Grid.ColumnSpan="4" />
<TextBox Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="5" />
<Button Content="提交" Grid.Column="2" Grid.Row="4" />
<Button Content="清除" Grid.Column="4" Grid.Row="4" />
</Grid>
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
2、StackPanel
可以把内部元素在纵向或横向上紧凑排列、形成栈式布局,通俗的讲就是像搭积木一样。基于这个特点,StackPanel适合的场景有:
- 同类元素需要紧凑排列(如制作菜单或列表);
- 移除其中的元素后能够自动补缺的布局或动画。
StackPanel使用三个属性来控制内部元素的布局:
属性名称 | 数据类型 | 可取值 | 描述 |
Orientation | Orientation枚举 |
Horizontal Vertical |
决定内部元素是横向累积还是纵向累积 |
HorizontalAlignment | HorizontalAlignment枚举 |
Left Center Right Stretch |
决定内部元素水平方向上的对齐方式 |
VerticalAlignment | VerticalAlignment枚举 |
Top Center Bottom Stretch |
决定内部元素竖直方向上的对齐方式 |
例如:
<Grid>
<GroupBox Header="请选择没错别字的的成语" BorderBrush="Black" Margin="5">
<StackPanel Margin="5">
<CheckBox Content="A.迫不及待" />
<CheckBox Content="B.妙不可言" />
<CheckBox Content="C.唉声叹气" />
<CheckBox Content="D.陈词滥调" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="确定" Width="60" Margin="5" />
<Button Content="取消" Width="60" Margin="5" />
</StackPanel>
</StackPanel>
</GroupBox>
</Grid>
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
3、Canvas
当控件放在Canvas里就会被附加上Canvas.X和Canvas.Y属性。适用的场合包括:
- 一经设计基本上不会再有改动的小型布局(如图标);
- 艺术性比较强的布局;
- 需要大量使用横纵坐标进行绝对定位的布局;
- 依赖于横纵坐标的动画。
示例:
<Canvas>
<TextBlock Text="用户名:" Canvas.Left="12" Canvas.Top="12" />
<TextBox Height="23" Width="200" BorderBrush="Black" Canvas.Left="66" Canvas.Top="9" />
<TextBlock Text="密码:" Canvas.Left="12" Canvas.Top="40.72" Height="16" Width="36" />
<TextBox Height="23" Width="200" BorderBrush="Black" Canvas.Left="66" Canvas.Top="38" />
<Button Content="确定" Width="80" Height="22" Canvas.Left="100" Canvas.Top="67" />
<Button Content="清除" Width="80" Height="22" Canvas.Left="186" Canvas.Top="67" />
</Canvas>
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
以上是用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内部剩余空间撑满。
<Grid>
<DockPanel>
<TextBox DockPanel.Dock="Top" Height="50" BorderBrush="Blue" />
<TextBox DockPanel.Dock="Left" Width="150" BorderBrush="BlueViolet" />
<TextBox BorderBrush="Aqua"/>
</DockPanel>
</Grid>
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
可以看到最顶上和最左边的设定了固定大小,右边这个作为最后一个元素则会把剩余空间撑满。
5、WrapPanel
WarpPanel内部采用的是流式布局,使用Orientation属性来控制流延伸的方向,使用HorizontalAlignment和VerticalAlignment两个属性控制内部控件的对齐。在流延伸的方向上,WrapPanel会排列尽可能多的控件,排不下的控件将会新起一行或一列继续排列。
<WrapPanel>
<Button Width="200" Height="200" Content="OK" />
<Button Width="200" Height="200" Content="OK" />
<Button Width="200" Height="200" Content="OK" />
<Button Width="200" Height="200" Content="OK" />
<Button Width="200" Height="200" Content="OK" />
<Button Width="200" Height="200" Content="OK" />
<Button Width="200" Height="200" Content="OK" />
<Button Width="200" Height="200" Content="OK" />
<Button Width="200" Height="200" Content="OK" />
<Button Width="200" Height="200" Content="OK" />
<Button Width="200" Height="200" Content="OK" />
</WrapPanel>
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
更改窗体的尺寸。WrapPanel会调整内部控件的排列。
三、Binding
1、两种写法:
XMAL代码
路径(Path)就是用来设置binding要关联的那个属性,ElementName为绑定源对象的元素的名称。
示例:
<TextBox x:Name="textbox1" Background="Aqua" Text="{Binding Path=Value,ElementName=Slider1}" />
<Slider x:Name="Slider1" Maximum="100" Minimum="0" />
<TextBox x:Name="textbox2" Background="Aquamarine" Text="{Binding Path=Text.Length,ElementName=textbox1,Mode=OneWay}" />//Mode为控制流方向
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
CS代码
Binding binding = new Binding(){Path = new PropertyPath("Value"), Source = Slider1};
BindingOperations.SetBinding(textbox3, TextBox.TextProperty, binding);
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
SetBinding (DependencyObject target, DependencyProperty dp, BindingBase binding);
target DependencyObject:绑定的绑定目标
dp DependencyProperty:绑定的目标属性
binding BindingBase:描述绑定的BindingBase对象
XMAL代码:
<TextBox x:Name="textbox3" Text="{Binding Path=Value,ElementName=Slider1}" />
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
等价于C#代码:
this.textbox3.SetBinding(TextBox.TextProperty, new Binding("Value"){ElementName = "Slider1"});
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
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属性来指定关注对象的哪个属性,简单例子:
<TextBlock x:Name="textBlock1" Background="Aquamarine" Text="{Binding Path=Value,ElementName=Slider1}" Margin="5" />//Value代表Slider目前的值
<Slider x:Name="Slider1" />
<TextBlock x:Name="textBlock2" Background="Aqua" Margin="5" />
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
Binding binding = new Binding { Path = new PropertyPath("Value"), Source = Slider1, };
textBlock2.SetBinding(TextBlock.TextProperty, binding);
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
上面这个C#代码也可使用Binding的构造器简写为:
textBlock2.SetBinding(TextBlock.TextProperty, new Binding("Value") { Source = Slider1, });
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
与上面第一点重复了
4、没有“Path”的Binding
若Binding源本身就是数据且不用Path来指明,典型的string,int等基本类型就是这样,无法指出通过它的哪个属性来访问这个数据,这时只需将Path的值设置为“.”或省略就可以了,在XAML代码里这个“.”可以省略不写,但在C#代码中不能省略。
<StackPanel.Resources>
<system:String x:Key="myString">
菩提本无树,明镜亦非台。
本来无一物,何处惹尘埃。
</system:String>
</StackPanel.Resources>
<TextBlock x:Name="textBlock3" Background="Bisque" Margin="5" Text="{Binding Path=.,Source={StaticResource ResourceKey=myString}}" />
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
等效的C#代码如下:
string myString = "菩提本无树,明镜亦非台。本来无一物,何处惹尘埃。";
textBlock3.SetBinding(TextBlock.TextProperty, new Binding(".") {Source = myString});
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
5、使用DataContext作为Binding源
WPF的UI布局是树形结构,每个结点都是控件,在UI元素树的每个结点都有DataContext。当一个Binding只知道自己的Path而不知道Source时,他会沿着UI元素树一路向树的根部找,每路过一个结点就看看这个结点的DataContext是否具有Path所指定的属性。若有,则把这个对象作为自己的Source;若无,则继续找如果到了树的根部仍没找到,那这个Binding就没有Source,因而也不会得到数据。示例:
先定义一个类:
public class Student
{
public string Name { get; set; }
public int Id { get; set; }
public string Sex { get; set; }
}
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
然后再XAML创建程序UI:
<Window x:Class="TestWPF.Test"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestWPF"//需要引入类所在命名空间
mc:Ignorable="d"
Title="Test" Height="450" Width="800">
<StackPanel>
<StackPanel.DataContext>
<local:Student Id="10" Name="Mike" Sex="男" />
</StackPanel.DataContext>
<Grid>
<StackPanel>
<TextBlock x:Name="textBlock4" Background="Chocolate" Margin="5" Text="{Binding Path=Id}" />
<TextBlock x:Name="textBlock5" Background="Teal" Margin="5" Text="{Binding Path=Name}" />
<TextBlock x:Name="textBlock6" Background="DeepSkyBlue" Margin="5" Text="{Binding Path=Sex}" />
</StackPanel>
</Grid>
</StackPanel>
</Window>
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
使用xmlns:local="clr-namespace:TestWPF",就可以在XAML代码中使用在C#代码中定义的Student类,使用代码:
<StackPanel.DataContext>
<local:Student Id="10" Name="Mike" Sex="男" />
</StackPanel.DataContext>
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
就为外层StackPanel的DataContext进行了赋值,三个TextBlock的Text通过Binding获取值,但为Binding指定了Path、没有指定Source。这样,这三个TextBlock的Binding就会自动向元素树的上层去寻找可用的DataContext对象。最后它们在最外层的StackPanel上找到了可用的DataContext对象。结果如下:
当Binding的Source本身就是数据,不需要属性时,Path和Source可省略不写,如:
<Window x:Class="TestWPF.Test"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=mscorlib"//指定String类型所需
mc:Ignorable="d"
Title="Test" Height="450" Width="800">
<StackPanel>
<StackPanel.DataContext>
<system:String>Hello Worldl!</system:String>
</StackPanel.DataContext>
<Grid>
<StackPanel>
<TextBlock Background="Aqua" Margin="5" Text="{Binding}" />
</StackPanel>
</Grid>
</StackPanel>
</Window>
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
6、使用集合对象作为列表控件的ItemsSource
WPF中的列表式控件们派生自ItemsControl类,自然也就继承了ItemsSource这个属性。ItemsSource属性可以接收一个IEnumerable接口派生类(子类)的实例作为自己的值(所有可被迭代遍历的集合都实现了这个接口,包括数组、List<T>等)。每个ItemControl都具有自己的条目容器(Item Container),例如,ListBox的条目容器是ListBoxItem、Combox的条目容器是ComboxItem。ItemSource里面保存的是一条一条的数据,想要把数据显示出来就要为数据穿上外衣,条目容器就起到了数据外衣的作用。这样将数据外衣和它所对应的条目容器关联起来呢?当然时依靠Binding!只要我们为一个ItemControl设置了ItemSource属性值,ItemControl会自动迭代其中的数据元素,为每个数据元素准备一个条目容器,并使用Binding元素在条目容器和数据元素之间建立起关联。示例:
<StackPanel x:Name="StackPanel" Background="LightBlue">
<TextBlock Text="Student ID:" FontWeight="Bold" Margin="5" />
<TextBox x:Name="TextBoxID" Margin="5" />
<TextBlock Text="Student List:" FontWeight="Bold" Margin="5" />
<ListBox x:Name="ListBoxStudents" Height="110" Margin="5">
</StackPanel>
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
效果图如下:
我们要实现的效果是把一个List<Student>集合的实例作为ListBox的ItemsSource,让ListBox显示Student的Name并使用TextBox显示ListBox当前选中条目的Id。需在窗体的构造器中写代码:
public Test()
{
InitializeComponent();
List<Student> stuList = new List<Student>
{
new Student { Id = 5, Name = "Amy", Sex = "女", },
new Student { Id = 8, Name = "John", Sex = "男", },
new Student { Id = 15, Name = "Mike", Sex = "男", },
new Student { Id = 29, Name = "Sarah", Sex = "女", },
};
//为ListBox设置Binding
ListBoxStudents.ItemsSource = stuList;
ListBoxStudents.DisplayMemberPath = "Name";
//为TextBox设置Binding
Binding binding = new Binding("SelectedItem.Id") { Source = ListBoxStudents };
TextBoxID.SetBinding(TextBox.TextProperty, binding);
}
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
运行效果图如下:
ListBoxStudents.DisplayMemberPath = "Name",注意到 它包含“Path”这个单词,说明它是个路径。当DisplayMemberPath属性被赋值后,ListBox在获得ItemSource的时候就会创建等量的ListBoxItem并以DisplayMemberPath属性值为Path创建Binding。
再看一个显式为数据设置DataTemplate的例子。先把C#代码中ListBoxStudents.DisplayMemberPath = "Name"删除,再在XAML中添加代码,ListBox的ItemTemplate属性的类型是DataTemplate,以下代码是为Student类型实例定做的:
<Window x:Class="TestWPF.Test"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="Test" Height="450" Width="800">
<StackPanel x:Name="StackPanel" Background="LightBlue">
<TextBlock Text="Student ID:" FontWeight="Bold" Margin="5" />
<TextBox x:Name="TextBoxID" Margin="5" />
<TextBlock Text="Student List:" FontWeight="Bold" Margin="5" />
<ListBox x:Name="ListBoxStudents" Height="110" Margin="5">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Id}" Width="30" />
<TextBlock Text="{Binding Path=Name}" Width="30" />
<TextBlock Text="{Binding Path=Sex}" Width="30" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
结果如下:
另外有一点:在使用集合类型作为列表控件的ItemsSource时一般会考虑使用ObservableCollection<T>代替List<T>,因为ObservableCollection<T>实现了INotifyCollectionChanged和INotifyPropertyChanged接口,能把集合的变化立刻通知显示它的的列表控件,改变会立刻显现出来。
7、使用XML数据作为Binding的源
以下为基于DOM标准的XML类库。
需要注意的是,当使用XML数据作为Binding的Source时我们将使用XPath属性而不是Path来指定数据的来源。
以下示例:
以下是一组学生信息的XML文本,存放于D:\Students.xml文件中。要把它显示在ListView中。
<?xml version="1.0" encoding="utf-8" ?>
<StudentList>
<Student Id ="1">
<Name>Tom</Name>
</Student>
<Student Id ="2">
<Name>Jack</Name>
</Student>
<Student Id ="3">
<Name>Joe</Name>
</Student>
</StudentList>
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
XAML部分如下:
<StackPanel>
<ListView x:Name="ListViewStudents" Height="130" Margin="5">
<ListView.View>
<GridView>
<GridViewColumn Header="Id" Width="80" DisplayMemberBinding="{Binding XPath=@Id}" />
<GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding XPath=Name}" />
</GridView>
</ListView.View>
</ListView>
<Button Content="Load" Click="ButtonBase_OnClick" Height="25" Margin="5,0" />
</StackPanel>
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
Butto的Click事件处理代码:
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
XmlDataProvider xdp = new XmlDataProvider();
xdp.Source = new Uri(@"D:\Students.xml");//Source可直接指定XML文档所在的位置
xdp.XPath = @"/StudentList/Student";
ListViewStudents.DataContext = xdp;
ListViewStudents.SetBinding(ListView.ItemsSourceProperty, new Binding());
}
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
XAML代码中最关键的两句是“DisplayMemberBinding="{Binding XPath=@Id}";”,和“DisplayMemberBinding={Binding XPath=Name}";”,它们分别为GridView的两列指明了关注的XML路径——明显,使用@符号加字符串表示的是XML元素的Attribute,不加@符号的字符串表示的是子级元素。
8、使用LINQ检索结果作为Binding的源
LINQ查询的结果是一个IEnumerable<T>类型对象,而IEnumerable<T>又派生于IEnumerable,所以它可以作为列表控件的ItemsSource来使用。示例:
UI界面:
<StackPanel>
<ListView x:Name="ListViewStudents1" Height="143" Margin="5">
<ListView.View>
<GridView>
<GridViewColumn Header="Id" Width="60" DisplayMemberBinding="{Binding Id}" />
<GridViewColumn Header="Name" Width="100" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Sex" Width="60" DisplayMemberBinding="{Binding Sex}" />
</GridView>
</ListView.View>
</ListView>
<Button x:Name="btn1" Content="Load" Height="25" Margin="5,0" Click="btn1_Click"></Button>
</StackPanel>
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
要从一个已经填充好的List<Student>对象中检索出所有女性,如下:
ListViewStudents1.ItemsSource = from student in stuList where student.Sex.Equals("女") select student;
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
如果数据存储在XML文件中,如下:
四、Binding对数据的转换与校验
1、Binding的数据校验
Binding的ValidationRules属性的类型是Collection<ValidationRule>,从它的名称可以看见,每个 Binding可以接收多个校验条件,每个条件都是ValidationRule类型对象。示例:检验TextBox的里输入的值是不是在0到100的范围内。
首先准备放置一个TextBox和Slider,XAML代码:
<StackPanel>
<TextBox x:Name="textBox1" Margin="5" />
<Slider x:Name="slider1" Minimum="-10" Maximum="110" Margin="5" />
</StackPanel>
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
为了进行校验,要准备一个ValidationRule派生类:
public class RangeValidationRule : ValidationRule
{
//需要实现Validate方法
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
double d = 0;
if (double.TryParse(value.ToString(), out d))
{
if (d >= 0 && d <= 100)
{
return new ValidationResult(true, null);
}
}
return new ValidationResult(false, "Validation Failed");
}
}
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
ValidationRule类是抽象类,在该派生类中要实现Validate方法,该方法的返回值类型为ValidationResult类型,如果校验成功,ValidationResult的IsVaild的属性会被置为True;如果校验失败,ValidationResult的IsVaild的属性会被置为False,并为其ErrorContent属性设置一个合适的消息的内容(一般是个字符串)。
然后再窗体的构造函数内建立Binding:
public MainWindow()
{
InitializeComponent();
Binding binding = new Binding("Value") { Source = this.slider1 };
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
RangeValidationRule rule = new RangeValidationRule();
binding.ValidationRules.Add(rule);
this.textBox1.SetBinding(TextBox.TextProperty, binding);
}
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
这样当输入不在[0,100]范围内的数字时,TextBox的边框变红,表示校验错误:
检验Source的数据:
将Slider的调到(-∞,0)U(100,+∞),发现TextBox没有进行校验,这时因为Binding只对Binding目标输入时进行校验,对于Source(Slider)传来的数据不进行校验。如果想要设置同时校验Source数据,我们需要将校验规则的ValidatesOnTargetUpdated设置为True。
public MainWindow()
{
InitializeComponent();
Binding binding = new Binding("Value") { Source = this.slider1 };
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
RangeValidationRule rule = new RangeValidationRule();
rule.ValidatesOnTargetUpdated = true;在此
binding.ValidationRules.Add(rule);
this.textBox1.SetBinding(TextBox.TextProperty, binding);
}
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
校验失败的信息显示
当校验失败时,Validate 返回的ValidationResult携带了一条错误信息,我们怎么在界面上显示这个错误信息呢?
首先,需要把Binding对象的NotifyOnValidationError置为true。这样当数据校验失败时,Binding就会发出一个信息,以Binding对象的Target为起点,沿着UI元素树向上传播。信号每到达一个节点,如果节点设置了对这个信号的侦听器(事件处理器),那么这个侦听器就会被触发处理这个信号。这个传递过程称为路由(Route)。
public MainWindow()
{
InitializeComponent();
Binding binding = new Binding("Value") { Source = this.slider1 };
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
RangeValidationRule rule = new RangeValidationRule();
rule.ValidatesOnTargetUpdated = true;
binding.ValidationRules.Add(rule);
//校验失败发出信号
binding.NotifyOnValidationError = true;
this.textBox1.SetBinding(TextBox.TextProperty, binding);
//关联事件处理器
this.textBox1.AddHandler(Validation.ErrorEvent,new RoutedEventHandler(this.ValidationError));
}
//事件处理器
void ValidationError(object sender, RoutedEventArgs e)
{
if (Validation.GetErrors(this.textBox1).Count > 0)
{
this.textBox1.ToolTip = Validation.GetErrors(this.textBox1)[0].ErrorContent.ToString();
}
}
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
2、Binding的数据转换
实际的开发中,我们经常会遇到Binding的Source和Target是不同的类型,如下面的例子,我们需要将一个Button的IsEnable属性绑定到一个TextBox的Text属性,实现的效果是当TextBox的输入为空时,Button不可用。
Bingding中有一个叫做Converter的属性,顾名思义,就是转换器的意思,就可以帮助我们实现这种效果。
首先,我们需要自己写一个Converter,方法是创建一个类,继承IValueConverter接口:
public class StringToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return false;
if (value is string&&
string.IsNullOrEmpty((string)value)==false)
{
return true;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
当数据从Binding的Source流向Target时,Convert方法将被调用;反之,ConvertBack将被调用。
在接口函数Convert中,我们进行从Binding的Source到Target类型的转换,在这里判断value是否为空,如果为空返回false,否则返回true;在ConvertBack中我们可以实现Binding的Target到Source类型的转换,同Convert的转换方法类似,在这里并没有实现,而是抛出了一个不用处理的NotImplementedException异常。
然后在XAML中实现Binding,如下:
<Window x:Class="_6_34.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:_6_34"//命名空间
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:StringToBoolConverter x:Key="s2b"/>
</Window.Resources>
<Grid>
<TextBox Height="23" HorizontalAlignment="Left" Margin="160,82,0,0" Name="textBox1" VerticalAlignment="Top" Width="120"
/>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="180,178,0,0" Name="button1" VerticalAlignment="Top" Width="75"
IsEnabled="{Binding Text,Converter={StaticResource s2b},ElementName=textBox1}"/>
</Grid>
</Window>
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
我们先在Window.Resources中创建了一个StringToBoolConverter对象,键为s2b,然后将Button的IsEnabled绑定到TextBox的Text属性上,Converter设置静态资源s2b,这样就实现了Binding的数据转换。
3、MultiBinding(多路Binding)
有时候,UI需要的信息不止一个数据来源,这时候就需要使用多路绑定MultiBinding。下面我们实现通过两个TextBox来控制Button的可用性:两个TextBox的不为空且内容一致时,Button才可用;否则不可用。
首先,搭建界面,XAML代码:
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="192,191,0,0" Name="button1" VerticalAlignment="Top" Width="75" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="169,75,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="169,122,0,0" Name="textBox2" VerticalAlignment="Top" Width="120" />
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
然后,创建一个Convert,此时需要继承自IMultiValueConverter,而不是之前的IValueConverter。
public class MultiBindingConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if(values.Cast<string>().Any(text=>string.IsNullOrEmpty(text))==false
&& values[0].ToString() == values[1].ToString())
{
return true;
}
return false;
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
在构造函数中实现多路绑定:
public MainWindow()
{
InitializeComponent();
//准备binding
Binding b1 = new Binding("Text") { Source = this.textBox1 };
Binding b2 = new Binding("Text") { Source = this.textBox2 };
//准备多路binding
MultiBinding mb = new MultiBinding() { Mode=BindingMode.OneWay };
mb.Bindings.Add(b1);
mb.Bindings.Add(b2);
MultiBindingConverter mbc=new MultiBindingConverter();
mb.Converter = mbc;
//设置关联
this.button1.SetBinding(Button.IsEnabledProperty, mb);
}
![](/i/l/?n=23&i=blog/3308392/202310/3308392-20231025170210321-1748139418.gif)
结果如下:
标签:binding,元素,Binding,笔记,Source,new,WPF,属性 From: https://www.cnblogs.com/shieryoufeng/p/17787706.html