参考书籍《深入浅出WPF》,原书第6章节学习笔记整理。
程序的本质是数据+算法
Binding基础
Binding是一座桥梁,它的源是逻辑层的对象、它的目标是UI层的控件对象。这样数据会不断的通过Binding送达UI层,被UI层展现。
1、属性
创建一个简单的类对象作为数据源,设置将类中的参数属性通过Binding传递给UI控件,则该属性即为路径(Path)。
除此之外,当属性值发生变化后需要自动通知Binding,将变化的数值传递给UI控件。
- 在属性的set语句中激发一个PropertyChanged事件
- 作为数据源的类实现System.ComponentModel中的INotifyPropertyChanged接口
当为Bing设置了数据源后,会自动侦听来自该接口的PropertyChanged事件
2、代码
注意:粘贴xml代码时,上半部分xmls不能粘贴,他是针对具体的工程建立的。
<Window
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" x:Class="WpfApplication1_8_18.MainWindow"
Title="Simple Binding" Height="110" Width="300" >
<StackPanel>
<TextBox x:Name="textBoxName" BorderBrush="Black" Margin="5"/>
<Button Content="Add Name" Margin="5" Click="Button_Click"/>
</StackPanel>
</Window>
窗体界面显示如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;//以上为程序自动添加的引用
//以下为额外添加引用
using System.ComponentModel;//添加引用
class Student : INotifyPropertyChanged //让作为数据源的Student类实现System.ComponentModel空间的INotifyPropertyChanged接口
{
public event PropertyChangedEventHandler PropertyChanged; //句柄
private string name; //Student类里面的局部变量
public string Name
{
get {
return name;}
set
{
name = value;//value?
//激发事件
if(this.PropertyChanged!=null)
{
this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name"));
}
}
}
}
namespace WpfApplication1_8_18
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
Student stu ;
public MainWindow()
{
InitializeComponent();
//准备数据源
stu = new Student();
//准备Binding
Binding binding = new Binding();
binding.Source = stu; //为Binding指定数据源
binding.Path = new PropertyPath("Name"); //为Binding指定访问路径
//使用Binding连接数据源与Binding目标:(目标,送达目标的哪个属性,使用的Binding实例)
BindingOperations.SetBinding(this.textBoxName, TextBox.TextProperty, binding);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//使用Binding把数据源和UI元素连接起来
stu.Name += "Name";
}
}
}
程序运行效果如下:
3、整个程序运行的流程如下:
4、借助Binding构造器进行简化
Binding的源
把控件作为Binding源
为了让UI产生一些联动效果,会用Binding在控件之间建立关联。
<Window
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" x:Class="WpfApplication1_8_18.MainWindow"
Title="Control as Source" Height="110" Width="300" >
<StackPanel>
<TextBox x:Name="textbox1" Text="{Binding Path=Value, ElementName=Slider1}" BorderBrush="Black" Margin="5"/>
<Slider x:Name="Slider1" Maximum="100" Minimum="0" Margin="5"/>
</StackPanel>
</Window>
上述程序中Binding的其它等价形式
语句:
<TextBox x:Name="textbox1" Text="{Binding Path=Value, ElementName=Slider1}" BorderBrush="Black" Margin="5"/>
等价于
<TextBox x:Name="textbox1" BorderBrush="Black" Margin="5"/>
this.textbox1.SetBinding(TextBox.TextProperty, new Binding("Value") {
ElementName = "Slider1" });
控制Binding的方向及数据更新
默认情况下为双向的。当控件只可读,不可修改时就需要设置为单向。
控制Binding数据流向为Mode:
- TwoWay
- OneWay
- OnTime
- OneWayToResource
- Default
上述示例中,滑动Slider则TextBox会显示当前值。反之,在TextBox中输入数值(输入50,按下Tab键使焦点离开TextBox),Slider也会滑动到相应位置。
设置为OneWay之后:滑动Slider则TextBox会改变显示值,但是在TextBox中输入数值之后Slider不会滑动了。
<Window
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" x:Class="WpfApplication1_8_18.MainWindow"
Title="Control as Source" Height="110" Width="300" >
<StackPanel>
<TextBox x:Name="textbox1" Text="{Binding Path=Value, ElementName=Slider1,Mode=OneWay}" BorderBrush="Black" Margin="5"/>
<Slider x:Name="Slider1" Maximum="100" Minimum="0" Margin="5"/>
</StackPanel>
</Window>
//等价的C#命令
this.textbox1.SetBinding(TextBox.TextProperty, new Binding("Value") {
Source = this.textbox1, Mode = BindingMode.OneWay });
ps:TextBox失去焦点之后Slider才会移动,是因为默认Binding属性UpdateSourceTrigger:
包含了PropertyChanged、LostFocus、Explicit和Default。而TextBox的默认行为与LostFocus一致,当修改为PropertyChanged,则Slider位置会即刻改变。
<Window
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" x:Class="WpfApplication1_8_18.MainWindow"
Title="Control as Source" Height="110" Width="300" >
<StackPanel>
<TextBox x:Name="textbox1" Text="{Binding Path=Value, ElementName=Slider1,UpdateSourceTrigger=PropertyChanged}" BorderBrush="Black" Margin="5"/>
<Slider x:Name="Slider1" Maximum="100" Minimum="0" Margin="5"/>
</StackPanel>
</Window>
pps: Binding还有NotifyOnSourceUpdated和NotifyOnTargetUpdated这2个bool类型属性,如果设为true,当源或者目标被更新后Binding会激发相应的SourceUpdated和TargetUpdated事件,可以通过监听这2个事件来找出有哪些数据源或者控件更新了。
Binding的路径Path
源对象有很多属性,Path决定Binding需要关注哪个属性。各种Path创建方式:
- 最简单的方式:
<TextBox x:Name="textbox1" Text="{Binding Path=Value, ElementName=Slider1}" BorderBrush="Black" Margin="5"/>
等价的C#:
Binding binding = new Binding() {
Path=new PropertyPath("Value"),Source=this.Slider1 };
this.textbox1.SetBinding(TextBox.TextProperty, binding);
//或者使用Binding构造器
Binding binding = new Binding("Value") {
Source = this.Slider1 };
this.textbox1.SetBinding(TextBox.TextProperty, binding);
- 集合中的索引器作为Path
<Window
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" x:Class="WpfApplication1_8_18.MainWindow"
Title="Control as Source" Height="110" Width="300" >
<StackPanel>
<TextBox x:Name="textbox1" BorderBrush="Black" Margin="5"/>
<TextBox x:Name="textbox2" Text="{Binding Path=Text.[3], ElementName=textbox1, Mode=OneWay}" BorderBrush="Black" Margin="5"/>
<!--上面的语句中Mode=OneWay必须填写,否则会报错-->
</StackPanel>
</Window>
//等价的C#语句,不同之处:此处用Source 、BindingMode
this.textbox2.SetBinding(TextBox.TextProperty, new Binding("Text.[3]") {
Source = this.textbox1, Mode = BindingMode.OneWay });
- 集合或者DataView的默认元素作为Path
<Window
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" x:Class="WpfApplication1_8_18.MainWindow"
Title="Control as Source" Height="120" Width="300" >
<StackPanel>
<TextBox x:Name="textbox1" BorderBrush="Black" Margin="5"/>
<TextBox x:Name="textbox2" BorderBrush="Black" Margin="5"/>
<TextBox x:Name="textbox3" BorderBrush="Black" Margin="5"/>
</StackPanel>
</Window>
//string_list 的默认元素为[0]=Tim
List<string> string_list = new List<string>() {
"Tim", "Tom", "Tic" };
this.textbox1.SetBinding(TextBox.TextProperty, new Binding("/") {
Source = string_list });
this.textbox2.SetBinding(TextBox.TextProperty, new Binding("/Length") {
Source = string_list, Mode = BindingMode.OneWay });
this.textbox3.SetBinding(TextBox.TextProperty, new Binding("/[2]") {
Source = string_list, Mode = BindingMode.OneWay });
- 子级集合中元素作为Path——使用多级/
<Window
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" x:Class="WpfApplication1_8_18.MainWindow"
Title="Control as Source" Height="120" Width="300" >
<StackPanel>
<TextBox x:Name="textbox1" BorderBrush="Black" Margin="5"/>
<TextBox x:Name="textbox2" BorderBrush="Black" Margin="5"/>
<TextBox x:Name="textbox3" BorderBrush="Black" Margin="5"/>
</StackPanel>
</Window>
class City
{
public string Name {
get; set; }
}
class Province
{
public string Name {
get; set; }
public List<City> CityList {
get; set; }
}
class Country
{
public string Name {
get; set; }
public List<Province> ProvinceList {
get; set; }
}
namespace WpfApplication1_8_18
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//Binding
List<Country> countryList = new List<Country>
{
//初始化嵌套类
new Country(){
Name="China",ProvinceList=new List<Province>(){
new Province(){
Name="HeBei", CityList=new List<City>(){
new City(){
Name="BeiJing" },new City(){
Name="ShiJiaZhuang"} }}}},
new Country(){
},//多个Country类之间的书写方式,里面内容省略..
new Country(){
}
};
this.textbox1.SetBinding(TextBox.TextProperty, new Binding("/Name") {
Source = countryList });
this.textbox2.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/Name") {
Source = countryList });
//应为"/ProvinceList[0].Name",教材中给出的"/ProvinceList.Name",但是运行后并无结果输出
this.textbox3.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/CityList/Name") {
Source = countryList });
//应为:"/ProvinceList/CityList[1].Name",教材中给出的"/ProvinceList/CityList.Name",但是运行后并无结果输出
}
}
}
程序运行结果如下:
此处默认显示“BeiJing”至于怎么显示“ShiJiaZhuang”??
//将显示命令由"/ProvinceList/CityList/Name"修改为"/ProvinceList/CityList[1].Name"即可
this.textbox3.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/CityList[1].Name") {
Source = countryList });
其中,嵌套类的初始化方法
//对一个class的正确赋值方法
List<City> city_1 = new List<City>() {
new City() {
Name = "city_a" }, new City {
Name = "city_b" } };
//country的赋值方法
List<Country> countryList = new List<Country> ()
{
new Country(){
Name="China",ProvinceList=new List<Province>(){
new Province(){
Name="HeBei", CityList=new List<City>(){
new City(){
Name="BeiJing" },new City(){
Name="ShiJiaZhuang"} }}}},
new Country(){
},//需要初始化多个Country的示例
new Country(){
}
};
没有Path的Binding
Binding源本身就是数据且不需要path来指明,如string 、int。
这里书本中写的不明确,导致第一次没有出来效果。这里的sys是一个映射名,在Windows开始标签内定义。
<Window x:Class="WpfApplication1_8_26_1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="200" Width="300">
<StackPanel>
<StackPanel.Resources>
<sys:String x:Key="myString">
菩提本无树,明镜亦非台。
本来无一物,何处惹尘埃。
</sys:String>
</StackPanel.Resources>
<TextBlock x:Name="textBlock1" TextWrapping="Wrap" Text="{Binding Path=.,Source={StaticResource ResourceKey=myString}}" FontSize="16" Margin="5"/>
</StackPanel>
</Window>
上面的代码也可以简写成:
<TextBlock x:Name="textBlock1" Text="{Binding.,Source={StaticResource ResourceKey=myString}}"/>
//或者
<TextBlock x:Name="