WPF 数据绑定Binding
自定义Binding
当为Binding
设置了继承System.ComponentModel.INotifyPropertyChanged
的数据源类,Binding
会自动侦听来自这个接口的PropertyChanged
事件。
<TextBox x:Name="textbox" Width="200" Height="100"></TextBox>
<Button Content="click me" Click="Button_Click"></Button>
...
using System.ComponentModel;
public partial class Window1 : Window
{
Student stu;
public Window1()
{
InitializeComponent();
stu = new Student();
Binding binding = new Binding("Name") { Source = stu };
//Binding binding = new Binding(){ Path = new PropertyPath("Name"), Source = stu };
textbox.SetBinding(TextBox.TextProperty, binding);
//BindingOperations.SetBinding(textbox, TextBox.TextProperty, binding);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
stu.Name += "Name";
}
}
class Student : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string name;
public string Name
{
get { return name; }
set
{
name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
}
}
}
控件间的Binding
<TextBox x:Name="textbox2" Width="200" Text="{Binding Path=Value,ElementName=slider}"></TextBox>
<!--<TextBox x:Name="textbox2" Width="200" Text="{Binding Value,ElementName=slider}"></TextBox>-->
<Slider x:Name="slider" Maximum="100" Minimum="0" Width="100"></Slider>
等价于
<TextBox x:Name="textbox2" Width="200"></TextBox>
<Slider x:Name="slider" Maximum="100" Minimum="0" Width="100"></Slider>
...
textbox2.SetBinding(TextBox.TextProperty, new Binding() { Path = new PropertyPath("Value"), ElementName = "slider" });
//textbox2.SetBinding(TextBox.TextProperty, new Binding("Value") { ElementName = "slider" });
此处ElementName = "slider"
与Source = slider
等价
Binding方向与数据更新
Mode
决定数据绑定的方向,其值为BindingMode
枚举
public enum BindingMode
{
TwoWay = 0,
OneWay = 1,//只读
OneTime = 2,
OneWayToSource = 3,//只写
Default = 4
}
UpdateSourceTrigger
决定数据更新的时机,其值为UpdateSourceTrigger
枚举
public enum UpdateSourceTrigger
{
Default = 0,
PropertyChanged= 1,
LostFocus = 2,
Explicit = 3
}
Binding的路径 (Path)
由于Binding
的重载,Path
值可以作为new Binding()
实例的入参也可以作为空入参实例的属性。以下语法等效:
{Binding Text}
{Binding Path=Text}
...
new Binding("Text"){}
new Binding(){Path=new PropertyPath("Text")}
Path
属性决定需要暴露的属性值,其值可以为PropertyPath
实例,且支持多级路径
Path = new PropertyPath("Text.Length")
- 当
Source
本身就是数据(如string
或int
),不需要Path
指明时,Path
的值可设为"."
,在XAML中可直接省略Path
属性
Binding的Source
使用DataContext
作为Binding
的源
UI树的每一个节点都有DataContext
,当一个Binding只有Path
没有Source
时会从当前元素开始向上逐级查找具有该Path
属性的DataContext
(其实本质上是DataContext
作为一个依赖属性,当标签不存在某属性时会从其父级元素继承)
<WrapPanel x:Name="WrapPanel" VerticalAlignment="Center">
<WrapPanel.DataContext>
<local:Man Age="20"></local:Man>
</WrapPanel.DataContext>
<TextBox Text="{Binding Age}" Width="200"></TextBox>
</WrapPanel>
//或
WrapPanel.DataContext=new Man(){Age="20"};
-
DataContext
属性是直接采用其中的对象,而不是把对象作为其一个属性 - 更常用的是直接对顶层的
DataContext
赋值为当前实例
<Button Content="{Binding A}" Width="50"></Button>
...
public partial class Dependency : Window
{
public string A
{
get; set;
}
public Dependency()
{
InitializeComponent();
A = "Click Me";
DataContext = this;
}
}
注意通过DataContext
只能初始化数据到视图并单向绑定,双向绑定还是需要事件机制
列表控件与ItemsSource
通常使用System.ComponentModel.ObservableCollection
类型代替List
,因其实现了INotifyPropertyChanged
接口,其变化可以通知到视图界面。
<ListBox x:Name="myListBox" Height="100"></ListBox>
...
myListBox.ItemsSource = new ObservableCollection<string>() { "A","B","C" };
//或
myListBox.ItemsSource = new ObservableCollection<object>() { new { name = "apple", diam = 4 },new { name = "banana", diam = 5 } };
myListBox.DisplayMemberPath = "name";
若不设置DisplayMemberPath
,也可通过对ItemTemplate
属性赋值(DataTemplate
类型)可以详细设置列表内容
<ListBox x:Name="myListBox" Height="100">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding name}" Width="200"></TextBox>
<TextBox Text="{Binding diam}" Width="200"></TextBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
导入XML作为Binding的数据源
使用XPath
而非Path
指定来源。
<?xml version="1.0" encoding="utf-8"?>
<StudentList>
<Student Id="1">
<Name>Tom</Name>
</Student>
<Student Id="2">
<Name>Jack</Name>
</Student>
</StudentList>
...
<ListView x:Name="myListView" Height="100">
<ListView.View>
<GridView>
<GridViewColumn Header="Id" Width="80" DisplayMemberBinding="{Binding XPath=@Id}"></GridViewColumn>
<GridViewColumn Header="Name" Width="80" DisplayMemberBinding="{Binding XPath=Name}"></GridViewColumn>
</GridView>
</ListView.View>
</ListView>
...
XmlDocument doc = new XmlDocument();
doc.Load(@"../../student.xml");
XmlDataProvider xdp = new XmlDataProvider()
{
Document = doc,
XPath = @"/StudentList/Student"
};
myListView.DataContext = xdp;
myListView.SetBinding(ListView.ItemsSourceProperty, new Binding());
-
XmlDataProvider
用于将XML作为数据源提供给Binding
- 加
@
的是XML的属性,不加的是其子级元素。 - 也可不声明
doc
,改为xdp.Source=new Uri(@"../../student.xml")
内联XML作为Binding的数据源
<Grid.Resources>
<XmlDataProvider x:Key="ExpenseDataSource" XPath="Expenses">
<x:XData>
<Expenses xmlns="">
<Person Name="Mike" Department="Legal">
<Expense ExpenseType="Lunch" ExpenseAmount="50" />
<Expense ExpenseType="Transportation" ExpenseAmount="50" />
</Person>
<Person Name="Lisa" Department="Marketing">
<Expense ExpenseType="Document printing"
ExpenseAmount="50"/>
<Expense ExpenseType="Gift" ExpenseAmount="125" />
</Person>
<Person Name="John" Department="Engineering">
<Expense ExpenseType="Magazine subscription"
ExpenseAmount="50"/>
<Expense ExpenseType="New machine" ExpenseAmount="600" />
<Expense ExpenseType="Software" ExpenseAmount="500" />
</Person>
<Person Name="Mary" Department="Finance">
<Expense ExpenseType="Dinner" ExpenseAmount="100" />
</Person>
</Expenses>
</x:XData>
</XmlDataProvider>
<DataTemplate x:Key="nameItemTemplate">
<Label Content="{Binding XPath=@Name}"/>
</DataTemplate>
</Grid.Resources>
...
<ListBox Name="peopleListBox" Grid.Column="1" Grid.Row="2"
ItemsSource="{Binding Source={StaticResource ExpenseDataSource}, XPath=Person}"
ItemTemplate="{StaticResource nameItemTemplate}">
</ListBox>
ObjectDataProvider
将对象/方法返回值作为数据源提供给 Binding
RelativeSource 通过标签位置关系进行绑定
ElementName
通过元素名绑定,Source
通过元素对象/数据对象绑定,RelativeSource
则通过标签的位置关系进行绑定
Binding 的数据校验
为ValidationRules
属性添加ValidationRule
类型对象
Binding 的数据转换
为Converter
属性创建继承IValueConverter
的类
多路Binding
MultiBinding
类可以将一组Binding
对象(可以分别拥有自己的校验与转换机制)聚合起来,最后共同决定传出的数据。
以下demo为textBox1和textBox2数据一致且textBox3和textBox4数据一致时button为可点击状态 :
<StackPanel>
<TextBox x:Name="textBox1" Height="23" Margin="5"></TextBox>
<TextBox x:Name="textBox2" Height="23" Margin="5"></TextBox>
<TextBox x:Name="textBox3" Height="23" Margin="5"></TextBox>
<TextBox x:Name="textBox4" Height="23" Margin="5"></TextBox>
<Button x:Name="button" Content="click me" Width="80" Margin="5"></Button>
</StackPanel>
...
public partial class Multi : Window
{
public Multi()
{
InitializeComponent();
SetMultiBinding();
}
private void SetMultiBinding()
{
Binding b1 = new Binding("Text") { Source = textBox1 };
Binding b2 = new Binding("Text") { Source = textBox2 };
Binding b3 = new Binding("Text") { Source = textBox3 };
Binding b4 = new Binding("Text") { Source = textBox4 };
MultiBinding mb = new MultiBinding() { Mode=BindingMode.OneWay };
mb.Bindings.Add(b1);//顺序敏感
mb.Bindings.Add(b2);
mb.Bindings.Add(b3);
mb.Bindings.Add(b4);
mb.Converter = new myConverter();
button.SetBinding(Button.IsEnabledProperty, mb);
}
}
public class myConverter : IMultiValueConverter
{
public object Convert(object[] values,Type targetType,object param,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[] targetType, object param, CultureInfo culture)
{
throw new NotImplementedException();
}
}