参考书籍《深入浅出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="textBlock2" Text="{Binding Source={StaticResource ResourceKey=myString}}"/>
Binding的Source
1、没有source的Binding
<Window x:Class="WpfApplication1_8_21.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1_8_21"
Title="MainWindow" Height="120" Width="300">
<StackPanel Background="LightBlue">
<StackPanel.DataContext>
<local:Student ID="6" Age="29" Name="Time"/>
</StackPanel.DataContext>
<Grid>
<StackPanel>
<TextBox Text="{Binding Path=ID}" Margin="5"/>
<TextBox Text="{Binding Path=Name}" Margin="5"/>
<TextBox Text="{Binding Path=Age}" Margin="5"/>
</StackPanel>
</Grid>
</StackPanel>
</Window>
namespace WpfApplication1_8_21
{
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}

xmlns:local=“clr-namespace:WpfApplication1_8_21"可以在XML中使用C#中定义的Student类。
当Path的值为”."时,也可以省略不写:DataContext是一个依赖属性,当没有给控件某个属性显示赋值时,控件会把自己容器的属性值借过来作为自己的属性值。
<Window x:Class="WpfApplication1_8_21.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"
FontSize="16" FontWeight="Bold"
Title="MainWindow" Height="150" Width="300">
<StackPanel Background="LightBlue">
<StackPanel.DataContext>
<sys:String>Hello DataContext!</sys:String>
</StackPanel.DataContext>
<Grid>
<StackPanel>
<TextBlock Text="{Binding}" Margin="5"/><!--TextBox则该写法会报错-->
<TextBlock Text="{Binding}" Margin="5"/>
<TextBlock Text="{Binding}" Margin="5"/>
</StackPanel>
</Grid>
</StackPanel>
</Window>

有关DataContext的基础知识:
若干Grid嵌套,最里层放置一个Button控件,为最外层Grid设置了DataContext属性值。因为内层Grid没有设置DataContext属性值,所以外层Grid的DataContext属性值会一直传递下去。也即:Butn1.DataContext=Grid DataContext=“6”
<Window x:Class="WpfApplication1_8_21.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FontSize="16" FontWeight="Bold"
Title="MainWindow" Height="150" Width="300">
<Grid DataContext="6">
<Grid>
<Grid>
<Button x:Name="Butn1" Content="OK" Click="Butn1_click"/>
</Grid>
</Grid>
</Grid>
</Window>
private void Butn1_click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Butn1.DataContext.ToString());
}

所以,当UI上的多个控件都通过Binding关注同一个对象时,不妨使用DataContext。(字太多,贴图吧)

使用集合对象作为列表控件的ItemsSource

- 第一种方法
这里书中写的没有实现:
//为textBox设置Binding
Binding binding = new Binding("SelectedItem.Id") { Source = this.listBoxStu };//书中的示例,运行后没有结果显示,显示栏为空
Binding binding = new Binding("SelectedIndex") { Source = this.listBoxStu };//自己试过了listBox以Select开头的命令后,符合要求的



<Window x:Class="WpfApplication1_8_21.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="300">
<StackPanel x:Name="StackP1" Background="LightBlue">
<TextBlock Text="StudentID:" FontWeight="Bold" Margin="5"/>
<TextBox x:Name="textBoxID" Margin="5"/>
<TextBlock Text="StudentList:" FontWeight="Bold" Margin="5"/>
<ListBox x:Name="listBoxStu" Height="110" Margin="5"/>
</StackPanel>
</Window>
namespace WpfApplication1_8_21
{
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//准备数据源
List<Student> stulist = new List<Student>()
{
new Student(){ID=0,Name="Tim",Age=29},
new Student(){ID=1,Name="Tom",Age=28},
new Student(){ID=2,Name="Tic", Age=27},
new Student(){ID=3,Name="Tinker",Age=26},
new Student(){ID=4,Name="Tam",Age=25},
new Student(){ID=5,Name="Titan",Age=24}
};
//为listBox设置Binding:source、path
this.listBoxStu.ItemsSource = stulist;
this.listBoxStu.DisplayMemberPath = "Name";
//为textBox设置Binding
Binding binding = new Binding("SelectedIndex") { Source = this.listBoxStu };
this.textBoxID.SetBinding(TextBox.TextProperty, binding);
//这段程序只在初始化时被执行一次,应该放在listbox的click事件中
//if(this.listBoxStu.SelectedItem!=null)
//{
// string a = this.listBoxStu.SelectedItem.ToString();
//}
}
- 第二种方法
删除上面的this.listBoxStu.DisplayMemberPath = “Name”,在XAML中通过代码实现。ListBox的ItemPlate属性的类型是DataTemplate
<Window x:Class="WpfApplication1_8_21.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="300">
<StackPanel x:Name="StackP1" Background="LightBlue">
<TextBlock Text="StudentID:" FontWeight="Bold" Margin="5"/>
<TextBox x:Name="textBoxID" Margin="5"/>
<TextBlock Text="StudentList:" FontWeight="Bold" Margin="5"/>
<ListBox x:Name="listBoxStu" Height="110" Margin="5">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=ID}" Width="30"/>
<TextBlock Text="{Binding Path=Name}" Width="60"/>
<TextBlock Text="{Binding Path=Age}" Width="30"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
namespace WpfApplication1_8_21
{
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//准备数据源
List<Student> stulist = new List<Student>()
{
new Student(){ID=0,Name="Tim",Age=29},
new Student(){ID=1,Name="Tom",Age=28},
new Student(){ID=2,Name="Tic", Age=27},
new Student(){ID=3,Name="Tinker",Age=26},
new Student(){ID=4,Name="Tam",Age=25},
new Student(){ID=5,Name="Titan",Age=24}
};
//为listBox设置Binding:source、path
this.listBoxStu.ItemsSource = stulist;
//this.listBoxStu.DisplayMemberPath = "Name";//该条语句在XAML中实现
//为textBox设置Binding
Binding binding = new Binding("SelectedIndex") { Source = this.listBoxStu };
this.textBoxID.SetBinding(TextBox.TextProperty, binding);
}


使用ADO.NET作为Binding的源

DataTable的DefaultView可以被赋值给ListBoxItemsSource
一般选择ListView控件显示DataTable
<Window x:Class="WpfApplication1_diushi.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="200" Width="300">
<StackPanel Background="LightBlue">
<ListBox x:Name="listBoxStudents" Height="130" Margin="5"/>
</StackPanel>
</Window>
using System.Data;//DataTable的头文件
namespace WpfApplication1_diushi
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataTable dt = new DataTable(); //DataTable dt = this.Load();的用法见后面说明
DataColumn id = new DataColumn("ID");
DataColumn name = new DataColumn("Name");
DataColumn age = new DataColumn("Age");
dt.Columns.Add(id);
dt.Columns.Add(name);
dt.Columns.Add(age);
DataRow row1 = dt.NewRow();//1
row1["ID"] = "1";
row1["Name"] = "Tim";
row1["Age"] = "29";
dt.Rows.Add(row1);
DataRow row2 = dt.NewRow();//2
row2["ID"] = "2";
row2["Name"] = "Tom";
row2["Age"] = "28";
dt.Rows.Add(row2);
//// dt.Rows.Add("2", "Tom", "28");
DataRow row3 = dt.NewRow();//3
row3["ID"] = "3";
row3["Name"] = "Tony";
row3["Age"] = "27";
dt.Rows.Add(row3);
DataRow row4 = dt.NewRow();
row4["ID"] = "4";
row4["Name"] = "Kyle";
row4["Age"] = "26";
dt.Rows.Add(row4);
DataRow row5 = dt.NewRow();
row5["ID"] = "5";
row5["Name"] = "Vina";
row5["Age"] = "25";
dt.Rows.Add(row5);
DataRow row6 = dt.NewRow();
row6["ID"] = "6";
row6["Name"] = "Emily";
row6["Age"] = "24";
dt.Rows.Add(row6);
this.listBoxStudents.DisplayMemberPath = "Name";
this.listBoxStudents.ItemsSource = dt.DefaultView;
//DataTable 的 DefaultView属性是一个DataView类型对象
}
}
}

如果改为ListView控件显示一个DataTable
<Window x:Class="WpfApplication1_diushi.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="206" Width="250">
<StackPanel Background="LightBlue">
<ListView x:Name="listViewStudents" Height="130" Margin="5">
<ListView.View>
<GridView>
<GridViewColumn Header="ID" Width="60" DisplayMemberBinding="{Binding ID}"/>
<GridViewColumn Header="Name" Width="80" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Header="Age" Width="60" DisplayMemberBinding="{Binding Age}"/>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</Window>
this.listViewStudents.ItemsSource = dt.DefaultView;
//或者
this.listViewStudents.DataContext = dt;
this.listViewStudents.SetBinding(ListView.ItemsSourceProperty, new Binding());
运行结果:

注意:原书中使用了一句DataTable dt=this.Load(),在开始的时候一直以为是引用了一个未声明函数,后来才意识到这是一个自定义函数,里面包含了DataTable 的数据。
using System.Data;
namespace WpfApplication1_8_26_1
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button_click(object sender, RoutedEventArgs e)
{
DataTable dt = this.Load();
}
private DataTable Load()
{
DataTable dt = new DataTable();
DataColumn id = new DataColumn("ID");
DataColumn name = new DataColumn("Name");
DataColumn age = new DataColumn("Age");
dt.Columns.Add(id);
dt.Columns.Add(name);
dt.Columns.Add(age);
DataRow row1 = dt.NewRow();//1
row1["ID"] = "1";
row1["Name"] = "Tim";
row1["Age"] = "29";
dt.Rows.Add(row1);
DataRow row2 = dt.NewRow();//2
row2["ID"] = "2";
row2["Name"] = "Tom";
row2["Age"] = "28";
dt.Rows.Add(row2);
//// dt.Rows.Add("2", "Tom", "28");
DataRow row3 = dt.NewRow();//3
row3["ID"] = "3";
row3["Name"] = "Tony";
row3["Age"] = "27";
dt.Rows.Add(row3);
DataRow row4 = dt.NewRow();
row4["ID"] = "4";
row4["Name"] = "Kyle";
row4["Age"] = "26";
dt.Rows.Add(row4);
DataRow row5 = dt.NewRow();
row5["ID"] = "5";
row5["Name"] = "Vina";
row5["Age"] = "25";
dt.Rows.Add(row5);
DataRow row6 = dt.NewRow();
row6["ID"] = "6";
row6["Name"] = "Emily";
row6["Age"] = "24";
dt.Rows.Add(row6);
return dt;
}
}
}
使用XML作为Binding的源
基于DOM(Document Object Model文档对象模型)

建立xml文件的方法:

注意:记事本文档中<xml…定义一行上面以及前面不可以有空格或者空行,否则转换完是一片空白。
<Window x:Class="WpfApplication1_8_21.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="300">
<StackPanel Background="LightBlue">
<ListView x:Name="listViewStu" 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="But_clic" Height="25" Margin="5"/>
</StackPanel>
</Window>
using System.IO;//xml,否则找不到XmlDocument 类型
using System.Xml; //xml
namespace WpfApplication1_8_21
{
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//准备数据源
List<Student> stulist = new List<Student>()
{
new Student(){ID=0,Name="Tim",Age=29},
new Student(){ID=1,Name="Tom",Age=28},
new Student(){ID=2,Name="Tic", Age=27},
new Student(){ID=3,Name="Tinker",Age=26},
new Student(){ID=4,Name="Tam",Age=25},
new Student(){ID=5,Name="Titan",Age=24}
};
}
private void But_clic(object sender, RoutedEventArgs e)
{
XmlDocument doc = new XmlDocument();
doc.Load(@"D:\RawData.xml");
XmlDataProvider xdp = new XmlDataProvider();
xdp.Document = doc;
xdp.XPath = @"/StudentList/Student";
this.listViewStu.DataContext = xdp;
this.listViewStu.SetBinding(ListView.ItemsSourceProperty, new Binding());
}
}
}

ps:XmlDataProvider 还有一个source的属性可以用来指定XML文档位置。故Click事件写可以:
private void But_clic(object sender, RoutedEventArgs e)
{
// XmlDocument doc = new XmlDocument();
// doc.Load(@"D:\RawData.xml");
XmlDataProvider xdp = new XmlDataProvider();
// xdp.Document = doc;
xdp.Source = new Uri(@"D:\RawData.xml");
xdp.XPath = @"/StudentList/Student";
this.listViewStu.DataContext = xdp;
this.listViewStu.SetBinding(ListView.ItemsSourceProperty, new Binding());
}
使用@符号加字符串表示的是XML元素的Attribute,不加@符号的字符串表示的是子级元素。
使用TreeView显示若干层目录的文件系统

<Window x:Class="WpfApplication1_8_21.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="260" Width="300">
<Window.Resources>
<XmlDataProvider x:Key="xdp" XPath="FileSystem/Folder">
<x:XData>
<FileSystem xmlns="">
<Folder Name="Books">
<Folder Name="Programming">
<Folder Name="Windows">
<Folder Name="WPF"/>
<Folder Name="MFC"/>
<Folder Name="Delphi"/>
</Folder>
</Folder>
<Folder Name="Tools">
<Folder Name="Development"/>
<Folder Name="Designment"/>
<Folder Name="Players"/>
</Folder>
</Folder>
</FileSystem>
</x:XData>
</XmlDataProvider>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding Source={StaticResource xdp}}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding XPath=Folder}">
<TextBlock Text="{Binding XPath=@Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Window>
如果直接把XmlDataProvider 写在XAML代码中,则需要使用<x:XData>…</x:XData>
使用LINQ检索结果作为Binding的源
LINQ的查询结果是一个IEnumerable类型对象,它可以作为列表控件ItemsSource来使用。
从一个填充好的list对象中检索出所有以字母T开头的名字
<Window x:Class="WpfApplication1_8_21.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="260" Width="300">
<StackPanel Background="LightBlue">
<ListView x:Name="listViewStudents" 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="Age" Width="80" DisplayMemberBinding="{Binding Age}"/>
</GridView>
</ListView.View>
</ListView>
<Button Content="Load" Height="25" Margin="5" Click="Button_Click"/>
</StackPanel>
</Window>
namespace WpfApplication1_8_21
{
public class Student
{
public int id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
List<Student> stulist = new List<Student>()
{
new Student(){id=0,Name="Tim",Age=29},
new Student(){id=1,Name="Tom",Age=28},
new Student(){id=2,Name="Mic", Age=27},
new Student(){id=3,Name="Minker",Age=26},
new Student(){id=4,Name="Mam",Age=25},
new Student(){id=5,Name="Mitan",Age=24}
};
this.listViewStudents.ItemsSource = from stu in stulist where stu.Name.StartsWith("T") select stu;
}
运行结果:

2、如果数据是存放在DataTable中
如果数据存放在已经填充好的DataTable中,首先引用头文件using System.Data;//DataTable需要该头文件
<Window x:Class="WpfApplication1_8_26.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="260" Width="300">
<StackPanel Background="LightBlue">
<ListView x:Name="listViewStudents" 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="Age" Width="80" DisplayMemberBinding="{Binding Age}"/>
</GridView>
</ListView.View>
</ListView>
<Button Content="Load" Height="25" Margin="5" Click="Button_Click"/>
</StackPanel>
</Window>
using System.Data;//DataTable需要该头文件
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_26
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public class Student
{
public int id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
DataTable dt = this.ret_dt();
this.listViewStudents.ItemsSource = from row in dt.Rows.Cast<DataRow>()
where Convert.ToString(row["Name"]).StartsWith("T")
select new Student()
{
id=int.Parse(row["Id"].ToString()),Name=row["Name"].ToString(),Age=int.Parse(row["Age"].ToString())
};
}
private DataTable ret_dt()
{
DataTable dt = new DataTable(); //DataTable dt = this.Load();
DataColumn id = new DataColumn("ID");
DataColumn name = new DataColumn("Name");
DataColumn age = new DataColumn("Age");
dt.Columns.Add(id);
dt.Columns.Add(name);
dt.Columns.Add(age);
DataRow row1 = dt.NewRow();//1
row1["ID"] = "1";
row1["Name"] = "Tim";
row1["Age"] = "29";
dt.Rows.Add(row1);
DataRow row2 = dt.NewRow();//2
row2["ID"] = "2";
row2["Name"] = "Tom";
row2["Age"] = "28";
dt.Rows.Add(row2);
//// dt.Rows.Add("2", "Tom", "28");
DataRow row3 = dt.NewRow();//3
row3["ID"] = "3";
row3["Name"] = "Tony";
row3["Age"] = "27";
dt.Rows.Add(row3);
DataRow row4 = dt.NewRow();
row4["ID"] = "4";
row4["Name"] = "Kyle";
row4["Age"] = "26";
dt.Rows.Add(row4);
DataRow row5 = dt.NewRow();
row5["ID"] = "5";
row5["Name"] = "Vina";
row5["Age"] = "25";
dt.Rows.Add(row5);
DataRow row6 = dt.NewRow();
row6["ID"] = "6";
row6["Name"] = "Emily";
row6["Age"] = "24";
dt.Rows.Add(row6);
return dt;
}
}
}

3、如果数据是存放在XML中
如果数据是写在XAML中:
数据存储在XML文件D:\RawData.xml中,如下图所示:

<Window x:Class="WpfApplication1_8_26.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="260" Width="300">
<StackPanel Background="LightBlue">
<ListView x:Name="listViewStudents" 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="Age" Width="80" DisplayMemberBinding="{Binding Age}"/>
</GridView>
</ListView.View>
</ListView>
<Button Content="Load" Height="25" Margin="5" Click="Button_Click"/>
</StackPanel>
</Window>
using System.Data;//DataTable需要该头文件
using System.Xml.Linq;//XDocument需要头文件
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_26
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public class Student
{
public int id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
XDocument xdoc = XDocument.Load(@"D:\RawData.xml");//该语句可以跨越XAML层级
this.listViewStudents.ItemsSource = from row in dt.Rows.Cast<DataRow>()
where Convert.ToString(row["Name"]).StartsWith("T")
select new Student()
{
id = int.Parse(row["Id"].ToString()),
Name = row["Name"].ToString(),
Age = int.Parse(row["Age"].ToString())
};
}
}
}

使用ObjectDataProvider对象作为Binding的Source
ObjectDataProvider把对象作为数据源提供给Binding。
<Window x:Class="WpfApplication1_8_21.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="260" Width="300">
<StackPanel Background="LightBlue">
<Button Content="Load" Height="25" Margin="5" Click="Button_Click"/>
</StackPanel>
</Window>
namespace WpfApplication1_8_21
{
// Calculator类具有加、减、乘、除的方法
class Calculator
{
public string Add(string arg1,string arg2)//加法
{
double x = 0;
double y = 0;
double z = 0;
if(double.TryParse(arg1,out x)&& double.TryParse(arg2,out y))
{
z = x + y;
return z.ToString();
}
return "Input Error!";
}
}
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
ObjectDataProvider odp = new ObjectDataProvider();
odp.ObjectInstance = new Calculator();
odp.MethodName = "Add";
odp.MethodParameters.Add("100");
odp.MethodParameters.Add("200");
MessageBox.Show(odp.Data.ToString());//返回结果为300
}

上面2个textBox输入数字后,下面的textBox实时显示结果,把代码写在一个SetBinding的方法中,在窗体的构造器里调用该方法:
<Window x:Class="WpfApplication1_8_21.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="260" Width="300">
<StackPanel Background="LightBlue">
<TextBox x:Name="textBoxArg1" Margin="5"/>
<TextBox x:Name="textBoxArg2" Margin="5"/>
<TextBox x:Name="textBoxResult" Margin="5"/>
</StackPanel>
</Window>
namespace WpfApplication1_8_21
{
//-----------------------------------------p112
// Calculator类具有加、减、乘、除的方法
class Calculator
{
public string Add(string arg1,string arg2)//加法
{
double x = 0;
double y = 0;
double z = 0;
if(double.TryParse(arg1,out x)&& double.TryParse(arg2,out y))
{
z = x + y;
return z.ToString();
}
return "Input Error!";
}
}
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.SetBinding();
}
private void SetBinding()
{
//创建并配置ObjectDataProvider对象
ObjectDataProvider odp = new ObjectDataProvider();
odp.ObjectInstance=new Calculator();
odp.MethodName="Add";
odp.MethodParameters.Add("0");//MethodParameters属性是类型敏感的
odp.MethodParameters.Add("0");
//以ObjectDataProvider对象为Source创建Binding
Binding bindingToArg1=new Binding("MethodParameters[0]")
{
Source=odp,
BindsDirectlyToSource=true,
UpdateSourceTrigger=UpdateSourceTrigger.PropertyChanged
};
Binding bindingToArg2=new Binding("MethodParameters[1]")
{
Source=odp,
BindsDirectlyToSource = true,//告诉Binding对象,从UI元素收集到的数据写入其直接Source(ObjectDataProvider对象),而不是ObjectDataProvider包装着的Calculator
UpdateSourceTrigger=UpdateSourceTrigger.PropertyChanged
};
Binding bindingToResult=new Binding("."){Source=odp};//数据源本身就代表数据时,用.表示PATH,且.在XAML中可以省略不写
//将Binding关联到UI元素上
this.textBoxArg1.SetBinding(TextBox.TextProperty,bindingToArg1);
this.textBoxArg2.SetBinding(TextBox.TextProperty,bindingToArg2);
this.textBoxResult.SetBinding(TextBox.TextProperty,bindingToResult);
}
}
}

使用Binding的RelativeSource
有时候不能明确作为Source的对象叫什么名字,但知道它与Binding目标对象在UI布局上有相对关系。
多层布局中放置一个textBox
<Window x:Class="WpfApplication1_8_24.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="210" Width="210">
<Grid x:Name="g1" Background="Red" Margin="10">
<DockPanel x:Name="d1" Background="Orange" Margin="10">
<Grid x:Name="g2" Background="Yellow" Margin="10">
<DockPanel x:Name="d2" Background="LawnGreen" Margin="10">
<TextBox x:Name="textBox1" FontSize="24" Margin="10"/>
</DockPanel>
</Grid>
</DockPanel>
</Grid>
</Window>
把textBox的text属性关联到外层容器的Name属性上:
namespace WpfApplication1_8_24
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// 告诉Binding从自己的第一层依次向外寻找,找到第一个Grid类型对象后把它当作自己的源
RelativeSource rs = new RelativeSource(RelativeSourceMode.FindAncestor);
rs.AncestorLevel = 1; //以Binding目标控件为起点的层级偏移量——d2偏移量1、g2偏移量2
rs.AncestorType = typeof(Grid); //告诉Binding寻找哪个类型的对象作为自己的源
Binding binding = new Binding("Name") { RelativeSource = rs };
this.textBox1.SetBinding(TextBox.TextProperty, binding);
}
}
}
或在XAML中插入等价代码:
<TextBox x:Name="textBox1" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid},AncestorLevel=1},Path=Name}"/>
运行效果如下所示:

如果按照如下方式修改代码:
<Window x:Class="WpfApplication1_8_24.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="210" Width="210">
<Grid x:Name="g1" Background="Red" Margin="10">
<DockPanel x:Name="d1" Background="Orange" Margin="10">
<Grid x:Name="g2" Background="Yellow" Margin="10">
<DockPanel x:Name="d2" Background="LawnGreen" Margin="10">
<TextBox x:Name="textBox1" Margin="10"/>
</DockPanel>
</Grid>
</DockPanel>
</Grid>
</Window>
namespace WpfApplication1_8_24
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
RelativeSource rs = new RelativeSource();
rs.AncestorLevel = 2;
rs.AncestorType = typeof(DockPanel);
Binding binding = new Binding("Name") { RelativeSource = rs };
this.textBox1.SetBinding(TextBox.TextProperty, binding);
}
}
}
或者在XAML中插入等效代码:
<TextBox x:Name="textBox1" Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type DockPanel},AncestorLevel=2},Path=Name}}"/>
运行效果如下:

如果Binding需要关联自身的Name属性:
<Window x:Class="WpfApplication1_8_24.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="210" Width="210">
<Grid x:Name="g1" Background="Red" Margin="10">
<DockPanel x:Name="d1" Background="Orange" Margin="10">
<Grid x:Name="g2" Background="Yellow" Margin="10">
<DockPanel x:Name="d2" Background="LawnGreen" Margin="10">
<TextBox x:Name="textBox1" Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type DockPanel},AncestorLevel=2},Path=Name}"/>
</DockPanel>
</Grid>
</DockPanel>
</Grid>
</Window>
namespace WpfApplication1_8_24
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//如果textBox需要关联自身的name属性
RelativeSource rs = new RelativeSource();
rs.Mode = RelativeSourceMode.Self;//其它取值:PreviousData、TemplatedParent、FindAncestor
Binding binding = new Binding("Name") { RelativeSource = rs };
this.textBox1.SetBinding(TextBox.TextProperty, binding);
}
}
}
图片显示如下:

Binding对数据的转换与校验
ValidationRules属性用于数据有效性的校验,Converter属性用于数据类型转换。
1、Binding对数据的校验
校验textBox中输入数值是否是0~100
<Window x:Class="WpfApplication1_8_24.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="210" Width="210">
<StackPanel>
<TextBox x:Name="textBox1" Margin="5"/>
<Slider x:Name="slider1" Minimum="0" Maximum="100" Margin="5"/>
</StackPanel>
</Window>
public class RangeValidationRule:ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
//需要实现Validate方法
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");
}
}
namespace WpfApplication1_8_24
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Binding binding = new Binding("Value") { Source = this.slider1 };
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
RangeValidationRule rvr = new RangeValidationRule();
binding.ValidationRules.Add(rvr);
this.textBox1.SetBinding(TextBox.TextProperty, binding);
}
}
}
程序运行效果图:


当输入>100的数值时,textBox会显示红色边框。
注意,此处Binding 只在textBox控件(需要外部输入的UI目标)更新数据时检验,而Binding 的Source(slider)更新数据时,不会进行检验。
如果需要开启Source的检验,将ValidateOnTargetUpdated设为True
<Window x:Class="WpfApplication1_8_24.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="210" Width="210">
<StackPanel>
<TextBox x:Name="textBox1" Margin="5"/>
<Slider x:Name="slider1" Minimum="-10" Maximum="110" Margin="5"/><!--(0,100)-->
</StackPanel>
</Window>
public class RangeValidationRule:ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
//需要实现Validate方法
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");
}
}
namespace WpfApplication1_8_24
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Binding binding = new Binding("Value") { Source = this.slider1 };
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
RangeValidationRule rvr = new RangeValidationRule();
rvr.ValidatesOnTargetUpdated = true;//当slider移出有效范围时,TextBox也会显示校验失败
binding.ValidationRules.Add(rvr);
this.textBox1.SetBinding(TextBox.TextProperty, binding);
}
}
}
运行效果:



当需要显示校验错误时Validate方法返回的ValidationResult对象携带的错误信息时,需要用到路由事件:

<Window x:Class="WpfApplication1_8_24.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="210" Width="210">
<StackPanel>
<TextBox x:Name="textBox1" Margin="5"/>
<Slider x:Name="slider1" Minimum="-10" Maximum="110" Margin="5"/><!--(0,100)-->
</StackPanel>
</Window>
public class RangeValidationRule:ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
//需要实现Validate方法
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");
}
}
namespace WpfApplication1_8_24
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Binding binding = new Binding("Value") { Source = this.slider1 };
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
RangeValidationRule rvr = new RangeValidationRule();
rvr.ValidatesOnTargetUpdated = true;//当slider移出有效范围时,TextBox也会显示校验失败
binding.ValidationRules.Add(rvr);
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();
}
}
}
}
如果校验失败,则TextBox的ToolTip就会提示用户,效果如下:

此处运行结果没有提示消息。。。。。。。p123
Binding 的数据转换
- Source里的数据是Y、N、X三个值,UI上对应的是CheckBox控件,需要把这三个值映射为它的IsChecked属性值。
- 当TextBox里已经输入了文字时用于登陆的Button才会出现,这是string类型与Visibility枚举类型或bool类型之间的转换。
- Source里的数据可能是Male或者Female,UI上对应的是用于显示头像的Image控件,这时需要将Source里的值转换成对应的头像图片URI(oneway)
IValueConverter为单值转换的接口,IMultiValueConverter为多值转换的接口。它们都属于System.Windows.Data命名空间
此时需要自己动手写Converter,方法是创建一个类并让这个类实现IValueConverter接口。

- Convert实现后台值向UI显示值之间的转换(原数据转换为目标数据格式)
- ConvertBack实现UI值向后台之间的转换(目标数据格式转换为原数据格式)
示例:飞机的State属性在UI里被映射为CheckBox,这里需要2个Converter:
- Category类型单项转换为String类型
- State与Bool类型之间双向转换
<Window x:Class="WpfApplication1_8_24.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1_8_24"
Title="MainWindow" Height="320" Width="300">
<Window.Resources>
<local:CategoryToSourceConverter x:Key="cts"/>
<!--声明转换器类名称空间 并定义一个转换器资源-->
<local:StateToNullableBoolConverter x:Key="stnb"/>
</Window.Resources>
<StackPanel Background="LightBlue">
<ListBox x:Name="listBoxPlane" Height="200" Margin="5">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Width="20" Height="20" Source="{Binding Path=Category,Converter={StaticResource cts}}"/>
<TextBlock Text="{Binding Path=Name}" Width="60" Margin="80,0"/>
<CheckBox IsThreeState="True" IsChecked="{Binding Path=State, Converter={StaticResource stnb}}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button x:Name="buttonLoad" Content="Load" Height="25" Margin="5" Click="buttonLoad_click"/>
<Button x:Name="buttonSave" Content="Save" Height="25" Margin="5" Click="buttonSave_click"/>
</StackPanel>
</Window>
using System.Globalization;//否则CultureInfo类别无法识别
using System.IO;//File相关命令
//种类
public enum Category
{
Bomber,
Fighter
}
//状态
public enum State
{
Available,
Locked,
Unknown
}
//飞机
public class Plane
{
public Category Category{get;set;}
public string Name{get;set;}
public State State{get;set;}
}
namespace WpfApplication1_8_24
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public class CategoryToSourceConverter : IValueConverter
{
//将Category转换为Uri
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)//当culture参数(区域性信息)与转换无关,在自定义转换器中可以忽略该参数。
{
Category c = (Category)value;
switch(c)
{
case Category.Bomber:
//return @"\Icons\Bomber.png";
return @"E:\HK\WpfApplication1_8-24\WpfApplication1_8-24\Icons\Bomber.png";//此处采用绝对地址
case Category.Fighter:
//return @"\Icons\Fighter.png";
return @"E:\HK\WpfApplication1_8-24\WpfApplication1_8-24\Icons\Fighter.png";
default:
return null;
}
}
//不会被调用
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class StateToNullableBoolConverter : IValueConverter
{
//将State转换为Bool?
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
State s = (State)value;
switch (s)
{
case State.Locked:
return false;
case State.Available:
return true;
case State.Unknown:
default:
return null;
}
}
//将bool?转换为state
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
bool? nb = (bool?)value;
switch (nb)
{
case true:
return State.Available;
case false:
return State.Locked;
case null:
default:
return State.Unknown;
}
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
//把一组飞机数据赋值给ListBox的ItemsSource属性
private void buttonLoad_click(object sender, RoutedEventArgs e)
{
List<Plane> planeList = new List<Plane>()
{
new Plane(){Category=Category.Bomber,Name="B-1",State=State.Unknown},
new Plane(){Category=Category.Bomber,Name="B-2",State=State.Unknown},
new Plane(){Category=Category.Fighter,Name="F-22",State=State.Unknown},
new Plane(){Category=Category.Fighter,Name="Su-47",State=State.Unknown},
new Plane(){Category=Category.Bomber,Name="B-52",State=State.Unknown},
new Plane(){Category=Category.Fighter,Name="J-10",State=State.Unknown}
};
this.listBoxPlane.ItemsSource = planeList;
}
//用户更改过的数据写入文件
private void buttonSave_click(object sender, RoutedEventArgs e)
{
StringBuilder sb = new StringBuilder();
foreach(Plane p in listBoxPlane.Items)
{
sb.AppendLine(string.Format("Category={0},Name={1},State={2}", p.Category, p.Name, p.State));//sb:Category=Bomber,Name=B-1,State=Locked
}
File.WriteAllText(@"D:\PlaneList.txt", sb.ToString()); //需要根据需要修改该地址,不需要事先创建.txt。
}
}
}
程序运行结果如下:

运行程序并单击CheckBox更改飞机的状态,单击Save按钮后,打开文件D:\PlaneList.txt,里面已经填写了所有显示的内容:
程序运行流程如下:

程序运行中的其它注意事项
1、WPF中图标的添加方法:
- 引用图标文件的绝对地址
case Category.Bomber:
return @"E:\HK\WpfApplication1_8-24\WpfApplication1_8-24\Icons\Bomber.png";//此处采用绝对地址
或者
<Image Source="E:\HK\WpfApplication1_8-24\WpfApplication1_8-24\Icons\Bomber.png"/>
- 采用相对地址,在工程文件夹中建立Icons文件如下:

参考博客WPF APP项目添加资源文件夹
case Category.Bomber:
return @"\Icons\Bomber.png";

如何添加上图中
在UI里,Plane的Category属性被影射为图标,该图标已经放入Icons文件夹。
我用Everything搜索.png,找到当前电脑上的许多图标文件。
2、CultureInfo头文件添加:
文档中未说明CultureInfo需要头文件:c#中的CultureInfo类,该博客中提到CultureInfo类位于System.Globalization命名空间内,这里需要注意添加该头文件。
3、IValueConverter接口继承类的书写位置:
因为此处在XAML中使用了local:CategoryToSourceConverter的表达方式,所以需要将继承的类写在namespace 空间中,否则会报错:


程序集被映射为名称空间local,以资源的形式创建2个Converter实例。在ListBox控件中添加用于显示数据的DataTemplate。
在TextBlock 中,Margin书写形式对于结果的影响:前期运行中,一直出现下面的图。
<TextBlock Text="{Binding Path=Name}" Width="60" Margin="80"/>

后来才发现下列语句中的Margin=“80,0”。我以前一直写作“80”。更多Margin 的内容参考:Margin 在WPF中的用法
<TextBlock Text="{Binding Path=Name}" Width="60" Margin="80,0"/>



MutltiBinding——多路Binding
UI需要的信息不止由一个数据源决定,凡是能用Binding的场合都可以用MutltiBinding

将MutltiBinding代码写在SetMultiBing方法中,并在窗体的构造器中调用。
注意:MutltiBinding对于添加子级Binding的顺序是敏感的,这个顺序决定了汇聚到Converter中数据的顺序。
MutltiBinding的Converter实现的是IMultiValueConverter接口

注意:此处 public class LogonMultiMindingConverter : IMultiValueConverter函数尽量写在namespace里面。
<Window x:Class="WpfApplication1_8_25.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="190" Width="300">
<Grid>
<StackPanel Background="LightBlue">
<TextBox x:Name="textbox1" Height="23" Margin="5"/>
<TextBox x:Name="textbox2" Height="23" Margin="5"/>
<TextBox x:Name="textbox3" Height="23" Margin="5"/>
<TextBox x:Name="textbox4" Height="23" Margin="5"/>
<Button x:Name="button1" Content="Submit" Width="80" Margin="5"/>
</StackPanel>
</Grid>
</Window>
using System.Globalization;//CultureInfo的头文件
namespace WpfApplication1_8_25
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public class LogonMultiMindingConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (!values.Cast<string>().Any(text => string.IsNullOrEmpty(text)) && values[0].ToString() == values[1].ToString() && values[2].ToString() == values[3].ToString())
{
return true;
}
return false;
}
//不会被调用
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.SetMultiBinding();
}
private void SetMultiBinding()
{
//准备基础Binding
Binding b1 = new Binding("Text") { Source = this.textbox1 };
Binding b2 = new Binding("Text") { Source = this.textbox2 };
Binding b3 = new Binding("Text") { Source = this.textbox3 };
Binding b4 = new Binding("Text") { Source = this.textbox4 };
//准备MultiBinding
MultiBinding mb = new MultiBinding() { Mode = BindingMode.OneWay };
mb.Bindings.Add(b1); //MultiBinding对于子Binding的顺序是敏感的
mb.Bindings.Add(b2);
mb.Bindings.Add(b3);
mb.Bindings.Add(b4);
mb.Converter = new LogonMultiMindingConverter();
//将Button与MultiBinding对象关联
this.button1.SetBinding(Button.IsEnabledProperty, mb);//IsEnabledProperty
}
}
}
程序运行效果图:

其中,在编译的时候出现一个报错:

参看博客WPF进阶之接口(1):IValueConverter,IMultiValueConverter,发现在写ConvertBack函数的格式出现错误。
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
//注意IMultiValueConverter中Convert、ConvertBack二者的参数变化
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
本文是基于《深入浅出WPF》的第6章节的学习笔记,详细介绍了WPF中的Binding基础知识,包括属性、代码示例、Binding的源、控制Binding的方向及数据更新、Source、使用多种数据源如ADO.NET、XML等,并探讨了数据转换与校验以及多路Binding的应用。通过Binding,实现了数据和UI之间的桥梁,确保数据的双向同步。
9395

被折叠的 条评论
为什么被折叠?



