<ListBox ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="true"/>
以上示例使用空绑定语法:{Binding}。 在此示例中,ListBox 从父 DockPanel 元素继承 DataContext(此示例中未显示)。 未指定路径时,默认为绑定到整个对象。 换句话说,此示例中的路径已省略,因为要将 ItemsSource 属性绑定到整个对象。 (有关深入讨论,请参阅绑定到集合部分。)
除了绑定到集合以外,在希望绑定到整个对象,而不是仅绑定到对象的单个属性时,也可以使用此方案。 例如,如果源对象的类型为 String,则可能仅希望绑定到字符串本身。 另一种常见情况是希望将一个元素绑定到一个具有多个属性的对象。
你可能需要应用自定义逻辑,以便数据对于绑定的目标属性有意义。 如果不存在默认类型转换,则自定义逻辑可能采用自定义转换器的形式。 有关转换器的信息,请参阅数据转换。
Binding 和 BindingExpression
在介绍数据绑定的其他功能和用法前,先介绍一下 BindingExpression 类会很有用。 如前面部分所述,Binding 类是用于绑定声明的高级类;该类提供许多供用户指定绑定特征的属性。 相关类 BindingExpression 是维持源与目标之间连接的基础对象。 一个绑定包含了可以在多个绑定表达式之间共享的所有信息。 BindingExpression 是无法共享的实例表达式,并包含 Binding 的所有实例信息。
举例来说,假设 myDataObject
是 MyData
类的实例,myBinding
是源 Binding 对象,而 MyData
是包含名为 ColorName
的字符串属性的定义类。 此示例将 TextBlock 的实例 myText
的文本内容绑定到 ColorName
。
// Make a new source
var myDataObject = new MyData();
var myBinding = new Binding("ColorName")
Source = myDataObject
// Bind the data source to the TextBox control's Text dependency property
myText.SetBinding(TextBlock.TextProperty, myBinding);
' Make a New source
Dim myDataObject As New MyData
Dim myBinding As New Binding("ColorName")
myBinding.Source = myDataObject
' Bind the data source to the TextBox control's Text dependency property
myText.SetBinding(TextBlock.TextProperty, myBinding)
可以使用同一 myBinding 对象来创建其他绑定。 例如,可使用 myBinding 对象将复选框的文本内容绑定到 ColorName。 在该方案中,将有两个 BindingExpression 实例共享 myBinding 对象。
通过对数据绑定对象调用 GetBindingExpression 来返回 BindingExpression 对象。 以下文章演示了 BindingExpression 类的一些用法:
从绑定目标属性获取绑定对象 (.NET Framework)
控制 TextBox 文本更新源的时间 (.NET Framework)
在创建绑定部分,该按钮为红色,因为其 Background 属性绑定到值为“Red”的字符串属性。 此字符串值有效是因为 Brush 类型中存在类型转换器,可用于将字符串值转换为 Brush。
将此信息添加到创建绑定部分的图中的情况如下所示。
但是,如果绑定源对象拥有的不是字符串类型的属性,而是 Color 类型的 Color 属性,该怎么办? 在这种情况下,为了使绑定正常工作,首先需要将 Color 属性值转换为 Background 属性可接受的值。 需要通过实现 IValueConverter 接口来创建一个自定义转换器,如以下示例所示。
[ValueConversion(typeof(Color), typeof(SolidColorBrush))]
public class ColorBrushConverter : IValueConverter
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
Color color = (Color)value;
return new SolidColorBrush(color);
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
return null;
<ValueConversion(GetType(Color), GetType(SolidColorBrush))>
Public Class ColorBrushConverter
Implements IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.Convert
Dim color As Color = CType(value, Color)
Return New SolidColorBrush(color)
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
Return Nothing
End Function
End Class
有关详细信息,请参阅IValueConverter。
现在,使用的是自定义转换器而不是默认转换,关系图如下所示。
重申一下,由于要绑定到的类型中提供了类型转换器,因此可以使用默认转换。 此行为取决于目标中可用的类型转换器。 如果无法确定,请创建自己的转换器。
下面提供了一些典型方案,在这些方案中,实现数据转换器非常有意义:
数据应根据区域性以不同方式显示。 例如,可能需要根据在特定区域性中使用的约定,实现货币转换器或日历日期/时间转换器。
使用的数据不一定会更改属性的文本值,但会更改其他某个值(如图像的源,或显示文本的颜色或样式)。 在这种情况下,可以通过转换可能不合适的属性绑定(如将文本字段绑定到表单元格的 Background 属性)来使用转换器。
多个控件或控件的多个属性会绑定到相同数据。 在这种情况下,主绑定可能仅显示文本,而其他绑定则处理特定的显示问题,但仍使用同一绑定作为源信息。
目标属性具有绑定集合,称为 MultiBinding。 对于 MultiBinding,使用自定义 IMultiValueConverter 从绑定的值中生成最终值。 例如,可以从红色、蓝色和绿色的值来计算颜色,这些值可能来自相同绑定源对象,也可能来自不同绑定源对象。 有关示例和信息,请参阅 MultiBinding。
绑定到集合
绑定源对象可以被视为其属性包含数据的单个对象,也可以被视为通常组合在一起的多态对象的数据集合(例如数据库查询的结果)。 目前为止,我们仅讨论了绑定到单个对象, 但绑定到数据集合也是常见方案。 例如,一种常见方案是使用 ItemsControl(例如 ListBox、ListView 或 TreeView)来显示数据集合,如在什么是数据绑定部分所示的应用中。
幸运的是,基本关系图仍然适用。 如果将 ItemsControl 绑定到集合,则关系图如下所示。
如图所示,若要将 ItemsControl 绑定到集合对象,则需要使用 ItemsControl.ItemsSource 属性。 你可以将 ItemsSource
视为 ItemsControl 的内容。 绑定为 OneWay,因为 ItemsSource
属性默认支持 OneWay
绑定。
如何实现集合
你可以枚举实现 IEnumerable 接口的任何集合。 但是,若要设置动态绑定,以便集合中的插入或删除操作可以自动更新 UI,则集合必须实现 INotifyCollectionChanged 接口。 此接口公开一个事件,只要基础集合发生更改,就应该引发该事件。
WPF 提供 ObservableCollection<T> 类,该类是公开 INotifyCollectionChanged 接口的数据集合的内置实现。 若要完全支持将数据值从源对象传输到目标,支持可绑定属性的集合中的每个对象还必须实现 INotifyPropertyChanged 接口。 有关详细信息,请参阅绑定源概述。
在实现自己的集合前,请考虑使用 ObservableCollection<T> 或现有集合类之一,例如 List<T>、Collection<T> 和 BindingList<T> 等。 如果有高级方案并且希望实现自己的集合,请考虑使用 IList,它提供可以按索引逐个访问的对象的非泛型集合,因而可提供最佳性能。
在 ItemsControl 绑定到数据集合后,你可能希望对数据进行排序、筛选或分组。 为此,应使用集合视图,这些视图是实现 ICollectionView 接口的类。
什么是集合视图?
集合视图这一层基于绑定源集合,它允许基于排序、筛选和分组查询来导航并显示源集合,而无需更改基础源集合本身。 集合视图还维护一个指向集合中当前项的指针。 如果源集合实现 INotifyCollectionChanged 接口,则 CollectionChanged 事件引发的更改会传播到视图。
由于视图不会更改基础源集合,因此每个源集合都可以有多个关联的视图。 例如,可以有 Task 对象的集合。 使用视图,可以通过不同方式显示相同数据。 例如,可能希望在页面左侧显示按优先级排序的任务,而在页面右侧显示按区域分组的任务。
视图创建方法
创建并使用视图的一种方式是直接实例化视图对象,然后将它用作绑定源。 以什么是数据绑定部分中所示的数据绑定演示应用为例。 该应用的实现方式是将 ListBox 绑定到基于数据集合的视图,而不是直接绑定到数据集合。 下面的示例摘自数据绑定演示应用。 CollectionViewSource 类是从 CollectionView 继承的类的 XAML 代理。 在此特定示例中,视图的 Source 绑定到当前应用对象的 AuctionItem 集合(类型为 ObservableCollection<T>)。
<Window.Resources>
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=AuctionItems}"
x:Key="listingDataView" />
</Window.Resources>
资源 listingDataView 随后用作应用中元素(例如 ListBox)的绑定源。
<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8"
ItemsSource="{Binding Source={StaticResource listingDataView}}" />
若要为同一集合创建另一个视图,则可以创建另一个 CollectionViewSource 实例,并为其提供不同的 x:Key
名称。
下表显示作为默认集合视图创建或由 CollectionViewSource 根据源集合类型创建的视图数据类型。
源集合类型
集合视图类型
使用默认视图
创建并使用集合视图的一种方式是指定集合视图作为绑定源。 WPF 还会为用作绑定源的每个集合创建一个默认集合视图。 如果直接绑定到集合,WPF 会绑定到该集合的默认视图。 此默认视图由同一集合的所有绑定共享,因此一个绑定控件或代码对默认视图所做的更改(例如排序或对当前项指针的更改,下文将对此进行讨论)会在同一集合的所有其他绑定中反映。
若要获取默认视图,请使用 GetDefaultView 方法。 有关示例,请参阅获取数据集合的默认视图 (.NET Framework)。
包含 ADO.NET DataTables 的集合视图
为了提高性能,ADO.NET DataTable 或 DataView 对象的集合视图将排序和筛选委托给 DataView,这导致排序和筛选在数据源的所有集合视图之间共享。 若要使每个集合视图都能独立进行排序和筛选,请使用每个集合视图自己的 DataView 对象进行初始化。
如前所述,视图可以将排序顺序应用于集合。 如同在基础集合中一样,数据可能具有或不具有相关的固有顺序。 借助集合视图,可以根据自己提供的比较条件来强制确定顺序,或更改默认顺序。 由于这是基于客户端的数据视图,因此一种常见情况是用户可能希望根据列对应的值,对多列表格数据进行排序。 通过使用视图,可以应用这种用户实施的排序,而无需对基础集合进行任何更改,甚至不必再次查询集合内容。 有关示例,请参阅在单击标题时对 GridView 列进行排序 (.NET Framework)。
以下示例演示了什么是数据绑定部分中的应用 UI 的“按类别和日期排序”CheckBox 的排序逻辑。
private void AddSortCheckBox_Checked(object sender, RoutedEventArgs e)
// Sort the items first by Category and then by StartDate
listingDataView.SortDescriptions.Add(new SortDescription("Category", ListSortDirection.Ascending));
listingDataView.SortDescriptions.Add(new SortDescription("StartDate", ListSortDirection.Ascending));
Private Sub AddSortCheckBox_Checked(sender As Object, e As RoutedEventArgs)
' Sort the items first by Category And then by StartDate
listingDataView.SortDescriptions.Add(New SortDescription("Category", ListSortDirection.Ascending))
listingDataView.SortDescriptions.Add(New SortDescription("StartDate", ListSortDirection.Ascending))
End Sub
视图还可以将筛选器应用于集合,以便视图仅显示完整集合的特定子集。 可以根据条件在数据中进行筛选。 例如,正如什么是数据绑定部分中的应用所做的那样,“仅显示成交商品”CheckBox 包含了筛选出成交价等于或大于 25 美元的项的逻辑。 如果选择了 CheckBox,则会执行以下代码将 ShowOnlyBargainsFilter 设置为 Filter 事件处理程序。
private void AddFilteringCheckBox_Checked(object sender, RoutedEventArgs e)
if (((CheckBox)sender).IsChecked == true)
listingDataView.Filter += ListingDataView_Filter;
listingDataView.Filter -= ListingDataView_Filter;
Private Sub AddFilteringCheckBox_Checked(sender As Object, e As RoutedEventArgs)
Dim checkBox = DirectCast(sender, CheckBox)
If checkBox.IsChecked = True Then
AddHandler listingDataView.Filter, AddressOf ListingDataView_Filter
RemoveHandler listingDataView.Filter, AddressOf ListingDataView_Filter
End If
End Sub
ShowOnlyBargainsFilter 事件处理程序具有以下实现。
private void ListingDataView_Filter(object sender, FilterEventArgs e)
// Start with everything excluded
e.Accepted = false;
// Only inlcude items with a price less than 25
if (e.Item is AuctionItem product && product.CurrentPrice < 25)
e.Accepted = true;
Private Sub ListingDataView_Filter(sender As Object, e As FilterEventArgs)
' Start with everything excluded
e.Accepted = False
Dim product As AuctionItem = TryCast(e.Item, AuctionItem)
If product IsNot Nothing Then
' Only include products with prices lower than 25
If product.CurrentPrice < 25 Then e.Accepted = True
End If
End Sub
如果直接使用其中一个 CollectionView 类而不是 CollectionViewSource,则可以使用 Filter 属性指定回叫。 有关示例,请参阅筛选视图中的数据 (.NET Framework)。
除了用来查看 IEnumerable 集合的内部类之外,所有集合视图都支持分组功能,用户可以利用此功能将集合视图中的集合划分成逻辑组。 这些组可以是显式的,由用户提供组列表;也可以是隐式的,这些组依据数据动态生成。
以下示例演示了“按类别分组”CheckBox 的逻辑。
// This groups the items in the view by the property "Category"
var groupDescription = new PropertyGroupDescription();
groupDescription.PropertyName = "Category";
listingDataView.GroupDescriptions.Add(groupDescription);
' This groups the items in the view by the property "Category"
Dim groupDescription = New PropertyGroupDescription()
groupDescription.PropertyName = "Category"
listingDataView.GroupDescriptions.Add(groupDescription)
有关其他分组示例,请参阅对实现 GridView 的 ListView 中的项进行分组 (.NET Framework)。
当前项指针
视图还支持当前项的概念。 可以在集合视图中的对象之间导航。 在导航时,你是在移动项指针,该指针可用于检索存在于集合中特定位置的对象。 有关示例,请参阅在数据 CollectionView 中的对象之间导航 (.NET Framework)。
由于 WPF 只通过使用视图(你指定的视图或集合的默认视图)绑定到集合,因此集合的所有绑定都有一个当前项指针。 绑定到视图时,Path
值中的斜杠(“/”)字符用于指定视图的当前项。 在下面的示例中,数据上下文是一个集合视图。 第一行绑定到集合。 第二行绑定到集合中的当前项。 第三行绑定到集合中当前项的 Description
属性。
<Button Content="{Binding }" />
<Button Content="{Binding Path=/}" />
<Button Content="{Binding Path=/Description}" />
还可以连着使用斜杠和属性语法以遍历集合的分层。 以下示例绑定到一个名为 Offices
的集合的当前项,此集合是源集合的当前项的属性。
<Button Content="{Binding /Offices/}" />
当前项指针可能会受对集合应用的任何排序或筛选操作的影响。 排序操作将当前项指针保留在所选的最后一项上,但集合视图现已围绕此指针重构。 (或许所选项以前曾位于列表的开头,但现在所选项可能位于中间的某个位置。)如果所选内容在筛选之后保留在视图中,则筛选操作会保留所选项。 否则,当前项指针会设置为经过筛选的集合视图的第一项。
主-从绑定方案
当前项的概念不仅适用于集合中各项的导航,也适用于主-从绑定方案。 再考虑一下什么是数据绑定部分中的应用 UI。 在该应用中,ListBox 中的选择确定 ContentControl 中显示的内容。 换句话说,选择 ListBox 项目时,ContentControl 显示所选项的详细信息。
只需将两个或更多控件绑定到同一视图即可实现主-从方案。 数据绑定演示中的以下示例演示了在什么是数据绑定部分中的应用 UI 上看到的 ListBox 和 ContentControl 的标记。
<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8"
ItemsSource="{Binding Source={StaticResource listingDataView}}" />
<ContentControl Name="Detail" Grid.Row="3" Grid.ColumnSpan="3"
Content="{Binding Source={StaticResource listingDataView}}"
ContentTemplate="{StaticResource detailsProductListingTemplate}"
Margin="9,0,0,0"/>
请注意,这两个控件都绑定到同一个源,即 listingDataView 静态资源(请参阅如何创建视图部分中关于此资源的定义)。 此绑定有效是因为将某个对象(在本例中为 ContentControl)绑定到集合视图时,它会自动绑定到该视图的 CurrentItem。 CollectionViewSource 对象会自动同步货币和选择。 如果列表控件未像本示例中那样绑定到 CollectionViewSource 对象,则需要将其 IsSynchronizedWithCurrentItem 属性设置为 true
才能起作用。
有关其他示例,请参阅绑定到集合并基于选择显示信息 (.NET Framework)和对层次结构数据使用主-从模式 (.NET Framework)。
你可能已经注意到上述示例使用了一个模板。 实际上,如果不使用模板(ContentControl 显式使用的模板以及 ListBox 隐式使用的模板),数据不会按照我们希望的方式显示。 现在,我们开始介绍下一节中的数据模板化。
数据模板化
如果不使用数据模板,数据绑定示例部分中的应用 UI 将如下所示:
如前面部分中的示例所示,ListBox 控件和 ContentControl 都绑定到 AuctionItem 的整个集合对象(更具体地说,是绑定到集合对象视图)。 如果未提供如何显示数据集合的特定说明,则 ListBox 会以字符串形式显示基础集合中的每个对象,ContentControl 会以字符串形式显示绑定到的对象。
为了解决该问题,应用定义了 DataTemplates。 如前面部分中的示例所示,ContentControl 显式使用 detailsProductListingTemplate 数据模板。 显示集合中的 AuctionItem 对象时,ListBox 控件隐式使用以下数据模板。
<DataTemplate DataType="{x:Type src:AuctionItem}">
<Border BorderThickness="1" BorderBrush="Gray"
Padding="7" Name="border" Margin="3" Width="500">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="86"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Polygon Grid.Row="0" Grid.Column="0" Grid.RowSpan="4"
Fill="Yellow" Stroke="Black" StrokeThickness="1"
StrokeLineJoin="Round" Width="20" Height="20"
Stretch="Fill"
Points="9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7"
Visibility="Hidden" Name="star"/>
<TextBlock Grid.Row="0" Grid.Column="1" Margin="0,0,8,0"
Name="descriptionTitle"
Style="{StaticResource smallTitleStyle}">Description:</TextBlock>
<TextBlock Name="DescriptionDTDataType" Grid.Row="0" Grid.Column="2"
Text="{Binding Path=Description}"
Style="{StaticResource textStyleTextBlock}"/>
<TextBlock Grid.Row="1" Grid.Column="1" Margin="0,0,8,0"
Name="currentPriceTitle"
Style="{StaticResource smallTitleStyle}">Current Price:</TextBlock>
<StackPanel Grid.Row="1" Grid.Column="2" Orientation="Horizontal">
<TextBlock Text="$" Style="{StaticResource textStyleTextBlock}"/>
<TextBlock Name="CurrentPriceDTDataType"
Text="{Binding Path=CurrentPrice}"
Style="{StaticResource textStyleTextBlock}"/>
</StackPanel>
</Grid>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=SpecialFeatures}">
<DataTrigger.Value>
<src:SpecialFeatures>Color</src:SpecialFeatures>
</DataTrigger.Value>
<DataTrigger.Setters>
<Setter Property="BorderBrush" Value="DodgerBlue" TargetName="border" />
<Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
<Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
<Setter Property="BorderThickness" Value="3" TargetName="border" />
<Setter Property="Padding" Value="5" TargetName="border" />
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Binding="{Binding Path=SpecialFeatures}">
<DataTrigger.Value>
<src:SpecialFeatures>Highlight</src:SpecialFeatures>
</DataTrigger.Value>
<Setter Property="BorderBrush" Value="Orange" TargetName="border" />
<Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
<Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
<Setter Property="Visibility" Value="Visible" TargetName="star" />
<Setter Property="BorderThickness" Value="3" TargetName="border" />
<Setter Property="Padding" Value="5" TargetName="border" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
使用这两个 DataTemplate 时,生成的 UI 即为什么是数据绑定部分中所示的 UI。 如屏幕截图所示,除了可以在控件中放置数据以外,使用 DataTemplate 还可以为数据定义引人注目的视觉对象。 例如,上述 DataTemplate 中使用了 DataTrigger,因而 SpecialFeatures 值为 HighLight 的 AuctionItem 会显示为带有橙色边框和一个星号。
有关数据模板的详细信息,请参阅数据模板化概述 (.NET Framework)。
接受用户输入的大多数应用都需要具有验证逻辑,以确保用户输入了预期信息。 可基于类型、范围、格式或特定于应用的其他要求执行验证检查。 本部分讨论数据验证在 WPF 中的工作原理。
将验证规则与绑定关联
WPF 数据绑定模型允许将 ValidationRules 与 Binding 对象关联。 例如,以下示例将 TextBox 绑定到名为 StartPrice
的属性,并将 ExceptionValidationRule 对象添加到 Binding.ValidationRules 属性。
<TextBox Name="StartPriceEntryForm" Grid.Row="2"
Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
<TextBox.Text>
<Binding Path="StartPrice" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
ValidationRule 对象检查属性的值是否有效。 WPF 有两种类型的内置 ValidationRule 对象:
ExceptionValidationRule 检查在绑定源属性更新期间引发的异常。 在以上示例中,StartPrice
为整数类型。 当用户输入的值无法转换为整数时,将引发异常,这会导致将绑定标记为无效。 用于显式设置 ExceptionValidationRule 的替代语法是在 Binding 或 MultiBinding 对象上将 ValidatesOnExceptions 属性设置为 true
。
DataErrorValidationRule 对象检查实现 IDataErrorInfo 接口的对象所引发的错误。 有关使用此验证规则的详细信息,请参阅 DataErrorValidationRule。 用于显式设置 DataErrorValidationRule 的替代语法是在 Binding 或 MultiBinding 对象上将 ValidatesOnDataErrors 属性设置为 true
。
还可以通过从 ValidationRule 类派生并实现 Validate 方法来创建自己的验证规则。 以下示例演示了什么是数据绑定部分中添加产品清单“起始日期”TextBox 所用的规则。
public class FutureDateRule : ValidationRule
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
// Test if date is valid
if (DateTime.TryParse(value.ToString(), out DateTime date))
// Date is not in the future, fail
if (DateTime.Now > date)
return new ValidationResult(false, "Please enter a date in the future.");
// Date is not a valid date, fail
return new ValidationResult(false, "Value is not a valid date.");
// Date is valid and in the future, pass
return ValidationResult.ValidResult;
Public Class FutureDateRule
Inherits ValidationRule
Public Overrides Function Validate(value As Object, cultureInfo As CultureInfo) As ValidationResult
Dim inputDate As Date
' Test if date is valid
If Date.TryParse(value.ToString, inputDate) Then
' Date is not in the future, fail
If Date.Now > inputDate Then
Return New ValidationResult(False, "Please enter a date in the future.")
End If
' // Date Is Not a valid date, fail
Return New ValidationResult(False, "Value is not a valid date.")
End If
' Date is valid and in the future, pass
Return ValidationResult.ValidResult
End Function
End Class
StartDateEntryForm TextBox 使用此 FutureDateRule,如以下示例所示。
<TextBox Name="StartDateEntryForm" Grid.Row="3"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
<TextBox.Text>
<Binding Path="StartDate" UpdateSourceTrigger="PropertyChanged"
Converter="{StaticResource dateConverter}" >
<Binding.ValidationRules>
<src:FutureDateRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
因为 UpdateSourceTrigger 值为 PropertyChanged,所以绑定引擎会在每次击键时更新源值,这意味着它还会在每次击键时检查 ValidationRules 集合中的每条规则。 我们会在“验证过程”一节中对此深入讨论。
提供视觉反馈
如果用户输入的值无效,你可能希望在应用 UI 上提供一些有关错误的反馈。 提供此类反馈的一种方法是将 Validation.ErrorTemplate 附加属性设置为自定义 ControlTemplate。 如前面部分所示,StartDateEntryForm TextBox 使用名为 validationTemplate 的 ErrorTemplate。 以下示例显示了 validationTemplate 的定义。
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="20">!</TextBlock>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
AdornedElementPlaceholder 元素指定应放置待装饰控件的位置。
此外,还可以使用 ToolTip 来显示错误消息。 StartDateEntryForm 和 StartPriceEntryFormTextBox 都使用样式 textStyleTextBox,该样式创建显示错误消息的 ToolTip。 以下示例显示了 textStyleTextBox 的定义。 如果绑定元素属性上的一个或多个绑定出错,则附加属性 Validation.HasError 为 true
。
<Style x:Key="textStyleTextBox" TargetType="TextBox">
<Setter Property="Foreground" Value="#333333" />
<Setter Property="MaxLength" Value="40" />
<Setter Property="Width" Value="392" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding (Validation.Errors).CurrentItem.ErrorContent, RelativeSource={RelativeSource Self}}" />
</Trigger>
</Style.Triggers>
</Style>
使用自定义 ErrorTemplate 和 ToolTip 时,StartDateEntryForm TextBox 在发生验证错误时如下所示。
如果 Binding 具有关联的验证规则,但未在绑定控件上指定 ErrorTemplate,则发生验证错误时,将使用默认的 ErrorTemplate 通知用户。 默认的 ErrorTemplate 是一个控件模板,它在装饰层中定义红色边框。 使用默认的 ErrorTemplate 和 ToolTip 时,StartPriceEntryForm TextBox 的 UI 在发生验证错误时如下所示。
有关如何提供逻辑以验证对话框中所有控件的示例,请参阅对话框概述中的“自定义对话框”部分。
通常,在目标的值传输到绑定源属性时会进行验证。 此传输在 TwoWay 和 OneWayToSource 绑定上发生。 重申一下,导致源更新的因素取决于 UpdateSourceTrigger 属性的值,如触发源更新的因素部分所述。
以下各项描述了验证过程。 只要验证过程中发生验证错误或其他类型的错误,该过程就会中断:
绑定引擎检查是否为该 Binding 定义了任何将 ValidationStep 设置为 RawProposedValue 的自定义 ValidationRule 对象,在这种情况下,绑定引擎将对每个 ValidationRule 调用 Validate 方法,直到其中一个出错或直到全部通过。
绑定引擎随后会调用转换器(如果存在)。
如果转换器成功,则绑定引擎会检查是否为该 Binding 定义了任何将 ValidationStep 设置为 ConvertedProposedValue 的自定义 ValidationRule 对象,在这种情况下,绑定引擎将对每个 ValidationRule(将 ValidationStep 设置为 ConvertedProposedValue)调用 Validate 方法,直到其中一个出错或直到全部通过。
绑定引擎设置源属性。
绑定引擎检查是否为该 Binding 定义了任何将 ValidationStep 设置为 UpdatedValue 的自定义 ValidationRule 对象,在这种情况下,绑定引擎将对每个 ValidationRule(将 ValidationStep 设置为 UpdatedValue)调用 Validate 方法,直到其中一个出错或直到全部通过。 如果 DataErrorValidationRule 与绑定关联并且其 ValidationStep 设置为默认的 UpdatedValue,则此时将检查 DataErrorValidationRule。 此时检查将 ValidatesOnDataErrors 设置为 true
的所有绑定。
绑定引擎检查是否为该 Binding 定义了任何将 ValidationStep 设置为 CommittedValue 的自定义 ValidationRule 对象,在这种情况下,绑定引擎将对每个 ValidationRule(将 ValidationStep 设置为 CommittedValue)调用 Validate 方法,直到其中一个出错或直到全部通过。
如果 ValidationRule 在整个过程中的任何时间都没有通过,则绑定引擎会创建 ValidationError 对象并将其添加到绑定元素的 Validation.Errors 集合中。 绑定引擎在任何给定步骤运行 ValidationRule 对象之前,它会删除在执行该步骤期间添加到绑定元素的 Validation.Errors 附加属性的所有 ValidationError。 例如,如果将 ValidationStep 设置为 UpdatedValue 的 ValidationRule 失败,则下次执行验证过程时,绑定引擎会在调用将 ValidationStep 设置为 UpdatedValue 的任何 ValidationRule 之前删除 ValidationError。
如果 Validation.Errors 不为空,则元素的 Validation.HasError 附加属性设置为 true
。 此外,如果 Binding 的 NotifyOnValidationError 属性设置为 true
,则绑定引擎将在元素上引发 Validation.Error 附加事件。
另请注意,任何方向(目标到源或源到目标)的有效值传输操作都会清除 Validation.Errors 附加属性。
如果绑定具有关联的 ExceptionValidationRule,或将 ValidatesOnExceptions 属性设置为 true
,并且在绑定引擎设置源时引发异常,则绑定引擎将检查是否存在 UpdateSourceExceptionFilter。 可以使用 UpdateSourceExceptionFilter 回叫来提供用于处理异常的自定义处理程序。 如果未在 Binding 上指定 UpdateSourceExceptionFilter,则绑定引擎会创建具有异常的 ValidationError 并将其添加到绑定元素的 Validation.Errors 集合中。
可以在与绑定相关的对象上设置附加属性 PresentationTraceSources.TraceLevel,以接收有关特定绑定状态的信息。
数据绑定演示
绑定声明概述
绑定源概述
DataErrorValidationRule