WPF数据绑定
紧接上一篇《WPF笔记汇总之控件进阶与面板布局》,这篇主要汇总一下WPF最重要的特性之一,那就是数据绑定,以及自定义对象的值转换和绑定的调试方法,与以往的WinForm的事件驱动开发方式不同,WPF采用的是数据驱动的方式。
1. 绑定初步
简单的使用绑定,就是采用{Binding Path=Text, ElementName=txtValue}的类似方式,Binding是绑定的标记,Path是绑定的属性,ElementName是绑定的元素,也可以称为数据源。
<StackPanel Margin="10">
<TextBox Name="txtValue" />
<WrapPanel Margin="0,10">
<TextBlock Text="Value: " FontWeight="Bold" />
<TextBlock Text="{Binding Path=Text, ElementName=txtValue}" />
</WrapPanel>
</StackPanel>
此外还可以通过后置代码来绑定, SetBinding()方法接受两个参数,一个是要绑定的依赖项属性,另一个是绑定对象。
<StackPanel Margin="10">
<TextBox Name="txtValue" />
<WrapPanel Margin="0,10">
<TextBlock Text="Value: " FontWeight="Bold" />
<TextBlock Name="lblValue" />
</WrapPanel>
</StackPanel>
public MainWindow()
{
InitializeComponent();
Binding binding = new Binding("Text");
binding.Source = txtValue;
lblValue.SetBinding(TextBlock.TextProperty, binding);
}
2. 数据上下文
数据上下文就是DataContext对象,它表示要绑定的源对象,本例子中的数据上下文对象是窗体本身。
<StackPanel Margin="15">
<WrapPanel>
<TextBlock Text="Window title: " />
<TextBox Text="{Binding Title, UpdateSourceTrigger=PropertyChanged}" Width="150" />
</WrapPanel>
<WrapPanel Margin="0,10,0,0">
<TextBlock Text="Window dimensions: " />
<TextBox Text="{Binding Width}" Width="50" />
<TextBlock Text=" x " />
<TextBox Text="{Binding Height}" Width="50" />
</WrapPanel>
</StackPanel>
后置代码
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
其中UpdateSourceTrigger属性,是用来设置触发数据源用的,Default是UpdateSourceTrigger的默认值。Default是根据不同的控件的默认行为而定的,其他选项是PropertyChanged, LostFocus和Explicit。一个是属性改变时更新,一个是失去焦点时更新,而最后一个Explicit是手动推送更新,使用Binding上的UpdateSource方法调用。用法实例如下:
<StackPanel Margin="15">
<WrapPanel>
<TextBlock Text="Window title: " />
<TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
<Button Name="btnUpdateSource" Click="btnUpdateSource_Click" Margin="5,0" Padding="5,0">*</Button>
</WrapPanel>
<WrapPanel Margin="0,10,0,0">
<TextBlock Text="Window dimensions: " />
<TextBox Text="{Binding Width, UpdateSourceTrigger=LostFocus}" Width="50" />
<TextBlock Text=" x " />
<TextBox Text="{Binding Height, UpdateSourceTrigger=PropertyChanged}" Width="50" />
</WrapPanel>
</StackPanel>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
private void btnUpdateSource_Click(object sender, RoutedEventArgs e)
{
BindingExpression binding = txtWindowTitle.GetBindingExpression(TextBox.TextProperty);
binding.UpdateSource();
}
}
3. 自定义绑定属性
如果要让UI中的任何内容得到更新,需要实现ObservableCollection和INotifyPropertyChanged这两个接口,ObservableCollection是用来通知任何目的地其内容的更改的一个列表,INotifyPropertyChanged接口是用来使自定义的对象能够警告UI层对其属性的更改。
<DockPanel Margin="10">
<StackPanel DockPanel.Dock="Right" Margin="10,0,0,0">
<Button Name="btnAddUser" Click="btnAddUser_Click">Add user</Button>
<Button Name="btnChangeUser" Click="btnChangeUser_Click" Margin="0,5">Change user</Button>
<Button Name="btnDeleteUser" Click="btnDeleteUser_Click">Delete user</Button>
</StackPanel>
<ListBox Name="lbUsers" DisplayMemberPath="Name"></ListBox>
</DockPanel>
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
private ObservableCollection<User> users = new ObservableCollection<User>();
public MainWindow()
{
InitializeComponent();
users.Add(new User() { Name = "John Doe" });
users.Add(new User() { Name = "Jane Doe" });
lbUsers.ItemsSource = users;
}
private void btnAddUser_Click(object sender, RoutedEventArgs e)
{
users.Add(new User() { Name = "New user" });
}
private void btnChangeUser_Click(object sender, RoutedEventArgs e)
{
if (lbUsers.SelectedItem != null)
(lbUsers.SelectedItem as User).Name = "Random Name";
}
private void btnDeleteUser_Click(object sender, RoutedEventArgs e)
{
if (lbUsers.SelectedItem != null)
users.Remove(lbUsers.SelectedItem as User);
}
}
/// <summary>
/// User类
/// </summary>
public class User : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return this.name; }
set
{
if (this.name != value)
{
this.name = value;
this.NotifyPropertyChanged("Name");
}
}
}
/// <summary>
/// 实现通知属性改变事件
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
4. 自定义对象值转换
要实现自定义的值转换,则需要使用值转换器。这些实现IValueConverter接口的小类将像中间人一样工作,并在源和目标之间转换值。其中有个StringFormat属性是转换显示的文本方式,ConverterCulture是转换为特定的文化输出,有时需要在命名空间中增加 xmlns:system=“clr-namespace:System;assembly=mscorlib” 的系统类库声明。
<Window.Resources>
<local:YesNoToBooleanConverter x:Key="YesNoToBooleanConverter" />
</Window.Resources>
<StackPanel Margin="10">
<TextBox Name="txtValue" />
<WrapPanel Margin="0,10">
<TextBlock Text="Current value is: " />
<TextBlock Text="{Binding ElementName=txtValue, Path=Text, Converter={StaticResource YesNoToBooleanConverter}}"></TextBlock>
</WrapPanel>
<CheckBox IsChecked="{Binding ElementName=txtValue, Path=Text, Converter={StaticResource YesNoToBooleanConverter}}" Content="Yes" />
<TextBlock Text="{Binding Source={x:Static system:DateTime.Now}, ConverterCulture='de-DE', StringFormat=German date: {0:D}}" />
<TextBlock Text="{Binding Source={x:Static system:DateTime.Now}, ConverterCulture='en-US', StringFormat=American date: {0:D}}" />
<TextBlock Text="{Binding Source={x:Static system:DateTime.Now}, ConverterCulture='ja-JP', StringFormat=Japanese date: {0:D}}" />
</StackPanel>
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
/// <summary>
/// 布尔值转换器
/// </summary>
public class YesNoToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
switch (value.ToString().ToLower())
{
case "yes":
return true;
case "no":
return false;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is bool)
{
if ((bool)value == true)
return "yes";
else
return "no";
}
return "no";
}
}
5. 调试绑定数据
两种方式可以跟踪调试数据绑定,一种是显示的声明跟踪级别,在输出窗口中查看打印的信息,一种是用虚拟转换器进行中断调试。
5.1 调整跟踪级别
<Window x:Class="WpfStudy.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:WpfStudy"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
mc:Ignorable="d"
Title="MainWindow" Height="600" Width="400"
Icon="me.png" WindowStartupLocation="CenterScreen">
<Window.Resources>
</Window.Resources>
<Grid Margin="10">
<TextBlock Text="{Binding Title, diag:PresentationTraceSources.TraceLevel=High}" />
</Grid>
5.2 虚拟转换器
在后台代码文件中定义一个DebugDummyConverter。 有Convert()和ConvertBack()方法,调用Debugger.Break(),可以在此处做断点调试用。
<Window.Resources>
<local:DebugDummyConverter x:Key="DebugDummyConverter" />
</Window.Resources>
<Grid Margin="10">
<TextBlock Text="{Binding Title, ElementName=wnd, Converter={StaticResource DebugDummyConverter}}" />
</Grid>
public class DebugDummyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Debugger.Break();
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Debugger.Break();
return value;
}
}