深入了解数据绑定Data binding in depth

重要的 APIImportant APIs

备注

本主题详细介绍数据绑定功能。This topic describes data binding features in detail. 有关简短、实用的简介,请参阅数据绑定概述For a short, practical introduction, see Data binding overview.

本主题介绍通用 Windows 平台 (UWP) 应用程序中的数据绑定。This topic is about data binding in Universal Windows Platform (UWP) applications. 此处讨论的 API 位于 Windows.UI.Xaml.Data 命名空间中 。The APIs discussed here reside in the Windows.UI.Xaml.Data namespace.

数据绑定是你的应用 UI 用来显示数据的一种方法,可以选择与该数据保持同步。Data binding is a way for your app's UI to display data, and optionally to stay in sync with that data. 借助数据绑定,你可以将关注的数据从关注的 UI 中分离开来,从而形成一个更简易的概念模型,并且使应用具有更好的可读性、可测试性和可维护性。Data binding allows you to separate the concern of data from the concern of UI, and that results in a simpler conceptual model as well as better readability, testability, and maintainability of your app.

当 UI首次显示时,你可以使用数据绑定以仅显示来自数据源的值,但不会对这些值中的更改做出响应。You can use data binding to simply display values from a data source when the UI is first shown, but not to respond to changes in those values. 这是一种称为“一次性”的绑定模式,非常适合运行时未更改的值 。This is a mode of binding called one-time, and it works well for a value that doesn't change during run-time. 或者,你可以选择“观察”值并在其更改时更新 UI。Alternatively, you can choose to "observe" the values and to update the UI when they change. 此模式称为“单向”,非常适合只读数据 。This mode is called one-way, and it works well for read-only data. 最后,你可以选择观察并更新,以便用户在 UI 中对值所做的更改能自动传回数据源。Ultimately, you can choose to both observe and update, so that changes that the user makes to values in the UI are automatically pushed back into the data source. 此模式称为“双向”,非常适合读写数据 。This mode is called two-way, and it works well for read-write data. 下面是一些示例。Here are some examples.

  • 你可以使用一次性模式,将 Image 绑定到当前用户的照片 。You could use the one-time mode to bind an Image to the current user's photo.
  • 你可以使用单向模式,将 ListView 绑定到按报纸剪辑分组的实时新闻报道的集合 。You could use the one-way mode to bind a ListView to a collection of real-time news articles grouped by newspaper section.
  • 你可以使用双向模式,将 TextBox 绑定到表单中的客户名称 。You could use the two-way mode to bind a TextBox to a customer's name in a form.

无论模式如何,都有两种类型的绑定,它们通常都在 UI 标记中进行声明。Independent of mode, there are two kinds of binding, and they're both typically declared in UI markup. 你既可以选用 {x:Bind} 标记扩展,也可以选用 {Binding} 标记扩展You can choose to use either the {x:Bind} markup extension or the {Binding} markup extension. 还可以在同一应用中(甚至是同一 UI 元素上)混合使用这两个标记扩展。And you can even use a mixture of the two in the same app—even on the same UI element. {x:Bind} 是 Windows 10 的新增内容,并且具备更好的性能。{x:Bind} is new for Windows 10 and it has better performance. 除非另有明确说明,否则本主题中所述的所有详细信息均适用于这两种绑定。All the details described in this topic apply to both kinds of binding unless we explicitly say otherwise.

用于演示 {x:Bind} 的应用示例Sample apps that demonstrate {x:Bind}

用于演示 {Binding} 的应用示例Sample apps that demonstrate {Binding}

每个绑定均由以下部分构成Every binding involves these pieces

  • 绑定源A binding source. 这是用于绑定的数据源,它可以是任意类的实例,其中该类具有其值要显示在 UI 中的成员。This is the source of the data for the binding, and it can be an instance of any class that has members whose values you want to display in your UI.
  • 绑定目标A binding target. 这是显示数据的 UI 中 FrameworkElementDependencyPropertyThis is a DependencyProperty of the FrameworkElement in your UI that displays the data.
  • 绑定对象A binding object. 这是用于将数据值从绑定源传递给绑定目标(或从绑定目标传递回绑定源)的组件。This is the piece that transfers data values from the source to the target, and optionally from the target back to the source. 绑定对象通过 {x:Bind}{Binding} 标记扩展在 XAML 加载时进行创建。The binding object is created at XAML load time from your {x:Bind} or {Binding} markup extension.

在以下部分中,我们将进一步探讨绑定源、绑定目标和绑定对象。In the following sections, we'll take a closer look at the binding source, the binding target, and the binding object. 我们将这些部分与关于将按钮内容绑定到名为 NextButtonText 的字符串属性(这属于名为 HostViewModel 的类)的示例链接在一起。And we'll link the sections together with the example of binding a button's content to a string property named NextButtonText, which belongs to a class named HostViewModel.

绑定源Binding source

下面是一个非常基础的可用作绑定源的类实现。Here's a very rudimentary implementation of a class that we could use as a binding source.

如果使用的是 C++/WinRT,请将新的 Midl 文件 (.idl) 项添加到项目中,如下面 C++/WinRT 代码示例清单中所示对其进行命名 。If you're using C++/WinRT, then add new Midl File (.idl) items to the project, named as shown in the C++/WinRT code example listing below. 将这些新文件的内容替换为列表中显示的 MIDL 3.0 代码,生成项目以生成 HostViewModel.h.cpp,然后将代码添加到生成的文件以匹配列表。Replace the contents of those new files with the MIDL 3.0 code shown in the listing, build the project to generate HostViewModel.h and .cpp, and then add code to the generated files to match the listing. 有关这些生成的文件以及如何将它们复制到项目中的详细信息,请参阅 XAML 控件;绑定到 C++/WinRT 属性For more info about those generated files and how to copy them into your project, see XAML controls; bind to a C++/WinRT property.

public class HostViewModel
{
    public HostViewModel()
    {
        this.NextButtonText = "Next";
    }

    public string NextButtonText { get; set; }
}
// HostViewModel.idl
namespace DataBindingInDepth
{
    runtimeclass HostViewModel
    {
        HostViewModel();
        String NextButtonText;
    }
}

// HostViewModel.h
// Implement the constructor like this, and add this field:
...
HostViewModel() : m_nextButtonText{ L"Next" } {}
...
private:
    std::wstring m_nextButtonText;
...

// HostViewModel.cpp
// Implement like this:
...
hstring HostViewModel::NextButtonText()
{
    return hstring{ m_nextButtonText };
}

void HostViewModel::NextButtonText(hstring const& value)
{
    m_nextButtonText = value;
}
...

HostViewModel 及其属性 NextButtonText 的实现仅适用于一次性绑定。That implementation of HostViewModel, and its property NextButtonText, are only appropriate for one-time binding. 而单向绑定和双向绑定也极为常见,在这两种绑定中,UI 会自动更新以响应绑定源的数据值的更改。But one-way and two-way bindings are extremely common, and in those kinds of binding the UI automatically updates in response to changes in the data values of the binding source. 为了让这些类型的绑定能正常工作,需要使绑定源对绑定对象“可观察”。In order for those kinds of binding to work correctly, you need to make your binding source "observable" to the binding object. 因此在我们的示例中,如果我们希望单向或双向绑定到 NextButtonText 属性,则该属性值在运行时所发生的任何更改均需对绑定对象可观察。So in our example, if we want to one-way or two-way bind to the NextButtonText property, then any changes that happen at run-time to the value of that property need to be made observable to the binding object.

执行此操作的方法之一是,从 DependencyObject 派生代表绑定源的类,并通过 DependencyProperty 公开数据值。One way of doing that is to derive the class that represents your binding source from DependencyObject, and expose a data value through a DependencyProperty. 以上就是 FrameworkElement 成为可观察绑定源的方式。That's how a FrameworkElement becomes observable. FrameworkElements 即时可用,是很好的绑定源。FrameworkElements are good binding sources right out of the box.

让类成为可观察绑定源,以及成为类(已拥有基类)的必需项的更便捷方法是实现 System.ComponentModel.INotifyPropertyChangedA more lightweight way of making a class observable—and a necessary one for classes that already have a base class—is to implement System.ComponentModel.INotifyPropertyChanged. 实际上,这仅涉及到实现一个名为 PropertyChanged 的事件。This really just involves implementing a single event named PropertyChanged. 下面展示了使用 HostViewModel 的示例。An example using HostViewModel is below.

...
using System.ComponentModel;
using System.Runtime.CompilerServices;
...
public class HostViewModel : INotifyPropertyChanged
{
    private string nextButtonText;

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    public HostViewModel()
    {
        this.NextButtonText = "Next";
    }

    public string NextButtonText
    {
        get { return this.nextButtonText; }
        set
        {
            this.nextButtonText = value;
            this.OnPropertyChanged();
        }
    }

    public void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        // Raise the PropertyChanged event, passing the name of the property whose value has changed.
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
// HostViewModel.idl
namespace DataBindingInDepth
{
    runtimeclass HostViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
    {
        HostViewModel();
        String NextButtonText;
    }
}

// HostViewModel.h
// Add this field:
...
    winrt::event_token PropertyChanged(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler);
    void PropertyChanged(winrt::event_token const& token) noexcept;

private:
    winrt::event<Windows::UI::Xaml::Data::PropertyChangedEventHandler> m_propertyChanged;
...

// HostViewModel.cpp
// Implement like this:
...
void HostViewModel::NextButtonText(hstring const& value)
{
    if (m_nextButtonText != value)
    {
        m_nextButtonText = value;
        m_propertyChanged(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"NextButtonText" });
    }
}

winrt::event_token HostViewModel::PropertyChanged(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
{
    return m_propertyChanged.add(handler);
}

void HostViewModel::PropertyChanged(winrt::event_token const& token) noexcept
{
    m_propertyChanged.remove(token);
}
...

现在,NextButtonText 属性可供观察。Now the NextButtonText property is observable. 当创作到该属性的单向或双向绑定(将在稍后介绍操作方法)时,生成的绑定对象将订阅 PropertyChanged 事件。When you author a one-way or a two-way binding to that property (we'll show how later), the resulting binding object subscribes to the PropertyChanged event. 引发该事件后,绑定对象的处理程序将接收一个包含已更改的属性名的参数。When that event is raised, the binding object's handler receives an argument containing the name of the property that has changed. 这是绑定对象知道已更改和重新读取的属性值的方式。That's how the binding object knows which property's value to go and read again.

这样你便无需多次实现上面所示的模式,如果使用的是 C#,则只需从 QuizGame 示例(位于“Common”文件夹中)提供的 BindableBase 基类派生即可。So that you don't have to implement the pattern shown above multiple times, if you're using C# then you can just derive from the BindableBase base class that you'll find in the QuizGame sample (in the "Common" folder). 下面是具体操作的一个示例。Here's an example of how that looks.

public class HostViewModel : BindableBase
{
    private string nextButtonText;

    public HostViewModel()
    {
        this.NextButtonText = "Next";
    }

    public string NextButtonText
    {
        get { return this.nextButtonText; }
        set { this.SetProperty(ref this.nextButtonText, value); }
    }
}
// Your BindableBase base class should itself derive from Windows::UI::Xaml::DependencyObject. Then, in HostViewModel.idl, derive from BindableBase instead of implementing INotifyPropertyChanged.

备注

对于 C++/WinRT,从基类派生的任何运行时类(在应用程序中声明)都称为可组合类 。For C++/WinRT, any runtime class that you declare in your application that derives from a base class is known as a composable class. 且可组合类存在一些限制。And there are constraints around composable classes. 若要使应用程序通过 Visual Studio 和 Microsoft Store 用于验证提交的 Windows 应用认证工具包测试,使 Microsoft Store 可成功纳入该应用程序,可组合类必须最终派生自 Windows 基类。For an application to pass the Windows App Certification Kit tests used by Visual Studio and by the Microsoft Store to validate submissions (and therefore for the application to be successfully ingested into the Microsoft Store), a composable class must ultimately derive from a Windows base class. 这意味着继承层次结构的根类必须是源于 Windows.* 名称空间的类型。Meaning that the class at the very root of the inheritance hierarchy must be a type originating in a Windows.* namespace. 如果确实需要从基类派生运行时类(例如,为要从中派生的所有视图模型实现 BindableBase 类),则可以从 Windows.UI.Xaml.DependencyObject 派生 。If you do need to derive a runtime class from a base class—for example, to implement a BindableBase class for all of your view models to derive from—then you can derive from Windows.UI.Xaml.DependencyObject.

通过使用 String.Emptynull 参数引发 PropertyChanged 事件,以指示对象上的所有非索引器属性应重新读取。Raising the PropertyChanged event with an argument of String.Empty or null indicates that all non-indexer properties on the object should be re-read. 可以通过将“Item[indexer]”的参数用于特定索引器(其中 indexer 是索引值),或将“Item[]”的值用于所有索引器,引发该事件以指示对象上的索引器属性已更改 。You can raise the event to indicate that indexer properties on the object have changed by using an argument of "Item[indexer]" for specific indexers (where indexer is the index value), or a value of "Item[]" for all indexers.

可以将绑定源视为其属性中包含数据的单个对象或一些对象的集合。A binding source can be treated either as a single object whose properties contain data, or as a collection of objects. 在 C# 和 Visual Basic 代码中,可以一次性绑定到实现 List(Of T) 的对象,以显示运行时未更改的集合 。In C# and Visual Basic code, you can one-time bind to an object that implements List(Of T) to display a collection that doesn't change at run-time. 对于可观察集合(在将项添加到集合或从集合中删除时进行观察),应改为单向绑定到 ObservableCollection(Of T) For an observable collection (observing when items are added to and removed from the collection), one-way bind to ObservableCollection(Of T) instead. 在 C++/CX 代码中,对于可观察集合和不可观察集合,均可绑定到 Vector<T>,并且 C++/WinRT 具有其自己的类型 。In C++/CX code, you can bind to Vector<T> for both observable and non-observable collections, and C++/WinRT has its own types. 若要绑定到你自己的集合类,请按照下表中的指南进行操作。To bind to your own collection classes, use the guidance in the following table.

方案Scenario C# 和 VB (CLR)C# and VB (CLR) C++/WinRTC++/WinRT C++/CXC++/CX
绑定到对象。Bind to an object. 可以为任何对象。Can be any object. 可以为任何对象。Can be any object. 对象必须具有 BindableAttribute 或实现 ICustomPropertyProviderObject must have BindableAttribute or implement ICustomPropertyProvider.
从绑定对象中获取属性更改通知。Get property change notifications from a bound object. 对象必须实现 INotifyPropertyChangedObject must implement INotifyPropertyChanged. 对象必须实现 INotifyPropertyChangedObject must implement INotifyPropertyChanged. 对象必须实现 INotifyPropertyChangedObject must implement INotifyPropertyChanged.
绑定到集合。Bind to a collection. List(Of T) List(Of T) IInspectableIBindableObservableVectorIVectorIVector of IInspectable, or IBindableObservableVector. 请参阅 XAML 项目控件;绑定到 C++/WinRT 集合使用 C++/WinRT 的集合See XAML items controls; bind to a C++/WinRT collection and Collections with C++/WinRT. Vector<T> Vector<T>
从绑定集合中获取集合更改通知。Get collection change notifications from a bound collection. ObservableCollection(Of T) ObservableCollection(Of T) IInspectableIObservableVectorIObservableVector of IInspectable. 例如,winrt::single_threaded_observable_vector<T>For example, winrt::single_threaded_observable_vector<T>. IObservableVector<T> IObservableVector<T>. Vector<T> 实现了此接口 。Vector<T> implements this interface.
实现支持绑定的集合。Implement a collection that supports binding. 扩展 List(Of T) 或者实现 IListIList(Of Object)、IEnumerableIEnumerable(Of Object)。Extend List(Of T) or implement IList, IList(Of Object), IEnumerable, or IEnumerable(Of Object). 绑定到通用 IList(Of T)IEnumerable(Of T) 的操作不受支持。Binding to generic IList(Of T) and IEnumerable(Of T) is not supported. 实现 IInspectableIVectorImplement IVector of IInspectable. 请参阅 XAML 项目控件;绑定到 C++/WinRT 集合使用 C++/WinRT 的集合See XAML items controls; bind to a C++/WinRT collection and Collections with C++/WinRT. 实现 IBindableVectorIBindableIterableIVector<Object^>、IIterable<Object^>、IVector<IInspectable*> 或 IIterable<IInspectable*> 。Implement IBindableVector, IBindableIterable, IVector<Object^>, IIterable<Object^>, IVector<IInspectable*>, or IIterable<IInspectable*>. 绑定到通用 IVector<T>IIterable<T> 的操作不受支持。Binding to generic IVector<T> and IIterable<T> is not supported.
实现支持集合更改通知的集合。Implement a collection that supports collection change notifications. 扩展 ObservableCollection(Of T) 或实现(非通用)IListINotifyCollectionChangedExtend ObservableCollection(Of T) or implement (non-generic) IList and INotifyCollectionChanged. 实现 IInspectableIBindableObservableVectorIObservableVectorImplement IObservableVector of IInspectable, or IBindableObservableVector. 实现 IBindableVectorIBindableObservableVectorImplement IBindableVector and IBindableObservableVector.
实现支持增量加载的集合。Implement a collection that supports incremental loading. 扩展 ObservableCollection(Of T) 或实现(非通用)IListINotifyCollectionChangedExtend ObservableCollection(Of T) or implement (non-generic) IList and INotifyCollectionChanged. 此外,还实现 ISupportIncrementalLoadingAdditionally, implement ISupportIncrementalLoading. 实现 IInspectableIBindableObservableVectorIObservableVectorImplement IObservableVector of IInspectable, or IBindableObservableVector. 此外,还实现 ISupportIncrementalLoading Additionally, implement ISupportIncrementalLoading 实现 IBindableVectorIBindableObservableVectorISupportIncrementalLoadingImplement IBindableVector, IBindableObservableVector, and ISupportIncrementalLoading.

你可以使用增量加载将列表控件绑定到任意的大型数据源,且仍获得高性能。You can bind list controls to arbitrarily large data sources, and still achieve high performance, by using incremental loading. 例如,你可以将列表控件绑定到必应图像查询结果,而无需一次性加载所有结果。For example, you can bind list controls to Bing image query results without having to load all the results at once. 你只需立即加载部分结果,再根据需要加载其他结果。Instead, you load only some results immediately, and load additional results as needed. 为支持增量加载,你必须在支持集合更改通知的数据源上实现 ISupportIncrementalLoadingTo support incremental loading, you must implement ISupportIncrementalLoading on a data source that supports collection change notifications. 当数据绑定引擎请求更多数据时,你的数据源必须发出相应的请求、集成结果,然后发送相应的通知以更新 UI。When the data binding engine requests more data, your data source must make the appropriate requests, integrate the results, and then send the appropriate notifications in order to update the UI.

绑定目标Binding target

在下面的两个示例中,Button.Content 属性是绑定目标,并且其值已设置为用于声明绑定对象的标记扩展。In the two examples below, the Button.Content property is the binding target, and its value is set to a markup extension that declares the binding object. 首先显示的是 {x:Bind},然后是 {Binding}First {x:Bind} is shown, and then {Binding}. 在标记中声明绑定这一做法很常见(其既便捷、易读又很实用)。Declaring bindings in markup is the common case (it's convenient, readable, and toolable). 不过,应避免标记和以命令性方式(编程方式)创建 Binding 类的实例(除非需要)。But you can avoid markup and imperatively (programmatically) create an instance of the Binding class instead if you need to.

<Button Content="{x:Bind ...}" ... />
<Button Content="{Binding ...}" ... />

如果使用的是 C++/WinRT 或 Visual C++ 组件扩展 (C++/CX),则需要将 BindableAttribute 属性添加到要使用 {Binding} 标记扩展的运行时类 。If you're using C++/WinRT or Visual C++ component extensions (C++/CX), then you'll need to add the BindableAttribute attribute to any runtime class that you want to use the {Binding} markup extension with.

重要

如果使用 C++/WinRT,则在安装了 Windows SDK 版本 10.0.17763.0(Windows 10 版本 1809)或更高版本的情况下,可使用 BindableAttribute 属性 。If you're using C++/WinRT, then the BindableAttribute attribute is available if you've installed the Windows SDK version 10.0.17763.0 (Windows 10, version 1809), or later. 如果没有该属性,为了能够使用 {Binding} 标记扩展,需要实现 ICustomPropertyProviderICustomProperty 接口。Without that attribute, you'll need to implement the ICustomPropertyProvider and ICustomProperty interfaces in order to be able to use the {Binding} markup extension.

使用 {x:Bind} 声明的绑定对象Binding object declared using {x:Bind}

在创作 {x:Bind} 标记之前,需先执行一个步骤。There's one step we need to do before we author our {x:Bind} markup. 我们需要从表示标记页面的类公开绑定源类。We need to expose our binding source class from the class that represents our page of markup. 通过将相关属性(本例中为 HostViewModel 类型)添加到 MainPage 页面类,来执行此操作 。We do that by adding a property (of type HostViewModel in this case) to our MainPage page class.

namespace DataBindingInDepth
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            this.ViewModel = new HostViewModel();
        }
    
        public HostViewModel ViewModel { get; set; }
    }
}
// MainPage.idl
import "HostViewModel.idl";

namespace DataBindingInDepth
{
    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
    {
        MainPage();
        HostViewModel ViewModel{ get; };
    }
}

// MainPage.h
// Include a header, and add this field:
...
#include "HostViewModel.h"
...
    DataBindingInDepth::HostViewModel ViewModel();

private:
    DataBindingInDepth::HostViewModel m_viewModel{ nullptr };
...

// MainPage.cpp
// Implement like this:
...
MainPage::MainPage()
{
    InitializeComponent();

}

DataBindingInDepth::HostViewModel MainPage::ViewModel()
{
    return m_viewModel;
}
...

该操作完成后,即可仔细看看声明绑定对象的标记。That done, we can now take a closer look at the markup that declares the binding object. 下面的示例使用与前面“绑定目标”部分中所使用的相同 Button.Content 绑定目标,并演示了将其绑定到 HostViewModel.NextButtonText 属性。The example below uses the same Button.Content binding target we used in the "Binding target" section earlier, and shows it being bound to the HostViewModel.NextButtonText property.

<!-- MainPage.xaml -->
<Page x:Class="DataBindingInDepth.Mainpage" ... >
    <Button Content="{x:Bind Path=ViewModel.NextButtonText, Mode=OneWay}" ... />
</Page>

注意,该值即是我们为 Path 指定的值。Notice the value that we specify for Path. 此值在页面的上下文中自行解释,在本例中路径首先用于引用我们刚添加到 MainPage 页面的 ViewModel 属性 。This value is interpreted in the context of the page itself, and in this case the path begins by referencing the ViewModel property that we just added to the MainPage page. 该属性将返回 HostViewModel 实例,这样我们便可以点入该对象以访问 HostViewModel.NextButtonText 属性。That property returns a HostViewModel instance, and so we can dot into that object to access the HostViewModel.NextButtonText property. 并且,我们将指定 Mode 以替代一次性绑定默认值 {x:Bind}And we specify Mode, to override the {x:Bind} default of one-time.

Path 属性支持各种用于绑定到嵌套属性、附加属性以及整数和字符串索引器的语法选项。The Path property supports a variety of syntax options for binding to nested properties, attached properties, and integer and string indexers. 有关详细信息,请参阅 Property-path 语法For more info, see Property-path syntax. 绑定到字符串索引器会为你提供绑定到动态属性的效果,而无需实现 ICustomPropertyProviderBinding to string indexers gives you the effect of binding to dynamic properties without having to implement ICustomPropertyProvider. 有关其他设置,请参阅 {x:Bind} 标记扩展For other settings, see {x:Bind} markup extension.

若要说明 HostViewModel.NextButtonText 属性确实可观察,请将 Click 事件处理程序添加到按钮,然后更新 HostViewModel.NextButtonText 的值 。To illustrate that the HostViewModel.NextButtonText property is indeed observable, add a Click event handler to the button, and update the value of HostViewModel.NextButtonText. 生成、运行并单击按钮以查看按钮的内容更新的值 。Build, run, and click the button to see the value of the button's Content update.

// MainPage.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
    this.ViewModel.NextButtonText = "Updated Next button text";
}
// MainPage.cpp
void MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
{
    ViewModel().NextButtonText(L"Updated Next button text");
}

备注

TextBox 失去焦点时,对 TextBox.Text 的更改将发送到双向绑定源,这并非发生在每次用户按键之后 。Changes to TextBox.Text are sent to a two-way bound source when the TextBox loses focus, and not after every user keystroke.

DataTemplate 和 x:DataTypeDataTemplate and x:DataType

DataTemplate 内(不论用作项模板、内容模板还是标头模板),Path 的值不在页面的上下文中进行解释,而在模板化的数据对象的上下文中进行解释。Inside a DataTemplate (whether used as an item template, a content template, or a header template), the value of Path is not interpreted in the context of the page, but in the context of the data object being templated. 在数据模板中使用 {x:Bind} ,以便在编译时可以对它的绑定进行验证(同时为它们生成有效代码)时,DataTemplate 需要使用 x:DataType 声明其数据对象的类型。When using {x:Bind} in a data template, so that its bindings can be validated (and efficient code generated for them) at compile-time, the DataTemplate needs to declare the type of its data object using x:DataType. 下面给出的示例可用作绑定到 SampleDataGroup 对象集合的项目控件的 ItemTemplateThe example given below could be used as the ItemTemplate of an items control bound to a collection of SampleDataGroup objects.

<DataTemplate x:Key="SimpleItemTemplate" x:DataType="data:SampleDataGroup">
    <StackPanel Orientation="Vertical" Height="50">
      <TextBlock Text="{x:Bind Title}"/>
      <TextBlock Text="{x:Bind Description}"/>
    </StackPanel>
  </DataTemplate>

你的路径中的弱类型对象Weakly-typed objects in your Path

例如,假设你有一个名为 SampleDataGroup 的类型,该类型实现名为 Title 的字符串属性。Consider for example that you have a type named SampleDataGroup, which implements a string property named Title. 并且你有一个属性 MainPage.SampleDataGroupAsObject,该属性属于类型对象但它实际上返回 SampleDataGroup 的实例。And you have a property MainPage.SampleDataGroupAsObject, which is of type object, but which actually returns an instance of SampleDataGroup. 由于在该类型对象上找不到 Title 属性,因此绑定 <TextBlock Text="{x:Bind SampleDataGroupAsObject.Title}"/> 将导致编译错误。The binding <TextBlock Text="{x:Bind SampleDataGroupAsObject.Title}"/> will result in a compile error because the Title property is not found on the type object. 该错误的解决方法是向 Path 语法添加强制转换,如下所示:<TextBlock Text="{x:Bind ((data:SampleDataGroup)SampleDataGroupAsObject).Title}"/>The remedy for this is to add a cast to your Path syntax like this: <TextBlock Text="{x:Bind ((data:SampleDataGroup)SampleDataGroupAsObject).Title}"/>. 下面是另一个示例,其中元素声明为对象但实际上是 TextBlock:<TextBlock Text="{x:Bind Element.Text}"/>Here's another example where Element is declared as object but is actually a TextBlock: <TextBlock Text="{x:Bind Element.Text}"/>. 而且强制转换可以解决该问题:<TextBlock Text="{x:Bind ((TextBlock)Element).Text}"/>And a cast remedies the issue: <TextBlock Text="{x:Bind ((TextBlock)Element).Text}"/>.

如果你的数据异步加载If your data loads asynchronously

在编译时为你的页面在分部类中生成支持 {x:Bind} 的代码。Code to support {x:Bind} is generated at compile-time in the partial classes for your pages. 可以在 obj 文件夹中找到这些文件,其名称类似于(适用于 C#)<view name>.g.csThese files can be found in your obj folder, with names like (for C#) <view name>.g.cs. 生成的代码包含可用于你的页面的 Loading 事件的处理程序,并且该处理程序在表示你的页面绑定的已生成类上调用 Initialize 方法。The generated code includes a handler for your page's Loading event, and that handler calls the Initialize method on a generated class that represent's your page's bindings. Initialize 依次调用 Update,以开始在绑定源和目标之间移动数据。Initialize in turn calls Update to begin moving data between the binding source and the target. 只在页面或用户控件的第一个测量阶段之前引发 LoadingLoading is raised just before the first measure pass of the page or user control. 因此异步加载你的数据时,可能未在调用 Initialize 时做好准备。So if your data is loaded asynchronously it may not be ready by the time Initialize is called. 因此,加载数据之后,可以通过调用 this.Bindings.Update(); 强制初始化一次性绑定。So, after you've loaded data, you can force one-time bindings to be initialized by calling this.Bindings.Update();. 如果只需针对异步加载的数据执行一次性绑定,则使用此方法对其进行初始化比使用以下方法方便:首先进行单向绑定,然后侦听更改。If you only need one-time bindings for asynchronously-loaded data then it's much cheaper to initialize them this way than it is to have one-way bindings and to listen for changes. 如果你的数据未经过细微的更改并且它可能作为某个特定操作的一部分进行更新,则你可以执行一次性绑定,并且通过对 Update 的调用随时强制执行手动更新。If your data does not undergo fine-grained changes, and if it's likely to be updated as part of a specific action, then you can make your bindings one-time, and force a manual update at any time with a call to Update.

备注

{x:Bind} 既不适用于后期绑定方案(例如导航 JSON 对象的字典结构),也不适用于鸭子类型 。{x:Bind} is not suited to late-bound scenarios, such as navigating the dictionary structure of a JSON object, nor duck typing. “鸭子类型”是基于属性名称的词法匹配的一种较弱形式的类型(“如果它的走路方式、游泳方式和叫声像鸭子,那么它就是一只鸭子”)。"Duck typing" is a weak form of typing based on lexical matches on property names (a in, "if it walks, swims, and quacks like a duck, then it's a duck"). 借助鸭子类型,与 Age 属性的绑定将会同样满足 Person 或 Wine 对象(假定这些类型都具有 Age 属性) 。With duck typing, a binding to the Age property would be equally satisfied with a Person or a Wine object (assuming that those types each had an Age property). 对于这些情况,请使用 {Binding} 标记扩展。For these scenarios, use the {Binding} markup extension.

使用 {Binding} 声明的绑定对象Binding object declared using {Binding}

如果使用的是 C++/WinRT 或 Visual C++ 组件扩展 (C++/CX),要使用 {Binding} 标记扩展,则需要将 BindableAttribute 属性添加到要绑定到的运行时类 。If you're using C++/WinRT or Visual C++ component extensions (C++/CX) then, to use the {Binding} markup extension, you'll need to add the BindableAttribute attribute to any runtime class that you want to bind to. 若要使用 {x:Bind},则不需要该属性。To use {x:Bind}, you don't need that attribute.

// HostViewModel.idl
// Add this attribute:
[Windows.UI.Xaml.Data.Bindable]
runtimeclass HostViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
...

重要

如果使用 C++/WinRT,则在安装了 Windows SDK 版本 10.0.17763.0(Windows 10 版本 1809)或更高版本的情况下,可使用 BindableAttribute 属性 。If you're using C++/WinRT, then the BindableAttribute attribute is available if you've installed the Windows SDK version 10.0.17763.0 (Windows 10, version 1809), or later. 如果没有该属性,为了能够使用 {Binding} 标记扩展,需要实现 ICustomPropertyProviderICustomProperty 接口。Without that attribute, you'll need to implement the ICustomPropertyProvider and ICustomProperty interfaces in order to be able to use the {Binding} markup extension.

默认情况下,{Binding} 假设你要绑定到标记页面的 DataContext{Binding} assumes, by default, that you're binding to the DataContext of your markup page. 因此,我们将页面的 DataContext 设置为绑定源类(本例中为 HostViewModel 类型)的实例。So we'll set the DataContext of our page to be an instance of our binding source class (of type HostViewModel in this case). 下面的示例展示了用于声明绑定对象的标记。The example below shows the markup that declares the binding object. 我们使用了与前面“绑定目标”部分中所使用的相同 Button.Content 绑定目标,并绑定到 HostViewModel.NextButtonText 属性。We use the same Button.Content binding target we used in the "Binding target" section earlier, and we bind to the HostViewModel.NextButtonText property.

<Page xmlns:viewmodel="using:DataBindingInDepth" ... >
    <Page.DataContext>
        <viewmodel:HostViewModel x:Name="viewModelInDataContext"/>
    </Page.DataContext>
    ...
    <Button Content="{Binding Path=NextButtonText}" ... />
</Page>
// MainPage.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
    this.viewModelInDataContext.NextButtonText = "Updated Next button text";
}
// MainPage.cpp
void MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
{
    viewModelInDataContext().NextButtonText(L"Updated Next button text");
}

注意,该值即是我们为 Path 指定的值。Notice the value that we specify for Path. 此值在页面 DataContext 的上下文中进行了解释,本示例已设置为 HostViewModel 的实例。This value is interpreted in the context of the page's DataContext, which in this example is set to an instance of HostViewModel. 路径引用 HostViewModel.NextButtonText 属性。The path references the HostViewModel.NextButtonText property. 我们可以省略 Mode,因为此处使用的是单向绑定默认值 {Binding}We can omit Mode, because the {Binding} default of one-way works here.

UI 元素的 DataContext 默认值是已继承的其父元素的值。The default value of DataContext for a UI element is the inherited value of its parent. 显然可通过显式设置 DataContext 来替代该默认值,默认情况下,该值又会被子元素继承。You can of course override that default by setting DataContext explicitly, which is in turn inherited by children by default. 如果你希望多个绑定使用同一个源,则在元素上显式设置 DataContext 会很有用。Setting DataContext explicitly on an element is useful when you want to have multiple bindings that use the same source.

绑定对象具有 Source 属性,其默认为声明绑定所在 UI 元素的 DataContextA binding object has a Source property, which defaults to the DataContext of the UI element on which the binding is declared. 你可以通过在绑定对象上显式设置 SourceRelativeSourceElementName 来替代此默认值(有关详细信息,请参阅 {Binding})。You can override this default by setting Source, RelativeSource, or ElementName explicitly on the binding (see {Binding} for details).

DataTemplate 内,DataContext 已自动设置为模板化的数据对象 。Inside a DataTemplate, the DataContext is automatically set to the data object being templated. 下面给出的示例可用作项目控件的 ItemTemplate,绑定到具有名为 TitleDescription 的字符串属性的任意类型的集合。The example given below could be used as the ItemTemplate of an items control bound to a collection of any type that has string properties named Title and Description.

<DataTemplate x:Key="SimpleItemTemplate">
    <StackPanel Orientation="Vertical" Height="50">
      <TextBlock Text="{Binding Title}"/>
      <TextBlock Text="{Binding Description"/>
    </StackPanel>
  </DataTemplate>

备注

默认情况下,当 TextBox 失去焦点时,TextBox.Text 的更改将发送到双向绑定源 。By default, changes to TextBox.Text are sent to a two-way bound source when the TextBox loses focus. 若要在每次用户按键之后发送更改,则在标记的绑定对象上将 UpdateSourceTrigger 设置为 PropertyChangedTo cause changes to be sent after every user keystroke, set UpdateSourceTrigger to PropertyChanged on the binding in markup. 当通过将 UpdateSourceTrigger 设置为 Explicit 将更改发送到绑定源时,也可完全控制这一情形。You can also completely take control of when changes are sent to the source by setting UpdateSourceTrigger to Explicit. 然后,在文本框(通常为 TextBox.TextChanged)上处理事件,调用绑定目标上的 GetBindingExpression 以获取 BindingExpression 对象,最后调用 BindingExpression.UpdateSource 以编程方式更新数据源。You then handle events on the text box (typically TextBox.TextChanged), call GetBindingExpression on the target to get a BindingExpression object, and finally call BindingExpression.UpdateSource to programmatically update the data source.

Path 属性支持各种用于绑定到嵌套属性、附加属性以及整数和字符串索引器的语法选项。The Path property supports a variety of syntax options for binding to nested properties, attached properties, and integer and string indexers. 有关详细信息,请参阅 Property-path 语法For more info, see Property-path syntax. 绑定到字符串索引器会为你提供绑定到动态属性的效果,而无需实现 ICustomPropertyProviderBinding to string indexers gives you the effect of binding to dynamic properties without having to implement ICustomPropertyProvider. ElementName 属性对于元素间的绑定非常有用。The ElementName property is useful for element-to-element binding. RelativeSource 属性具有多种用途,其中之一是用作模板化 ControlTemplate 内绑定的更有效的替代方法。The RelativeSource property has several uses, one of which is as a more powerful alternative to template binding inside a ControlTemplate. 有关其他设置,请参阅 {Binding} 标记扩展Binding 类。For other settings, see {Binding} markup extension and the Binding class.

如果绑定源和绑定目标不是同一类型,会怎样?What if the source and the target are not the same type?

如果你希望基于 Boolean 属性值控制 UI 元素的可见性,或者希望 UI 元素显示为某种颜色(这是数字值的范围或趋势函数),亦或是希望在需要字符串的 UI 元素属性中显示日期/时间值,则需要将值从一种类型转化为另一种类型。If you want to control the visibility of a UI element based on the value of a boolean property, or if you want to render a UI element with a color that's a function of a numeric value's range or trend, or if you want to display a date and/or time value in a UI element property that expects a string, then you'll need to convert values from one type to another. 存在这样的情形:其正确的解决方案是从绑定源类公开其他正确类型的属性,并在此处让转换逻辑保持封装和可测试。There will be cases where the right solution is to expose another property of the right type from your binding source class, and keep the conversion logic encapsulated and testable there. 但是,当你拥有大量源和目标属性或这两者的大型组合时,上述方案既不灵活也不可扩展。But that isn't flexible nor scalable when you have large numbers, or large combinations, of source and target properties. 在这种情况下,你有以下几个选项:In that case you have a couple of options:

  • 如果使用 {x:Bind},则你可以直接绑定到函数以执行该转换。If using {x:Bind} then you can bind directly to a function to do that conversion
  • 或者可以指定一个值转换器,该转换器是一个旨在执行转换的对象。Or you can specify a value converter which is an object designed to perform the conversion

值转换器Value Converters

下面是适用于一次性或单向绑定的值转换器,可将 DateTime 值转换为包含月份的字符串值。Here's a value converter, suitable for a one-time or a one-way binding, that converts a DateTime value to a string value containing the month. 该类实现 IValueConverterThe class implements IValueConverter.

public class DateToStringConverter : IValueConverter
{
    // Define the Convert method to convert a DateTime value to 
    // a month string.
    public object Convert(object value, Type targetType, 
        object parameter, string language)
    {
        // value is the data from the source object.
        DateTime thisdate = (DateTime)value;
        int monthnum = thisdate.Month;
        string month;
        switch (monthnum)
        {
            case 1:
                month = "January";
                break;
            case 2:
                month = "February";
                break;
            default:
                month = "Month not found";
                break;
        }
        // Return the value to pass to the target.
        return month;
    }

    // ConvertBack is not implemented for a OneWay binding.
    public object ConvertBack(object value, Type targetType, 
        object parameter, string language)
    {
        throw new NotImplementedException();
    }
}
// See the "Formatting or converting data values for display" section in the "Data binding overview" topic.

下面介绍了如何在绑定对象标记中使用该值转换器。And here's how you consume that value converter in your binding object markup.

<UserControl.Resources>
  <local:DateToStringConverter x:Key="Converter1"/>
</UserControl.Resources>
...
<TextBlock Grid.Column="0" 
  Text="{x:Bind ViewModel.Month, Converter={StaticResource Converter1}}"/>
<TextBlock Grid.Column="0" 
  Text="{Binding Month, Converter={StaticResource Converter1}}"/>

如果 Converter 参数是为绑定定义的,则绑定引擎会调用 ConvertConvertBack 方法。The binding engine calls the Convert and ConvertBack methods if the Converter parameter is defined for the binding. 从绑定源传递数据时,绑定引擎会调用 Convert 并将返回的数据传递给绑定目标。When data is passed from the source, the binding engine calls Convert and passes the returned data to the target. 从(用于双向绑定)绑定目标传递数据时,绑定引擎会调用 ConvertBack 并将返回的数据传递给绑定源。When data is passed from the target (for a two-way binding), the binding engine calls ConvertBack and passes the returned data to the source.

转换器还有可选参数:ConverterLanguage(该参数允许指定在转换中使用的语言)和 ConverterParameter(该参数允许为转换逻辑传递一个参数) 。The converter also has optional parameters: ConverterLanguage, which allows specifying the language to be used in the conversion, and ConverterParameter, which allows passing a parameter for the conversion logic. 有关使用转换器参数的示例,请参阅 IValueConverterFor an example that uses a converter parameter, see IValueConverter.

备注

如果在转换中存在错误,请不要引发异常。If there is an error in the conversion, do not throw an exception. 而是返回 DependencyProperty.UnsetValue,它将停止数据传输。Instead, return DependencyProperty.UnsetValue, which will stop the data transfer.

若要显示在无法解析绑定源时所使用的默认值,则在标记的绑定上对象设置 FallbackValue 属性。To display a default value to use whenever the binding source cannot be resolved, set the FallbackValue property on the binding object in markup. 这对处理转换和格式错误很有用。This is useful to handle conversion and formatting errors. 这对要绑定到可能未存在于异型绑定集合中所有对象上的源属性也同样有用。It is also useful to bind to source properties that might not exist on all objects in a bound collection of heterogeneous types.

如果你将文本控件绑定到某个值(不是字符串),则数据绑定引擎会将该值转换为字符串。If you bind a text control to a value that is not a string, the data binding engine will convert the value to a string. 如果该值是引用类型,数据绑定引擎将通过调用 ICustomPropertyProvider.GetStringRepresentationIStringable.ToString(如果提供)来检索字符串值,否则将调用 Object.ToStringIf the value is a reference type, the data binding engine will retrieve the string value by calling ICustomPropertyProvider.GetStringRepresentation or IStringable.ToString if available, and will otherwise call Object.ToString. 但是请注意,绑定引擎将忽略隐藏基类实现的任何 ToString 实现。Note, however, that the binding engine will ignore any ToString implementation that hides the base-class implementation. 子类实现应替代基类 ToString 方法。Subclass implementations should override the base class ToString method instead. 同样,在本机语言中,也会显示所有托管对象以实现 ICustomPropertyProviderIStringableSimilarly, in native languages, all managed objects appear to implement ICustomPropertyProvider and IStringable. 但是,对 GetStringRepresentationIStringable.ToString 的所有调用都路由到 Object.ToString 或该方法的替代,从不路由到隐藏基类实现的新 ToString 实现。However, all calls to GetStringRepresentation and IStringable.ToString are routed to Object.ToString or an override of that method, and never to a new ToString implementation that hides the base-class implementation.

备注

从 Windows 10 版本 1607 开始,XAML 框架向 Visibility 转换器提供内置布尔值。Starting in Windows 10, version 1607, the XAML framework provides a built in boolean to Visibility converter. 转换器将 true 映射到 Visible 枚举值并将 false 映射到 Collapsed,以便你可以将 Visibility 属性绑定到布尔值,而无需创建转换器。The converter maps true to the Visible enumeration value and false to Collapsed so you can bind a Visibility property to a boolean without creating a converter. 若要使用内置转换器,你的应用的最低目标 SDK 版本必须为 14393 或更高版本。To use the built in converter, your app's minimum target SDK version must be 14393 or later. 当你的应用面向较早版本的 Windows 10 时,你无法使用它。You can't use it when your app targets earlier versions of Windows 10. 有关目标版本的详细信息,请参阅版本自适应代码For more info about target versions, see Version adaptive code.

{x:Bind} 中的函数绑定Function binding in {x:Bind}

{x:Bind} 使绑定步骤中的最后一步可以是一个函数。{x:Bind} enables the final step in a binding path to be a function. 这可以用于执行转换,以及执行依赖多个属性的绑定。This can be used to perform conversions, and to perform bindings that depend on more than one property. 请参阅 x:Bind 中的函数 See Functions in x:Bind

元素间的绑定Element-to-element binding

可以将一个 XAML 元素的属性绑定到另一个 XAML 元素的属性。You can bind the property of one XAML element to the property of another XAML element. 下面是在标记中进行的该操作的一个示例。Here's an example of how that looks in markup.

<TextBox x:Name="myTextBox" />
<TextBlock Text="{x:Bind myTextBox.Text, Mode=OneWay}" />

重要

有关使用 C++/WinRT 的元素间的绑定的必要工作流,请参阅元素间的绑定For the necessary workflow for element-to-element binding using C++/WinRT, see Element-to-element binding.

带有 {x:Bind} 的资源字典Resource dictionaries with {x:Bind}

{x:Bind} 标记扩展依赖于代码生成,因此它需要一个包含可调用 InitializeComponent 的构造函数的代码隐藏文件(以供初始化生成的代码)。The {x:Bind} markup extension depends on code generation, so it needs a code-behind file containing a constructor that calls InitializeComponent (to initialize the generated code). 通过实例化资源字典的类型(以便调用 InitializeComponent)而不是引用其文件名,来重用它。You re-use the resource dictionary by instantiating its type (so that InitializeComponent is called) instead of referencing its filename. 下面是针对你拥有一个现有资源字典并且想要在其中使用 {x:Bind} 这一情形的示例。Here's an example of what to do if you have an existing resource dictionary and you want to use {x:Bind} in it.

TemplatesResourceDictionary.xamlTemplatesResourceDictionary.xaml

<ResourceDictionary
    x:Class="ExampleNamespace.TemplatesResourceDictionary"
    .....
    xmlns:examplenamespace="using:ExampleNamespace">
    
    <DataTemplate x:Key="EmployeeTemplate" x:DataType="examplenamespace:IEmployee">
        <Grid>
            <TextBlock Text="{x:Bind Name}"/>
        </Grid>
    </DataTemplate>
</ResourceDictionary>

TemplatesResourceDictionary.xaml.csTemplatesResourceDictionary.xaml.cs

using Windows.UI.Xaml.Data;
 
namespace ExampleNamespace
{
    public partial class TemplatesResourceDictionary
    {
        public TemplatesResourceDictionary()
        {
            InitializeComponent();
        }
    }
}

MainPage.xamlMainPage.xaml

<Page x:Class="ExampleNamespace.MainPage"
    ....
    xmlns:examplenamespace="using:ExampleNamespace">

    <Page.Resources>
        <ResourceDictionary>
            .... 
            <ResourceDictionary.MergedDictionaries>
                <examplenamespace:TemplatesResourceDictionary/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Page.Resources>
</Page>

事件绑定和 ICommandEvent binding and ICommand

{x:Bind} 支持一种名为事件绑定的功能。{x:Bind} supports a feature called event binding. 借助此功能,你可以使用绑定为事件指定处理程序(这是一种附加选项),但使用代码隐藏文件上的某一方法处理事件除外。With this feature, you can specify the handler for an event using a binding, which is an additional option on top of handling events with a method on the code-behind file. 假设你的 MainPage 类上具有 RootFrame 属性。Let's say you have a RootFrame property on your MainPage class.

public sealed partial class MainPage : Page
{
    ...
    public Frame RootFrame { get { return Window.Current.Content as Frame; } }
}

随即你可以将按钮的 Click 事件绑定到由 RootFrame 属性返回的 Frame 对象上的方法,如下所示。You can then bind a button's Click event to a method on the Frame object returned by the RootFrame property like this. 注意,我们还将按钮的 IsEnabled 属性绑定到同一 Frame 的另一成员。Note that we also bind the button's IsEnabled property to another member of the same Frame.

<AppBarButton Icon="Forward" IsCompact="True"
IsEnabled="{x:Bind RootFrame.CanGoForward, Mode=OneWay}"
Click="{x:Bind RootFrame.GoForward}"/>

重载方法不适用于借助此技术来处理事件。Overloaded methods cannot be used to handle an event with this technique. 此外,如果用于处理事件的方法包含一些参数,则这些参数均须根据各自对应的事件参数类型进行赋值。Also, if the method that handles the event has parameters then they must all be assignable from the types of all of the event's parameters, respectively. 在此情况下,Frame.GoForward 不重载且不含任何参数(但即使其包含两个 object 参数,也仍然有效)。In this case, Frame.GoForward is not overloaded and it has no parameters (but it would still be valid even if it took two object parameters). 不过,因为 Frame.GoBack 已重载,所以我们无法通过此技术使用该方法 。Frame.GoBack is overloaded, though, so we can't use that method with this technique.

事件绑定技术类似于实现和使用命令(一个命令返回一种属性,该属性将返回实现 ICommand 接口的对象)。The event binding technique is similar to implementing and consuming commands (a command is a property that returns an object that implements the ICommand interface). {x:Bind}{Binding} 均适用于命令。Both {x:Bind} and {Binding} work with commands. 可使用 QuizGame 示例(位于“Common”文件夹中)中提供的 DelegateCommand 帮助程序类,这样便无需多次实现命令模式。So that you don't have to implement the command pattern multiple times, you can use the DelegateCommand helper class that you'll find in the QuizGame sample (in the "Common" folder).

绑定到文件夹或文件集合Binding to a collection of folders or files

你可以使用 Windows.Storage 命名空间中的 API 来检索文件夹和文件数据。You can use the APIs in the Windows.Storage namespace to retrieve folder and file data. 然而,各种 GetFilesAsyncGetFoldersAsyncGetItemsAsync 方法都不会返回适合绑定到列表控件的值。However, the various GetFilesAsync, GetFoldersAsync, and GetItemsAsync methods do not return values that are suitable for binding to list controls. 必须绑定到 FileInformationFactory 类的 GetVirtualizedFilesVectorGetVirtualizedFoldersVectorGetVirtualizedItemsVector 方法的返回值。Instead, you must bind to the return values of the GetVirtualizedFilesVector, GetVirtualizedFoldersVector, and GetVirtualizedItemsVector methods of the FileInformationFactory class. 下面的代码示例来自 StorageDataSource 和 GetVirtualizedFilesVector 示例,它将显示典型的使用模式。The following code example from the StorageDataSource and GetVirtualizedFilesVector sample shows the typical usage pattern. 请谨记在应用包清单中声明 picturesLibrary 功能,并确认你的 Pictures 库文件夹中有图片。Remember to declare the picturesLibrary capability in your app package manifest, and confirm that there are pictures in your Pictures library folder.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    var library = Windows.Storage.KnownFolders.PicturesLibrary;
    var queryOptions = new Windows.Storage.Search.QueryOptions();
    queryOptions.FolderDepth = Windows.Storage.Search.FolderDepth.Deep;
    queryOptions.IndexerOption = Windows.Storage.Search.IndexerOption.UseIndexerWhenAvailable;

    var fileQuery = library.CreateFileQueryWithOptions(queryOptions);

    var fif = new Windows.Storage.BulkAccess.FileInformationFactory(
        fileQuery,
        Windows.Storage.FileProperties.ThumbnailMode.PicturesView,
        190,
        Windows.Storage.FileProperties.ThumbnailOptions.UseCurrentScale,
        false
        );

    var dataSource = fif.GetVirtualizedFilesVector();
    this.PicturesListView.ItemsSource = dataSource;
}

通常,你需要使用此方法来创建文件和文件夹信息的只读视图。You will typically use this approach to create a read-only view of file and folder info. 可以创建到文件和文件夹属性的双向绑定,例如,让用户在音乐视图中对歌曲进行评级。You can create two-way bindings to the file and folder properties, for example to let users rate a song in a music view. 然而,任何更改都不会永久保留,除非你调用相应的 SavePropertiesAsync 方法(例如,MusicProperties.SavePropertiesAsync)。However, any changes are not persisted until you call the appropriate SavePropertiesAsync method (for example, MusicProperties.SavePropertiesAsync). 当项目失去焦点时,你应该提交更改,因为这样可以触发选择重置。You should commit changes when the item loses focus because this triggers a selection reset.

注意,使用此技术的双向绑定仅适用于索引位置(例如“音乐”)。Note that two-way binding using this technique works only with indexed locations, such as Music. 你可以通过调用 FolderInformation.GetIndexedStateAsync 方法来确定是否索引某个位置。You can determine whether a location is indexed by calling the FolderInformation.GetIndexedStateAsync method.

还请注意,某个虚拟化矢量可能会在填充其值之前为某些项目返回 nullNote also that a virtualized vector can return null for some items before it populates their value. 例如,应该首先检查 null,然后才能使用绑定到虚拟化矢量的列表控件的 SelectedItem 值,或者改为使用 SelectedIndexFor example, you should check for null before you use the SelectedItem value of a list control bound to a virtualized vector, or use SelectedIndex instead.

绑定到按键分组的数据Binding to data grouped by a key

若要获取项的扁平集合(例如,由 BookSku 类表示的书籍)并通过将某一常见属性(例如,BookSku.AuthorName 属性)用作某种键来对项进行分组,则将该结果称之为分组数据 。If you take a flat collection of items (books, for example, represented by a BookSku class) and you group the items by using a common property as a key (the BookSku.AuthorName property, for example) then the result is called grouped data. 当对数据进行分组时,它将不再是简单集合。When you group data, it is no longer a flat collection. 分组数据是组对象的集合,其中每个组对象都具有Grouped data is a collection of group objects, where each group object has

  • 一个键和a key, and
  • 一个其属性与该键匹配的项集合。a collection of items whose property matches that key.

再以书籍为例,按作者名称对书籍进行分组的结果即为作者名称组的集合,其中每组都具有To take the books example again, the result of grouping the books by author name results in a collection of author name groups where each group has

  • 一个键(这是作者名称)和a key, which is an author name, and
  • 一个其 AuthorName 属性与组键匹配的 BookSku 集合 。a collection of the BookSkus whose AuthorName property matches the group's key.

通常情况下,若要显示集合,可将项目控件(例如 ListViewGridView)的 ItemsSource 直接绑定到返回集合的属性。In general, to display a collection, you bind the ItemsSource of an items control (such as ListView or GridView) directly to a property that returns a collection. 如果这是项的简单集合,则无需执行任何特殊操作。If that's a flat collection of items then you don't need to do anything special. 但是,如果它是组对象的集合(像在绑定到分组数据时一样),则需要使用一个名为 CollectionViewSource 的中间对象,该对象位于项目控件和绑定源之间。But if it's a collection of group objects (as it is when binding to grouped data) then you need the services of an intermediary object called a CollectionViewSource which sits between the items control and the binding source. 先将 CollectionViewSource 绑定到返回分组数据的属性,然后将项目控件绑定到 CollectionViewSourceYou bind the CollectionViewSource to the property that returns grouped data, and you bind the items control to the CollectionViewSource. CollectionViewSource 的额外增值功能可用于跟踪当前项,以便你可以通过将多个项目控件全都绑定到同一 CollectionViewSource 来使其保持同步。An extra value-add of a CollectionViewSource is that it keeps track of the current item, so you can keep more than one items control in sync by binding them all to the same CollectionViewSource. 也可以通过 CollectionViewSource.View 属性返回的对象的 ICollectionView.CurrentItem 属性,以编程方式访问当前项。You can also access the current item programmatically through the ICollectionView.CurrentItem property of the object returned by the CollectionViewSource.View property.

若要激活 CollectionViewSource 的分组功能,请将 IsSourceGrouped 设置为 trueTo activate the grouping facility of a CollectionViewSource, set IsSourceGrouped to true. 是否还需要设置 ItemsPath 属性完全取决于你的组对象的创作方式。Whether you also need to set the ItemsPath property depends on exactly how you author your group objects. 组对象的创作方式有以下两种:“属于组”模式和“包含组”模式。There are two ways to author a group object: the "is-a-group" pattern, and the "has-a-group" pattern. 在“属于组”模式中,组对象派生自集合类型(例如,List<T> ),因此事实上组对象本身就是项目组。In the "is-a-group" pattern, the group object derives from a collection type (for example, List<T>), so the group object actually is itself the group of items. 使用此模式,无需设置 ItemsPathWith this pattern you do not need to set ItemsPath. 在“包含组”模式中,组对象具有一个或多个集合类型属性(例如 List<T> ),因此“包含组”的单个项目组采用单个属性的形式(而多个项目组则采用多个属性的形式)。In the "has-a-group" pattern, the group object has one or more properties of a collection type (such as List<T>), so the group "has a" group of items in the form of a property (or several groups of items in the form of several properties). 使用此模式,需要将 ItemsPath 设置为包含项目组的属性名。With this pattern you need to set ItemsPath to the name of the property that contains the group of items.

下面的示例阐述了“包含组”模式。The example below illustrates the "has-a-group" pattern. 页面类有一个名为 ViewModel 的属性,该属性将返回我们的视图模型的实例。The page class has a property named ViewModel, which returns an instance of our view model. CollectionViewSource 绑定到视图模型的 Authors 属性(Authors 是组对象的集合),还指定了它是包含分组项目的 Author.BookSkus 属性。The CollectionViewSource binds to the Authors property of the view model (Authors is the collection of group objects) and also specifies that it's the Author.BookSkus property that contains the grouped items. 最后,GridView 绑定到 CollectionViewSource 且具有其已定义的组样式,这样它便可以采用分组形式呈现项目。Finally, the GridView is bound to the CollectionViewSource, and has its group style defined so that it can render the items in groups.

<Page.Resources>
    <CollectionViewSource
    x:Name="AuthorHasACollectionOfBookSku"
    Source="{x:Bind ViewModel.Authors}"
    IsSourceGrouped="true"
    ItemsPath="BookSkus"/>
</Page.Resources>
...
<GridView
ItemsSource="{x:Bind AuthorHasACollectionOfBookSku}" ...>
    <GridView.GroupStyle>
        <GroupStyle
            HeaderTemplate="{StaticResource AuthorGroupHeaderTemplateWide}" ... />
    </GridView.GroupStyle>
</GridView>

你可以采用以下两种方式之一实现“属于组”模式。You can implement the "is-a-group" pattern in one of two ways. 一种方法是创作你自己的组类。One way is to author your own group class. List<T> 派生类(其中 T 是项目类型)。Derive the class from List<T> (where T is the type of the items). 例如,public class Author : List<BookSku>For example, public class Author : List<BookSku>. 另一种方式是,使用 LINQ 表达式,从诸如 BookSku 项目的属性值等动态创建组对象(以及组类)。The second way is to use a LINQ expression to dynamically create group objects (and a group class) from like property values of the BookSku items. 此方法(仅维护项目的简单列表并将其动态分组在一起)是从云服务访问数据的应用的典型用法。This approach—maintaining only a flat list of items and grouping them together on the fly—is typical of an app that accesses data from a cloud service. 可以灵活地按作者或流派对书籍进行分组,而无需特殊组类,如 AuthorGenreYou get the flexibility to group books by author or by genre (for example) without needing special group classes such as Author and Genre.

下面的示例使用 LINQ 阐述了“属于组”模式。The example below illustrates the "is-a-group" pattern using LINQ. 在本示例中我们按流派对书籍进行分组,并在组标题中显示流派名。This time we group books by genre, displayed with the genre name in the group headers. 这由引用组 Key 的值的“Key”属性路径进行指示。This is indicated by the "Key" property path in reference to the group Key value.

using System.Linq;
...
private IOrderedEnumerable<IGrouping<string, BookSku>> genres;

public IOrderedEnumerable<IGrouping<string, BookSku>> Genres
{
    get
    {
        if (this.genres == null)
        {
            this.genres = from book in this.bookSkus
                          group book by book.genre into grp
                          orderby grp.Key
                          select grp;
        }
        return this.genres;
    }
}

请记住,在将 {x:Bind} 用于数据模板时,需通过设置 x:DataType 值来指示要绑定到的类型。Remember that when using {x:Bind} with data templates we need to indicate the type being bound to by setting an x:DataType value. 如果类型是我们无法在标记中解释的泛型,则需要在组样式标头模板中改用 {Binding}If the type is generic then we can't express that in markup so we need to use {Binding} instead in the group style header template.

    <Grid.Resources>
        <CollectionViewSource x:Name="GenreIsACollectionOfBookSku"
        Source="{x:Bind Genres}"
        IsSourceGrouped="true"/>
    </Grid.Resources>
    <GridView ItemsSource="{x:Bind GenreIsACollectionOfBookSku}">
        <GridView.ItemTemplate x:DataType="local:BookTemplate">
            <DataTemplate>
                <TextBlock Text="{x:Bind Title}"/>
            </DataTemplate>
        </GridView.ItemTemplate>
        <GridView.GroupStyle>
            <GroupStyle>
                <GroupStyle.HeaderTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Key}"/>
                    </DataTemplate>
                </GroupStyle.HeaderTemplate>
            </GroupStyle>
        </GridView.GroupStyle>
    </GridView>

SemanticZoom 控件是使用户查看和导航分组数据的绝佳方法。A SemanticZoom control is a great way for your users to view and navigate grouped data. Bookstore2 示例应用演示了如何使用 SemanticZoomThe Bookstore2 sample app illustrates how to use the SemanticZoom. 在该应用中,你可以查看按作者分组的书籍列表(放大视图),也可以缩小以查看作者的跳转列表(缩小视图)。In that app, you can view a list of books grouped by author (the zoomed-in view) or you can zoom out to see a jump list of authors (the zoomed-out view). 与在书籍列表中上下滚动相比,跳转列表提供了更快速的浏览方式。The jump list affords much quicker navigation than scrolling through the list of books. 实际上,放大视图和缩小视图是绑定到同一 CollectionViewSourceListViewGridView 控件。The zoomed-in and zoomed-out views are actually ListView or GridView controls bound to the same CollectionViewSource.

SemanticZoom 图示

当绑定到分层数据(如类别中的子类别)时,你可以选择使用一系列的项目控件,在 UI 中显示分层级别。When you bind to hierarchical data—such as subcategories within categories—you can choose to display the hierarchical levels in your UI with a series of items controls. 所选项目控件决定后续项目控件的内容。A selection in one items control determines the contents of subsequent items controls. 你可以使列表保持同步,方法是将各列表绑定到其各自的 CollectionViewSource 并将 CollectionViewSource 实例一同绑定到一个链中。You can keep the lists synchronized by binding each list to its own CollectionViewSource and binding the CollectionViewSource instances together in a chain. 这称为主视图/详细信息(或列表/详细信息)视图。This is called a master/details (or list/details) view. 有关详细信息,请参阅如何绑定到分层数据并创建主视图/详细信息视图For more info, see How to bind to hierarchical data and create a master/details view.

诊断并调试数据绑定问题Diagnosing and debugging data binding problems

绑定标记包含属性名称(对于 C#,有时是字段和方法)。Your binding markup contains the names of properties (and, for C#, sometimes fields and methods). 因此,在重命名属性时,你还需要更改对其进行引用的任何绑定。So when you rename a property, you'll also need to change any binding that references it. 若忘记执行此操作,会导致出现一个典型的数据绑定 Bug,并且你的应用将不能编译或不能正常运行。Forgetting to do that leads to a typical example of a data binding bug, and your app either won't compile or won't run correctly.

{x:Bind}{Binding} 创建的绑定对象在功能上大致等同。The binding objects created by {x:Bind} and {Binding} are largely functionally equivalent. 但是,{x:Bind} 具有绑定源的类型信息,并且会在编译时生成源代码。But {x:Bind} has type information for the binding source, and it generates source code at compile-time. 借助 {x:Bind},可使用剩余代码检测同一类型的问题。With {x:Bind} you get the same kind of problem detection that you get with the rest of your code. 这包括编译时对绑定表达式的验证,以及通过在作为页面的部分类生成的源代码中设置断点来调试。That includes compile-time validation of your binding expressions, and debugging by setting breakpoints in the source code generated as the partial class for your page. 可以在 obj 文件夹的文件中找到这些类,如文件夹名为(适用于 C#)<view name>.g.csThese classes can be found in the files in your obj folder, with names like (for C#) <view name>.g.cs). 如果你有绑定方面的问题,请打开 Microsoft Visual Studio 调试器中的出现未处理的异常时中断If you have a problem with a binding then turn on Break On Unhandled Exceptions in the Microsoft Visual Studio debugger. 该调试器将在该点处中断执行,以便你可以调试哪里出现了问题。The debugger will break execution at that point, and you can then debug what has gone wrong. 对于绑定源节点图形的每个部分,{x:Bind} 生成的代码均遵循相同的模式,你可以使用 “调用堆栈”窗口中的信息来帮助确定导致该问题出现的调用顺序。The code generated by {x:Bind} follows the same pattern for each part of the graph of binding source nodes, and you can use the info in the Call Stack window to help determine the sequence of calls that led up to the problem.

{Binding} 不具有绑定源的类型信息。{Binding} does not have type information for the binding source. 但在通过附加的调试器运行你的应用时,Visual Studio 中的 “输出”窗口中将显示所有绑定错误。But when you run your app with the debugger attached, any binding errors appear in the Output window in Visual Studio.

使用代码创建绑定Creating bindings in code

注意  由于不能使用代码创建 {x:Bind} 绑定,因此此部分仅适用于 {Binding}Note  This section only applies to {Binding}, because you can't create {x:Bind} bindings in code. 不过,使用 DependencyObject.RegisterPropertyChangedCallback 同样可以获得 {x:Bind} 的某些相同优势,这使你可以在任何依赖属性上注册更改通知。However, some of the same benefits of {x:Bind} can be achieved with DependencyObject.RegisterPropertyChangedCallback, which enables you to register for change notifications on any dependency property.

还可以使用规程代码而不是 XAML 来将 UI 元素连接到数据。You can also connect UI elements to data using procedural code instead of XAML. 为此,请创建一个新 Binding 对象,设置相应的属性,然后调用 FrameworkElement.SetBindingBindingOperations.SetBindingTo do this, create a new Binding object, set the appropriate properties, then call FrameworkElement.SetBinding or BindingOperations.SetBinding. 如果你希望在运行时选择绑定属性值或在多个控件中共享单个绑定,则以编程方式创建绑定将十分有用。Creating bindings programmatically is useful when you want to choose the binding property values at run-time or share a single binding among multiple controls. 但是请注意,调用 SetBinding 后,无法更改绑定属性值。Note, however, that you cannot change the binding property values after you call SetBinding.

以下示例演示了如何使用代码实现绑定。The following example shows how to implement a binding in code.

<TextBox x:Name="MyTextBox" Text="Text"/>
// Create an instance of the MyColors class 
// that implements INotifyPropertyChanged.
MyColors textcolor = new MyColors();

// Brush1 is set to be a SolidColorBrush with the value Red.
textcolor.Brush1 = new SolidColorBrush(Colors.Red);

// Set the DataContext of the TextBox MyTextBox.
MyTextBox.DataContext = textcolor;

// Create the binding and associate it with the text box.
Binding binding = new Binding() { Path = new PropertyPath("Brush1") };
MyTextBox.SetBinding(TextBox.ForegroundProperty, binding);
' Create an instance of the MyColors class 
' that implements INotifyPropertyChanged. 
Dim textcolor As New MyColors()

' Brush1 is set to be a SolidColorBrush with the value Red. 
textcolor.Brush1 = New SolidColorBrush(Colors.Red)

' Set the DataContext of the TextBox MyTextBox. 
MyTextBox.DataContext = textcolor

' Create the binding and associate it with the text box.
Dim binding As New Binding() With {.Path = New PropertyPath("Brush1")}
MyTextBox.SetBinding(TextBox.ForegroundProperty, binding)

{x:Bind} 和 {Binding} 功能比较{x:Bind} and {Binding} feature comparison

功能Feature {x:Bind}{x:Bind} {Binding}{Binding} 注释Notes
Path 为默认属性Path is the default property {x:Bind a.b.c} {Binding a.b.c}
Path 属性Path property {x:Bind Path=a.b.c} {Binding Path=a.b.c} 在 x:Bind 中,Path 默认为 Page 的根,而非 DataContext 的根。In x:Bind, Path is rooted at the Page by default, not the DataContext.
索引Indexer {x:Bind Groups[2].Title} {Binding Groups[2].Title} 绑定到集合中的指定项。Binds to the specified item in the collection. 仅支持基于整数的索引。Only integer-based indexes are supported.
附加属性Attached properties {x:Bind Button22.(Grid.Row)} {Binding Button22.(Grid.Row)} 附加属性用括号进行指定。Attached properties are specified using parentheses. 如果未在 XAML 命名空间中声明该属性,则在其前面加上 xml 命名空间,这应该映射到文档的标头处的代码命名空间中。If the property is not declared in a XAML namespace, then prefix it with an xml namespace, which should be mapped to a code namespace at the head of the document.
强制转换Casting {x:Bind groups[0].(data:SampleDataGroup.Title)} 不需要。Not needed. 强制转换用括号进行指定。Casts are specified using parentheses. 如果未在 XAML 命名空间中声明该属性,则在其前面加上 xml 命名空间,这应该映射到文档的标头处的代码命名空间中。If the property is not declared in a XAML namespace, then prefix it with an xml namespace, which should be mapped to a code namespace at the head of the document.
ConverterConverter {x:Bind IsShown, Converter={StaticResource BoolToVisibility}} {Binding IsShown, Converter={StaticResource BoolToVisibility}} 转换器必须在 Page/ResourceDictionary 的根目录处或在 App.xaml 中进行声明。Converters must be declared at the root of the Page/ResourceDictionary, or in App.xaml.
ConverterParameter, ConverterLanguageConverterParameter, ConverterLanguage {x:Bind IsShown, Converter={StaticResource BoolToVisibility}, ConverterParameter=One, ConverterLanguage=fr-fr} {Binding IsShown, Converter={StaticResource BoolToVisibility}, ConverterParameter=One, ConverterLanguage=fr-fr} 转换器必须在 Page/ResourceDictionary 的根目录处或在 App.xaml 中进行声明。Converters must be declared at the root of the Page/ResourceDictionary, or in App.xaml.
TargetNullValueTargetNullValue {x:Bind Name, TargetNullValue=0} {Binding Name, TargetNullValue=0} 在绑定表达式的叶为 null 时使用。Used when the leaf of the binding expression is null. 对于字符串值,应使用单引号。Use single quotes for a string value.
FallbackValueFallbackValue {x:Bind Name, FallbackValue='empty'} {Binding Name, FallbackValue='empty'} 绑定的路径的任何部分(叶除外)为 null 时使用。Used when any part of the path for the binding (except for the leaf) is null.
ElementNameElementName {x:Bind slider1.Value} {Binding Value, ElementName=slider1} 使用 {x:Bind},绑定到某一字段;Path 默认位于 Page 的根处,以便任意命名的元素均可通过其字段进行访问。With {x:Bind} you're binding to a field; Path is rooted at the Page by default, so any named element can be accessed via its field.
RelativeSource:SelfRelativeSource: Self <Rectangle x:Name="rect1" Width="200" Height="{x:Bind rect1.Width}" ... /> <Rectangle Width="200" Height="{Binding Width, RelativeSource={RelativeSource Self}}" ... /> 借助 {x:Bind},对元素进行命名并在 Path 中使用其名称。With {x:Bind}, name the element and use its name in Path.
RelativeSource:TemplatedParentRelativeSource: TemplatedParent 不需要Not needed {Binding <path>, RelativeSource={RelativeSource TemplatedParent}} 借助 ControlTemplate 上的{x:Bind} TargetType,指示绑定到模板父级。With {x:Bind} TargetType on ControlTemplate indicates binding to template parent. 对于 {Binding},在大多数情况下,常规模板绑定可在控件模板中使用。For {Binding} Regular template binding can be used in control templates for most uses. 但在使用 TemplatedParent 时,需要使用转换器或双向绑定。<But use TemplatedParent where you need to use a converter, or a two-way binding.<
Source 不需要Not needed <ListView ItemsSource="{Binding Orders, Source={StaticResource MyData}}"/> 对于 {x:Bind},可以直接使用命名元素,使用属性或静态路径。For {x:Bind} you can directly use the named element, use a property or a static path.
模式Mode {x:Bind Name, Mode=OneWay} {Binding Name, Mode=TwoWay} 模式可以是一次性、单向或双向。Mode can be OneTime, OneWay, or TwoWay. {x:Bind} defaults to OneTime; {Binding} defaults to OneWay.{x:Bind} defaults to OneTime; {Binding} defaults to OneWay.
UpdateSourceTriggerUpdateSourceTrigger {x:Bind Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged} {Binding UpdateSourceTrigger=PropertyChanged} UpdateSourceTrigger 可以是 Default、LostFocus 或 PropertyChanged。UpdateSourceTrigger can be Default, LostFocus, or PropertyChanged. {x:Bind} 不支持 UpdateSourceTrigger=Explicit。{x:Bind} does not support UpdateSourceTrigger=Explicit. {x:Bind} 可在所有情况下(TextBox.Text 除外,它使用 LostFocus 行为)使用 PropertyChanged 行为。{x:Bind} uses PropertyChanged behavior for all cases except TextBox.Text, where it uses LostFocus behavior.