WPF:初学者指南 - 第 5 部分(共 n 部分)






4.97/5 (154投票s)
WPF数据绑定的介绍。
序言与致谢
我是一名.NET程序员,但工作繁忙。我使用VB.NET和C#,ASP.NET / WinForms / WPF / WCF,Flash,Silverlight,等等。基本上,我什么都涉猎。但是当我开始写这个系列文章时,我自然选择了我的最爱语言(碰巧是C#)。后来我收到一封邮件,有人要求我用VB.NET和C#发布这个系列的代码。我简单地说我没有时间。所以这个人(Robert Ranck)主动提出帮助,根据我的原始C#项目进行VB.NET的翻译。
因此,对于本文和随后您将在这里找到的VB.NET项目,我请您感谢Robert Ranck。谢谢Robert,您的贡献必将使这个系列对所有.NET开发人员更加开放。
还要感谢Karl Shifflett(又名博客/文章机器,也称为Molenator),他回答了我愚蠢的VB.NET问题。我还想提一下,Karl刚刚开始了一个更高级的WPF系列文章(目前将是VB.NET,但希望也会出现C#版本)。Karl的新系列非常出色,我敦促大家鼓励Karl继续这个系列。用一种语言写完整个系列都不容易,更何况两种。Karl的第一篇文章就在这里,您可以自己看看。我个人很喜欢。
引言
本文是我的WPF初学者系列文章中的第五篇。在本文中,我们将讨论数据绑定。本系列文章的计划安排仍大致如下:
- 布局
- XAML 与 代码 / 标记扩展和资源
- 命令和事件
- 依赖属性(上一篇文章)
- 数据绑定(本文)
- 样式/模板
在本文中,我旨在简要介绍以下内容:
我将**不会**涵盖以下基于集合的绑定领域,因此如果您想了解更多相关信息,您需要使用提供的链接进行一些额外研究(看,我至少研究了正确的链接给您)
数据绑定背后的通用概念
数据绑定其实并不是什么新东西(好吧,WPF中如何实现是新的),但我们已经用ASP.NET和WinForms绑定了一段时间了。WPF借鉴了两者的优点,创建了一个非常非常好的绑定框架。但是,对于那些从未听说过的人来说,这种绑定到底是什么呢?
嗯,基本上,绑定允许UI元素从其他UI元素或业务逻辑对象/类获取它们的数据。所以从理论上讲,它非常简单,我们有一个提供值的源,我们有一个想要使用源值的目标,我们通过绑定将它们“粘合”在一起。
典型的绑定安排如下
通常,每个绑定都包含这四个组件:绑定目标对象、目标属性、绑定源以及绑定源中要使用的值的路径。
目标属性必须是依赖属性(我刚讲过)。大多数UIElement属性都是依赖属性,大多数依赖属性(只读属性除外)默认支持数据绑定。
这是绑定中发生情况的非常简化版本。
当然,为了方便这些绑定操作,需要考虑许多独立的因素和语法片段。在下面的小节中,您将看到一些(不,不是所有,我一年都讲不完)绑定语法和创建良好绑定 Ideas/Approaches。
数据上下文 (DataContext)
在我们深入了解数据绑定的细节之前,有一件重要的事情需要了解,那就是每个FrameworkElement
都具有的DataContext
属性。DataContext
是一个概念,它允许元素从其父元素继承有关用于绑定的数据源信息,以及绑定的其他特性,例如路径。DataContext
可以直接设置为公共语言运行时(CLR)对象,绑定将评估该对象的属性。或者,您可以将数据上下文设置为DataSourceProvider
对象。
此依赖属性继承属性值。如果子元素没有通过局部值或样式建立的其他DataContext
值,则属性系统将把该值设置为最近的已分配此值的父元素的DataContext
值。
在XAML中,DataContext
最常被设置为绑定声明。您可以使用属性元素语法或属性语法。通常设置如下
<Window.Resources>
<src:LeagueList x:Key="MyList" />
...
...
</Window.Resources>
...
...
<DockPanel DataContext="{Binding Source={StaticResource MyList}}">
您还可以通过使用<someElement>.DataContext = <someValue>
格式的代码来设置DataContext
。
另一件需要注意的事情是,如果某个对象继承自父级的DataContext
,但省略了它将用于绑定的字段,例如
<MenuItem Tag="{Binding}">
这意味着用作其父级DataContext
的整个对象将用于分配给Tag
属性。
基本数据绑定概念
在我们开始深入探讨数据绑定之前,有几个关键领域需要首先介绍。即
- 数据流向
- 什么触发源更新
所以我们只花一点时间讨论这两个主题
数据流向
如上文数据绑定背后的通用概念一节所示,绑定的流向可以是双向的。在使用数据绑定时,可以配置多种可能性。这些通过Binding.Mode
值来控制
OneWay
绑定导致源属性的更改自动更新目标属性,但目标属性的更改不会传播回源属性。如果被绑定的控件是隐式只读的,则此类型的绑定是合适的。TwoWay
绑定导致源属性或目标属性的更改自动更新另一个。此类型的绑定适用于可编辑表单或其他完全交互式UI场景。OneTime
导致源属性初始化目标属性,但后续更改不会传播。这意味着如果数据上下文发生更改或数据上下文中的对象发生更改,则更改将反映在目标属性中。如果使用当前状态快照或数据确实是静态的,则此类型的绑定是合适的。OneWayToSource
是OneWay
绑定的反向;当目标属性改变时,它会更新源属性。Default
导致使用目标属性的默认Mode
值。
使用Binding.Mode
属性来指定数据流的方向。为了检测单向或双向绑定中的源更改,源必须实现合适的属性更改通知机制,例如INotifyPropertyChanged
。有关示例,请参阅:如何:实现属性更改通知。
更改通知是数据绑定中需要学习的非常重要的一课,我们现在就需要了解它。所以,让我们看看使用接口INotifyPropertyChanged
的例子。
为了支持OneWay
或TwoWay
绑定,使您的绑定目标属性能够自动反映绑定源的动态变化,您的类需要提供适当的属性更改通知;这就是INotifyPropertyChanged
的使用之处。
using System.ComponentModel;
namespace SDKSample
{
// This class implements INotifyPropertyChanged
// to support one-way and two-way bindings
// (such that the UI element updates when the source
// has been changed dynamically)
public class Person : INotifyPropertyChanged
{
private string name;
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
public Person()
{
}
public Person(string value)
{
this.name = value;
}
public string PersonName
{
get { return name; }
set
{
name = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("PersonName");
}
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
这是VB.NET版本
Imports System.ComponentModel
' This class implements INotifyPropertyChanged
' to support one-way and two-way bindings
' (such that the UI element updates when the source
' has been changed dynamically)
Public Class Person
Implements INotifyPropertyChanged
Private personName As String
Sub New()
End Sub
Sub New(ByVal Name As String)
Me.personName = Name
End Sub
' Declare the event
Public Event PropertyChanged As PropertyChangedEventHandler
Implements INotifyPropertyChanged.PropertyChanged
Public Property Name() As String
Get
Return personName
End Get
Set(ByVal value As String)
personName = value
' Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("Name")
End Set
End Property
' Create the OnPropertyChanged method to raise the event
Protected Sub OnPropertyChanged(ByVal name As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
End Sub
End Class
要实现INotifyPropertyChanged
,您需要声明PropertyChanged
事件并创建OnPropertyChanged
方法。然后,对于您想要更改通知的每个属性,只要该属性更新,您就调用OnPropertyChanged
。
我无法向您解释INotifyPropertyChanged
接口有多么重要,但请相信我,它非常非常重要,如果您打算在WPF中使用绑定,请习惯使用INotifyPropertyChanged
接口。
什么触发源更新
TwoWay
或OneWayToSource
绑定会监听目标属性的更改并将其传播回源。这被称为更新源。例如,您可以编辑TextBox
的文本来更改底层源值。如上一节所述,数据流的方向由绑定的Binding.Mode
属性的值决定。
然而,您的源值是在您编辑文本时更新,还是在您编辑完文本并将鼠标从TextBox
移开后更新?绑定的Binding.UpdateSourceTrigger
属性决定了什么触发源的更新。可用的选项如下
下表以TextBox
为例,为每个Binding.UpdateSourceTrigger
值提供了一个示例场景
UpdateSourceTrigger值 | 源值何时更新 |
LostFocus (TextBox.Text 的默认值) |
当TextBox 控件失去焦点时 |
PropertyChanged |
在您键入TextBox 时 |
Explicit (显式) |
当应用程序调用UpdateSource 时 |
基本数据绑定语法
在Binding
类中有许多属性可以使用,因此,我没有时间涵盖所有这些属性,但我将尝试讲解最常见的语法片段。由于大多数绑定通常在XAML中设置,我将重点关注XAML语法,但需要注意的是,在XAML中可以完成的任何事情也可以在C#/VB.NET代码中完成。
好的,那么我们来看看基本语法(我们将在下面的部分中介绍更高级的内容)。
最基本的绑定形式是创建绑定到现有元素值的绑定(这将在下面更详细地介绍)。我只是想先介绍语法,然后回过头来向您展示如何完成Binding.Mode
和Binding.UpdateSourceTrigger
。
所以这里可能是你将看到的最简单的绑定之一。这个例子有两个按钮:第一个按钮 (btnSource
) 有一个黄色的Background
属性。第二个按钮使用第一个按钮 (btnSource
) 作为Binding
的源,其中第一个按钮 (btnSource
) 的Background
值被用来设置第二个按钮的Background
。
<Button x:Name="btnSource" Background="Yellow"
Width="150" Height="30">Yellow BUtton</Button>
<Button Background="{Binding ElementName=btnSource, Path=Background}"
Width="150"
Height="30">I am bound to be Yellow Background</Button>
这很简单,对吧?但我只是想回过头来快速看看我们如何在绑定语法中使用Binding.Mode
和Binding.UpdateSourceTrigger
属性。
结果是,这相当容易。我们只需将额外的属性及其所需值添加到绑定表达式中,例如
<TextBox x:Name="txtSource" Width="150" Height="30"/>
<TextBox Width="150" Height="30" Text="{Binding ElementName=txtSource,
Path=Text, Mode=TwoWay, UpdateSourceTrigger=LostFocus }"/>
重要提示
回忆起第2部分,我提到Binding
是一个标记MarkupExtension
。因此,XAML解析器知道如何处理{}部分。但实际上,这只是一种简写,(如果您愿意)也可以使用更长、更详细的语法来表达,如下所示
<Button Margin="10,0,0,0" Content="Im bound to btnSource, using long Binding syntax">
<Button.Background>
<Binding ElementName="btnSource" Path="Background"/>
</Button.Background>
</Button>
这是一个您必须自己做出的决定;就我个人而言,我更喜欢{}语法,尽管如果您使用{}语法,在Visual Studio中将无法获得任何智能感知帮助。
绑定到UI元素
当您着手设置绑定时,需要考虑几个不同的事项
- 我想绑定到哪个属性?
- 我们应该从哪个属性进行绑定?
- 它需要是
OneWay
还是TwoWay
等?如果需要是TwoWay
/OneWayToSource
绑定,那么源属性是依赖属性吗?必须是依赖属性才能执行TwoWay
/OneWayToSource
绑定。
一旦你了解或考虑了所有这些,它真的就像ABC一样简单。作为演示解决方案的一部分,你会找到一个名为“BindingToUIElements”的项目,运行后它将看起来像下面这样
这个简单的演示应用程序展示了三种不同的绑定。我将简要讨论一下。
1. 简单元素绑定(默认模式)
这个简单的例子使用第一个按钮的Background
作为其他两个Button
的Background
的源值。
其代码如下:
<!-- Simple Element Binding-->
<Label Content="Simple Element Binding"
Margin="5,0,0,0" FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Horizontal"
Margin="10,10,10,10" Background="Gainsboro">
<Label Content="Simple Element Binding"/>
<Button x:Name="btnSource" Margin="10,0,0,0"
Background="Pink" Content="Im btnSource"/>
<Button Margin="10,0,0,0" Background="{Binding ElementName=btnSource,
Path=Background}" Content="Im bound to btnSource"/>
<Button Margin="10,0,0,0"
Content="Im bound to btnSource, using long Binding syntax">
<Button.Background>
<Binding ElementName="btnSource" Path="Background"/>
</Button.Background>
</Button>
</StackPanel>
2. 更精细的绑定(默认模式)
这个简单的例子使用ComboBox
的SelectedItem.Content
作为绑定的源。其中Button
的Background
根据ComboBox
的SelectedItem.Content
而改变。
其代码如下:
<Label Content="More Elaborate Binding"
Margin="5,0,0,0" FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Horizontal"
Margin="10,10,10,10" Background="Gainsboro">
<Label Content="Choose a color"/>
<ComboBox Name="cmbColor" SelectedIndex="0">
<ComboBoxItem>Green</ComboBoxItem>
<ComboBoxItem>Blue</ComboBoxItem>
<ComboBoxItem>Red</ComboBoxItem>
</ComboBox>
<Button Margin="10,0,0,0" Background="{Binding ElementName=cmbColor,
Path=SelectedItem.Content}" Content="Im bound to btnSource"/>
</StackPanel>
3. 使用UpdateSourceTrigger的双向绑定
这个简单的例子使用了两个TextBox
,应用了TwoWay
Binding.Mode
,并且Binding.UpdateSourceTrigger
设置为PropertyChanged
,这意味着当第二个TextBox
的值改变时,绑定的源将被更新。
其代码如下:
<!-- Using UpdateSourceTrigger/Mode-->
<Label Content="Using UpdateSourceTrigger/Mode"
Margin="5,0,0,0" FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Horizontal"
Margin="10,10,10,10" Background="Gainsboro">
<TextBlock TextWrapping="Wrap"
Text="This uses TwoWay Binding and
UpdateSourceTrigger=PropertyChanged.Type
in one textbox then the other,
and see them update each other"
Width="400"/>
<TextBox x:Name="txtSource" Width="50"
Height="25" Margin="5,0,0,0"/>
<TextBox Width="50" Height="25" Margin="5,0,0,0"
Text="{Binding ElementName=txtSource,
Path=Text, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged }"/>
</StackPanel>
绑定到XML
如今,XML被大量使用,无论是用于配置信息和数据交换,甚至用于UI设计。请记住,XAML是XML的派生。
但这并非XML数据的所有用途。我们实际上可以绑定到XML数据。这在XAML中相当容易实现。我们可以让XAML包含XML数据(尽管这可能不常见),或者使用外部XML文件。
无论哪种方式,通常的做法是在XAML/代码中使用XmlDataProvider
。就像我前面说的,我认为大多数绑定都会在XAML中完成,我也会坚持这种做法。
作为演示解决方案的一部分,您将找到一个名为“BindingToXML”的项目,运行后它将看起来如下:
此演示应用程序的前两个部分使用XAML中包含的XML数据,后两个部分使用外部XML文件。
使用XAML中包含数据的XmlDataProvider(内联XML数据)
完全有可能将所有XML数据都保存在XAML文件中,并使用这些数据作为绑定的源。让我们看一个例子
<Window x:Class="BindingToXML.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
WindowStartupLocation="CenterScreen"
Title="Window1" Height="800" Width="800">
<Window.Resources>
<!-- inline XML data-->
<XmlDataProvider x:Key="xmlData" XPath="Films">
<x:XData>
<Films xmlns="">
<Film Type="Horror" Year="1987">
<Director>Sam Raimi</Director>
<Title>Evil Dead II</Title>
</Film>
<Film Type="Murder" Year="1991">
<Director>Jonathan Demme</Director>
<Title>Silence Of The Lambs</Title>
</Film>
<Film Type="Sc" Year="1979">
<Director>Ridley Scott</Director>
<Title>Alien</Title>
</Film>
</Films>
</x:XData>
</XmlDataProvider>
</Window.Resources>
<ScrollViewer>
<StackPanel Orientation="Vertical">
<!-- Simple XPath Binding using inline XML data-->
<Label Content="Show all Films (using inline XML data)"
Margin="5,0,0,0" FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Horizontal" Margin="10,10,10,10"
Background="Gainsboro">
<ListBox ItemsSource="{Binding Source={StaticResource xmlData},
XPath=Film/Title}"/>
</StackPanel>
<!-- More Complicated XPath Binding using inline XML data-->
<Label Content="Show Only Films After 1991 (using inline XML data)"
Margin="5,0,0,0" FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Horizontal" Margin="10,10,10,10"
Background="Gainsboro">
<ListBox ItemsSource="{Binding Source={StaticResource xmlData},
XPath=*[@Year>\=1991]}"/>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Window>
从这个例子可以看出,我们为XmlDataProvider
使用了内联(在XAML中)XML数据集。并且我们将XmlDataProvider
用作ListBox
的BindingSource
。在这个例子中,第一个ListBox
显示了所有电影/标题,因为我们只是使用Binding.XPath=Film/Title
来获取电影/标题节点集,所以我们获得了所有显示的标题。
第二个ListBox
有点挑剔,它使用了一点XPath符号来遍历属性轴,并且只获取那些年份大于1991的节点,因此返回的节点较少。
使用外部XML文件的XmlDataProvider
正如我所说,更常见的是将外部XML文件与XmlDataProvider
一起使用,这可以通过以下方式完成。其中XmlDataProvider
的Source
属性设置为外部XML文件。
<XmlDataProvider x:Key="xmlDataSeperateFile"
XPath="Resteraunts" Source="XMLFile1.xml">
</XmlDataProvider>
这种安排与我们之前看到的非常相似,我们可以使用XPath获取所有节点,或者使用XPath只匹配那些属性符合要求的节点。在这个例子中,第二个ListBox
只显示XML文件中的“墨西哥”餐厅,使用以下XPath
<ListBox ItemsSource="{Binding Source={StaticResource
xmlDataSeperateFile}, XPath=*[@Type\=\'Mexican\']}"/>
这是完整的例子
<Window x:Class="BindingToXML.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
WindowStartupLocation="CenterScreen"
Title="Window1" Height="800" Width="800">
<Window.Resources>
<!-- external XML data-->
<XmlDataProvider x:Key="xmlDataSeperateFile" XPath="Resteraunts"
Source="XMLFile1.xml">
</XmlDataProvider>
</Window.Resources>
<ScrollViewer>
<StackPanel Orientation="Vertical">
<!-- Simple XPath Binding using seperate XML data-->
<Label Content="Show all Resteraunts (using seperate XML data)"
Margin="5,0,0,0" FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Horizontal" Margin="10,10,10,10"
Background="Gainsboro">
<ListBox ItemsSource="{Binding Source=
{StaticResource xmlDataSeperateFile},
XPath=Resteraunt/Name}"/>
</StackPanel>
<!-- More Complicated XPath Binding using seperate XML data-->
<Label Content="Show Only Mexican Resteraunts (using inline XML data)"
Margin="5,0,0,0" FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Horizontal" Margin="10,10,10,10"
Background="Gainsboro">
<ListBox ItemsSource="{Binding Source={StaticResource xmlDataSeperateFile},
XPath=*[@Type\=\'Mexican\']}"/>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Window>
这是相关的XML文件(如果您对其结构感到好奇的话)
<?xml version="1.0" encoding="utf-8" ?>
<Resteraunts xmlns="">
<Resteraunt Type="Meat">
<Name>The MeatHouse</Name>
<Phone>01237 78516</Phone>
<email>yada@here.com</email>
</Resteraunt>
<Resteraunt Type="Veggie">
<Name>VegHead</Name>
<Phone>99999</Phone>
<email>mmm-vegies@here.com</email>
</Resteraunt>
<Resteraunt Type="Mexican">
<Name>Mexican't (El Mariachi)</Name>
<Phone>464654654</Phone>
<email>mex@here.com</email>
</Resteraunt>
</Resteraunts>
注意:使用XML提供绑定值是可行的,但不要指望通过将Binding.Mode
设置为TwoWay
就能简单地更新XML。那行不通。XML数据绑定是一种简单的单向/不可更新的安排。
绑定到XLINQ
虽然我不会深入探讨这一点,但绑定女王Beatriz Costa在这里有一篇很棒的博客文章此处,如果您好奇的话。
绑定到集合
通常,WPF开发很可能会在某个时候涉及到绑定到整个集合。现在,这在WPF中做起来非常非常容易。和大多数事情一样,有很多方法可以做到这一点。我将概述两种可能的方法,但当然,会有更多。我只是喜欢这些方法,仅此而已。
有一件重要的事情**总是**要记住,那就是变更通知。回想一下,我们通过使用INotifyPropertyChanged
接口来解决单个类的问题。但是对于将包含对象的集合,我们应该怎么做呢?
好在,现在有一个很好的ObservableCollection
,它很好地填补了这个空缺。这个集合负责在集合中添加/删除元素时通知UI。我们仍然需要确保每个持有的对象使用INotifyPropertyChanged
接口进行自己的更改通知。但是通过使用ObservableCollection
和实现INotifyPropertyChanged
的类,我们就可以高枕无忧了,任何变化都不会被我们错过。
作为演示解决方案的一部分,您会找到一个名为“BindingToCollections”的项目,运行后会如下图所示
因此,绑定到这样的集合变得轻而易举。这里有两种可能使用ListBox
es的方法:
在代码隐藏中绑定到集合
第一个ListBox
的ItemSource
在代码隐藏中设置如下
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
namespace BindingToCollections
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
private People people = new People();
public Window1()
{
InitializeComponent();
people.Add(new Person { PersonName = "Judge Mental" });
people.Add(new Person { PersonName = "Office Rocker" });
people.Add(new Person { PersonName = "Sir Real" });
this.lstBox1.ItemsSource = people;
}
}
}
以及VB.NET版本
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Data
Imports System.Windows.Documents
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Imaging
Imports System.Windows.Navigation
Imports System.Windows.Shapes
Imports System.Collections.ObjectModel
''' <summary>
''' Interaction logic for Window1.xaml
''' </summary>
Partial Public Class Window1
Inherits Window
Private people As New People()
Public Sub New()
InitializeComponent()
Dim _Person As New Person
_Person.PersonName = "Judge Mental"
people.Add(_Person)
_Person.PersonName = "Office Rocker"
people.Add(_Person)
_Person.PersonName = "Sir Real"
people.Add(_Person)
lstBox1.ItemsSource = people
End Sub
End Class
此绑定的匹配XAML如下
<Window x:Class="BindingToCollections.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindingToCollections"
WindowStartupLocation="CenterScreen"
Title="Window1" Height="800" Width="800">
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<StackPanel Orientation="Vertical">
<!-- ListBox Source Set In Code Behind-->
<StackPanel Orientation="Vertical">
<Label Content="ListBox Source Set In Code Behind"
Margin="5,0,0,0" FontSize="14"
FontWeight="Bold" />
<ListBox x:Name="lstBox1"/>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Window>
在XAML中绑定到集合
第二个ListBox
的ItemSource
在XAML中设置如下
<Window x:Class="BindingToCollections.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindingToCollections"
WindowStartupLocation="CenterScreen"
Title="Window1" Height="800"
Width="800">
<Window.Resources>
<local:People x:Key="people">
<local:Person PersonName="Freddy Star"/>
<local:Person PersonName="Stick Head"/>
</local:People>
</Window.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<StackPanel Orientation="Vertical">
<!-- ListBox Source Set By Using Resources -->
<StackPanel Orientation="Vertical">
<Label Content="ListBox Source Set By Using Resources"
Margin="5,0,0,0" FontSize="14"
FontWeight="Bold" />
<ListBox x:Name="lstBox2"
ItemsSource="{Binding Source={StaticResource people}}"/>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Window>
您可以看到,我们在XAML的资源部分中直接拥有一个People
对象的实例,并且声明的People
对象有几个类型为Person
的子级。这都归功于XAML解析器,它知道应该使用ObserverableCollection
的Add()
方法添加子级。
我应该指出,这些例子仅仅是为了演示如何绑定到集合。在生产系统中,集合可能属于BAL层/或MVVM模式中的模型一部分。我只是为了快速演示,以便大家理解要点。
我想提醒大家注意的另一件事是ListBox
中项目的渲染。看看它们是如何简单地显示“BindingToCollection.Persons”作为纯文本的。
这显然不是我们想要的效果,但发生这种情况是因为WPF不知道要显示Person
对象的哪些属性以及如何显示它们。这将是我下一篇关于模板的文章的主题。我不会再写更多关于此的内容,但请记住,我们可以使用DataTemplate
来改变数据项的外观。如果您真的等不及,可以看看这些链接
注意:如我之前所述,我没有涵盖分组/排序/筛选等内容,如果您确实想了解更多,请使用本文开头提供的链接。
数据绑定值转换器
想象一下这样一种情况:我们有一个绑定的数据值,希望以某种方式对其进行格式化,例如使用短日期格式而不是长日期格式。到目前为止,我们只使用了原始的绑定值。这不可能实现,我们需要确保绑定的值包含我们想要显示的内容。幸运的是,WPF有一个妙招。我们可以使用一个实现IValueConverter
接口的类来为绑定提供一个新值。
ValueConverters就像WPF世界的*sprintf*。您可以字面上使用ValueConverter为绑定提供一个新对象。这可能与原始对象是相同类型的对象,也可能是一个全新的对象。例如,在WPF中,有一个Freezable Object的原则,它是一个不可变对象。如果您尝试动画化一个Frozen对象,将会遇到问题。我过去通过使用Clone ValueConverter为绑定提供一个克隆对象来解决这个问题,然后该克隆对象用于动画。
但更常见的是,您可能会将ValueConverters用于小的格式更改。
ValueConverters位于源值和绑定的目标属性之间,其唯一目的是获取一个值并提供一个不同的值。
IValueConverter
接口包含以下必须实现的方法:
object Convert(object value, Type targetType, object parameter, CultureInfo culture)
用于将源对象转换为目标将使用的新值
object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
用于将目标对象转换回源。当绑定的Binding.Mode
已设置为TwoWay
或OneWayToSource
时,将使用此方法。通常情况下,此方法不会被使用,只会简单地抛出异常。
那么,我们如何在代码中使用这些ValueConverter呢?嗯,很简单,我们使用正常的绑定表达式,但我们还声明了Binding.Converter
属性要使用的转换器。我们来看一个例子,好吗?
作为演示解决方案的一部分,您将找到一个名为“ValueConverters”的项目,运行后它将看起来如下:
这个小例子实际上使用了两个ValueConverter,上面那个将单词转换为用于给Rectangle
着色的Brush
。第二个ValueConverter在代码隐藏中使用了显式设置的DataContext
,其中两个Label
的DataContext
设置为新的DateTime
。让我们看看代码
首先,是XAML
<Window x:Class="ValueConverters.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ValueConverters"
WindowStartupLocation="CenterScreen"
Title="Window1" Height="800" Width="800">
<Window.Resources>
<local:WordToColorConverter x:Key="wordToColorConv"/>
<local:DateConverter x:Key="dateConv"/>
</Window.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<StackPanel Orientation="Vertical">
<!-- Using Vaue Converter To Convert Text Into Colors -->
<Label Content="Using Vaue Converter To Convert Text To Fill Color"
Margin="5,0,0,0" FontSize="14"
FontWeight="Bold" />
<StackPanel Orientation="Horizontal"
Margin="10,10,10,10" Background="Gainsboro">
<TextBlock TextWrapping="Wrap"
Text="Using Vaue Converter. Type 'hot' or 'cold'
into the textbox and watch the rectangle change color"
Width="400"/>
<TextBox x:Name="txtSource" Width="50"
Height="25" Margin="5,0,0,0"/>
<Rectangle Width="50"
Height="25" Margin="5,0,0,0"
Fill="{Binding ElementName=txtSource, Path=Text,
Converter={StaticResource wordToColorConv}}" />
</StackPanel>
<!-- Using Vaue Converter To Convert Date To Short Date -->
<Label Content="Using Vaue Converter To Convert Date To Short Date"
Margin="5,0,0,0" FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Vertical"
Margin="10,10,10,10" Background="Gainsboro">
<StackPanel Orientation="Horizontal">
<Label Content="LongDate"/>
<Label x:Name="lblLongDate" Content="{Binding Path=Now}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="ShortDate thanks to value converter"/>
<Label x:Name="lblShortDate" Content="{Binding Path=Now,
Converter={StaticResource dateConv}}"/>
</StackPanel>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Window>
这是此Window
的代码隐藏
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace ValueConverters
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
lblLongDate.DataContext = new DateTime();
lblShortDate.DataContext = new DateTime();
}
}
}
最后,转换器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Windows.Media;
using System.Globalization;
namespace ValueConverters
{
[ValueConversion(typeof(String), typeof(SolidColorBrush))]
public class WordToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
string boundWord = value as string;
SolidColorBrush returnBrush = null;
switch (boundWord.ToLower().Trim())
{
case "hot":
returnBrush = new SolidColorBrush(Colors.Red);
break;
case "cold":
returnBrush = new SolidColorBrush(Colors.Green);
break;
default:
returnBrush = new SolidColorBrush(Colors.Yellow);
break;
}
return returnBrush;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new Exception("Cant convert back");
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Windows.Media;
using System.Globalization;
namespace ValueConverters
{
[ValueConversion(typeof(DateTime), typeof(String))]
public class DateConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
DateTime date = (DateTime)value;
return date.ToShortDateString();
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
string strValue = value.ToString();
DateTime resultDateTime;
if (DateTime.TryParse(strValue, out resultDateTime))
{
return resultDateTime;
}
return value;
}
}
}
这是VB.NET版本;代码隐藏
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Data
Imports System.Windows.Documents
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Imaging
Imports System.Windows.Navigation
Imports System.Windows.Shapes
''' <summary>
''' Interaction logic for Window1.xaml
''' </summary>
Partial Public Class Window1
Inherits Window
Public Sub New()
InitializeComponent()
lblLongDate.DataContext = New DateTime()
lblShortDate.DataContext = New DateTime()
End Sub
End Class
VB.NET 转换器
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows.Data
Imports System.Windows.Media
Imports System.Globalization
<ValueConversion(GetType(String), GetType(SolidColorBrush))> _
Public Class WordToColorConverter
Implements IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As Type,
ByVal parameter As Object, ByVal culture As CultureInfo)
As Object Implements IValueConverter.Convert
Dim boundWord As String = TryCast(value, String)
Dim returnBrush As SolidColorBrush = Nothing
Select Case boundWord.ToLower().Trim()
Case "hot"
returnBrush = New SolidColorBrush(Colors.Red)
Exit Select
Case "cold"
returnBrush = New SolidColorBrush(Colors.Green)
Exit Select
Case Else
returnBrush = New SolidColorBrush(Colors.Yellow)
Exit Select
End Select
Return returnBrush
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type,
ByVal parameter As Object, ByVal culture As CultureInfo)
As Object Implements IValueConverter.ConvertBack
Throw New Exception("Cant convert back")
End Function
End Class
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows.Data
Imports System.Windows.Media
Imports System.Globalization
<ValueConversion(GetType(DateTime), GetType(String))> _
Public Class DateConverter
Implements IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As Type,
ByVal parameter As Object, ByVal culture As CultureInfo)
As Object Implements IValueConverter.Convert
Dim [date] As DateTime = DirectCast(value, DateTime)
Return [date].ToShortDateString()
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type,
ByVal parameter As Object, ByVal culture As CultureInfo)
As Object Implements IValueConverter.ConvertBack
Dim strValue As String = value.ToString()
Dim resultDateTime As DateTime
If DateTime.TryParse(strValue, resultDateTime) Then
Return resultDateTime
End If
Return value
End Function
End Class
注意:我没有涵盖MultiBinding
,因为Josh Smith在他的优秀文章中对此进行了非常出色的阐述,我觉得没有必要再写更多关于此的内容。
数据绑定验证
到目前为止,我们已经了解了绑定到值/其他元素/XML等,并看到了如何转换值。这很好。但是使用绑定的主要关注点之一是验证输入的数据,特别是当我们有一个Binding.Mode
属性设置为TwoWay
或OneWayToSource
时。我们确实需要确保只允许有效数据发送回源对象。这不仅是正确的做法,而且还确保我们不会将垃圾数据发送回我们的持久化存储(数据库/文件等)。那么我们该怎么做呢?我们需要对我们的绑定应用某种验证。
幸运的是,微软意识到了这一点,并为我们提供了必要的“弹药”来创建良好、行为规范、经过验证的数据绑定。验证主要有三种方式,我们将在下面逐一介绍。
作为演示解决方案的一部分,您将找到一个名为“Validation”的项目,运行后会如下图所示:
视觉验证变更
作为所附“验证”项目的一部分,我包含了一些样式。这是一个我们尚未涵盖的领域。目前您需要知道的是,无效的TextBox
输入将导致相关的TextBox
的视觉外观通过使用样式或模板而改变。另一件事是TextBox.ToolTip
如何显示正确的验证消息。嗯,这也是通过一些巧妙的绑定完成的,如下所示
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
正如我所说,我不想过多纠结于样式/模板,因为它们是下一篇文章的讨论内容,但我认为这个元素值得一提。
基于异常的验证
在绑定中使用验证最简单的方法可能是使用基于异常的验证规则。这基本上意味着如果尝试更新绑定属性时出现异常,我们可以使用异常通知用户。通常,这可以通过在工具提示上显示消息并像之前讨论的那样更改TextBox
的视觉效果来完成。如果我们有一个小的测试类来绑定,如下所示
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Validation
{
public class TestClass
{
public int Age { get; set; }
public DateTime StartDate { get; set; }
public TestClass()
{
StartDate = DateTime.Now;
}
}
}
这是VB.NET版本
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Public Class TestClass
Private m_Age As Integer = 1
Private m_StartDate As DateTime
Public Property Age() As Integer
Get
Return m_Age
End Get
Set(ByVal value As Integer)
m_Age = value
End Set
End Property
Public Property StartDate() As DateTime
Get
Return m_StartDate
End Get
Set(ByVal value As DateTime)
m_StartDate = value
End Set
End Property
Public Sub New()
m_StartDate = DateTime.Now
End Sub
End Class
我们通过代码隐藏设置绑定如下(使用DataContext
)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Validation
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new TestClass();
txtIDataErrorInfoAge.DataContext = new Person();
txtIDataErrorInfoName.DataContext = new Person();
}
}
}
以及VB.NET版本
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Data
Imports System.Windows.Documents
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Imaging
Imports System.Windows.Navigation
Imports System.Windows.Shapes
''' <summary>
''' Interaction logic for Window1.xaml
''' </summary>
Partial Public Class Window1
Inherits Window
Public Sub New()
InitializeComponent()
Me.DataContext = New TestClass
txtIDataErrorInfoAge.DataContext = New Person
txtIDataErrorInfoName.DataContext = New Person
End Sub
End Class
然后,我们可以在XAML中按如下方式使用基于异常的验证
<Window x:Class="Validation.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Validation"
Title="Window1" Height="800" Width="800"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<!-- Exception/ValidationRule Based Validitaion TextBox Style -->
<Style x:Key="textStyleTextBox" TargetType="TextBox">
<Setter Property="Foreground" Value="#333333" />
<Style.Triggers>
<Trigger Property="Validation.HasError"
Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- ValidationRule Based Validitaion Control Template -->
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red"
FontSize="20">!</TextBlock>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
</Window.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<StackPanel Orientation="Vertical">
<!-- Exception Based Validitaion -->
<Label Content="Exception Based Validitaion" Margin="5,0,0,0"
FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Horizontal" Margin="10,10,10,10"
Background="Gainsboro">
<TextBlock TextWrapping="Wrap" Text="Exception Based Validitaion,
type an non integer value" Width="400"/>
<TextBox Name="txtException"
Style="{StaticResource textStyleTextBox}"
Width="120" Height="25"
Margin="5,0,0,0"
Text="{Binding Path=Age,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnExceptions=True}" />
</StackPanel>
</StackPanel>
</ScrollViewer>
</Window>
需要注意的是,Binding.ValidatesOnExceptions=True
表示将使用WPF内置的ExceptionValidationRule
来创建适当的验证消息,该消息将显示在TextBox.Tooltip
中。
基于自定义ValidationRules的验证
自定义ValidationRule
s在原则上与内置的ExceptionValidationRule
相似,但这次我们使用自己的ValidationRule
来创建适当的验证消息。这是一个自定义ValidationRule
的示例,其中绑定的输入值必须是将来日期时间的DateTime
。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Globalization;
namespace Validation
{
class FutureDateValidationRule : ValidationRule
{
public override ValidationResult Validate(object value,
CultureInfo cultureInfo)
{
DateTime date;
try
{
date = DateTime.Parse(value.ToString());
}
catch (FormatException)
{
return new ValidationResult(false,
"Value is not a valid date.");
}
if (DateTime.Now.Date > date)
{
return new ValidationResult(false,
"Please enter a date in the future.");
}
else
{
return ValidationResult.ValidResult;
}
}
}
}
这是VB.NET版本
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows.Controls
Imports System.Globalization
Public Class FutureDateValidationRule
Inherits ValidationRule
Public Overloads Overrides Function Validate(ByVal value As Object,
ByVal cultureInfo As CultureInfo) As ValidationResult
Dim [date] As DateTime
Try
[date] = DateTime.Parse(value.ToString())
Catch generatedExceptionName As FormatException
Return New ValidationResult(False, "Value is not a valid date.")
End Try
If DateTime.Now.[Date] > [date] Then
Return New ValidationResult(False, "Please enter a date in the future.")
Else
Return ValidationResult.ValidResult
End If
End Function
End Class
我们通过代码隐藏设置绑定如下(使用DataContext
)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Validation
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new TestClass();
txtIDataErrorInfoAge.DataContext = new Person();
txtIDataErrorInfoName.DataContext = new Person();
}
}
}
以及VB.NET版本
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Data
Imports System.Windows.Documents
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Imaging
Imports System.Windows.Navigation
Imports System.Windows.Shapes
''' <summary>
''' Interaction logic for Window1.xaml
''' </summary>
Partial Public Class Window1
Inherits Window
Public Sub New()
InitializeComponent()
Me.DataContext = New TestClass
txtIDataErrorInfoAge.DataContext = New Person
txtIDataErrorInfoName.DataContext = New Person
End Sub
End Class
然后我们可以在XAML中使用这个自定义的FutureDateValidationRule
<Window x:Class="Validation.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Validation"
Title="Window1" Height="800" Width="800"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<!-- Exception/ValidationRule Based Validitaion TextBox Style -->
<Style x:Key="textStyleTextBox" TargetType="TextBox">
<Setter Property="Foreground" Value="#333333" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- ValidationRule Based Validitaion Control Template -->
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red"
FontSize="20">!</TextBlock>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
</Window.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<StackPanel Orientation="Vertical">
<!-- ValidationRule Based Validitaion -->
<Label Content="ValidationRule Based Validitaion" Margin="5,0,0,0"
FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Horizontal"
Margin="10,10,10,10"
Background="Gainsboro">
<TextBlock TextWrapping="Wrap"
Text="ValidationRule Based Validitaion,
type a future date" Width="400"/>
<TextBox Name="txtStartDate"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
Style="{StaticResource textStyleTextBox}"
Width="150" Height="25"
Margin="5,0,0,0">
<TextBox.Text>
<!-- As we need to supply a child ValidationRule,
we need to use Property/Element Syntax -->
<Binding Path="StartDate"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:FutureDateValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Window>
这里需要注意的是,因为我们需要将新的FutureDateValidationRule
添加到Binding.ValidationRules
中,所以我们需要使用属性作为元素语法。
.NET 3.5 使用 IDataErrorInfo 的方式
随着.NET 3.5的发布,LINQ也随之而来,当然还有WPF的一些改进。其中之一是一个名为IDataErrorInfo
的新接口,它改变了部分验证的执行位置。它将验证从单独的验证类移回了实际的业务对象本身。
我们来看一个实现IDataErrorInfo
接口的简单类示例
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace Validation
{
/// <summary>
/// This is the new .NET 3.5 method where each
/// Business Object has its own validation
/// using the IDataErrorInfo interface
/// </summary>
public class Person : IDataErrorInfo
{
public int Age { get; set; }
public string Name { get; set; }
public Person()
{
this.Age = 0;
this.Name = "sacha";
}
#region IDataErrorInfo Members
public string Error
{
get
{
return null;
}
}
/// <summary>
/// Examines the property that was changed and provides the
/// correct error message based on some rules
/// </summary>
/// <param name="name">The property that changed</param>
/// <returns>a error message string</returns>
public string this[string name]
{
get
{
string result = null;
//basically we need one of these blocks
//for each property you wish to validate
switch (name)
{
case "Age":
if (this.Age < 0 || this.Age > 150)
{
result = "Age must not be less than 0 or greater than 150.";
}
break;
case "Name":
if (this.Name == string.Empty)
{
result = "Name can't be empty";
}
if (this.Name.Length > 5)
{
result = "Name can't be more than 5 characters";
}
break;
}
return result;
}
}
#endregion
}
}
这是VB.NET版本
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.ComponentModel
''' <summary>
''' This is the new .NET 3.5 method where
''' each Business Object has its own validation
''' using the IDataErrorInfo interface
''' </summary>
Public Class Person
Implements IDataErrorInfo
Private m_Age As Integer
Private m_Name As String
Public Property Age() As Integer
Get
Return m_Age
End Get
Set(ByVal value As Integer)
m_Age = value
End Set
End Property
Public Property Name() As String
Get
Return m_Name
End Get
Set(ByVal value As String)
m_Name = value
End Set
End Property
Public Sub New()
Me.Age = 0
Me.Name = "sacha"
End Sub
#Region "IDataErrorInfo Members"
Public ReadOnly Property [Error]() As String Implements _
System.ComponentModel.IDataErrorInfo.Error
Get
Return Nothing
End Get
End Property
''' <summary>
''' Examines the property that was changed and provides the
''' correct error message based on some rules
''' </summary>
''' <param name="Name">The property that changed</param>
''' <returns>a error message string</returns>
Default Public ReadOnly Property Item(ByVal Name As String)
As String Implements System.ComponentModel.IDataErrorInfo.Item
'Default Public ReadOnly Property Item(ByVal name As String) As String
Get
Dim result As String = Nothing
'basically we need one of these blocks
'for each property you wish to validate
Select Case Name
Case "Age"
If Me.Age < 0 OrElse Me.Age > 150 Then
result = "Age must not be less than 0 or greater than 150."
End If
Exit Select
Case "Name"
If Me.Name = String.Empty Then
result = "Name can't be empty"
End If
If Me.Name.Length > 5 Then
result = "Name can't be more than 5 characters"
End If
Exit Select
End Select
Return result
End Get
End Property
#End Region
End Class
基本上,该接口允许我们使用public string this[string name]
语法验证已更改的属性。这次XAML有点不同,绑定不再需要使用单独的验证类,因此我们可以简单地使用简写XAML语法,即带有{}大括号的语法。同样,如果您喜欢更长、更详细的语法,也可以这样做。
<Window x:Class="Validation.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Validation"
Title="Window1" Height="800" Width="800"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<!-- Exception/ValidationRule Based Validitaion TextBox Style -->
<Style x:Key="textStyleTextBox" TargetType="TextBox">
<Setter Property="Foreground" Value="#333333" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<StackPanel Orientation="Vertical">
<!-- IDataErrorInfo Based Validitaion -->
<Label Content="IDataErrorInfo Based Validitaion"
Margin="5,0,0,0"
FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Horizontal"
Margin="10,10,10,10"
Background="Gainsboro">
<TextBlock TextWrapping="Wrap"
Text="IDataErrorInfo Based Validitaion,
type a number below 0 or above 150 " Width="400"/>
<!-- Age Entry Area -->
<Label Content="Age"/>
<TextBox Name="txtIDataErrorInfoAge"
Style="{StaticResource textStyleTextBox}"
Width="60" Height="25"
Margin="5,0,0,0"
Text="{Binding Path=Age,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnExceptions=True,
ValidatesOnDataErrors=True}" />
<!-- Name Entry Area -->
<Label Content="Name"/>
<TextBox Name="txtIDataErrorInfoName"
Style="{StaticResource textStyleTextBox}"
Width="60" Height="25"
Margin="5,0,0,0"
Text="{Binding Path=Name,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True}" />
</StackPanel>
</StackPanel>
</ScrollViewer>
</Window>
请注意,这次我们设置了Binding.ValidatesOnDataErrors=True
;这意味着将包含DataErrorValidationRule
,它使用我们讨论过的IDataErrorInfo
实现。
警告:谨防标签页
我最近收到了Karl Shifllet发来的一封很酷的邮件,警告WPF中当您使用标签页时验证错误会消失的bug。您应该熟悉Karl关于此问题的博客文章。
我们完成了
数据绑定远不止我这里所涵盖的内容;例如,我没有涵盖分组/排序/过滤等,并且可能遗漏了一些数据绑定点和语法。我将这些领域留作读者的练习。本文旨在介绍数据绑定;希望您喜欢它。如果您喜欢,请投票并留下评论,也许可以阅读本系列的下一篇文章。谢谢!
参考文献
其他优质资源
历史
- 08/02/14:初次发布。