原创,转载请注明出处:WPF DataBinding之我见
一、DataBinding介绍
数据绑定是在应用程序 UI 与业务逻辑之间建立连接的过程。 如果绑定具有正确设置并且数据提供正确通知,则当数据更改其值时,绑定到数据的元素会自动反映更改。 数据绑定可能还意味着如果元素中数据的外部表现形式发生更改,则基础数据可以自动更新以反映更改。 例如,如果用户编辑 TextBox 元素中的值,则基础数据值会自动更新以反映该更改。下图表示Binding模型:
其中,Binding目标必须是依赖对象,XAMAL控件都继承自DependencyObject,若想自定义的类的对象也能成为Binding目标,则该类必须继承自DependencyObject。依赖对象类里面有个依赖属性注册类,调用该注册类就能声明一个依赖属性,之后再定义类似于CLR公共属性的操作(get与set需要调用特定函数,具体实现参考后面示例 )。
Binding数据源则无限制,可以是CLR属性或者XAMAL控件,也可以是自定义依赖对象的依赖属性或者是继承自INotifyPropertyChanged的类对象属性。普通CLR属性不具备当自身发生改变时自动通知目标数据的功能,而依赖属性以及继承自INotifyPropertyChanged的类对象属性就具备此功能。
目标与数据源的联系是通过Binding对象实现的。Binding是一个特殊类,能够设置绑定的数据流方向(OneWay、TwoWay、OneWayToSource)、数据转换器(当数据源与目标数据类型不匹配时,包括双向转换)、校验器、数据更新方式等。
下面就通过三种数据绑定方式来进一步了解DataBinding。
二、控件间的DataBinding
XAMAL控件都是继承自DependencyObject,故控件间的依赖属性可以互相绑定,即可当做源也可作为目标,三种数据流方向都可设定,另外还可设定数据更新方式。不同控件一般会有不同的默认数据流方向,TwoWay方式下两个方向的数据更新方式会不大一样,一般从源到目标是PropertyChanged,从目标到源则是LostFocus。使用示例如下:
XAMAL文件:
<Window x :Class="DataBinding_controls.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height ="350" Width="525">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width ="2*" />
<ColumnDefinition Width ="4*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height ="*" />
<RowDefinition Height ="*" />
<RowDefinition Height ="*" />
</Grid.RowDefinitions>
<TextBlock Grid.RowSpan ="3" TextWrapping="Wrap"> 数据绑定方式演示, <LineBreak /> 其中数据的更新方式UpdateSourceTrigger默认为LostFocus, 另外还有Explicit以及PropertyChanged <LineBreak /> 第三个示例就使用了PropertyChanged </TextBlock>
<StackPanel Grid.Row ="0" Grid.Column = "1" Orientation="Horizontal">
<TextBlock> OneWay:============== </TextBlock >
<StackPanel>
<TextBlock FontSize ="12" Foreground="Red"> Source</TextBlock >
<TextBox x :Name="txb1"> Hello1</TextBox >
</StackPanel>
<StackPanel>
<TextBlock FontSize ="12" Foreground="Red"> --》Target</TextBlock >
<TextBox Text ="{Binding Path =Text,ElementName=txb1, Mode=OneWay}"/>
</StackPanel>
</StackPanel>
<StackPanel Grid.Row ="1" Grid.Column = "1" Orientation="Horizontal">
<TextBlock> TwoWay(TextBlock Default):==</TextBlock >
<StackPanel>
<TextBlock FontSize ="12" Foreground="Red"> Source</TextBlock >
<TextBox x :Name="txb2"> Hello2</TextBox >
</StackPanel>
<StackPanel>
<TextBlock FontSize ="12" Foreground="Red"> 《==》Target</TextBlock >
<TextBox Text ="{Binding Path =Text,ElementName=txb2, Mode=TwoWay}"/>
</StackPanel>
</StackPanel>
<StackPanel Grid.Row ="2" Grid.Column = "1" Orientation="Horizontal">
<TextBlock> OneWayToSource:======== </TextBlock >
<StackPanel>
<TextBlock FontSize ="12" Foreground="Red"> Source </TextBlock >
<TextBox x :Name="txb3"> Hello3</TextBox >
</StackPanel>
<StackPanel>
<TextBlock FontSize ="12" Foreground="Red"> 《--Target</TextBlock >
<!--以下绑定在cs代码中实现-->
<TextBox x :Name ="xxj"/>
</StackPanel>
</StackPanel>
</Grid >
</Window>
后台CS文件:
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 DataBinding_controls
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow ()
{
InitializeComponent();
//Binding binding = new Binding();
设置源对象
//binding.Source = txb3;
设置源属性
//binding.Path = new PropertyPath("Text");
//binding.Mode = BindingMode.OneWayToSource;
//binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
添加到目标属性
//xxj.SetBinding(TextBox.TextProperty, binding);
//以上操作等价于以下两句
Binding binding = new Binding( "Text") { Source = txb3, Mode = BindingMode.OneWayToSource , UpdateSourceTrigger = UpdateSourceTrigger. PropertyChanged};
xxj. SetBinding(TextBox .TextProperty, binding); //等价于BindingOperations.SetBinding(xxj, TextBox.TextProperty, binding);
}
}
}
三、自定义数据源实现INotifyPropertyChanged
INotifyPropertyChanged是一个系统的接口类,里面只是定义了一个事件,该事件用于当属性值发生改变时通知Binding目标。普通类的CLR属性并不具备自动通知功能,依赖对象的依赖属性则是自动包含了此功能。若想自定义类的属性能够实现自动通知更改但又不想让该类变为依赖类,则可让该类继承INotifyPropertyChanged接口,这样就能保证普通CLR属性具备自动通知更改的能力。
使用示例如下:
XAMAL文件:
<Window x :Class="Databinding_INotifyPropertyChanged.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height ="350" Width="525">
<StackPanel Margin="20">
<TextBlock Width ="40" Height="20" HorizontalAlignment="Left">Name: </TextBlock>
<TextBox Width ="100" HorizontalAlignment="Left" Text="{Binding Name, Mode =OneWay}"/>
<TextBlock Width ="40" Height="20" HorizontalAlignment="Left">Age: </TextBlock>
<TextBox Width ="100" HorizontalAlignment="Left" Text="{Binding Age, Mode =TwoWay}"/>
<Button Content ="随机替换年龄" Width="100" HorizontalAlignment ="Left" Click="Button_Click"/>
</StackPanel >
</Window>
后台CS文件:
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;
namespace Databinding_INotifyPropertyChanged
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public Student stu = new Student( "xiao ming", 12);
public MainWindow ()
{
InitializeComponent();
this.DataContext = this.stu;
stu.PropertyChanged += sourceDataChanged;
}
void sourceDataChanged (object sender, PropertyChangedEventArgs e)
{
MessageBox.Show (e.PropertyName. ToString() + "改变" );
}
private void Button_Click( object sender , RoutedEventArgs e)
{
Random test = new Random();
stu.Age = Convert. ToInt32(test .NextDouble() * 100);
}
}
public class Student : INotifyPropertyChanged
{
public Student (string _name, int _age)
{
this.name = _name;
this.age = _age;
}
private string name;
public string Name
{
get
{
return this .name;
}
set
{
if (this .name != value)
{
this.name = value;
OnProperyChanged("Name" );
}
}
}
private int age;
public int Age
{
get
{
return this .age;
}
set
{
if (this .age != value)
{
this.age = value;
OnProperyChanged("Age" );
}
}
}
//系统自动添加往PropertyChanged添加委托,参考http://www.xxbar.net/thread-706561-1-1.html
public event PropertyChangedEventHandler PropertyChanged;
public void OnProperyChanged( string propertyName )
{
if (PropertyChanged != null)
{
PropertyChanged(this , new PropertyChangedEventArgs( propertyName));
}
}
}
}
四、自定义DependencyObject
依赖对象里面包含依赖属性,依赖属性一方面可以节省对象占用空间,另一方面可以通过数据绑定从其他对象的属性中实时获取数值,具体来龙去脉参考WPF基础入门(4)-什么是依赖属性
普通CLR属性可以通过作为数据源并设定数据流方式为TwoWay获取客户端某个控件的值
,示例如下:
XAMAL文件:
<Window x :Class="DataBinding_DependencyObject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height ="150" Width="200">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height ="25"/>
<RowDefinition Height ="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width ="20"/>
<ColumnDefinition Width ="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row ="0" Grid.Column="0"> B:</TextBlock >
<TextBox Name ="txbB" Width="120" Grid.Row="1" Grid.Column ="1" Text="jjj"/>
<Button Grid.Row ="1" Grid.ColumnSpan="2" Click="Button_Click">点击查看内部对象值 </Button>
</Grid >
</Window>
后台CS文件:
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 DataBinding_DependencyObject
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public school longZhong;
public MainWindow ()
{
InitializeComponent();
DataContext = this ;
longZhong = new school();
Binding binding = new Binding( "Name") { Source = longZhong, Mode = BindingMode .TwoWay };
BindingOperations.SetBinding (txbB, TextBox. TextProperty, binding );
}
private void Button_Click( object sender , RoutedEventArgs e)
{
if (longZhong != null)
{
MessageBox.Show (longZhong. Name);
}
}
}
public class school
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
}
继承了INotifyPropertyChanged接口的类属性在此基础上还能提供更改通知。但这种数据获取方式毕竟有所限制,最起码你必须设定为TwoWay。此限制的根本原因在于普通CLR属性以及继承了INotifyPropertyChanged接口的类属性不能作为Binding目标(setBinding接口的Binding目标必须是DependencyObject类型)。为了打破限制,用户可以自定义依赖类(只需继承DependencyObject),这样其对象的依赖属性就能和普通控件属性一样随意设置各种绑定方式,并且同样具备自动通知更改的能力
。使用示例如下:
XAMAL文件:
<Window x :Class="DataBinding_DependencyObject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height ="150" Width="200">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height ="25"/>
<RowDefinition Height ="25"/>
<RowDefinition Height ="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width ="20"/>
<ColumnDefinition Width ="*"/>
</Grid.ColumnDefinitions>
<TextBlock> A:</TextBlock >
<TextBox Name ="txbA" Width="120" Grid.Column="1" Text =" uu"/>
<TextBlock Grid.Row ="1" Grid.Column="0"> B:</TextBlock >
<TextBox Name ="txbB" Width="120" Grid.Row="1" Grid.Column ="1" Text="jjj"/>
<Button Grid.Row ="2" Grid.ColumnSpan="2" Click="Button_Click">点击查看内部对象值 </Button>
</Grid >
</Window>
后台CS文件:
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 DataBinding_DependencyObject
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public school longZhong;
public MainWindow ()
{
InitializeComponent();
DataContext = this ;
longZhong = new school();
Binding binding = new Binding( "Text") { Source = txbA, Mode = BindingMode.TwoWay };
BindingOperations.SetBinding (longZhong, school.NameProperty , binding);
Binding binding1 = new Binding( "Name") { Source = longZhong, Mode = BindingMode .TwoWay };
BindingOperations.SetBinding (txbB, TextBox. TextProperty, binding1 );
}
private void Button_Click( object sender , RoutedEventArgs e)
{
//school hh = new school();
//hh.Name = txbA.Text;
if (longZhong != null)
{
MessageBox.Show (longZhong. Name);
}
}
}
public class school : DependencyObject
{
public string Name
{
get { return (string) GetValue(NameProperty ); }
set { SetValue (NameProperty, value); }
}
// Using a DependencyProperty as the backing store for Name. This enables animation, styling, binding, etc...
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register ("Name", typeof(string ), typeof( school));
}
}