WPF程序设计指南: Binding(数据绑定)[下]
注:一下内容及代码基本来自Charles Petzold著,蔡学庸译,电子工业出版社出版的《Windows Presentation Foundation 程序设计指南》一书
引言:在上一篇WPF: Binding(数据绑定)[上]中所提到的Binding,都是使用ElementName Property来设置数据源的,这一篇将叙述如何通过Binding的另外两个属性:Source和RelativeSource来设定绑定源(注意:这三个属性只能设置一个,否则会冲突),以及“如何让数据可以当作绑定源”和“如何在格式化转换源数据”
1. Binding一个对象的静态字段或者静态属性
- 在前面的一篇文章:WPF:Resource中我们知道,获取一个类型的静态字段或者静态属性的时候,我们可以使用x:Static获得,但是当我们需要获取一个某一个对象的静态字段或者属性,这时候我们就需要用到绑定。
- 例如,System.Globalization命名空间中的DateTimeFormatInfo类型的DayNames属性(非静态),它返回一个字符串数组,是一周中每一天的名字。其中DateTimeFormatInfo类本身提供的一个静态属性:DateTimeFormatInfo.CurrentInfo,返回一个DateTimeFormatInfo类型的实例,表示当前计算机设置的文化的时间信息。
代码代码说明:<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:g="clr-namespace:System.Globalization;assembly=mscorlib">
<!-- Bind ListBox ItemsSource to DayNames property of DateTimeFormatInfo. -->
<ListBox Name="lstbox"
HorizontalAlignment="Center"
Margin="24"
ItemsSource="{Binding
Source={x:Static g:DateTimeFormatInfo.CurrentInfo},
Path=DayNames,
Mode=OneTime}" />
<!-- Bind TextBlock Text to SelectedItem property of ListBox. -->
<TextBlock HorizontalAlignment="Center"
Text="{Binding ElementName=lstbox,
Path=SelectedItem, Mode=OneWay}" />
</StackPanel>- 第一个绑定,将保存有七天的名称在字符串数组设置为ListBox的ItemsSource,显示出来
- 第二个绑定,将ListBox中选定的一天显示在TextBlock中。
- 此绑定的Mode属性只能为OneTime,因为DateTimeFormatInfo类型没有数据发生改变是发出通知的机制,不会在星期的日子发生改变时发出通知,因此此绑定只能获取DayNames属性一次。
2. 通过定义依赖属性(继承DenpendencyObject类)获得数据改变的通知机制
- 当一个属性为依赖属性(Denpendency Property)时,数据改变的通知机制会自动获得,因此,如果需要一个数据源到目标实现多次传递(实时在目标上反映出更新),需要定义Denpendency Property。
- 如果数据源不是Visual对象,可以选择不继承自FrameworkElement(这也是一种定义Denpendency Property的方法),而是继承自DenpendencyObject类,这个类提供了你要用来实现Denpendency Property的“SetValue和GetValue方法”
-
以下是一个自定义的时间类型,继承自DenpendencyObject类:
代码// Define DependencyProperty.
public static DependencyProperty DateTimeProperty =
DependencyProperty.Register("DateTime", typeof(DateTime),
typeof(ClockTicker1));
// Expose DependencyProperty as CLR property.
public DateTime DateTime
{
set { SetValue(DateTimeProperty, value); }
get { return (DateTime) GetValue(DateTimeProperty); }
}
// Constructor sets timer.
public ClockTicker1()
{
DispatcherTimer timer = new DispatcherTimer();
timer.Tick += TimerOnTick;
timer.Interval = TimeSpan.FromSeconds(1);
timer.Start();
}
// Timer event handler sets DateTime property.
void TimerOnTick(object sender, EventArgs args)
{
DateTime = DateTime.Now;
}代码说明:
此时间类型定义了一个DateTime属性,由名为DateTimeProperty的dependency property支持
1.
2. TimerOnTick每秒被调用一次,重新设定DateTime的值,由此会导致SetValue调用,从而引发数据更改通知
可以在XAML文件中绑定该数据,实时显示现在的时间(格式所限不显示秒)代码Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:Petzold.DigitalClock"
Title="Digital Clock"
SizeToContent="WidthAndHeight"
ResizeMode="CanMinimize"
FontFamily="Bookman Old Style"
FontSize="36pt">
<Window.Resources>
<src:ClockTicker1 x:Key="clock" />
</Window.Resources>
<Window.Content>
<Binding Source="{StaticResource clock}" Path="DateTime" />
</Window.Content>
</Window>
3. 通过定义事件(实现INotifyPropertyChanged接口)获得数据改变的通知机制
- INotifyPropertyChanged接口要求根据 PropertyChangedEventHandler delegate定义一个名为PropertyChanged(必须为该名称)的事件
- 当属性值发生改变时,触发该事件(DateTime为属性名)
if (PropertyChanged != null)
PropertyChanged(this,
new PropertyChangedEventArgs("DateTime")); -
具体实例
代码using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Threading;
namespace Petzold.FormattedDigitalClock
{
public class ClockTicker2 : INotifyPropertyChanged
{
// Event required by INotifyPropertyChanged interface.
public event PropertyChangedEventHandler PropertyChanged;
// Public property.
public DateTime DateTime
{
get { return DateTime.Now; }
}
// Constructor sets timer.
public ClockTicker2()
{
DispatcherTimer timer = new DispatcherTimer();
timer.Tick += TimerOnTick;
timer.Interval = TimeSpan.FromSeconds(1);
timer.Start();
}
// Timer event handler triggers PropertyChanged event.
void TimerOnTick(object sender, EventArgs args)
{
if (PropertyChanged != null){
PropertyChanged(this,
new PropertyChangedEventArgs("DateTime"));}
}
}
}
4. 实现格式化转换器
转换器是一个实现了IValueConverter或者IMultiValueConverter接口的类,在上一篇WPF: Binding(数据绑定)[上]有所介绍。通过在binding中传递传唤器的附加参数,可以格式化转换之后的数据。- 实现IValueConverter接口格式化转换后的数据,其中附加参数param为用来格式化的“格式字符串”
代码using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace Petzold.FormattedDigitalClock
{
public class FormattedTextConverter : IValueConverter
{
public object Convert(object value, Type typeTarget,
object param, CultureInfo culture)
{
if (param is string)
return String.Format(param as string, value);
return value.ToString();
}
public object ConvertBack(object value, Type typeTarget,
object param, CultureInfo culture)
{
return null;
}
}
}在XAML文件中使用这个转换器。
代码代码说明:<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:Petzold.FormattedDigitalClock"
Title="Formatted Digital Clock"
SizeToContent="WidthAndHeight"
ResizeMode="CanMinimize"
FontFamily="Bookman Old Style"
FontSize="36pt">
<Window.Resources>
<src:ClockTicker2 x:Key="clock" />
<src:FormattedTextConverter x:Key="conv" />
</Window.Resources>
<Window.Content>
<Binding Source="{StaticResource clock}" Path="DateTime"
Converter="{StaticResource conv}"
ConverterParameter="... {0:T} ..." />
</Window.Content>
</Window>
(1). 通过设置Binding类的ConverterParameter参数,传递格式字符串, 可以显示秒钟
(2). 绑定的数据即为实现INotifyPropertyChanged接口的ClockTicker2类的DateTime属性
(3). 注意由于语法的限制,如果要设置ConverterParameter为{0:T},必须写成:ConverterParameter="{}{0:T} " ,即在前面加一个空的大括号。 - 实现IMultiValueConverter接口格式化转换后的数据,同样使用附加的参数来实现数据格式化(只不过格式化的对象是数据数组)
代码public class FormattedMultiTextConverter : IMultiValueConverter
{
public object Convert(object[] value, Type typeTarget,
object param, CultureInfo culture)
{
return String.Format((string)param, value);
}
public object[] ConvertBack(object value, Type[] typeTarget,
object param, CultureInfo culture)
{
return null;
}
}在XAML文件中使用:
代码<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:src="clr-namespace:Petzold.EnvironmentInfo2"
Title="Environment Info">
<Window.Resources>
<src:FormattedMultiTextConverter x:Key="conv" />
</Window.Resources>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource conv}"
ConverterParameter=
"Operating System Version: {0}

.NET Version: {1}

Machine Name: {2}

User Name: {3}

User Domain Name: {4}

System Directory: {5}

Current Directory: {6}

Command Line: {7}" >
<Binding Source="{x:Static s:Environment.OSVersion}" />
<Binding Source="{x:Static s:Environment.Version}" />
<Binding Source="{x:Static s:Environment.MachineName}" />
<Binding Source="{x:Static s:Environment.UserName}" />
<Binding Source="{x:Static s:Environment.UserDomainName}" />
<Binding Source="{x:Static s:Environment.SystemDirectory}" />
<Binding Source="{x:Static s:Environment.CurrentDirectory}" />
<Binding Source="{x:Static s:Environment.CommandLine}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Window>代码说明:
(1). "
"为换行符
(2). 该代码通过x:Static获取本地计算机的静态信息,并分行打印
5. 第三种数据源:使用Binding类的RelativeSource属性
除了通过设置Binding的ElementName 和Source Property来设定绑定源,还有第三种选择:RelativeSource Property。它可以获取当前element及其祖先的有关数据
- 如想获取当前element数据,设置RelativeSource Property为
RelativeSource={RelativeSource self}
- 如想获取其父类信息,设置RelativeSource Property为
RelativeSource={RelativeSource AncestorType={x:Type StackPanel}}
- 如想获取其父类的父类的信息,设置RelativeSource Property为
RelativeSource={RelativeSource AncestorType={x
:Type StackPanel}, AncestorLevel=2}
<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
TextBlock.FontSize="12" >
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center">
<TextBlock Text="This TextBlock has a FontFamily of " />
<TextBlock Text="{Binding RelativeSource={RelativeSource self},
Path=FontFamily}" />
<TextBlock Text=" and a FontSize of " />
<TextBlock Text="{Binding RelativeSource={RelativeSource self},
Path=FontSize}" />
</StackPanel>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center">
<TextBlock Text="This TextBlock is inside a StackPanel with " />
<TextBlock Text=
"{Binding RelativeSource={RelativeSource
AncestorType={x:Type StackPanel}},
Path=Orientation}" />
<TextBlock Text=" orientation" />
</StackPanel>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center">
<TextBlock Text="The parent StackPanel has " />
<TextBlock Text=
"{Binding RelativeSource={RelativeSource
AncestorType={x:Type StackPanel}, AncestorLevel=2},
Path=Orientation}" />
<TextBlock Text=" orientation" />
</StackPanel>
</StackPanel>
代码说明:
(1)第一个和第二个绑定分别获取当前TextBlock自己的字体类型和大小
(2)第二个绑定显示当前TextBlock所在的stackpanel的Orientation属性值
(3)第二个绑定显示当前TextBlock所在的stackpanel所在的stackpanel的Orientation属性值