WPF的Binding
在传统的Windows软件中,大部分都是UI驱动程序的模式,也可以说事件驱动程序。WPF作为Winform的升级,它把UI驱动程序彻底改变了,核心回到了数据驱动程序的模式上面,这样,程序就回到了算法和数据。数据,才是真正需要重点处理的。
Binding最为重要的一个特点是通讯,连接着前台与后台。首先看一下Binding最简单的使用方法:
一、元素之间的绑定
这里有3个控件,Slider、TextBox、Label,其中TextBox和Label都作为目标,Slider都作为数据源,把Slider中的值交由两个控件体现,移动滑块,TextBox会自动显示Value的值,也就是FontSize的值。因为两个绑定都设置了双向绑定,所以可以在文本框中输入值,然后丢失焦点,也能反馈回去。
看一下XAML中的绑定语句,这里用的ElementName就是制定要绑定的对象的名字,Path就是要绑定的依赖项属性,Mode就是绑定方式,这里需要说明的是Mode有五种方式:
ⅰ. OneWay 单向绑定
ⅱ. TwoWay 双向绑定
ⅲ. OneTime,最初根据源属性值设置目标属性,以后就忽略所有改变,就是说,只进行初始化。
ⅳ. OneWayToSource,这和OneWay相反。
ⅴ. Default,这是默认形式,它根据目标属性自动设置。
如果把TextBox中的值修改成其他的,滑条位置没有改变,字体大小也没有改变,这是怎么回事呢?当TextBox失去焦点的时候,就会发生相应的改变了。这是因为这个绑定中的默认更新机制,更新机制Binding.UpdateSourceTrigger,这个属性有4个枚举值:
ⅰ. PropertyChange,当值改变的时候,就更新。
ⅱ. LostFocus,当失去焦点的时候更新。
ⅲ. Explicit,当调用BindingExpression.UpdateSource()方法的时候更新,其他情况不会更新。
ⅳ. Default,默认形式。
注意:以上四种更新机制的设定,只会影响源数据,而不会影响目标数据。
二、元素自身的绑定
除了可以绑定别的元素,也可以绑定自身的其他属性,例如Slider自身的Opacity属性和自身的Value属性绑定,当滑块向左移动的时候,会逐渐隐藏起来。
三、后台数据与元素之间的绑定
前面说了元素之间的绑定和元素自身的绑定,最后重点来了,后台数据和前台元素的绑定,这种绑定方式很好地体现了数据驱动程序的运行模式。
首先新建Person类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfHouTaiBindingQianTai
{
public class Person
{
/// <summary>
/// 姓名
/// </summary>
public string Name { get; set; }
/// <summary>
/// 年龄
/// </summary>
public int Age { get; set; }
}
}
页面后台代码:
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;
namespace WpfHouTaiBindingQianTai
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Person person = new Person { Name = "SoftEasy" };
Binding binding = new Binding() { Path = new PropertyPath("Name"), Source = person };
this.TxtName.SetBinding(TextBox.TextProperty, binding);
}
}
}
<Window x:Class="WpfHouTaiBindingQianTai.MainWindow"
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:WpfHouTaiBindingQianTai"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBox x:Name="TxtName" HorizontalAlignment="Left" Margin="24,69,0,0"
TextWrapping="Wrap" VerticalAlignment="Top" Height="60" Width="600"/>
</Grid>
</Window>
显示效果:
数据绑定的方式已经写完了。Binding是一条高速公路,那么为了提高数据传递的合法性和有效性,我们要在这条高速公路中建立起一系列的关卡,有的用来转换数据,有的用来校验数据,下面说一下Binding对数据的校验和转换。
(一)Binding的数据校验
Binding的数据校验工作是派生自ValidationRule类,并且对Validate方法进行重写的自定义类!
看一下实例:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace WpfHouTaiBindingQianTai
{
public class DataValidationRule : ValidationRule
{
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, "验证失败!");
}
}
}
先设计一个校验类,它继承ValidationRule类并且重写Validate方法。使用这个类的时候是创建Binding的时候设置校验的。
代码如下:
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;
namespace WpfHouTaiBindingQianTai
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//Person person = new Person { Name = "SoftEasy" };
//Binding binding = new Binding() { Path = new PropertyPath("Name"), Source = person };
//this.TxtName.SetBinding(TextBox.TextProperty, binding);
Binding binding = new Binding("Value") { Source = this.slider };
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
DataValidationRule dataValidationRule = new DataValidationRule();
dataValidationRule.ValidatesOnTargetUpdated = true;
binding.ValidationRules.Add(dataValidationRule);
binding.NotifyOnValidationError = true;
this.TxtFontSize.SetBinding(TextBox.TextProperty, binding);
this.TxtFontSize.AddHandler(Validation.ErrorEvent, new RoutedEventHandler(this.ValidationError));
}
/// <summary>
/// 验证错误
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void ValidationError(object sender,RoutedEventArgs e)
{
if(Validation.GetErrors(this.TxtFontSize).Count > 0)
{
this.TxtFontSize.ToolTip = Validation.GetErrors(this.TxtFontSize)[0].ErrorContent.ToString();
}
}
}
}
<Window x:Class="WpfHouTaiBindingQianTai.MainWindow"
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:WpfHouTaiBindingQianTai"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Label x:Name="LblFontSize" Content="SoftEasy" FontSize="{Binding ElementName=slider ,Path=Value,Mode=TwoWay}"
HorizontalAlignment="Left" Margin="24,18,0,0" VerticalAlignment="Top"/>
<Slider x:Name="slider" HorizontalAlignment="Left" Margin="24,117,0,0"
VerticalAlignment="Top" Width="330" Maximum="200" Minimum="20"/> //这里的最大值改成了200,为了测试让它报错
<TextBox x:Name="TxtFontSize" HorizontalAlignment="Left" Height="23" Margin="24,69,0,0"
TextWrapping="Wrap" Text="{Binding ElementName=slider ,Path=Value ,Mode=TwoWay}"
VerticalAlignment="Top" Width="120"/>
</Grid>
</Window>
显示效果:
因为设置了传过去的值不能是超过0~100,所以当超过了就显示红色边框。在Binding中,默认是会认为数据源是肯定正确的,所以如果将TextBox作为数据源,而Slider作为目标,数据源输入错误是没有显示的,那么怎么解决这个问题呢?设置 dataValidationRule.ValidatesOnTargetUpdated = true;
(二)Binding的数据转换
Binding还有另外一种机制称为数据转换,当Source端指定的Path属性值和Target端指定的目标属性不一致的时候,我们可以添加数据转换器(Convert)。上面我们提到的问题实际上就是double和string类型相互转换的问题,因为处理起来比较简单,所以WPF类库就自己帮我们做了,但有些数据类型转换就不是WPF能帮我们做的了,当遇到这些情况,我们只能自己动手来写Converter,方法是创建一个类并让这个类实现 IValueConverter接口。
//TimeConver.cs
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
namespace WpfHouTaiBindingQianTai
{
/// <summary>
/// 自定义事件转换
/// </summary>
public class TimeConver : IValueConverter
{
//当值从绑定源传播给绑定目标时,调用方法Convert
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value == null)
{
return DependencyProperty.UnsetValue;
}
DateTime date = (DateTime)value;
return date.ToString("yyyy-MM-dd");
}
//当值从绑定目标传播给绑定源时,调用此方法ConvertBack
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
string str = value as string;
DateTime txtDate;
if(DateTime.TryParse(str,out txtDate))
{
return txtDate;
}
return DependencyProperty.UnsetValue;
}
}
}
这个就是日期转换类,它有两个方法:
ⅰ. 当值从绑定源传播给绑定目标时,调用方法Convert
ⅱ. 当值从绑定目标传播给绑定源时,调用此方法ConvertBack,方法ConvertBack的实现必须是方法Convert的反向实现。
这两个方法分别在里面写入怎么转换,转换成什么类型就是返回类型。
把这个绑定的Convert属性设置成我们设计的转换类的实例就可以了.