WPF程序设计指南: Binding(数据绑定)[下]

时间:2022-02-06 11:45:23

注:一下内容及代码基本来自Charles Petzold著,蔡学庸译,电子工业出版社出版的《Windows Presentation Foundation 程序设计指南》一书

引言:在上一篇WPF: Binding(数据绑定)[上]中所提到的Binding,都是使用ElementName Property来设置数据源的,这一篇将叙述如何通过Binding的另外两个属性:SourceRelativeSource来设定绑定源(注意:这三个属性只能设置一个,否则会冲突),以及“如何让数据可以当作绑定源”和“如何在格式化转换源数据”

1. Binding一个对象的静态字段或者静态属性

  • 在前面的一篇文章:WPF:Resource中我们知道,获取一个类型的静态字段或者静态属性的时候,我们可以使用x:Static获得,但是当我们需要获取一个某一个对象的静态字段或者属性,这时候我们就需要用到绑定。
  • 例如,System.Globalization命名空间中的DateTimeFormatInfo类型的DayNames属性(非静态),它返回一个字符串数组,是一周中每一天的名字。其中DateTimeFormatInfo类本身提供的一个静态属性:DateTimeFormatInfo.CurrentInfo,返回一个DateTimeFormatInfo类型的实例,表示当前计算机设置的文化的时间信息
    WPF程序设计指南: Binding(数据绑定)[下]WPF程序设计指南: Binding(数据绑定)[下]代码
            
            
            
    < 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 >
    代码说明:
    1. 第一个绑定,将保存有七天的名称在字符串数组设置为ListBox的ItemsSource,显示出来
    2. 第二个绑定,将ListBox中选定的一天显示在TextBlock中。
    3. 此绑定的Mode属性只能为OneTime,因为DateTimeFormatInfo类型没有数据发生改变是发出通知的机制,不会在星期的日子发生改变时发出通知,因此此绑定只能获取DayNames属性一次。

2. 通过定义依赖属性(继承DenpendencyObject类)获得数据改变的通知机制 

  • 当一个属性为依赖属性(Denpendency Property)时,数据改变的通知机制会自动获得,因此,如果需要一个数据源到目标实现多次传递(实时在目标上反映出更新),需要定义Denpendency Property。
  • 如果数据源不是Visual对象,可以选择不继承自FrameworkElement(这也是一种定义Denpendency Property的方法),而是继承自DenpendencyObject类,这个类提供了你要用来实现Denpendency Property的“SetValue和GetValue方法”
  • 以下是一个自定义的时间类型,继承自DenpendencyObject类:

    WPF程序设计指南: Binding(数据绑定)[下]WPF程序设计指南: Binding(数据绑定)[下]代码
         
         
         
    // 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;
    }

    代码说明:
    1.

    此时间类型定义了一个DateTime属性,由名为DateTimePropertydependency property支持
    2. TimerOnTick每秒被调用一次,重新设定DateTime的值,由此会导致SetValue调用,从而引发数据更改通知
    可以在XAML文件中绑定该数据,实时显示现在的时间(格式所限不显示秒)

    WPF程序设计指南: Binding(数据绑定)[下]WPF程序设计指南: Binding(数据绑定)[下]代码
         
         
         
    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 " ));
  • 具体实例

    WPF程序设计指南: Binding(数据绑定)[下]WPF程序设计指南: Binding(数据绑定)[下]代码
          
          
          
    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中传递传唤器的附加参数,可以格式化转换之后的数据。

  1. 实现IValueConverter接口格式化转换后的数据,其中附加参数param为用来格式化的“格式字符串”
    WPF程序设计指南: Binding(数据绑定)[下]WPF程序设计指南: Binding(数据绑定)[下]代码
         
         
         
    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文件中使用这个转换器。

    WPF程序设计指南: Binding(数据绑定)[下]WPF程序设计指南: Binding(数据绑定)[下]代码
         
         
         
    < 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} " ,即在前面加一个空的大括号。
  2. 实现IMultiValueConverter接口格式化转换后的数据,同样使用附加的参数来实现数据格式化(只不过格式化的对象是数据数组)
    WPF程序设计指南: Binding(数据绑定)[下]WPF程序设计指南: Binding(数据绑定)[下]代码
         
         
         
    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文件中使用:

    WPF程序设计指南: Binding(数据绑定)[下]WPF程序设计指南: Binding(数据绑定)[下]代码
         
         
         
    < 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}
    &#x000A;.NET Version: {1}
    &#x000A;Machine Name: {2}
    &#x000A;User Name: {3}
    &#x000A;User Domain Name: {4}
    &#x000A;System Directory: {5}
    &#x000A;Current Directory: {6}
    &#x000A;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). "&#x000A"为换行符
     (2). 该代码通过x:Static获取本地计算机的静态信息,并分行打印 

5. 第三种数据源:使用Binding类的RelativeSource属性

除了通过设置Binding的ElementNameSource Property来设定绑定源,还有第三种选择:RelativeSource Property。它可以获取当前element及其祖先的有关数据

  1. 如想获取当前element数据,设置RelativeSource Property为
          
          
          
    RelativeSource = {RelativeSource self}
  2. 如想获取其父类信息,设置RelativeSource Property为
           
           
           
    RelativeSource = {RelativeSource AncestorType = {x:Type StackPanel}}
  3. 如想获取其父类的父类的信息,设置RelativeSource Property为
            
            
            
    RelativeSource = {RelativeSource AncestorType = {x
    :Type StackPanel}, AncestorLevel
    = 2 }

 完整的实例:  
WPF程序设计指南: Binding(数据绑定)[下]WPF程序设计指南: Binding(数据绑定)[下]代码
       
       
       
< 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属性值