WPF简单数据绑定
准备工作
1、首先实现一个简单的页面布局
|
代码:一个简单的个人信息编辑器布局(该布局代码具有典型意义) |
|
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="WithoutBinding" Height="135" Width="200"> <Grid Name="grid"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <TextBlock Grid.Row="0" Grid.Column="0" Margin="5" VerticalAlignment="Center">Name:</TextBlock> <TextBox Name="nameTextBox" Grid.Row="0" Grid.Column="1" Margin="5" /> <TextBlock Grid.Row="1" Grid.Column="0" Margin="5" VerticalAlignment="Center" >Age:</TextBlock> <TextBox Name="ageTextBox" Grid.Row="1" Grid.Column="1" Margin="5" /> <Button Name="birthdayButton" Grid.Row="2" Grid.Column="1" Margin="5">Birthday</Button> </Grid> </Window> |
2、实现一个类,该类必须要实现InotifyPropertyChanged接口
|
代码:InotifyPropertyChanged接口的标准实现 |
|
public class Person:INotifyPropertyChanged { //1、该接口就只有一个事件PropertyChanged定义 public event PropertyChangedEventHandler PropertyChanged; //4、Notify方法 protected void Notify(string propName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propName)); } }
private string name; private int age;
public int Age { get { return age; } //2、注意这里的实现,只要改变了值,就要触发PropertyChanged事件 set { if (age == value) return; age = value; //3、这里为了使代码更简练,将所有属性的调用PropertyChanged事件 //部分的代码进行了封装,Notify方法里面 Notify("Age"); } }
public string Name { get { return name; } set { if (name == value) return; name = value; Notify("Name"); } } public Person(string name, int age) { Name = name; Age = age; } }
|
手工进行数据同步
3、下面我们分两个部分来进行实现,首先,我们手动实现数据绑定的过程
|
代码:手工实现数据绑定 |
|
public partial class MainWindow : Window { //0、这里创建一个Person的对象 private Person person = new Person("Tom", 11); public MainWindow() { InitializeComponent(); this.nameTextBox.Text = person.Name; this.ageTextBox.Text = person.Age.ToString(); //1、跟踪对象变化:该类要为Person实例对象注册PropertyChanged事件 person.PropertyChanged += person_PropertyChanged; //这一段添加事件也可写在XAML里面 //3、跟踪界面变化:要注册TextChanged实现 nameTextBox.TextChanged += nameTextBox_TextChanged; ageTextBox.TextChanged += ageTextBox_TextChanged; birthdayButton.Click += birthdayButton_Click; } //2、在事件处理函数中要将Person类成员中的变化反应到用户界面中,并视情况转换成字符串 private void person_PropertyChanged(object sender, PropertyChangedEventArgs e) { switch (e.PropertyName) { case "Name": nameTextBox.Text = person.Name; break; case "Age": ageTextBox.Text = person.Age.ToString(); break; } } private void birthdayButton_Click(object sender, RoutedEventArgs e) { ++person.Age; //this.ageTextBox.Text = person.Age.ToString(); MessageBox.Show(string.Format("Happy Birthday,{0},age{1}", person.Name, person.Age), "Birthday"); }
//4、在处理函数中要将界面的数据赋值给对象数据,并进行必要的类型转换 private void nameTextBox_TextChanged(object sender, TextChangedEventArgs e) { person.Name = nameTextBox.Text; }
private void ageTextBox_TextChanged(object sender, TextChangedEventArgs e) { int age = 0; //这里要注意复习下TryParse的用法 if (int.TryParse(ageTextBox.Text, out age)) { person.Age = age; } } }
|
开始数据绑定
核心元素:Binding类、Path属性、DataContext属性
4、可见,手工实现数据同步还是比较繁琐的,有更好的方法,那就是数据绑定,好,进入正题
我们把手工进行数据绑定时处理PropertyChanged事件和TextChanged的那些代码全部删除,1、给Window类加上一个命名空间:
|
xmlns:local="clr-namespace:WpfApplication1" |
5、然后添加Binding类的绑定语法
|
<TextBlock Grid.Row="0" Grid.Column="0" Margin="5" VerticalAlignment="Center">Name:</TextBlock> <TextBox Name="nameTextBox" Grid.Row="0" Grid.Column="1" Margin="5" Text="{Binding Path='Name'}" /> <TextBlock Grid.Row="1" Grid.Column="0" Margin="5" VerticalAlignment="Center" >Age:</TextBlock> <TextBox Name="ageTextBox" Grid.Row="1" Grid.Column="1" Margin="5" Text="{Binding Path='Age'}" /> <Button Name="birthdayButton" Grid.Row="2" Grid.Column="1" Margin="5">Birthday</Button>
|
6、再给Grid添加关联数据
|
grid.DataContext = person; |
现在就可以实现与上面手工的数据同步一样的功效了,是不是很爽啊,这里需要注意的是,数据类实现必须要实现InotifyPropertyChanged接口
一些其他用法和概念:
隐式数据源:当我们设置了DataContext属性时,我们就是使用的隐式数据源,并且查找隐式数据源的工作是迭代进行的
数据岛:我们可以把数据写在XAML文件里,具体如下:
|
代码:数据岛的示例 |
|
<Window.Resources> <local:Person x:Key="Tom" Name="Tom" Age="15" /> </Window.Resources> |
这里就相当于我们定义了个Person类型的变量,需要注意的是设置x:Key属性,因为资源本质上来说是个哈希表,我们可以通过在XAML里面访问和在后台代码访问,分别形式如下:
XAML: {StaticResource Tom} C#: Person peron = (Person)this.FindResource(“Tom”);
显示数据源:
应用场景:当我们要为每一个Binding对象分别设置数据源时,我们就需要用显式数据源
说白了就是设置其Source属性
|
代码:显式数据源 |
|
<TextBox Name="nameTextBox" Grid.Row="0" Grid.Column="1" Margin="5" Text="{Binding Path=Name, Source={StaticResource Tom}}" />
|
绑定到其他控件:
应用场景:我们有时候需要将该属性绑定到其他控件的某一属性时,就需要绑定到其他控件。
就是使用Binding类的ElementName属性=该控件的Name属性
|
代码:绑定到其他控件 |
|
<Button Name="birthdayButton" Grid.Row="2" Grid.Column="1" Margin="5" Foreground="{Binding Path=Foreground,ElementName=ageTextBox}">Birthday</Button>
|
数值转换:
应用场景:当数据源的数据类型与绑定的属性数据类型不一致时
核心方案:实现IValueConverter接口
|
代码:两个简单的转换器 |
|
//16位数值转换器 public class Base16Converter : IValueConverter { //用于将绑定源数据转换成目标属性数据类型 public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return ((int)value).ToString("x"); }
//用于将目标属性数据转换成绑定源数据 public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return int.Parse((string)value, System.Globalization.NumberStyles.HexNumber); } } //Age转换成背景画刷转换器 public class AgeToForegroundConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (targetType != typeof(Brush)) { return null; } int age = int.Parse(value.ToString()); return (age > 25 ? Brushes.Red : Brushes.Black); }
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } }
|
使用转换器,将转换器声明为资源,就可以在XAML中进行访问了
|
代码: |
|
<Window.Resources> <local:Person x:Key="Tom" Name="Tom" Age="15" /> <local:AgeToForegroundConverter x:Key="ageConverter" /> <local:Base16Converter x:Key="base16Converter" /> </Window.Resources>
|
|
<TextBox ... Text="{Binding Path=Age, Converter={StaticResource base16Converter}}" /> |
验证
应用场景:确保用户输入正确格式的数据
核心方案:Binding的ValidationRules属性,Validation.Errors属性
(这里我没有理解的是关于这个Validation静态属性,究竟是属于哪一个类里面的,如果是全局的,那么怎么与当前这个Binding类相关,我只能理解为这个类型可根据当前上下文关系设置作用域)
1、设置验证规则
|
代码: |
|
<TextBox Name="ageTextBox" Grid.Row="1" Grid.Column="1" Margin="5" Foreground="{Binding Path=Age, Converter={StaticResource ageConverter}}"> <TextBox.Text> <Binding Path="Age" NotifyOnValidationError="True"> <Binding.ValidationRules> <ExceptionValidationRule /> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox>
|
这个时候默认的是在TextBox失去焦点后,WPF会对非法数据以红色进行高亮显示
2、手动捕获ValidationError事件(不推荐)
需要注意的是Binding的NotifyOnValidationError设为True才能引发该事件
|
代码: |
|
public MainWindow() { InitializeComponent(); //grid.DataContext = person;
birthdayButton.Click += birthdayButton_Click;
//这个语法有点特殊,因为是静态方法,所以第一个参数要指明是哪个控件对象绑定处理函数 Validation.AddErrorHandler(ageTextBox, ageTextBox_ValidationError); } |
|
private void ageTextBox_ValidationError(object sender, ValidationErrorEventArgs e) { //MessageBox.Show((string)e.Error.ErrorContent,"Validation Error"); ageTextBox.ToolTip = (string)e.Error.ErrorContent;
} |
3、自定义验证规则
|
代码:创建自定义的验证规则,需要继承ValidatRule类 |
|
public class NumberRangeRule : ValidationRule { private int min; public int Min { get { return min; } set { min = value; } } private int max; public int Max { get { return max; } set { max = value; } } public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) { int number; if (!int.TryParse((string)value, out number)) { return new ValidationResult(false, "Invalid number format"); } if (number < min || number > max) { return new ValidationResult(false, string.Format("number is out of range ({0}-{1})", min, max)); }
//该句等价于: //return new ValidationResult(true, null); return ValidationResult.ValidResult; }
|
|
使用自定义验证规则 |
|
<Binding.ValidationRules> <local:NumberRangeRule Min="0" Max="128" /> </Binding.ValidationRules> |
4、绑定错误消息
不需要捕获ValidationError的代码,不需要设置NotifyOnValidationError为True,完成一样的功能
|
代码: |
|
<TextBox Name="ageTextBox" ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" Grid.Row="1" Grid.Column="1" Margin="5" Foreground="{Binding Path=Age, Converter={StaticResource ageConverter}}"> <TextBox.Text> <Binding Path="Age" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <local:NumberRangeRule Min="0" Max="128" /> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox>
|
上面那段代码使用了相对数据源RelativeSource={RelativeSource Self},等价于ElementName=ageTextBox
5、验证规则触发时机
上面那段代码中的UpdateSourceTrigger="PropertyChanged",可选参数为Defaut、PropertyChanged、LostFocus、Explicit

浙公网安备 33010602011771号