《Programming WPF》翻译 第4章 2.数据绑定

我们手动编写代码保证UI和数据同步。有效将两组属性隐式的绑定在一起,一组来自Person对象,另一组来自显示Person对象的控件。数据绑定用于显式的将属性从一个对象绑定到另一个,保持它们的同步,并转换为适当的类型,正如图4-7所示。

4-7



4.2.1 绑定

取代以在代码中手动设置TextBox对象的Text属性并保证它们是最新的,数据绑定允许我们使用Binding对象的实例来设置Text属性,正如示例4-8所示。

示例4-8

<TextBox >
  
<TextBox.Text>
    
<Binding Path="Age" />
  
</TextBox.Text>
</TextBox>

在示例4-8中,我们已经使用了在第一章介绍的属性元素语法,创建了一个Binding类的实例,初始化它的Path属性为“Age”,而且将Binding对象设置为TextBox对象的Text属性值。使用绑定的标签(也在第一章介绍过),我们可以示例4-8简写为示例4-9

示例4-9

<TextBox TextContent="{Binding Path=Age}" />

作为一个更短的删节版,你可以一起省略指定Path,而且Binding也可以知道这是什么意思,正如示例4-10

示例4-10

<TextBox TextContent="{Binding Age}" />

我更喜欢显示的语法声明,因此我不会使用示例4-10的语法,但我不做评价——如果你喜欢用的话

Binding类提供了各种各样有趣的工具,用来管理属性间的绑定,但是我们更关心的是Path属性。大多数场合,你可能认为Path作为一个对象的属性名,是作为数据源。因此,示例4-10binding语句建立了一个TextBoxText属性和某个对象的Name属性之间的绑定,正如图4-8所示。

4-8



在这个绑定中,
TextBox控件是绑定目标,它扮演一个消费者的角色——当绑定源——提供数据的对象有改变时。绑定目标可以是一个WPF元素,但是只允许绑定到元素的依赖属性。(在第9章介绍)

另一方面,你可以将任意公有的CLR属性绑定到绑定源,绑定源在这个示例中没有具体命名,因此我们有一些自由度——关于这个对象在运行期来自哪里,因此也易于将多个控件绑定到同一个对象上(像nameage这两个文本框,都绑定到同一个Person对象)。

普遍地,绑定源数据来自数据上下文

4.2.2 隐式的数据上下文

数据上下文是绑定机制寻找数据源的地方如果没有任何额外的特殊指令(我们随后要讨论这些指令)。在WPF中,每个FrameworkElementFrameworkContentElement都有一个DataContext属性,这个属性是Object类型的,所以可以将任何对象放入其中,例如stringPersonList<Person>等等。当寻找一个使用绑定源的对象时,绑定对象从它所定义的位置向上遍历控件树,寻找一个非空的DataContext属性。

这种遍历是便捷的,因为这意味着在同一个父控件中,任意两个控件都可以绑定到同一个数据源。例如,我们的两个文本框控件都是grid的子控件,而且每一个都会在同一个数据上下文中搜索,如图4-9所示。

4-9



工作步骤如下:

l        绑定机制在TextBox自身搜索非空DataContext属性

l        绑定机制在Grid搜索非空DataContext属性

l        绑定机制在Window搜索非空DataContext属性

为这两个文本框控件都提供一个非空DataContext,在Window1类的构造函数中,设置共用的Person对象作为gridDataContext属性,正如示例4-11

示例4-11

// Window1.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;

namespace PersonBinding {
  
public partial class Window1 : Window {
    Person person 
= new Person("Tom"9);

    
public Window1(  ) {
      InitializeComponent(  );

      
// Let the grid know its data context
      grid.DataContext = person;

      
this.birthdayButton.Click += birthdayButton_Click;
    }


    
void birthdayButton_Click(object sender, RoutedEventArgs e) {
      
// Data binding keeps person and the text boxes synchronized
      ++person.Age;
      MessageBox.Show(
        
string.Format(
          
"Happy Birthday, {0}, age {1}!",
          person.Name,
          person.Age),
        
"Birthday");
    }

  }

}

因此,为了我们的应用程序的功能性如图4-9所示,数据同步的代码减少到,为每一个显示数据的xaml属性设置一个绑定对象,以及使用数据上下文为Binding搜索数据。没有必要初始化UI代码或者事件句柄,来复制和转换数据(注意示例4-11中椭圆的不足)

清楚起见,实现INotifyPropertyChanged的用途绝非偶然。这是WPF数据绑定引擎保持UI同步于对象属性改变的接口。没有这个接口,UI的改变仍然可以传达到对象,但是绑定引擎没有办法知道什么时候改变UI

一个没有实现INotifyPropertyChanged接口的对象发生改变,绑定引擎没有办法知道什么时候改变UI——这种说法不是完全正确。一种可以知道的方法是,如果一个对象实现了PropertyNameChanged事件——正如.NET 1.x中规定的数据绑定(如SizeChangedTextChanged等等)——而.NET是向后兼容的。另一种方法是,手动调用BindingExpression对象的UpdateTarget方法,联合赈灾讨论的绑定机制。

然而,可靠的说,实现INotifyPropertyChanged是一种可取的方式,来支持属性改变通知,在WPF数据绑定中。

4.2.3可声明的数据

当我们的程序还在尝试去模仿更复杂的应用程序时,可能是从持久化形式加载“私人数据”,以及将其保存在应用程序的Session中,不难想象这样的场景,在编译期就知道某些数据的位置。可能是示例数据(如前面示例中的Tom);或者是众所周知的数据,而且在Session中不会改变,如

应用程序默认设置或错误信息。很多应用程序有独立于UI工作的字符串资源,但是仍然包装在这个应用程序中——这样做使之易于维护和本地化,将其脱离与UI逻辑本身,降低了数据与UI的耦合。到目前为止,在我们的示例中,我们已经将这些众所周知的数据保存在代码中,但是xaml是一个更好的选择,不仅因为易于在xaml中维护数据,还有xaml对本地化的支持(在第6章介绍)。

正如在第1章讨论到的,xaml是一种描述对象图表的语言,因此实际上任何带有默认构造函数的类型都可以在xaml中初始化,回忆示例4-2Person类有一个默认的构造函数,因此我们可以创建一个Person实例在我们的xaml应用程序中,如示例4-12

示例4-12

 

<?Mapping XmlNamespace="local" ClrNamespace="PersonBinding" ?>
<Window  xmlns:local="local">
  
<Window.Resources>
    
<local:Person x:Key="Tom" Name="Tom" Age="9" />
  
</Window.Resources>
  
<Grid></Grid>
</Window>

我们在window标签中的资源元素中创建了一些“数据岛”,使用xaml的映射语法(在第一章介绍),引进了Person类型。

通过在xaml中使用指定的Person标签,我们能够声明性的设置gridDataContext属性,而不是在后台代码文件中以编程方式设置,如示例4-13

示例4-13

<!-- Window1.xaml -->
<?Mapping XmlNamespace="local" ClrNamespace="PersonBinding" ?>
<Window  xmlns:local="local">
  
<Window.Resources>
    
<local:Person x:Key="Tom" Name="Tom" Age="9" />
  
</Window.Resources>
  
<Grid DataContext="{StaticResource Tom}">
    
    
<TextBlock >Name:</TextBlock>
    
<TextBox  Text="{Binding Path=Name}" />
    
<TextBlock >Age:</TextBlock>
    
<TextBox  Text="{Binding Path=Age}" />
    
<Button  x:Name="birthdayButton">Birthday</Button>
  
</Grid>
</Window>

现在,我们将Person对象的创建转移到了xaml中,我们已经更新了Birthday按钮的Click事件句柄,从使用一个成员变量到使用定义在资源中的数据,正如示例4-14所示。

示例4-14

public partial class Window1 : Window {
  
  
void birthdayButton_Click(object sender, RoutedEventArgs e) {
    Person person 
= (Person)this.FindResource("Tom"));
    
++person.Age;
    MessageBox.Show();
  }

}

在示例4-14中,我们使用了FindResource方法(在第一章介绍过,会在第6章详细介绍),用来从主窗体的资源中拖出Person对象。通过最小的改动,结果

是再次呈现图4-6所示的两个窗体。唯一的不同是不必接触后台代码文件就可以在编译期维护或本地化已知数据。(第6章讨论了本地化xaml资源)

4.2.4显示的数据源

一旦你已经得到一个命名的资源,你可以在xaml中显示的绑定对象源,而不是依赖于隐式的绑定到控件树上某处的一个非空的DataContext属性。显示的数据源方式是有用的,如果你有多个数据源,例如,两个Person对象。设置显示的数据源给绑定中的Source属性,如示例4-15所示。

示例4-15

 

<!-- Window1.xaml -->
<Window >
  
<Window.Resources>
    
<local:Person x:Key="Tom"  />
    
<local:Person x:Key="John"  />
  
</Window.Resources>
  
<Grid>
    
    
<TextBox x:Name="tomTextBox"
      Text
="
        {Binding
          Path=Name,
          Source={StaticResource Tom}}"
 />

    
<TextBox x:Name="johnTextBox"
      Text
="
        {Binding
          Path=Name,
          Source={StaticResource John}}"
 />
    
  
</Grid>
</Window>

在示例4-14中,,我们绑定了两个文本框到两个Person对象,使用Binding对象的Source属性,显示的绑定到每个Person对象。

隐式绑定和显示绑定的对比

通常说,当我们在多个控件中共享数据时,我发现隐式的绑定方式最有用,因为所有需要的是一点代码,用来在一个单独的父对象上设置DataContext属性。另一方面,如果我已经得到多个数据源,我真的喜欢使用Source属性在我们Binding对象上,从而使数据来源更加清晰。

4.2.5数值转换

目前为止,我们的应用程序示例展示了绑定数据到文本框的文字。然而,这并没有完全阻止你绑定到控件的其他属性,例如ForegroundFontWeightHeight等等。举例来说,我们可能判定任何大于25岁的都是无所顾虑的,因此应该在UI中标记为红色(或者,它们是濒临灭绝的。无论哪一个都使你更加可能推荐这本书给你的朋友)。当某人的年龄随着点击Birthday按钮而增长,我们想要保持UI是最新的,这意味着我们已经得到了数据绑定的完美候选者。想象下面示例4-16的能力。

示例4-16

<!-- Window1.xaml -->
<Window >
  
<Window.Resources>
    
<local:Person x:Key="Tom"  />
  
</Window.Resources>
  
<Grid>
    
    
<TextBox
      
Text="{Binding Path=Age}"
      Foreground
="{Binding Path=Age, }"
      
    
/>
    
  
</Grid>
</Window>

在示例4-16中,我们将age文本框的Text属性绑定到Person对象的Age属性,正如我们已经看到的,但是我们也已经将文本框的Foreground属性绑定到了Person对象的同一个Age属性。随着Tom年龄的改变,我们想要更新文本框的前景色。然而,由于AgeInt32类型的而ForegroundBrush类型的,这就需要一个从Int32Brush的映射,从而应用数据帮定从AgeForeground。这就是值转换器的工作。

一个值转换器(或者简称为“转换器”),是IValueConverter接口的一种实现,其中有两个方法:ConvertConvertBackConvert方法用于将源数据转换为UI目标数据,如从Int32BrushConvertBack方法用于将UI数据转换回源数据。在这两种情形中,dangqian值和希望转换的目标类型都需要传入方法中。

为了将一个Age属性的Int32类型转换为Foreground属性的Brush,我们可以在Convert方法中,按照我们满意的方式,实现无论哪一种映射。如图4-17

示例4-17

 

public class AgeToForegroundConverter : IValueConverter {
  
// Called when converting the Age to a Foreground brush
  public object Convert(object value, Type targetType, {
    Debug.Assert(targetType 
== typeof(Brush));

    
// DANGER! After 25, it's all down hill
    int age = int.Parse(value.ToString(  ));
    
return (age > 25 ? Brushes.Red : Brushes.Black);
  }


  
// Called when converting a Foreground brush back to an Age
  public object ConvertBack(object value, {
    
// should never be called
    throw new NotImplementedException(  );
  }

}

在示例4-17中,我们已经实现了Convert方法,以再次确认我们寻找到一个Foreground笔刷,并以不同的年龄分发适当的笔刷。既然我们没有提供任何工具来改变用来显示年龄的Foreground笔刷,就没有理由实现ConvertBack方法了。

我选择AgeToForegroundConverter作为类的名称,是因为我有特殊的意图:我在我的转换器中生成了Int32装换为Brush的简单转换。即使这个转换器可以被嵌入到任何地方用来将Int32装换为Brush,我也可能有非常不一样的需求对于HeightToBackgroundConverter而言,仅仅作为一个示例。

一旦你得到一个转换器类,这将易于在xaml中创建一个实例,就像我们刚刚对Person对象所做的,如示例4-18所示。

示例4-18

<!-- Window1.xaml -->
<?Mapping XmlNamespace="local" ClrNamespace="PersonBinding" ?>
<Window  xmlns:local="local">
  
<Window.Resources>
    
<local:Person x:Key="Tom"  />
    
<local:AgeToForegroundConverter
      
x:Key="AgeToForegroundConverter" />
  
</Window.Resources>
  
<Grid DataContext="{StaticResource Tom}">
    
    
<TextBox
      
Text="{Binding Path=Age}"
      Foreground
="
        {Binding
          Path=Age,
          Converter={StaticResource AgeToForegroundConverter}}"

       
/>
    
  
</Grid>
</Window>


在示例4-18中,一旦我们在xaml中有了一个命名的转换器对象,我们确定它作为Age属性和Foreground笔刷的装换器,通过设置绑定对象的Converter属性。图4-10显示了这一转换的结果。

4-10



在图
4-10中,注意到随着Tom的年龄从开端增长,转换器转换笔刷的前景色从黑到红。当数据改变时这个改变迅速发生,并没有显示的代码强迫使之执行,正如使用其他种类的数据绑定一样。

posted @ 2008-04-04 12:24  包建强  Views(1339)  Comments(4Edit  收藏  举报