原文链接,大部分是机器翻译,仅做了小部分修改。英、中文对照,看不懂的看英文。
Data binding overview in WPF
2019/09/19
Data binding in Windows Presentation Foundation (WPF) provides a simple and consistent way for apps to present and interact with data. Elements can be bound to data from a variety of data sources in the form of .NET objects and XML. Any ContentControl such as Button and any ItemsControl, such as ListBox and ListView, have built-in functionality to enable flexible styling of single data items or collections of data items. Sort, filter, and group views can be generated on top of the data.
WPF中的数据绑定为应用提供了一种简单而一致的方式来呈现数据并与数据进行交互。元素可以以.NET对象和XML的形式绑定到来自各种数据源的数据。任何ContentControl(如 Button和任何ItemControl(如ListBox和ListView)都具有内置功能,可灵活设置单个数据项或数据项集合的样式。可以在数据之上生成Sort、filter和group views。
The data binding functionality in WPF has several advantages over traditional models, including inherent support for data binding by a broad range of properties, flexible UI representation of data, and clean separation of business logic from UI.
与传统模型相比,WPF中的数据绑定功能具有多种优势,包括由各种属性对数据绑定的固有支持、灵活的数据UI表示以及业务逻辑与UI的完全分离。
This article first discusses concepts fundamental to WPF data binding and then covers the usage of the Binding class and other features of data binding.
首先讨论WPF数据绑定的基本概念,然后介绍绑定类的使用以及数据绑定的其他功能。
What is data binding?
什么是数据绑定?
Data binding is the process that establishes a connection between the app UI and the data it displays. If the binding has the correct settings and the data provides the proper notifications, when the data changes its value, the elements that are bound to the data reflect changes automatically. Data binding can also mean that if an outer representation of the data in an element changes, then the underlying data can be automatically updated to reflect the change. For example, if the user edits the value in a TextBox element, the underlying data value is automatically updated to reflect that change.
数据绑定是在应用UI及其显示的数据之间建立连接的过程。如果绑定具有正确的设置,并且数据提供了正确的通知,则当数据更改其值时,绑定到数据的元素会自动反映更改。数据绑定还意味着,如果元素中数据发生更改,则可以自动更新基础数据以反映更改。例如,如果用户在TextBox元素中编辑值,则基础数据值将自动更新以反映该更改。
A typical use of data binding is to place server or local configuration data into forms or other UI controls. In WPF, this concept is expanded to include binding a broad range of properties to a variety of data sources. In WPF, dependency properties of elements can be bound to .NET objects (including ADO.NET objects or objects associated with Web Services and Web properties) and XML data.
数据绑定的典型用途是将服务器或本地配置数据放入窗体或其他UI控件中。在WPF中此概念已扩展,以包括将各种属性绑定到各种数据源。在WPF中元素的依赖项属性可以绑定到.NET对象(包括ADO.NET对象或与Web服务和Web属性关联的对象)和XML数据。
For an example of data binding, take a look at the following app UI from the Data Binding Demo, which displays a list of auction items.
有关数据绑定的示例,请查看Data Binding Demo中的以下应用UI,该演示显示拍卖项目列表。
The app demonstrates the following features of data binding:
该应用程序演示了数据绑定的以下功能:
- The content of the ListBox is bound to a collection of AuctionItem objects. An AuctionItem object has properties such as Description, StartPrice, StartDate, Category, SpecialFeatures, and so on.
- ListBox的内容绑定到一系列AuctionItem对象。AuctionItem对象具有"Description"、"StartPrice "、"StartDate"、"Category "、"SpecialFeatures"等属性。
- The data (AuctionItem objects) displayed in the ListBox is templated so that the description and the current price are shown for each item. The template is created by using a DataTemplate. In addition, the appearance of each item depends on the SpecialFeatures value of the AuctionItem being displayed. If the SpecialFeatures value of the AuctionItem is Color, the item has a blue border. If the value is Highlight, the item has an orange border and a star. The Data Templating section provides information about data templating.
- ListBox中显示的数据(AuctionItem对象)将模板化,以便显示每个项目的说明和当前价格。使用DataTemplate创建模板。此外,每个项目的外观取决于所显示的AuctionItem的特殊功能值。如果拍卖项目的特殊功能值为"颜色",则该项目具有蓝色边框。如果值为"高光",则项目具有橙色边框和星形。"Data Templating数据模板化"部分提供有关数据模板的信息。
- The user can group, filter, or sort the data using the CheckBoxes provided. In the image above, the Group by category and Sort by category and date CheckBoxes are selected. You may have noticed that the data is grouped based on the category of the product, and the category name is in alphabetical order. It is difficult to notice from the image but the items are also sorted by the start date within each category. Sorting is done using a collection view. The Binding to collections section discusses collection views.
- 用户可以使用CheckBoxes对数据进行分组、筛选或排序。在上图中,选中了按类别分组和按类别和日期排序CheckBoxes。您可能已经注意到,数据是根据产品的类别分组的,并且类别名称按字母顺序排列。很难从图像中注意到,但项目也按每个类别中的开始日期排序。排序使用集合视图完成。"Binding to collections绑定到集合"部分讨论集合视图
- When the user selects an item, the ContentControl displays the details of the selected item. This experience is called the Master-detail scenario. The Master-detail scenario section provides information about this type of binding.
- 当用户选择项目时,ContentControl 将显示所选项目的详细信息。此示例称为主-细方案。主-细方案提供有关此类绑定的信息。
- The type of the StartDate property is DateTime, which returns a date that includes the time to the millisecond. In this app, a custom converter has been used so that a shorter date string is displayed. The Data conversion section provides information about converters.
- StartDate属性的类型是DateTime,它返回包含时间到毫秒的日期。在此应用程序中,已使用自定义转换器,以便显示较短的日期字符串。数据转换部分提供有关转换器的信息。
When the user selects the Add Product button, the following form comes up.
当用户选择"添加产品"按钮时,将出现以下窗体。
The user can edit the fields in the form, preview the product listing using the short or detailed preview panes, and select Submit to add the new product listing. Any existing grouping, filtering and sorting settings will apply to the new entry. In this particular case, the item entered in the above image will be displayed as the second item within the Computer category.
用户可以编辑表单中的字段,使用简短或详细的预览窗格预览产品列表,然后选择"提交"以添加新产品列表。任何现有的分组、筛选和排序设置将应用于新条目。在此特定情况下,上述图像中输入的项目将显示为"计算机"类别中的第二个项目。
Not shown in this image is the validation logic provided in the Start Date TextBox. If the user enters an invalid date (invalid formatting or a past date), the user will be notified with a ToolTip and a red exclamation point next to the TextBox. The Data Validation section discusses how to create validation logic.
此图像中未显示的是开始日期文本框中提供的验证逻辑。如果用户输入无效日期(格式无效或过期日期),则会在TextBox旁边使用工具提示和红色感叹号通知用户。数据验证部分讨论如何创建验证逻辑。
Before going into the different features of data binding outlined above, we will first discuss the fundamental concepts that are critical to understanding WPF data binding.
在介绍上面概述的数据绑定的不同功能之前,我们将首先讨论对理解WPF数据绑定至关重要的基本概念。
Basic data binding concepts
基本数据绑定概念
Regardless of what element you are binding and the nature of your data source, each binding always follows the model illustrated by the following figure.
无论您绑定的是哪个元素以及数据源的性质,每个绑定始终遵循下图所示的模型。
As the figure shows, data binding is essentially the bridge between your binding target and your binding source. The figure demonstrates the following fundamental WPF data binding concepts:
如图所示,数据绑定本质上是绑定目标和绑定源之间的桥梁。下图演示了以下基本的 WPF 数据绑定概念:
Typically, each binding has four components:
通常,每个绑定有四个组成部分:
- A binding target object.
- 绑定目标对象。
- A target property.
- 目标属性。
- A binding source.
- 绑定源。
- A path to the value in the binding source to use.
- 要使用的绑定源中的值的路径。
For example, if you want to bind the content of a TextBox to the Employee.Name property, your target object is the TextBox, the target property is the Text property, the value to use is Name, and the source object is the Employee object.
例如,如果要将TextBox的内容绑定到Employee.Name属性,则目标对象是TextBox,目标属性是Text属性,要使用的值是Name,源对象是Employer对象。
The target property must be a dependency property. Most UIElement properties are dependency properties, and most dependency properties, except read-only ones, support data binding by default. (Only types derived from DependencyObject can define dependency properties; and all UIElement types derive from DependencyObject.)
目标属性必须是依赖项属性。大多数UIElement属性是依赖项属性,默认情况下,除只读属性外,大多数依赖项属性都支持数据绑定。(只有派生自DependencyObject的类型才能定义依赖项属性;并且所有UIElement类型都派生自依赖项对象。
Although not shown in the figure, it should be noted that the binding source object is not restricted to being a custom .NET object. WPF data binding supports data in the form of .NET objects and XML. To provide some examples, your binding source may be a UIElement, any list object, an ADO.NET or Web Services object, or an XmlNode that contains your XML data. For more information, see Binding sources overview.
虽然图中未显示,但应注意的是,绑定源对象并不限于自定义.NET对象。WPF数据绑定支持.NET对象和XML形式的数据。为了提供一些示例,绑定源可以是UIElement、任何列表对象、ADO.NET或Web服务对象,或者包含XML数据的XmlNode。有关详细信息,请参阅绑定源概述。
It is important to remember that when you are establishing a binding, you are binding a binding target to a binding source. For example, if you are displaying some underlying XML data in a ListBox using data binding, you are binding your ListBox to the XML data.
请务必记住,在建立绑定时,要将绑定目标绑定到绑定源。例如,如果使用数据绑定在ListBox中显示一些基础XML数据,则表示将ListBox绑定到XML数据。
To establish a binding, you use the Binding object. The rest of this article discusses many of the concepts associated with and some of the properties and usage of the Binding object.
要建立绑定,请使用绑定对象。本文的其余部分将讨论与绑定对象相关的许多概念以及一些属性和用法。
Direction of the data flow
数据流的方向
As indicated by the arrow in the previous figure, the data flow of a binding can go from the binding target to the binding source (for example, the source value changes when a user edits the value of a TextBox) and/or from the binding source to the binding target (for example, your TextBox content is updated with changes in the binding source) if the binding source provides the proper notifications.
如上图中的箭头所示,如果绑定源提供了正确的通知,则绑定的数据流可以从绑定目标转到绑定源(例如,当用户编辑 TextBox 的值时源值更改)和/或从绑定源到绑定目标(例如,您的 TextBox 内容会随绑定源中的更改进行更新)。
You may want your app to enable users to change the data and propagate it back to the source object. Or you may not want to enable users to update the source data. You can control the flow of data by setting the Binding.Mode.
您可能希望你的应用使用户能够更改数据并将更改传递回源对象。或者,您可能不希望允许用户更新源数据。您可以通过设置绑定模式来控制数据流。
This figure illustrates the different types of data flow:
下图说明了不同类型的数据流:
OneWay binding causes changes to the source property to automatically update the target property, but changes to the target property are not propagated back to the source property. This type of binding is appropriate if the control being bound is implicitly read-only. For instance, you may bind to a source such as a stock ticker, or perhaps your target property has no control interface provided for making changes, such as a data-bound background color of a table. If there is no need to monitor the changes of the target property, using the OneWay binding mode avoids the overhead of the TwoWay binding mode.
OneWay绑定会导致对源属性的更改自动更新目标属性,但对目标属性的更改不会传递回源属性。如果绑定的控件是隐式只读的,则这种类型的绑定是合适的。例如,您可以绑定到源(如股票代码代码),或者目标属性没有为进行更改而提供的控件接口,例如表的数据绑定背景颜色。如果无需监视目标属性的更改,则使用OneWay绑定模式可避免双路绑定模式的开销。
TwoWay binding causes changes to either the source property or the target property to automatically update the other. This type of binding is appropriate for editable forms or other fully interactive UI scenarios. Most properties default to OneWay binding, but some dependency properties (typically properties of user-editable controls such as the TextBox.Text and CheckBox.IsChecked default to TwoWay binding. A programmatic way to determine whether a dependency property binds one-way or two-way by default is to get the property metadata with DependencyProperty.GetMetadata and then check the Boolean value of the FrameworkPropertyMetadata.BindsTwoWayByDefault property.
双向绑定会导致对源属性或目标属性的更改自动更新另一个属性。这种类型的绑定适用于可编辑的窗体或其他完全交互式的 UI 方案。大多数属性默认为 OneWay 绑定,但某些依赖项属性(通常是用户可编辑控件的属性,如 TextBox.Text 和 CheckBox.IsChecked 默认为双路绑定。默认情况下,确定依赖项属性是单向绑定还是双向绑定的编程方法是,使用DependencyProperty.GetMetadata获取元数据,然后检查FrameworkPropertyMetadata.BindsTwoWayByDefault属性的Boolean值。
OneWayToSource is the reverse of OneWay binding; it updates the source property when the target property changes. One example scenario is if you only need to reevaluate the source value from the UI.
OneWayToSource是单向绑定的逆向方式;它在目标属性更改时更新源属性。一个示例方案是,如果您只需要从UI重新评估源值。
Not illustrated in the figure is OneTime binding, which causes the source property to initialize the target property but does not propagate subsequent changes. If the data context changes or the object in the data context changes, the change is not reflected in the target property. This type of binding is appropriate if either a snapshot of the current state is appropriate or the data is truly static. This type of binding is also useful if you want to initialize your target property with some value from a source property and the data context is not known in advance. This mode is essentially a simpler form of OneWay binding that provides better performance in cases where the source value does not change.
图中未说明的是 OneTime 绑定,它会导致源属性初始化目标属性,但不会传播后续更改。如果数据上下文更改或数据上下文中的对象发生更改,则更改不会反映在目标属性中。如果当前状态的快照是适当的,或者数据是真正静态的,则这种类型的绑定是适当的。如果要使用源属性中的某些值初始化目标属性,并且数据上下文事先不知道,则这种类型的绑定也很有用。此模式本质上是 OneWay 绑定的一种更简单的形式,可在源值不更改的情况下提供更好的性能。
To detect source changes (applicable to OneWay and TwoWay bindings), the source must implement a suitable property change notification mechanism such as INotifyPropertyChanged. See How to: Implement property change notification for an example of an INotifyPropertyChanged implementation.
要检测源更改(适用于单向和双向绑定),源必须实现适当的属性更改通知机制,如 INotifyPropertyChanged。参看如何:INotifyPropertyChanged 实现的示例实现属性更改通知。
The Binding.Mode property provides more information about binding modes and an example of how to specify the direction of a binding.
Binding.Mode 属性提供有关绑定模式的详细信息以及如何指定绑定方向的示例。
What triggers source updates
什么操作会触发源更新
Bindings that are TwoWay or OneWayToSource listen for changes in the target property and propagate them back to the source, known as updating the source. For example, you may edit the text of a TextBox to change the underlying source value.
"双向"或"单向"的绑定会侦听目标属性中的更改,并将它们传递回源,称为更新源。例如,您可以编辑 TextBox 的文本以更改基础源值。
However, is your source value updated while you are editing the text or after you finish editing the text and the control loses focus? The Binding.UpdateSourceTrigger property determines what triggers the update of the source. The dots of the right arrows in the following figure illustrate the role of the Binding.UpdateSourceTrigger property.
但是,在编辑文本时还是完成编辑文本且控件失去焦点后,源值是否更新?Binding.UpdateSourceTrigger 属性决定了何时触发源更新。下图中右箭头的点说明了 Binding.UpdateSourceTrigger 属性的作用。
If the UpdateSourceTrigger value is UpdateSourceTrigger.PropertyChanged, then the value pointed to by the right arrow of TwoWay or the OneWayToSource bindings is updated as soon as the target property changes. However, if the UpdateSourceTrigger value is LostFocus, then that value only is updated with the new value when the target property loses focus.
如果 UpdateSourceTrigger 值为 UpdateSourceTrigger.PropertyChanged,则一旦目标属性发生更改,由" TwoWay"或 OneWayToSource 绑定右箭头指向的值将更新。但是,如果 UpdateSourceTrigger 值为"LostFocus",则仅当目标属性失去焦点时,该值才会使用新值更新。
Similar to the Mode property, different dependency properties have different default UpdateSourceTrigger values. The default value for most dependency properties is PropertyChanged, while the TextBox.Text property has a default value of LostFocus. PropertyChanged means the source updates usually happen whenever the target property changes. Instant changes are fine for CheckBoxes and other simple controls. However, for text fields, updating after every keystroke can diminish performance and denies the user the usual opportunity to backspace and fix typing errors before committing to the new value.
与 Mode 属性类似,不同的依赖项属性具有不同的默认 UpdateSourceTrigger 值。大多数依赖项属性的默认值为属性更改,而 TextBox.Text 属性的默认值为 LostFocus。属性更改意味着源更新通常在目标属性更改时发生。对于 CheckBox 和其他简单控件,即时更改可以。但是,对于文本字段,每次击键后更新可能会降低性能,并剥夺用户在提交新值之前返回空间和修复键入错误的常规机会。
See the UpdateSourceTrigger property page for information about how to find the default value of a dependency property.
有关如何查找依赖项属性的默认值的信息,请参阅 UpdateSourceTrigger 属性页。
The following table provides an example scenario for each UpdateSourceTrigger value using the TextBox as an example.
下表提供了每个 UpdateSourceTrigger 值的示例方案,以使用TextBox为例。
UpdateSourceTrigger value |
When the source value is updated |
Example scenario for TextBox |
LostFocus (default for TextBox.Text) |
When the TextBox control loses focus. |
A TextBox that is associated with validation logic (see Data Validation below). |
PropertyChanged |
As you type into the TextBox. |
TextBox controls in a chat room window. |
Explicit |
When the app calls UpdateSource. |
TextBox controls in an editable form (updates the source values only when the user clicks the submit button). |
For an example, see How to: Control when the TextBox text updates the source.
Creating a binding
创建绑定
To restate some of the concepts discussed in the previous sections, you establish a binding using the Binding object, and each binding usually has four components: a binding target, a target property, a binding source, and a path to the source value to use. This section discusses how to set up a binding.
为了重说前几节中讨论的一些概念,您可以使用 Binding 对象建立绑定,每个绑定通常有四个组件:绑定目标、目标属性、绑定源和要使用的源值的路径。本节讨论如何设置绑定。
Consider the following example, in which the binding source object is a class named MyData that is defined in the SDKSample namespace. For demonstration purposes, MyData has a string property named ColorName whose value is set to "Red". Thus, this example generates a button with a red background.
请考虑以下示例,其中绑定源对象是在 SDKSample 命名空间中定义的名为 MyData 的类。出于演示目的,MyData 具有名为 ColorName 的字符串属性,其值设置为" Red "。因此,此示例生成一个带有红色背景的按钮。
1 <DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 2 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 3 xmlns:c="clr-namespace:SDKSample"> 4 <DockPanel.Resources> 5 <c:MyData x:Key="myDataSource"/> 6 </DockPanel.Resources> 7 <DockPanel.DataContext> 8 <Binding Source="{StaticResource myDataSource}"/> 9 </DockPanel.DataContext> 10 <Button Background="{Binding Path=ColorName}" 11 Width="150" Height="30"> 12 I am bound to be RED! 13 </Button> 14 </DockPanel>
For more information on the binding declaration syntax and examples of how to set up a binding in code, see Binding Declarations Overview.
If we apply this example to our basic diagram, the resulting figure looks like the following. This figure describes a OneWay binding because the Background property supports OneWay binding by default.
如果我们将此示例应用于基本关系图,则生成的图形如下所示。此图描述了单向绑定,因为后台属性默认支持 OneWay 绑定。
You may wonder why this binding works even though the ColorName property is of type string while the Background property is of type Brush. This binding uses default type conversion, which is discussed in the Data conversion section.
您可能想知道为什么,即使是 ColorName 属性的类型字符串,而Background属性是Brush类型,此绑定也能工作。此绑定使用默认类型转换,在Data conversion部分中讨论。
Specifying the binding source
Notice that in the previous example, the binding source is specified by setting the DockPanel.DataContext property. The Button then inherits the DataContext value from the DockPanel, which is its parent element. To reiterate, the binding source object is one of the four necessary components of a binding. Therefore, without the binding source object being specified, the binding would do nothing.
请注意,在前面的示例中,绑定源是通过设置 DockPanel.DataContext 属性来指定的。然后,Button从 DockPanel 继承 DataContext 值,该值是其父元素。重申一下,绑定源对象是绑定的四个必要组件之一。因此,如果不指定绑定源对象,绑定将不执行任何操作。
There are several ways to specify the binding source object. Using the DataContext property on a parent element is useful when you are binding multiple properties to the same source. However, sometimes it may be more appropriate to specify the binding source on individual binding declarations. For the previous example, instead of using the DataContext property, you can specify the binding source by setting the Binding.Source property directly on the binding declaration of the button, as in the following example.
有几种方法可以指定绑定源对象。将多个属性绑定到同一源时,在父元素上使用 DataContext 属性非常有用。但是,有时在单个绑定声明上指定绑定源可能更合适。对于前面的示例,您可以直接在按钮的绑定声明上设置 Binding.Source 属性,而不是使用 DataContext 属性来指定绑定源,如下面的示例所示。
1 <DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 2 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 3 xmlns:c="clr-namespace:SDKSample"> 4 <DockPanel.Resources> 5 <c:MyData x:Key="myDataSource"/> 6 </DockPanel.Resources> 7 <Button Background="{Binding Source={StaticResource myDataSource}, Path=ColorName}" 8 Width="150" Height="30"> 9 I am bound to be RED! 10 </Button> 11 </DockPanel>
Other than setting the DataContext property on an element directly, inheriting the DataContext value from an ancestor (such as the button in the first example), and explicitly specifying the binding source by setting the Binding.Source property on the binding (such as the button the last example), you can also use the Binding.ElementName property or the Binding.RelativeSource property to specify the binding source. The ElementName property is useful when you are binding to other elements in your app, such as when you are using a slider to adjust the width of a button. The RelativeSource property is useful when the binding is specified in a ControlTemplate or a Style. For more information, see How to: Specify the binding source.
除了直接在元素上设置 DataContext 属性外,从祖先继承 DataContext 值(如第一个示例中的按钮),并通过在绑定上设置 Binding.Source 属性来显式指定绑定源(例如,还可以使用 Binding.ElementName 属性或绑定.相对源属性来指定绑定源。当您绑定到应用中的其他元素时,例如使用滑块调整按钮的宽度时,"元素名"属性非常有用。在 ControlTemplate 或 Style 中指定绑定时,相对源属性非常有用。有关详细信息,请参阅如何:指定绑定源。
Specifying the path to the value
If your binding source is an object, you use the Binding.Path property to specify the value to use for your binding. If you are binding to XML data, you use the Binding.XPath property to specify the value. In some cases, it may be applicable to use the Path property even when your data is XML. For example, if you want to access the Name property of a returned XmlNode (as a result of an XPath query), you should use the Path property in addition to the XPath property.
For more information, see the Path and XPath properties.
Although we have emphasized that the Path to the value to use is one of the four necessary components of a binding, in the scenarios that you want to bind to an entire object, the value to use would be the same as the binding source object. In those cases, it is applicable to not specify a Path. Consider the following example.
尽管我们强调要使用的值路径是绑定的四个必要组件之一,但在要绑定到整个对象的方案中,要使用的值将与绑定源对象相同。在这些情况下,它适用于不指定路径。请考虑以下示例。
1 <ListBox ItemsSource="{Binding}" 2 IsSynchronizedWithCurrentItem="true"/>
The above example uses the empty binding syntax: {Binding}. In this case, the ListBox inherits the DataContext from a parent DockPanel element (not shown in this example). When the path is not specified, the default is to bind to the entire object. In other words, in this example, the path has been left out because we are binding the ItemsSource property to the entire object. (See the Binding to collections section for an in-depth discussion.)
上面的示例使用空绑定语法: [绑定]。在这种情况下,ListBox 从父 DockPanel 元素继承 DataContext(在此示例中未显示)。如果未指定路径,则默认为绑定到整个对象。换句话说,在此示例中,路径被遗漏了,因为我们将 ItemsSource 属性绑定到整个对象。(有关深入讨论,请参阅绑定到集合部分。
Other than binding to a collection, this scenario is also useful when you want to bind to an entire object instead of just a single property of an object. For example, if your source object is of type String, you may simply want to bind to the string itself. Another common scenario is when you want to bind an element to an object with several properties.
除了绑定到集合之外,如果要绑定到整个对象,而不是仅绑定到对象的单个属性,此方案也很有用。例如,如果源对象为 String 类型,则可能只想绑定到字符串本身。另一种常见方案是,您希望将元素绑定到具有多个属性的对象。
You may need to apply custom logic so that the data is meaningful to your bound target property. The custom logic may be in the form of a custom converter if default type conversion does not exist. See Data conversion for information about converters.
您可能需要应用自定义逻辑,以便数据对绑定的目标属性有意义。如果默认类型转换不存在,则自定义逻辑可能采用自定义转换器的形式。有关转换器的信息,请参阅数据转换。
Binding and BindingExpression
绑定和绑定表达式
Before getting into other features and usages of data binding, it is useful to introduce the BindingExpression class. As you have seen in previous sections, the Binding class is the high-level class for the declaration of a binding; it provides many properties that allow you to specify the characteristics of a binding. A related class, BindingExpression, is the underlying object that maintains the connection between the source and the target. A binding contains all the information that can be shared across several binding expressions. A BindingExpression is an instance expression that cannot be shared and contains all the instance information of the Binding.
在介绍数据绑定的其他功能和用法之前,引入 BindingExpression 类非常有用。如前面几节所述,绑定类是绑定声明的高级类;但是,绑定类是绑定声明的高级类。它提供了许多属性,允许您指定绑定的特征。相关类 BindingExpression 是维护源和目标之间连接的基础对象。绑定包含可在多个绑定表达式之间共享的所有信息。绑定表达式是不能共享的实例表达式,包含绑定的所有实例信息。
Consider the following example, where myDataObject is an instance of the MyData class, myBinding is the source Binding object, and MyData is a defined class that contains a string property named MyDataProperty. This example binds the text content of myText, an instance of TextBlock, to MyDataProperty.
请考虑以下示例,其中 myDataObject 是 MyData 类的实例,myBinding 是源绑定对象,MyData 是包含名为 MyDataProperty 的字符串属性的已定义类。本示例将 myText(文本块的实例)的文本内容绑定到 MyDataProperty。
1 // Make a new source 2 var myDataObject = new MyData(); 3 var myBinding = new Binding("ColorName") 4 { 5 Source = myDataObject 6 }; 7 8 // Bind the data source to the TextBox control's Text dependency property 9 myText.SetBinding(TextBlock.TextProperty, myBinding);
You can use the same myBinding object to create other bindings. For example, you can use the myBinding object to bind the text content of a check box to MyDataProperty. In that scenario, there will be two instances of BindingExpression sharing the myBinding object.
您可以使用相同的 myBinding 对象创建其他绑定。例如,可以使用 myBinding 对象将复选框的文本内容绑定到 MyDataProperty。在这种情况下,将有两个绑定表达式实例共享 myBinding 对象。
A BindingExpression object is returned by calling GetBindingExpression on a data-bound object. The following articles demonstrate some of the usages of the BindingExpression class:
通过调用数据绑定对象上的 GetBindingExpression 来返回绑定表达式。以下文章演示了绑定表达式类的一些用法:
- Get the binding object from a bound target property
- 从绑定的目标属性获取绑定对象
- Control When the TextBox text updates the source
- 控制文本框文本何时更新源
Data conversion
数据转换
In the previous example, the button is red because its Background property is bound to a string property with the value "Red". This string value works because a type converter is present on the Brush type to convert the string value to a Brush.
在前面的示例中,按钮为红色,因为它的"背景"属性绑定到具有值"红色"的字符串属性。此字符串值之所以有效,是因为 Brush 类型上存在一个类型转换器,用于将字符串值转换为画笔。
Adding this information to the figure the Creating a Binding section looks like this.
将此信息添加到图"创建绑定"部分如下所示。
However, what if instead of having a property of type string your binding source object has a Color property of type Color? In that case, in order for the binding to work you would need to first turn the Color property value into something that the Background property accepts. You would need to create a custom converter by implementing the IValueConverter interface, as in the following example.
但是,如果绑定源对象具有颜色类型属性,而不是具有字符串类型的属性,该怎么办?在这种情况下,为了使绑定正常工作,您需要首先将 Color 属性值转换为"背景"属性接受的内容。您需要通过实现 IValue 转换器接口来创建自定义转换器,如下例所示。
1 [ValueConversion(typeof(Color), typeof(SolidColorBrush))] 2 public class ColorBrushConverter : IValueConverter 3 { 4 public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 5 { 6 Color color = (Color)value; 7 return new SolidColorBrush(color); 8 } 9 10 public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 11 { 12 return null; 13 } 14 }
See IValueConverter for more information.
有关详细信息,请参阅 IValue 转换器。
Now the custom converter is used instead of default conversion, and our diagram looks like this.
现在使用自定义转换器而不是默认转换,我们的图如下所示。
To reiterate, default conversions may be available because of type converters that are present in the type being bound to. This behavior will depend on which type converters are available in the target. If in doubt, create your own converter.
重申一下,默认转换可能可用,因为绑定到的类型中存在类型转换器。此行为将取决于目标中可用的类型转换器。如果有疑问,请创建自己的转换器。
The following are some typical scenarios where it makes sense to implement a data converter:
以下是实现数据转换器的一些典型方案:
Your data should be displayed differently, depending on culture. For instance, you might want to implement a currency converter or a calendar date/time converter based on the conventions used in a particular culture.
您的数据的显示方式应有所不同,具体取决于区域性。例如,您可能希望基于特定区域性中使用的约定实现货币转换器或日历日期/时间转换器。
The data being used is not necessarily intended to change the text value of a property, but is instead intended to change some other value, such as the source for an image, or the color or style of the display text. Converters can be used in this instance by converting the binding of a property that might not seem to be appropriate, such as binding a text field to the Background property of a table cell.
正在使用的数据不一定旨在更改属性的文本值,而是旨在更改一些其他值,例如图像的源或显示文本的颜色或样式。在此实例中,可以通过转换看似不合适的属性的绑定(例如将文本字段绑定到表单元格的"背景"属性)来使用此实例。
More than one control or multiple properties of controls are bound to the same data. In this case, the primary binding might just display the text, whereas other bindings handle specific display issues but still use the same binding as source information.
超过一个控件或者控件的多个属性绑定到同一数据。在这种情况下,主绑定可能只显示文本,而其他绑定处理特定的显示问题,但仍使用与源信息相同的绑定。
A target property has a collection of bindings, which is termed MultiBinding. For MultiBinding, you use a custom IMultiValueConverter to produce a final value from the values of the bindings. For example, color may be computed from red, blue, and green values, which can be values from the same or different binding source objects. See MultiBinding for examples and information.
目标属性具有绑定的集合,称为多绑定。对于多绑定,您可以使用自定义 IMultiValue 转换器从绑定的值生成最终值。例如,颜色可以从红色、蓝色和绿色值计算,这些值可以是相同或不同绑定源对象的值。有关示例和信息,请参阅多绑定。
Binding to collections
绑定到集合
A binding source object can be treated either as a single object whose properties contain data or as a data collection of polymorphic objects that are often grouped together (such as the result of a query to a database). So far we've only discussed binding to single objects. However, binding to a data collection is a common scenario. For example, a common scenario is to use an ItemsControl such as a ListBox, ListView, or TreeView to display a data collection, such as in the app shown in the What is data binding section.
绑定源对象可以被视为属性包含数据的单个对象,也可以被视为通常分组在一起的多态对象的数据收集(如对数据库的查询结果)。到目前为止,我们只讨论了绑定到单个对象。但是,绑定到数据收集是常见方案。例如,常见方案是使用"列表框、ListView"或"树视图"等项控件来显示数据收集,如在"什么是数据绑定"部分中显示的应用中。
Fortunately, our basic diagram still applies. If you are binding an ItemsControl to a collection, the diagram looks like this.
幸运的是,我们的基本图表仍然适用。如果要将ItemsControl 绑定到集合,则关系图如下所示。
As shown in this diagram, to bind an ItemsControl to a collection object, ItemsControl.ItemsSource property is the property to use. You can think of ItemsSource as the content of the ItemsControl. The binding is OneWay because the ItemsSource property supports OneWay binding by default.
如图所示,要将ItemsControl 绑定到集合对象,ItemsControl.ItemsSource 属性是要使用的属性。您可以将项目源视为项目控制的内容。绑定是单向,因为 ItemsSource 属性默认支持 OneWay 绑定。
How to implement collections
如何实现集合
You can enumerate over any collection that implements the IEnumerable interface. However, to set up dynamic bindings so that insertions or deletions in the collection update the UI automatically, the collection must implement the INotifyCollectionChanged interface. This interface exposes an event that should be raised whenever the underlying collection changes.
您可以枚举实现IE 不接口的任何集合。但是,要设置动态绑定,以便集合中的插入或删除自动更新 UI,集合必须实现 INotifyCollectionChanged 接口。此接口公开每当基础集合发生更改时应引发的事件。
WPF provides the ObservableCollection<T> class, which is a built-in implementation of a data collection that exposes the INotifyCollectionChanged interface. To fully support transferring data values from source objects to targets, each object in your collection that supports bindable properties must also implement the INotifyPropertyChanged interface. For more information, see Binding sources overview.
WPF 提供可观察集合<T>类,这是公开INotifyCollectionChanged接口的数据收集的内置实现。要完全支持将数据值从源对象传输到目标,集合中支持可绑定属性的每个对象还必须实现 INotifyPropertyChanged 接口。有关详细信息,请参阅绑定源概述。
Before implementing your own collection, consider using ObservableCollection<T> or one of the existing collection classes, such as List<T>, Collection<T>, and BindingList<T>, among many others. If you have an advanced scenario and want to implement your own collection, consider using IList, which provides a non-generic collection of objects that can be individually accessed by the index, and thus provides the best performance.
在实现您自己的集合之前,请考虑使用可观察集合<T> 或现有集合类之一,如 List<T>集合<T>和绑定列表<Lt<T>等。如果您有高级方案,并且想要实现自己的集合,请考虑使用 IList,它提供非泛型对象集合,索引可以单独访问这些对象,从而提供最佳性能。
Collection views
集合视图
Once your ItemsControl is bound to a data collection, you may want to sort, filter, or group the data. To do that, you use collection views, which are classes that implement the ICollectionView interface.
将ItemsControl 绑定到数据收集后,您可能需要对数据进行排序、筛选或分组。为此,可以使用集合视图,这些集合视图是实现 ICollectionView接口的类。
What Are collection views?
什么是集合视图?
A collection view is a layer on top of a binding source collection that allows you to navigate and display the source collection based on sort, filter, and group queries, without having to change the underlying source collection itself. A collection view also maintains a pointer to the current item in the collection. If the source collection implements the INotifyCollectionChanged interface, the changes raised by the CollectionChanged event are propagated to the views.
集合视图是绑定源集合之上的图层,允许您基于排序、筛选和分组查询导航和显示源集合,而无需更改基础源集合本身。集合视图还维护指向集合中当前项的指针。如果源集合实现INotifyCollectionChanged 接口,则收集更改事件引发的更改将传播到视图。
Because views do not change the underlying source collections, each source collection can have multiple views associated with it. For example, you may have a collection of Task objects. With the use of views, you can display that same data in different ways. For example, on the left side of your page you may want to show tasks sorted by priority, and on the right side, grouped by area.
由于视图不更改基础源集合,因此每个源集合可以有多个与之关联的视图。例如,您可能具有 Task 对象的集合。使用视图时,可以以不同的方式显示相同的数据。例如,在页面的左侧,您可能希望显示按优先级排序的任务,在右侧显示按区域分组的任务。
How to create a view
如何创建视图
One way to create and use a view is to instantiate the view object directly and then use it as the binding source. For example, consider the Data binding demo app shown in the What is data binding section. The app is implemented such that the ListBox binds to a view over the data collection instead of the data collection directly. The following example is extracted from the Data binding demo app. The CollectionViewSource class is the XAML proxy of a class that inherits from CollectionView. In this particular example, the Source of the view is bound to the AuctionItems collection (of type ObservableCollection<T>) of the current app object.
创建和使用视图的一种方法是直接实例化视图对象,然后将其用作绑定源。例如,请考虑"什么是数据绑定"部分中显示的数据绑定演示应用。应用实现,以便 ListBox 绑定到数据收集上的视图,而不是直接数据收集。下面的示例从数据绑定演示应用中提取。集合视图源类是从集合视图继承的类的 XAML 代理。在此特定示例中,视图的源绑定到当前应用对象的"可观察集合"集合(可观察集合类型)。
1 <Window.Resources> 2 <CollectionViewSource 3 Source="{Binding Source={x:Static Application.Current}, Path=AuctionItems}" 4 x:Key="listingDataView" /> 5 </Window.Resources>
The resource listingDataView then serves as the binding source for elements in the app, such as the ListBox.
listingDataView资源然后充当应用中元素的绑定源,如 ListBox。
1 <ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8" 2 ItemsSource="{Binding Source={StaticResource listingDataView}}" />
To create another view for the same collection, you can create another CollectionViewSource instance and give it a different x:Key name.
要为同一集合创建另一个视图,可以创建另一个CollectionViewSource 实例,并为其指定不同的 x:Key 名称。
The following table shows what view data types are created as the default collection view or by CollectionViewSource based on the source collection type.
下表显示了创建哪些视图数据类型作为默认集合视图或基于源集合类型创建的CollectionViewSource。
Source collection type |
Collection view type |
Notes |
An internal type based on CollectionView |
Cannot group items. |
|
Fastest. |
||
Using a default view
使用默认视图
Specifying a collection view as a binding source is one way to create and use a collection view. WPF also creates a default collection view for every collection used as a binding source. If you bind directly to a collection, WPF binds to its default view. This default view is shared by all bindings to the same collection, so a change made to a default view by one bound control or code (such as sorting or a change to the current item pointer, discussed later) is reflected in all other bindings to the same collection.
将集合视图指定为绑定源是创建和使用集合视图的一种方式。WPF 还会为每个用作绑定源的集合创建默认集合视图。如果直接绑定到集合,WPF 将绑定到其默认视图。此默认视图由同一集合的所有绑定共享,因此由一个绑定控件或代码对默认视图所做的更改(如排序或对当前项指针的更改,稍后讨论)将反映在对同一集合的所有其他绑定中。
To get the default view, you use the GetDefaultView method. For an example, see Get the default view of a data collection.
要获取默认视图,请使用GetDefaultView 方法。例如,请参阅获取数据收集的默认视图。
Collection views with ADO.NET DataTables
具有ADO.NET数据表的集合视图
To improve performance, collection views for ADO.NET DataTable or DataView objects delegate sorting and filtering to the DataView, which causes sorting and filtering to be shared across all collection views of the data source. To enable each collection view to sort and filter independently, initialize each collection view with its own DataView object.
为了提高性能,ADO.NET DataTable或 DataView对象的集合视图将排序和筛选委托给 DataView,这将导致在数据源的所有集合视图中共享排序和筛选。要使每个集合视图能够独立排序和筛选,使用自己的 DataView 对象初始化每个集合视图。
Sorting
排序
As mentioned before, views can apply a sort order to a collection. As it exists in the underlying collection, your data may or may not have a relevant, inherent order. The view over the collection allows you to impose an order, or change the default order, based on comparison criteria that you supply. Because it is a client-based view of the data, a common scenario is that the user might want to sort columns of tabular data per the value that the column corresponds to. Using views, this user-driven sort can be applied, again without making any changes to the underlying collection or even having to requery for the collection content. For an example, see Sort a GridView column when a header is clicked.
如前所述,视图可以对集合应用排序顺序。由于数据存在于基础集合中,因此数据可能具有相关固有顺序,也可能没有。集合上的视图允许您根据提供的比较条件下达订单或更改默认订单。因为它是基于客户端的数据视图,因此通常的情况是,用户可能希望根据列对应的值对表格数据的列进行排序。使用视图,可以应用此用户驱动的排序,同样无需对基础集合进行任何更改,甚至不必重新查询集合内容。例如,请参阅单击标题时对 GridView 列进行排序。
The following example shows the sorting logic of the "Sort by category and date" CheckBox of the app UI in the What is data binding section.
下面的示例在"什么是数据绑定"部分中显示了应用 UI的"按类别和日期排序"复选框的排序逻辑。
1 private void AddSortCheckBox_Checked(object sender, RoutedEventArgs e) 2 { 3 // Sort the items first by Category and then by StartDate 4 listingDataView.SortDescriptions.Add(new SortDescription("Category", ListSortDirection.Ascending)); 5 listingDataView.SortDescriptions.Add(new SortDescription("StartDate", ListSortDirection.Ascending)); 6 }
Filtering
筛选
Views can also apply a filter to a collection, so that the view shows only a certain subset of the full collection. You might filter on a condition in the data. For instance, as is done by the app in the What is data binding section, the "Show only bargains" CheckBox contains logic to filter out items that cost $25 or more. The following code is executed to set ShowOnlyBargainsFilter as the Filter event handler when that CheckBox is selected.
视图还可以将筛选器应用于集合,以便视图仅显示完整集合的特定子集。您可以根据数据中的条件进行筛选。例如,正如应用在"什么是数据绑定"部分所做的那样,"只显示讨价还价"复选框包含用于筛选出成本为 25 美元或以上的项目的逻辑。执行以下代码,以在选中该复选框时将"显示仅讨价还价"筛选器设置为筛选器事件处理程序。
1 private void AddFilteringCheckBox_Checked(object sender, RoutedEventArgs e) 2 { 3 if (((CheckBox)sender).IsChecked == true) 4 listingDataView.Filter += ListingDataView_Filter; 5 else 6 listingDataView.Filter -= ListingDataView_Filter; 7 }
The ShowOnlyBargainsFilter event handler has the following implementation.
显示只讨价还价筛选事件处理程序具有以下实现。
1 private void ListingDataView_Filter(object sender, FilterEventArgs e) 2 { 3 // Start with everything excluded 4 e.Accepted = false; 5 6 // Only inlcude items with a price less than 25 7 if (e.Item is AuctionItem product && product.CurrentPrice < 25) 8 e.Accepted = true; 9 }
If you are using one of the CollectionView classes directly instead of CollectionViewSource, you would use the Filter property to specify a callback. For an example, see Filter Data in a View.
如果直接使用一个集合视图类而不是 CollectionViewSource,则可以使用 Filter 属性指定回调。例如,请参阅"视图"中的"筛选数据"。
Grouping
分组
Except for the internal class that views an IEnumerable collection, all collection views support grouping, which allows the user to partition the collection in the collection view into logical groups. The groups can be explicit, where the user supplies a list of groups, or implicit, where the groups are generated dynamically depending on the data.
除了查看IE 不计集合的内部类外,所有集合视图都支持分组,这允许用户将集合视图中的集合分区到逻辑组中。组可以是显式的,其中用户提供组列表,也可以是隐式组,其中组根据数据动态生成。
The following example shows the logic of the "Group by category" CheckBox.
下面的示例显示了"按类别分组"复选框的逻辑。
1 // This groups the items in the view by the property "Category" 2 var groupDescription = new PropertyGroupDescription(); 3 groupDescription.PropertyName = "Category"; 4 listingDataView.GroupDescriptions.Add(groupDescription);
For another grouping example, see Group Items in a ListView That Implements a GridView.
有关另一个分组示例,请参阅实现网格视图 的列表视图中的"对项目进行分组"。
Current item pointers
当前项指针
Views also support the notion of a current item. You can navigate through the objects in a collection view. As you navigate, you are moving an item pointer that allows you to retrieve the object that exists at that particular location in the collection. For an example, see Navigate through the objects in a data CollectionView.
视图还支持当前项的概念。您可以在集合视图中浏览对象。导航时,将移动一个项指针,该指针允许您检索存在于集合中该特定位置的对象。例如,请参阅在数据收集视图中浏览对象。
Because WPF binds to a collection only by using a view (either a view you specify, or the collection's default view), all bindings to collections have a current item pointer. When binding to a view, the slash ("/") character in a Path value designates the current item of the view. In the following example, the data context is a collection view. The first line binds to the collection. The second line binds to the current item in the collection. The third line binds to the Description property of the current item in the collection.
由于 WPF 仅通过使用视图(指定的视图或集合的默认视图)绑定到集合,因此对集合的所有绑定都有一个当前项指针。绑定到视图时,Path 值中的斜杠 ("/") 字符将指定视图的当前项。在下面的示例中,数据上下文是一个集合视图。第一行绑定到集合。第二行绑定到集合中的当前项。第三行绑定到集合中当前项的"描述"属性。
1 <Button Content="{Binding }" /> 2 <Button Content="{Binding Path=/}" /> 3 <Button Content="{Binding Path=/Description}" />
The slash and property syntax can also be stacked to traverse a hierarchy of collections. The following example binds to the current item of a collection named Offices, which is a property of the current item of the source collection.
也可以堆叠斜杠和属性语法以遍历集合的层次结构。下面的示例绑定到名为 Offices 的集合的当前项,该项是源集合的当前项的属性。
1 <Button Content="{Binding /Offices/}" />
The current item pointer can be affected by any sorting or filtering that is applied to the collection. Sorting preserves the current item pointer on the last item selected, but the collection view is now restructured around it. (Perhaps the selected item was at the beginning of the list before, but now the selected item might be somewhere in the middle.) Filtering preserves the selected item if that selection remains in view after the filtering. Otherwise, the current item pointer is set to the first item of the filtered collection view.
当前项指针可能受应用于集合的任何排序或筛选的影响。排序将保留所选最后一个项上的当前项指针,但集合视图现在围绕它进行了重组。(可能所选项目以前位于列表的开头,但现在所选项目可能位于中间的某个位置。如果筛选后所选内容仍保留在视图中,则筛选将保留所选项。否则,当前项指针将设置为筛选的集合视图的第一项。
Master-detail binding scenario
主-详细信息绑定方案
The notion of a current item is useful not only for navigation of items in a collection, but also for the master-detail binding scenario. Consider the app UI in the What is data binding section again. In that app, the selection within the ListBox determines the content shown in the ContentControl. To put it in another way, when a ListBox item is selected, the ContentControl shows the details of the selected item.
当前项的概念不仅可用于链接集合中的项导航,而且可用于主详细信息绑定方案。再次在"什么是数据绑定"部分中考虑应用UI。在该应用程序中,ListBox中的选择确定内容控制中显示的内容。换一种方式,在选择ListBox项目时,ContentControl 将显示所选项目的详细信息。
You can implement the master-detail scenario simply by having two or more controls bound to the same view. The following example from the Data binding demo shows the markup of the ListBox and the ContentControl you see on the app UI in the What is data binding section.
只需将两个或多个控件绑定到同一视图,即可实现主-详细信息方案。数据绑定演示中的以下示例显示了ListBox 和内容控制在应用 UI 中"什么是数据绑定"部分中看到的标记。
1 <ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8" 2 ItemsSource="{Binding Source={StaticResource listingDataView}}" /> 3 <ContentControl Name="Detail" Grid.Row="3" Grid.ColumnSpan="3" 4 Content="{Binding Source={StaticResource listingDataView}}" 5 ContentTemplate="{StaticResource detailsProductListingTemplate}" 6 Margin="9,0,0,0"/>
Notice that both of the controls are bound to the same source, the listingDataView static resource (see the definition of this resource in the How to create a view section). This binding works because when a singleton object (the ContentControl in this case) is bound to a collection view, it automatically binds to the CurrentItem of the view. The CollectionViewSource objects automatically synchronize currency and selection. If your list control is not bound to a CollectionViewSource object as in this example, then you would need to set its IsSynchronizedWithCurrentItem property to true for this to work.
请注意,两个控件都绑定到同一个源,即列表DataView静态资源(请参阅"如何创建视图"部分中此资源的定义)。此绑定之所以有效,是因为当单个对象(本例中为ContentControl)绑定到集合视图时,它会自动绑定到视图的"当前对象"。收集视图源对象自动同步货币和选择。如果列表控件未像本示例中那样绑定到CollectionViewSource 对象,则需要将其 IsSynchronizedWithCurrentItem 属性设置为 true,才能正常工作。
For other examples, see Bind to a collection and display information based on selection and Use the master-detail pattern with hierarchical data.
有关其他示例,请参阅绑定到集合并显示基于所选内容的信息,并使用具有分层数据的主详细信息模式。
You may have noticed that the above example uses a template. In fact, the data would not be displayed the way we wish without the use of templates (the one explicitly used by the ContentControl and the one implicitly used by the ListBox). We now turn to data templating in the next section.
您可能已经注意到上面的示例使用模板。事实上,如果没有使用模板(内容控制明确使用的模板和ListBox隐式使用的模板),数据就不会按我们所希望的方式显示。现在,我们将在下一节中介绍数据模板化。
Data templating
数据模板化
Without the use of data templates, our app UI in the What is data binding section would look like the following.
不使用数据模板,我们的应用 UI 在"什么是数据绑定"部分如下所示。
As shown in the example in the previous section, both the ListBox control and the ContentControl are bound to the entire collection object (or more specifically, the view over the collection object) of AuctionItems. Without specific instructions of how to display the data collection, the ListBox displays the string representation of each object in the underlying collection, and the ContentControl displays the string representation of the object it is bound to.
如上一节中的示例所示,ListBox 控件和 ContentControl 都绑定到拍卖项的整个集合对象(或更具体地说,收藏对象的视图)。如果没有有关如何显示数据收集的具体说明,ListBox 将显示基础集合中每个对象的字符串表示形式,ContentControl 显示绑定到的对象的字符串表示形式。
To solve that problem, the app defines DataTemplates. As shown in the example in the previous section, the ContentControl explicitly uses the detailsProductListingTemplate data template. The ListBox control implicitly uses the following data template when displaying the AuctionItem objects in the collection.
为了解决这个问题,应用程序定义了数据模板。如上一节示例所示,ContentControl 显式使用产品清单模板的详细信息数据模板。在集合中显示"拍卖项目"对象时,ListBox 控件隐式使用以下数据模板。
1 <DataTemplate DataType="{x:Type src:AuctionItem}"> 2 <Border BorderThickness="1" BorderBrush="Gray" 3 Padding="7" Name="border" Margin="3" Width="500"> 4 <Grid> 5 <Grid.RowDefinitions> 6 <RowDefinition/> 7 <RowDefinition/> 8 <RowDefinition/> 9 <RowDefinition/> 10 </Grid.RowDefinitions> 11 <Grid.ColumnDefinitions> 12 <ColumnDefinition Width="20"/> 13 <ColumnDefinition Width="86"/> 14 <ColumnDefinition Width="*"/> 15 </Grid.ColumnDefinitions> 16 17 <Polygon Grid.Row="0" Grid.Column="0" Grid.RowSpan="4" 18 Fill="Yellow" Stroke="Black" StrokeThickness="1" 19 StrokeLineJoin="Round" Width="20" Height="20" 20 Stretch="Fill" 21 Points="9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7" 22 Visibility="Hidden" Name="star"/> 23 24 <TextBlock Grid.Row="0" Grid.Column="1" Margin="0,0,8,0" 25 Name="descriptionTitle" 26 Style="{StaticResource smallTitleStyle}">Description:</TextBlock> 27 28 <TextBlock Name="DescriptionDTDataType" Grid.Row="0" Grid.Column="2" 29 Text="{Binding Path=Description}" 30 Style="{StaticResource textStyleTextBlock}"/> 31 32 <TextBlock Grid.Row="1" Grid.Column="1" Margin="0,0,8,0" 33 Name="currentPriceTitle" 34 Style="{StaticResource smallTitleStyle}">Current Price:</TextBlock> 35 36 <StackPanel Grid.Row="1" Grid.Column="2" Orientation="Horizontal"> 37 <TextBlock Text="$" Style="{StaticResource textStyleTextBlock}"/> 38 <TextBlock Name="CurrentPriceDTDataType" 39 Text="{Binding Path=CurrentPrice}" 40 Style="{StaticResource textStyleTextBlock}"/> 41 </StackPanel> 42 </Grid> 43 </Border> 44 <DataTemplate.Triggers> 45 <DataTrigger Binding="{Binding Path=SpecialFeatures}"> 46 <DataTrigger.Value> 47 <src:SpecialFeatures>Color</src:SpecialFeatures> 48 </DataTrigger.Value> 49 <DataTrigger.Setters> 50 <Setter Property="BorderBrush" Value="DodgerBlue" TargetName="border" /> 51 <Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" /> 52 <Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" /> 53 <Setter Property="BorderThickness" Value="3" TargetName="border" /> 54 <Setter Property="Padding" Value="5" TargetName="border" /> 55 </DataTrigger.Setters> 56 </DataTrigger> 57 <DataTrigger Binding="{Binding Path=SpecialFeatures}"> 58 <DataTrigger.Value> 59 <src:SpecialFeatures>Highlight</src:SpecialFeatures> 60 </DataTrigger.Value> 61 <Setter Property="BorderBrush" Value="Orange" TargetName="border" /> 62 <Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" /> 63 <Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" /> 64 <Setter Property="Visibility" Value="Visible" TargetName="star" /> 65 <Setter Property="BorderThickness" Value="3" TargetName="border" /> 66 <Setter Property="Padding" Value="5" TargetName="border" /> 67 </DataTrigger> 68 </DataTemplate.Triggers> 69 </DataTemplate>
With the use of those two DataTemplates, the resulting UI is the one shown in the What is data binding section. As you can see from that screenshot, in addition to letting you place data in your controls, DataTemplates allow you to define compelling visuals for your data. For example, DataTriggers are used in the above DataTemplate so that AuctionItems with SpecialFeatures value of HighLight would be displayed with an orange border and a star.
使用这两个 DataTemplate 时,生成的 UI 就是"什么是数据绑定"部分中显示的 UI。从屏幕截图中可以看到,除了允许您将数据放置在控件中外,DataTemplate 还允许您为数据定义引人注目的视觉对象。例如,上述DataTemplate中使用DataTrigger s,以便具有 HighLight 特殊功能值的拍卖项目将显示橙色边框和星形。
For more information about data templates, see the Data templating overview.
有关数据模板的详细信息,请参阅数据模板概述。
Data validation
数据验证
Most app that take user input need to have validation logic to ensure that the user has entered the expected information. The validation checks can be based on type, range, format, or other app-specific requirements. This section discusses how data validation works in WPF.
大多数接受用户输入的应用都需要具有验证逻辑,以确保用户已输入预期信息。验证检查可以基于类型、范围、格式或其他特定于应用的要求。本节讨论数据验证在 WPF 中的工作方式。
Associating validation rules with a binding
将验证规则与绑定关联
The WPF data binding model allows you to associate ValidationRules with your Binding object. For example, the following example binds a TextBox to a property named StartPrice and adds a ExceptionValidationRule object to the Binding.ValidationRules property.
WPF 数据绑定模型允许您将验证规则与绑定对象相关联。例如,下面的示例将TextBox绑定到名为 StartPrice 的属性,并将异常验证规则对象添加到绑定.验证规则属性。
1 <TextBox Name="StartPriceEntryForm" Grid.Row="2" 2 Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2"> 3 <TextBox.Text> 4 <Binding Path="StartPrice" UpdateSourceTrigger="PropertyChanged"> 5 <Binding.ValidationRules> 6 <ExceptionValidationRule /> 7 </Binding.ValidationRules> 8 </Binding> 9 </TextBox.Text> 10 </TextBox>
A ValidationRule object checks whether the value of a property is valid. WPF has two types of built-in ValidationRule objects:
验证规则对象检查属性的值是否有效。WPF 有两种类型的内置验证规则对象:
A ExceptionValidationRule checks for exceptions thrown during the update of the binding source property. In the previous example, StartPrice is of type integer. When the user enters a value that cannot be converted to an integer, an exception is thrown, causing the binding to be marked as invalid. An alternative syntax to setting the ExceptionValidationRule explicitly is to set the ValidatesOnExceptions property to true on your Binding or MultiBinding object.
异常验证规则检查在更新绑定源属性期间引发的异常。在前面的示例中,StartPrice 的类型为整数。当用户输入无法转换为整数的值时,将引发异常,从而导致绑定被标记为无效。显式设置异常验证规则的另一种语法是在绑定或多绑定对象上将ValidatesOnExceptions 属性设置为 true。
A DataErrorValidationRule object checks for errors that are raised by objects that implement the IDataErrorInfo interface. For an example of using this validation rule, see DataErrorValidationRule. An alternative syntax to setting the DataErrorValidationRule explicitly is to set the ValidatesOnDataErrors property to true on your Binding or MultiBinding object.
数据错误验证规则对象检查实现 IDataErrorInfo接口的对象引发的错误。有关使用此验证规则的示例,请参阅数据错误验证规则。显式设置 DataError 验证规则的替代方法是在绑定或多绑定对象上将ValidatesOnDataErrors 属性设置为 true。
You can also create your own validation rule by deriving from the ValidationRule class and implementing the Validate method. The following example shows the rule used by the Add Product Listing "Start Date" TextBox from the What is data binding section.
还可以通过从验证规则类派生并实现验证方法来创建自己的验证规则。下面的示例显示添加产品列表"开始日期"文本框从"什么是数据绑定"部分使用的规则。
1 public class FutureDateRule : ValidationRule 2 { 3 public override ValidationResult Validate(object value, CultureInfo cultureInfo) 4 { 5 // Test if date is valid 6 if (DateTime.TryParse(value.ToString(), out DateTime date)) 7 { 8 // Date is not in the future, fail 9 if (DateTime.Now > date) 10 return new ValidationResult(false, "Please enter a date in the future."); 11 } 12 else 13 // Date is not a valid date, fail 14 return new ValidationResult(false, "Value is not a valid date."); 15 16 // Date is valid and in the future, pass 17 return ValidationResult.ValidResult; 18 } 19 }
The StartDateEntryForm TextBox uses this FutureDateRule, as shown in the following example.
开始日期文本框使用此未来日期规则,如以下示例所示。
1 <TextBox Name="StartDateEntryForm" Grid.Row="3" 2 Validation.ErrorTemplate="{StaticResource validationTemplate}" 3 Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2"> 4 <TextBox.Text> 5 <Binding Path="StartDate" UpdateSourceTrigger="PropertyChanged" 6 Converter="{StaticResource dateConverter}" > 7 <Binding.ValidationRules> 8 <src:FutureDateRule /> 9 </Binding.ValidationRules> 10 </Binding> 11 </TextBox.Text> 12 </TextBox>
Because the UpdateSourceTrigger value is PropertyChanged, the binding engine updates the source value on every keystroke, which means it also checks every rule in the ValidationRules collection on every keystroke. We discuss this further in the Validation Process section.
由于UpdateSourceTrigger值是属性更改,绑定引擎会在每次击键时更新源值,这意味着它还会在每次击键时检查验证规则集合中的每个规则。我们将在验证过程部分进一步讨论这一点。
Providing visual feedback
提供视觉反馈
If the user enters an invalid value, you may want to provide some feedback about the error on the app UI. One way to provide such feedback is to set the Validation.ErrorTemplate attached property to a custom ControlTemplate. As shown in the previous subsection, the StartDateEntryForm TextBox uses an ErrorTemplate called validationTemplate. The following example shows the definition of validationTemplate.
如果用户输入无效值,则可能需要提供有关应用 UI 上的错误的一些反馈。提供此类反馈的一种方法是将验证.ErrorTemplate 附加到自定义 ControlTemplate的属性。如上一小节所示,StartDateEntryForm 文本框使用名为"验证模板"的错误模板。下面的示例显示了验证模板的定义。
1 <ControlTemplate x:Key="validationTemplate"> 2 <DockPanel> 3 <TextBlock Foreground="Red" FontSize="20">!</TextBlock> 4 <AdornedElementPlaceholder/> 5 </DockPanel> 6 </ControlTemplate>
The AdornedElementPlaceholder element specifies where the control being adorned should be placed.
"装饰元素"元素指定要修饰的控件应放置的位置。
In addition, you may also use a ToolTip to display the error message. Both the StartDateEntryForm and the StartPriceEntryFormTextBoxes use the style textStyleTextBox, which creates a ToolTip that displays the error message. The following example shows the definition of textStyleTextBox. The attached property HasError is true when one or more of the bindings on the properties of the bound element are in error.
此外,您还可以使用工具提示来显示错误消息。"开始日期入口"和"开始价格输入"文本框都使用样式文本文本框,这将创建显示错误消息的工具提示。下面的示例显示了文本样式文本框的定义。当绑定元素属性上的一个或多个绑定出错时,附加属性HasError为 true。
1 <Style x:Key="textStyleTextBox" TargetType="TextBox"> 2 <Setter Property="Foreground" Value="#333333" /> 3 <Setter Property="MaxLength" Value="40" /> 4 <Setter Property="Width" Value="392" /> 5 <Style.Triggers> 6 <Trigger Property="Validation.HasError" Value="true"> 7 <Setter Property="ToolTip" 8 Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/> 9 </Trigger> 10 </Style.Triggers> 11 </Style>
With the custom ErrorTemplate and the ToolTip, the StartDateEntryForm TextBox looks like the following when there is a validation error.
使用自定义错误模板和工具提示,当出现验证错误时,StartDateEntryForm 文本框如下所示。
If your Binding has associated validation rules but you do not specify an ErrorTemplate on the bound control, a default ErrorTemplate will be used to notify users when there is a validation error. The default ErrorTemplate is a control template that defines a red border in the adorner layer. With the default ErrorTemplate and the ToolTip, the UI of the StartPriceEntryForm TextBox looks like the following when there is a validation error.
如果绑定具有关联的验证规则,但您未在绑定控件上指定ErrorTemplate,则在出现验证错误时,将使用默认ErrorTemplate 通知用户。默认 ErrorTemplate 是一个控件模板,用于定义装饰器图层中的红色边框。使用默认错误模板和工具提示,当出现验证错误时,StartPriceEntryForm 文本框的 UI 如下所示。
For an example of how to provide logic to validate all controls in a dialog box, see the Custom Dialog Boxes section in the Dialog boxes overview.
有关如何提供逻辑以验证对话框中的所有控件的示例,请参阅对话框概述中的"自定义对话框"部分。
Validation process
验证过程
Validation usually occurs when the value of a target is transferred to the binding source property. This transfer occurs on TwoWay and OneWayToSource bindings. To reiterate, what causes a source update depends on the value of the UpdateSourceTrigger property, as described in the What triggers source updates section.
验证通常在目标的值转移到绑定源属性时发生。此传输发生在双向和单向来源绑定上。重申一下,导致源更新的原因取决于 UpdateSourceTrigger 属性的值,如"什么触发源更新"部分中所述。
The following items describe the validation process. If a validation error or other type of error occurs at any time during this process, the process is halted:
以下项描述验证过程。如果在此过程中的任何时候发生验证错误或其他类型的错误,则该过程将停止:
The binding engine checks if there are any custom ValidationRule objects defined whose ValidationStep is set to RawProposedValue for that Binding, in which case it calls the Validate method on each ValidationRule until one of them runs into an error or until all of them pass.
绑定引擎检查是否有任何自定义验证规则对象定义的验证步骤设置为该绑定的 RawFortoValue,在这种情况下,它会在每个验证规则上调用验证方法,直到其中一个对象遇到错误或直到它们全部通过为止。
The binding engine then calls the converter, if one exists.
然后,绑定引擎调用转换器(如果存在)。
If the converter succeeds, the binding engine checks if there are any custom ValidationRule objects defined whose ValidationStep is set to ConvertedProposedValue for that Binding, in which case it calls the Validate method on each ValidationRule that has ValidationStep set to ConvertedProposedValue until one of them runs into an error or until all of them pass.
如果转换器成功,绑定引擎将检查是否有任何自定义验证规则对象定义的验证步骤设置为该绑定的"转换的拟议值",在这种情况下,它会在每个验证规则上调用验证方法,该验证步骤将验证步骤设置为"转换的拟值",直到其中一个对象遇到错误或直到所有验证规则都运行错误。他们通过。
The binding engine sets the source property.
绑定引擎设置源属性。
The binding engine checks if there are any custom ValidationRule objects defined whose ValidationStep is set to UpdatedValue for that Binding, in which case it calls the Validate method on each ValidationRule that has ValidationStep set to UpdatedValue until one of them runs into an error or until all of them pass. If a DataErrorValidationRule is associated with a binding and its ValidationStep is set to the default, UpdatedValue, the DataErrorValidationRule is checked at this point. At this point any binding that has the ValidatesOnDataErrors set to true is checked.
绑定引擎检查是否有任何自定义验证规则对象定义的验证步骤设置为该绑定的UpdatedValue,在这种情况下,它会在每个验证规则上调用验证方法,该验证步骤将验证步骤设置为"更新值",直到其中一个对象遇到错误或直到它们全部通过。如果数据错误验证规则与绑定关联,并且其验证步骤设置为默认值"更新值",则此时将选中数据错误验证规则。此时,将检查将ValidatesOnData 错误设置为 true 的任何绑定。
The binding engine checks if there are any custom ValidationRule objects defined whose ValidationStep is set to CommittedValue for that Binding, in which case it calls the Validate method on each ValidationRule that has ValidationStep set to CommittedValue until one of them runs into an error or until all of them pass.
绑定引擎检查是否有任何自定义验证规则对象定义的验证步骤设置为该绑定的"提交值",在这种情况下,它会在每个验证规则上调用验证方法,该验证步骤将验证步骤设置为"提交值",直到其中一个对象遇到错误或直到它们全部通过为止。
If a ValidationRule does not pass at any time throughout this process, the binding engine creates a ValidationError object and adds it to the Errors collection of the bound element. Before the binding engine runs the ValidationRule objects at any given step, it removes any ValidationError that was added to the Errors attached property of the bound element during that step. For example, if a ValidationRule whose ValidationStep is set to UpdatedValue failed, the next time the validation process occurs, the binding engine removes that ValidationError immediately before it calls any ValidationRule that has ValidationStep set to UpdatedValue.
如果验证规则在整个过程中任何时候都未通过,绑定引擎将创建验证Error对象并将其添加到绑定元素的 Errors 集合中。在绑定引擎在任何给定步骤上运行验证规则对象之前,它将删除在该步骤期间添加到绑定元素的Error附加属性中的任何验证错误。例如,如果验证步骤设置为"更新值"的验证规则失败,则下次执行验证过程时,绑定引擎会在调用任何将验证步骤设置为"更新值"的验证规则之前立即删除该验证错误。
When Errors is not empty, the HasError attached property of the element is set to true. Also, if the NotifyOnValidationError property of the Binding is set to true, then the binding engine raises the Validation.Error attached event on the element.
当错误不为空时,元素的HasError 附加属性设置为 true。此外,如果绑定的 NotifyOnValidationError 属性设置为true,则绑定引擎将引发元素上的验证.错误附加事件。
Also note that a valid value transfer in either direction (target to source or source to target) clears the Errors attached property.
另请注意,任一方向的有效值转移(目标到源或源到目标)将清除附加的 Errors 属性。
If the binding either has an ExceptionValidationRule associated with it, or had the ValidatesOnExceptions property is set to true and an exception is thrown when the binding engine sets the source, the binding engine checks to see if there is a UpdateSourceExceptionFilter. You have the option to use the UpdateSourceExceptionFilter callback to provide a custom handler for handling exceptions. If an UpdateSourceExceptionFilter is not specified on the Binding, the binding engine creates a ValidationError with the exception and adds it to the Errors collection of the bound element.
如果绑定具有与其关联的异常验证规则,或者将 ValidatesOnExceptions 属性设置为 true,并在绑定引擎设置源时引发异常,则绑定引擎将检查是否有 UpdateSourceExceptionFilter。您可以选择使用 UpdateSourceExceptionFilter 回调来提供用于处理异常的自定义处理程序。如果在绑定上未指定 UpdateSourceExceptionFilter,绑定引擎将创建一个具有异常的验证错误并将其添加到绑定元素的 Errors 集合中。
Debugging mechanism
调试机制
You can set the attached property TraceLevel on a binding-related object to receive information about the status of a specific binding.
您可以在与绑定相关的对象上设置附加属性TraceLevel,以接收有关特定绑定状态的信息。
See also