数据绑定概述Data binding overview

本主题介绍了如何在通用 Windows 平台 (UWP) 应用中将控件(或其他 UI 元素)绑定到单个项目,或者将项目控件绑定到项目集合。This topic shows you how to bind a control (or other UI element) to a single item or bind an items control to a collection of items in a Universal Windows Platform (UWP) app. 此外,我们还介绍了如何控制项的呈现、基于所选内容实现详细信息视图,以及转换数据以供显示。In addition, we show how to control the rendering of items, implement a details view based on a selection, and convert data for display. 有关更多详细信息,请参阅深入了解数据绑定For more detailed info, see Data binding in depth.

必备条件Prerequisites

此主题假设你知道如何创建基本的 UWP 应用。This topic assumes that you know how to create a basic UWP app. 有关创建你的第一个 UWP 应用的说明,请参阅 Windows 应用入门For instructions on creating your first UWP app, see Get started with Windows apps.

创建项目Create the project

创建一个新的“空白应用程序(Windows 通用)” 项目。Create a new Blank Application (Windows Universal) project. 将它命名为“Quickstart”。Name it "Quickstart".

绑定到单个项目Binding to a single item

每个绑定均由一个绑定目标和一个绑定源构成。Every binding consists of a binding target and a binding source. 通常,绑定目标是控件或其他 UI 元素的属性,而绑定源是类实例(数据模型或视图模型)的属性。Typically, the target is a property of a control or other UI element, and the source is a property of a class instance (a data model, or a view model). 本示例演示了如何将控件绑定到单个项目。This example shows how to bind a control to a single item. 绑定目标是 TextBlockText 属性。The target is the Text property of a TextBlock. 绑定源是一个名为 Recording 的简单类的实例,该类表示音频录制。The source is an instance of a simple class named Recording that represents an audio recording. 我们先来看一下类。Let's look at the class first.

如果使用的是 C# 或 C++/CX,请将一个新类添加到项目中,并将该类命名 Recording 。If you're using C# or C++/CX, then add a new class to your project, and name the class Recording.

如果使用的是 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 代码,生成项目以生成 Recording.h``.cpp``RecordingViewModel.h.cpp,然后将代码添加到生成的文件以匹配列表。Replace the contents of those new files with the MIDL 3.0 code shown in the listing, build the project to generate Recording.h and .cpp and RecordingViewModel.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.

namespace Quickstart
{
    public class Recording
    {
        public string ArtistName { get; set; }
        public string CompositionName { get; set; }
        public DateTime ReleaseDateTime { get; set; }
        public Recording()
        {
            this.ArtistName = "Wolfgang Amadeus Mozart";
            this.CompositionName = "Andante in C for Piano";
            this.ReleaseDateTime = new DateTime(1761, 1, 1);
        }
        public string OneLineSummary
        {
            get
            {
                return $"{this.CompositionName} by {this.ArtistName}, released: "
                    + this.ReleaseDateTime.ToString("d");
            }
        }
    }
    public class RecordingViewModel
    {
        private Recording defaultRecording = new Recording();
        public Recording DefaultRecording { get { return this.defaultRecording; } }
    }
}
// Recording.idl
namespace Quickstart
{
    runtimeclass Recording
    {
        Recording(String artistName, String compositionName, Windows.Globalization.Calendar releaseDateTime);
        String ArtistName{ get; };
        String CompositionName{ get; };
        Windows.Globalization.Calendar ReleaseDateTime{ get; };
        String OneLineSummary{ get; };
    }
}

// RecordingViewModel.idl
import "Recording.idl";

namespace Quickstart
{
    runtimeclass RecordingViewModel
    {
        RecordingViewModel();
        Quickstart.Recording DefaultRecording{ get; };
    }
}

// Recording.h
// Add these fields:
...
#include <sstream>
...
private:
    std::wstring m_artistName;
    std::wstring m_compositionName;
    Windows::Globalization::Calendar m_releaseDateTime;
...

// Recording.cpp
// Implement like this:
...
Recording::Recording(hstring const& artistName, hstring const& compositionName, Windows::Globalization::Calendar const& releaseDateTime) :
    m_artistName{ artistName.c_str() },
    m_compositionName{ compositionName.c_str() },
    m_releaseDateTime{ releaseDateTime } {}

hstring Recording::ArtistName(){ return hstring{ m_artistName }; }
hstring Recording::CompositionName(){ return hstring{ m_compositionName }; }
Windows::Globalization::Calendar Recording::ReleaseDateTime(){ return m_releaseDateTime; }

hstring Recording::OneLineSummary()
{
    std::wstringstream wstringstream;
    wstringstream << m_compositionName.c_str();
    wstringstream << L" by " << m_artistName.c_str();
    wstringstream << L", released: " << m_releaseDateTime.MonthAsNumericString().c_str();
    wstringstream << L"/" << m_releaseDateTime.DayAsString().c_str();
    wstringstream << L"/" << m_releaseDateTime.YearAsString().c_str();
    return hstring{ wstringstream.str().c_str() };
}
...

// RecordingViewModel.h
// Add this field:
...
#include "Recording.h"
...
private:
    Quickstart::Recording m_defaultRecording{ nullptr };
...

// RecordingViewModel.cpp
// Implement like this:
...
Quickstart::Recording RecordingViewModel::DefaultRecording()
{
    Windows::Globalization::Calendar releaseDateTime;
    releaseDateTime.Year(1761);
    releaseDateTime.Month(1);
    releaseDateTime.Day(1);
    m_defaultRecording = winrt::make<Recording>(L"Wolfgang Amadeus Mozart", L"Andante in C for Piano", releaseDateTime);
    return m_defaultRecording;
}
...
// Recording.h
#include <sstream>
namespace Quickstart
{
    public ref class Recording sealed
    {
    private:
        Platform::String^ artistName;
        Platform::String^ compositionName;
        Windows::Globalization::Calendar^ releaseDateTime;
    public:
        Recording(Platform::String^ artistName, Platform::String^ compositionName,
            Windows::Globalization::Calendar^ releaseDateTime) :
            artistName{ artistName },
            compositionName{ compositionName },
            releaseDateTime{ releaseDateTime } {}
        property Platform::String^ ArtistName
        {
            Platform::String^ get() { return this->artistName; }
        }
        property Platform::String^ CompositionName
        {
            Platform::String^ get() { return this->compositionName; }
        }
        property Windows::Globalization::Calendar^ ReleaseDateTime
        {
            Windows::Globalization::Calendar^ get() { return this->releaseDateTime; }
        }
        property Platform::String^ OneLineSummary
        {
            Platform::String^ get()
            {
                std::wstringstream wstringstream;
                wstringstream << this->CompositionName->Data();
                wstringstream << L" by " << this->ArtistName->Data();
                wstringstream << L", released: " << this->ReleaseDateTime->MonthAsNumericString()->Data();
                wstringstream << L"/" << this->ReleaseDateTime->DayAsString()->Data();
                wstringstream << L"/" << this->ReleaseDateTime->YearAsString()->Data();
                return ref new Platform::String(wstringstream.str().c_str());
            }
        }
    };
    public ref class RecordingViewModel sealed
    {
    private:
        Recording ^ defaultRecording;
    public:
        RecordingViewModel()
        {
            Windows::Globalization::Calendar^ releaseDateTime = ref new Windows::Globalization::Calendar();
            releaseDateTime->Year = 1761;
            releaseDateTime->Month = 1;
            releaseDateTime->Day = 1;
            this->defaultRecording = ref new Recording{ L"Wolfgang Amadeus Mozart", L"Andante in C for Piano", releaseDateTime };
        }
        property Recording^ DefaultRecording
        {
            Recording^ get() { return this->defaultRecording; };
        }
    };
}

// Recording.cpp
#include "pch.h"
#include "Recording.h"

接下来,从表示标记页的类公开绑定源类。Next, expose the binding source class from the class that represents your page of markup. 我们通过将类型 RecordingViewModel 的属性添加到 MainPage 来执行此操作。We do that by adding a property of type RecordingViewModel to MainPage.

如果使用 C++/WinRT,则首先更新 MainPage.idlIf you're using C++/WinRT, then first update MainPage.idl. 生成项目以重新生成 MainPage.h.cpp,并将这些生成的文件中的更改合并到项目中的文件中。Build the project to regenerate MainPage.h and .cpp, and merge the changes in those generated files into the ones in your project.

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

// MainPage.h
// Add this property and this field:
...
#include "RecordingViewModel.h"
...
    Quickstart::RecordingViewModel ViewModel();

private:
    Quickstart::RecordingViewModel m_viewModel{ nullptr };
...

// MainPage.cpp
// Implement like this:
...
MainPage::MainPage()
{
    InitializeComponent();
    m_viewModel = winrt::make<RecordingViewModel>();
}
Quickstart::RecordingViewModel MainPage::ViewModel()
{
    return m_viewModel;
}
...
// MainPage.h
...
#include "Recording.h"

namespace Quickstart
{
    public ref class MainPage sealed
    {
    private:
        RecordingViewModel ^ viewModel;
    public:
        MainPage();

        property RecordingViewModel^ ViewModel
        {
            RecordingViewModel^ get() { return this->viewModel; };
        }
    };
}

// MainPage.cpp
...
MainPage::MainPage()
{
    InitializeComponent();
    this->viewModel = ref new RecordingViewModel();
}

最后一步是将 TextBlock 绑定到 ViewModel.DefaultRecording.OneLiner 属性。The last piece is to bind a TextBlock to the ViewModel.DefaultRecording.OneLiner property.

<Page x:Class="Quickstart.MainPage" ... >
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Text="{x:Bind ViewModel.DefaultRecording.OneLineSummary}"
    HorizontalAlignment="Center"
    VerticalAlignment="Center"/>
    </Grid>
</Page>

如果使用 C++/WinRT,则需要删除 MainPage::ClickHandler 函数以便生成项目 。If you're using C++/WinRT, then you'll need to remove the MainPage::ClickHandler function in order for the project to build.

下面是结果。Here's the result.

绑定文本块

绑定到项目集合Binding to a collection of items

一个常见情形是绑定到业务对象的集合。A common scenario is to bind to a collection of business objects. 在 C# 和 Visual Basic 中,通用 ObservableCollection<T> 类是数据绑定的一个很好的集合选择,因为它实现了 INotifyPropertyChangedINotifyCollectionChanged 接口。In C# and Visual Basic, the generic ObservableCollection<T> class is a good collection choice for data binding, because it implements the INotifyPropertyChanged and INotifyCollectionChanged interfaces. 当添加或删除项目或者列表本身的属性更改时,这些接口将向绑定提供更改通知。These interfaces provide change notification to bindings when items are added or removed or a property of the list itself changes. 如果你希望你的绑定控件使用集合中的对象属性更改进行更新,则业务对象也应该实现 INotifyPropertyChangedIf you want your bound controls to update with changes to properties of objects in the collection, the business object should also implement INotifyPropertyChanged. 有关详细信息,请参阅深入了解数据绑定For more info, see Data binding in depth.

如果使用 C++/WinRT,则可以在 XAML 项目控件;绑定到 C++/WinRT 集合中了解有关绑定到可观察集合的更多信息。If you're using C++/WinRT, then you can learn more about binding to an observable collection in XAML items controls; bind to a C++/WinRT collection. 如果你先阅读该主题,则会更清楚下面显示的 C++/WinRT 代码清单的意图。If you read that topic first, then the intent of the C++/WinRT code listing shown below will be clearer.

下面这个示例演示了将 ListView 绑定到 Recording 对象的集合。This next example binds a ListView to a collection of Recording objects. 让我们先将该集合添加到视图模型。Let's start by adding the collection to our view model. 只需将这些新成员添加到 RecordingViewModel 类。Just add these new members to the RecordingViewModel class.

public class RecordingViewModel
{
    ...
    private ObservableCollection<Recording> recordings = new ObservableCollection<Recording>();
    public ObservableCollection<Recording> Recordings{ get{ return this.recordings; } }
    public RecordingViewModel()
    {
        this.recordings.Add(new Recording(){ ArtistName = "Johann Sebastian Bach",
            CompositionName = "Mass in B minor", ReleaseDateTime = new DateTime(1748, 7, 8) });
        this.recordings.Add(new Recording(){ ArtistName = "Ludwig van Beethoven",
            CompositionName = "Third Symphony", ReleaseDateTime = new DateTime(1805, 2, 11) });
        this.recordings.Add(new Recording(){ ArtistName = "George Frideric Handel",
            CompositionName = "Serse", ReleaseDateTime = new DateTime(1737, 12, 3) });
    }
}
// RecordingViewModel.idl
// Add this property:
...
#include <winrt/Windows.Foundation.Collections.h>
...
Windows.Foundation.Collections.IVector<IInspectable> Recordings{ get; };
...

// RecordingViewModel.h
// Change the constructor declaration, and add this property and this field:
...
    RecordingViewModel();
    Windows::Foundation::Collections::IVector<Windows::Foundation::IInspectable> Recordings();

private:
    Windows::Foundation::Collections::IVector<Windows::Foundation::IInspectable> m_recordings;
...

// RecordingViewModel.cpp
// Update/add implementations like this:
...
RecordingViewModel::RecordingViewModel()
{
    std::vector<Windows::Foundation::IInspectable> recordings;

    Windows::Globalization::Calendar releaseDateTime;
    releaseDateTime.Month(7); releaseDateTime.Day(8); releaseDateTime.Year(1748);
    recordings.push_back(winrt::make<Recording>(L"Johann Sebastian Bach", L"Mass in B minor", releaseDateTime));

    releaseDateTime = Windows::Globalization::Calendar{};
    releaseDateTime.Month(11); releaseDateTime.Day(2); releaseDateTime.Year(1805);
    recordings.push_back(winrt::make<Recording>(L"Ludwig van Beethoven", L"Third Symphony", releaseDateTime));

    releaseDateTime = Windows::Globalization::Calendar{};
    releaseDateTime.Month(3); releaseDateTime.Day(12); releaseDateTime.Year(1737);
    recordings.push_back(winrt::make<Recording>(L"George Frideric Handel", L"Serse", releaseDateTime));

    m_recordings = winrt::single_threaded_observable_vector<Windows::Foundation::IInspectable>(std::move(recordings));
}

Windows::Foundation::Collections::IVector<Windows::Foundation::IInspectable> RecordingViewModel::Recordings() { return m_recordings; }
...
// Recording.h
...
public ref class RecordingViewModel sealed
{
private:
    ...
    Windows::Foundation::Collections::IVector<Recording^>^ recordings;
public:
    RecordingViewModel()
    {
        ...
        releaseDateTime = ref new Windows::Globalization::Calendar();
        releaseDateTime->Year = 1748;
        releaseDateTime->Month = 7;
        releaseDateTime->Day = 8;
        Recording^ recording = ref new Recording{ L"Johann Sebastian Bach", L"Mass in B minor", releaseDateTime };
        this->Recordings->Append(recording);
        releaseDateTime = ref new Windows::Globalization::Calendar();
        releaseDateTime->Year = 1805;
        releaseDateTime->Month = 2;
        releaseDateTime->Day = 11;
        recording = ref new Recording{ L"Ludwig van Beethoven", L"Third Symphony", releaseDateTime };
        this->Recordings->Append(recording);
        releaseDateTime = ref new Windows::Globalization::Calendar();
        releaseDateTime->Year = 1737;
        releaseDateTime->Month = 12;
        releaseDateTime->Day = 3;
        recording = ref new Recording{ L"George Frideric Handel", L"Serse", releaseDateTime };
        this->Recordings->Append(recording);
    }
    ...
    property Windows::Foundation::Collections::IVector<Recording^>^ Recordings
    {
        Windows::Foundation::Collections::IVector<Recording^>^ get()
        {
            if (this->recordings == nullptr)
            {
                this->recordings = ref new Platform::Collections::Vector<Recording^>();
            }
            return this->recordings;
        };
    }
};

然后,将 ListView 绑定到 ViewModel.Recordings 属性。And then bind a ListView to the ViewModel.Recordings property.

<Page x:Class="Quickstart.MainPage" ... >
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <ListView ItemsSource="{x:Bind ViewModel.Recordings}"
        HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Grid>
</Page>

我们尚未提供适用于 Recording 类的数据模板,因此 UI 框架的最佳做法是针对 ListView 中的每个项来调用 ToStringWe haven't yet provided a data template for the Recording class, so the best the UI framework can do is to call ToString for each item in the ListView. ToString 的默认实现是返回类型名称。The default implementation of ToString is to return the type name.

绑定列表视图 1

为了解决此问题,我们可以重写 ToString 以返回 OneLineSummary 的值,或者提供一个数据模板 。To remedy this, we can either override ToString to return the value of OneLineSummary, or we can provide a data template. 数据模板选项是更为常见的解决方案,并且是一个更灵活的解决方案。The data template option is a more usual solution, and a more flexible one. 使用内容控件的 ContentTemplate 属性或项目控件的 ItemTemplate 属性来指定数据模板。You specify a data template by using the ContentTemplate property of a content control or the ItemTemplate property of an items control. 下面是可用于设计适用于 Recording 的数据模板以及结果图示的两种方式。Here are two ways we could design a data template for Recording together with an illustration of the result.

<ListView ItemsSource="{x:Bind ViewModel.Recordings}"
HorizontalAlignment="Center" VerticalAlignment="Center">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="local:Recording">
            <TextBlock Text="{x:Bind OneLineSummary}"/>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

绑定列表视图 2

<ListView ItemsSource="{x:Bind ViewModel.Recordings}"
HorizontalAlignment="Center" VerticalAlignment="Center">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="local:Recording">
            <StackPanel Orientation="Horizontal" Margin="6">
                <SymbolIcon Symbol="Audio" Margin="0,0,12,0"/>
                <StackPanel>
                    <TextBlock Text="{x:Bind ArtistName}" FontWeight="Bold"/>
                    <TextBlock Text="{x:Bind CompositionName}"/>
                </StackPanel>
            </StackPanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

绑定列表视图 3

有关 XAML 语法的详细信息,请参阅使用 XAML 创建 UIFor more information about XAML syntax, see Create a UI with XAML. 有关控件布局的详细信息,请参阅使用 XAML 定义布局For more information about control layout, see Define layouts with XAML.

添加详细信息视图Adding a details view

你可以选择在 ListView 项目中显示 Recording 对象的所有详细信息。You can choose to display all the details of Recording objects in ListView items. 但这样做会占用大量空间。But that takes up a lot of space. 不过,你可以在该项目中仅显示足够多的数据来标识它,然后在用户做出选择时,你可以在 UI 的单个部分(即,详细信息视图)中显示选定项的所有详细信息。Instead, you can show just enough data in the item to identify it and then, when the user makes a selection, you can display all the details of the selected item in a separate piece of UI known as the details view. 这种排列也称为主视图/详细信息视图或列表/详细信息视图。This arrangement is also known as a master/details view, or a list/details view.

有两种方法可用来执行此操作。There are two ways to go about this. 你可以将详细信息视图绑定到 ListViewSelectedItem 属性。You can bind the details view to the SelectedItem property of the ListView. 或者,可以使用 CollectionViewSource,在此种情况下将 ListView 和详细信息视图同时绑定到 CollectionViewSource(这将为你处理当前选定的项目) 。Or you can use a CollectionViewSource, in which case you bind both the ListView and the details view to the CollectionViewSource (doing so takes care of the currently-selected item for you). 下面介绍这两种技术,它们都提供相同结果(图中所示)。Both techniques are shown below, and they both give the same results (shown in the illustration).

备注

到目前为止,本主题中我们仅使用了 {x:Bind} 标记扩展,而将在下面介绍的这两种技术要求更为灵活(但性能较低)的 {Binding} 标记扩展So far in this topic we've only used the {x:Bind} markup extension, but both of the techniques we'll show below require the more flexible (but less performant) {Binding} markup extension.

如果使用的是 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.

重要

如果使用 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.

首先介绍的是 SelectedItem 技术。First, here's the SelectedItem technique.

// No code changes necessary for C#.
// Recording.idl
// Add this attribute:
...
[Windows.UI.Xaml.Data.Bindable]
runtimeclass Recording
...
[Windows::UI::Xaml::Data::Bindable]
public ref class Recording sealed
{
    ...
};

只需对标记进行更改。The only other change necessary is to the markup.

<Page x:Class="Quickstart.MainPage" ... >
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <ListView x:Name="recordingsListView" ItemsSource="{x:Bind ViewModel.Recordings}">
                <ListView.ItemTemplate>
                    <DataTemplate x:DataType="local:Recording">
                        <StackPanel Orientation="Horizontal" Margin="6">
                            <SymbolIcon Symbol="Audio" Margin="0,0,12,0"/>
                            <StackPanel>
                                <TextBlock Text="{x:Bind CompositionName}"/>
                            </StackPanel>
                        </StackPanel>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
            <StackPanel DataContext="{Binding SelectedItem, ElementName=recordingsListView}"
            Margin="0,24,0,0">
                <TextBlock Text="{Binding ArtistName}"/>
                <TextBlock Text="{Binding CompositionName}"/>
                <TextBlock Text="{Binding ReleaseDateTime}"/>
            </StackPanel>
        </StackPanel>
    </Grid>
</Page>

对于 CollectionViewSource 技术,先添加 CollectionViewSource 作为页面资源。For the CollectionViewSource technique, first add a CollectionViewSource as a page resource.

<Page.Resources>
    <CollectionViewSource x:Name="RecordingsCollection" Source="{x:Bind ViewModel.Recordings}"/>
</Page.Resources>

然后,在 ListView(无需再对其进行命名)和详细信息视图上调节绑定,以使用 CollectionViewSourceAnd then adjust the bindings on the ListView (which no longer needs to be named) and on the details view to use the CollectionViewSource. 请注意,如果将详细信息视图直接绑定到 CollectionViewSource,即表示你希望绑定到绑定中的当前项目,其中在集合本身上无法找到路径。Note that by binding the details view directly to the CollectionViewSource, you're implying that you want to bind to the current item in bindings where the path cannot be found on the collection itself. 无需将 CurrentItem 属性指定为绑定路径,尽管由于不确定性你可以这样做)。There's no need to specify the CurrentItem property as the path for the binding, although you can do that if there's any ambiguity).

...
<ListView ItemsSource="{Binding Source={StaticResource RecordingsCollection}}">
...
<StackPanel DataContext="{Binding Source={StaticResource RecordingsCollection}}" ...>
...

而在每种情况下结果均相同。And here's the identical result in each case.

备注

如果使用的是 C++,那么 UI 不会如下图所示:ReleaseDateTime 属性的呈现方式不同 。If you're using C++, then your UI won't look exactly like the illustration below: the rendering of the ReleaseDateTime property is different. 有关此内容的详细讨论,请参阅以下部分。See the following section for more discussion of this.

绑定列表视图 4

设置数据值的格式或对其进行转换,以供显示Formatting or converting data values for display

以上呈现有一个问题。There is an issue with the rendering above. ReleaseDateTime 属性不只是日期,而是日期时间(如果使用的是 C++,则为日历) 。The ReleaseDateTime property is not just a date, it's a DateTime (if you're using C++, then it's a Calendar). 因此在 C# 中,它的显示精度比我们所需要的要大。So, in C#, it's being displayed with more precision than we need. 并且在 C++ 中,其呈现为类型名称。And in C++ it's being rendered as a type name. 解决方案是将字符串属性添加到返回 this.ReleaseDateTime.ToString("d") 等效项的 Recording 类 。One solution is to add a string property to the Recording class that returns the equivalent of this.ReleaseDateTime.ToString("d"). 将该属性命名为 ReleaseDate 可指示它将返回一个日期,而不是返回日期和时间 。Naming that property ReleaseDate would indicate that it returns a date, and not a date-and-time. 将其命名为 ReleaseDateAsString 可进一步指示它将返回一个字符串。Naming it ReleaseDateAsString would further indicate that it returns a string.

一个更灵活的解决方案是使用称为值转换器的工具。A more flexible solution is to use something known as a value converter. 下面是如何创作你自己的值转换器的示例。Here's an example of how to author your own value converter. 如果使用的是 C#,则将下面的代码添加到 Recording.cs 源代码文件中。If you're using C#, then add the code below to your Recording.cs source code file. 如果使用的是 C++/WinRT,则将新的 Midl 文件 (.idl) 项添加到项目中,如下面 C++/WinRT 代码示例清单中所示对其进行命名,生成项目以生成 StringFormatter.h.cpp,将这些文件添加到项目,然后将代码清单粘贴到其中 。If you're using C++/WinRT, then add a new Midl File (.idl) item to the project, named as shown in the C++/WinRT code example listing below, build the project to generate StringFormatter.h and .cpp, add those files to your project, and then paste the code listings into them. 此外,将 #include "StringFormatter.h" 添加到 MainPage.hAlso add #include "StringFormatter.h" to MainPage.h.

public class StringFormatter : Windows.UI.Xaml.Data.IValueConverter
{
    // This converts the value object to the string to display.
    // This will work with most simple types.
    public object Convert(object value, Type targetType,
        object parameter, string language)
    {
        // Retrieve the format string and use it to format the value.
        string formatString = parameter as string;
        if (!string.IsNullOrEmpty(formatString))
        {
            return string.Format(formatString, value);
        }

        // If the format string is null or empty, simply
        // call ToString() on the value.
        return value.ToString();
    }

    // No need to implement converting back on a one-way binding
    public object ConvertBack(object value, Type targetType,
        object parameter, string language)
    {
        throw new NotImplementedException();
    }
}
// StringFormatter.idl
namespace Quickstart
{
    runtimeclass StringFormatter : [default] Windows.UI.Xaml.Data.IValueConverter
    {
        StringFormatter();
    }
}

// StringFormatter.h
#pragma once

#include "StringFormatter.g.h"
#include <sstream>

namespace winrt::Quickstart::implementation
{
    struct StringFormatter : StringFormatterT<StringFormatter>
    {
        StringFormatter() = default;

        Windows::Foundation::IInspectable Convert(Windows::Foundation::IInspectable const& value, Windows::UI::Xaml::Interop::TypeName const& targetType, Windows::Foundation::IInspectable const& parameter, hstring const& language);
        Windows::Foundation::IInspectable ConvertBack(Windows::Foundation::IInspectable const& value, Windows::UI::Xaml::Interop::TypeName const& targetType, Windows::Foundation::IInspectable const& parameter, hstring const& language);
    };
}

namespace winrt::Quickstart::factory_implementation
{
    struct StringFormatter : StringFormatterT<StringFormatter, implementation::StringFormatter>
    {
    };
}

// StringFormatter.cpp
#include "pch.h"
#include "StringFormatter.h"
#include "StringFormatter.g.cpp"

namespace winrt::Quickstart::implementation
{
    Windows::Foundation::IInspectable StringFormatter::Convert(Windows::Foundation::IInspectable const& value, Windows::UI::Xaml::Interop::TypeName const& /* targetType */, Windows::Foundation::IInspectable const& /* parameter */, hstring const& /* language */)
    {
        // Retrieve the value as a Calendar.
        Windows::Globalization::Calendar valueAsCalendar{ value.as<Windows::Globalization::Calendar>() };

        std::wstringstream wstringstream;
        wstringstream << L"Released: ";
        wstringstream << valueAsCalendar.MonthAsNumericString().c_str();
        wstringstream << L"/" << valueAsCalendar.DayAsString().c_str();
        wstringstream << L"/" << valueAsCalendar.YearAsString().c_str();
        return winrt::box_value(hstring{ wstringstream.str().c_str() });
    }

    Windows::Foundation::IInspectable StringFormatter::ConvertBack(Windows::Foundation::IInspectable const& /* value */, Windows::UI::Xaml::Interop::TypeName const& /* targetType */, Windows::Foundation::IInspectable const& /* parameter */, hstring const& /* language */)
    {
        throw hresult_not_implemented();
    }
}
...
public ref class StringFormatter sealed : Windows::UI::Xaml::Data::IValueConverter
{
public:
    virtual Platform::Object^ Convert(Platform::Object^ value, TypeName targetType, Platform::Object^ parameter, Platform::String^ language)
    {
        // Retrieve the value as a Calendar.
        Windows::Globalization::Calendar^ valueAsCalendar = dynamic_cast<Windows::Globalization::Calendar^>(value);

        std::wstringstream wstringstream;
        wstringstream << L"Released: ";
        wstringstream << valueAsCalendar->MonthAsNumericString()->Data();
        wstringstream << L"/" << valueAsCalendar->DayAsString()->Data();
        wstringstream << L"/" << valueAsCalendar->YearAsString()->Data();
        return ref new Platform::String(wstringstream.str().c_str());
    }

    // No need to implement converting back on a one-way binding
    virtual Platform::Object^ ConvertBack(Platform::Object^ value, TypeName targetType, Platform::Object^ parameter, Platform::String^ language)
    {
        throw ref new Platform::NotImplementedException();
    }
};
...

备注

对于上面的 C++/WinRT 代码清单,在 StringFormatter.idl 中,我们会使用默认属性将 IValueConverter 声明为默认接口 。For the C++/WinRT code listing above, in StringFormatter.idl, we use the default attribute to declare IValueConverter as the default interface. 在列表中,StringFormatter 只有一个构造函数,并且没有方法,因此不会为其生成默认接口 。In the listing, StringFormatter has only a constructor, and no methods, so no default interface is generated for it. 如果不会将实例成员添加到 StringFormatter,则 default 属性是理想选择,因为不需要 QueryInterface 调用 IValueConverter 方法 。The default attribute is optimal if you won't be adding instance members to StringFormatter, because no QueryInterface will be required to call the IValueConverter methods. 或者,你可以提示要生成的默认 IStringFormatter 接口,并通过使用 default_interface 属性批注运行时类本身来实现此操作 。Alternatively, you can prompt a default IStringFormatter interface to be generated, and you do that by annotating the runtime class itself with the default_interface attribute. 如果将实例成员添加到 StringFormatter(调用频率比 IValueConverter 的方法更高),则该选项是最佳的,因为这样就不需要 QueryInterface 调用实例成员 。That option is optimal if you add instance members to StringFormatter that are called more often than the methods of IValueConverter are, because then no QueryInterface will be required to call the instance members.

现在,我们可以将 StringFormatter 的实例添加为页面资源,并可在显示 ReleaseDateTime 属性的 TextBlock 的绑定中使用 。Now we can add an instance of StringFormatter as a page resource and use it in the binding of the TextBlock that displays the ReleaseDateTime property.

<Page.Resources>
    <local:StringFormatter x:Key="StringFormatterValueConverter"/>
</Page.Resources>
...
<TextBlock Text="{Binding ReleaseDateTime,
    Converter={StaticResource StringFormatterValueConverter},
    ConverterParameter=Released: \{0:d\}}"/>
...

如上所示,为了格式设置灵活性,我们使用标记通过转换器参数将格式字符串传递到转换器。As you can see above, for formatting flexibility we use the markup to pass a format string into the converter by way of the converter parameter. 在本主题所示的代码示例中,只有 C# 值转换器使用该参数。In the code examples shown in this topic, only the C# value converter makes use of that parameter. 但你可以轻松地将 C++ 样式格式字符串作为转换器参数传递,并在值转换器中通过格式设置函数(如 wprintf 或 swprintf)使用它 。But you could easily pass a C++-style format string as the converter parameter, and use that in your value converter with a formatting function such as wprintf or swprintf.

下面是结果。Here's the result.

显示具有自定义格式的日期

备注

从 Windows 10 版本 1607 开始,XAML 框架提供内置 Boolean-to-Visibility 转换器。Starting in Windows 10, version 1607, the XAML framework provides a built-in Boolean-to-Visibility converter. 转换器将 true 映射到 Visibility.Visible 枚举值并将 false 映射到 Visibility.Collapsed,以便你可以将 Visibility 属性绑定到布尔值,无需创建转换器 。The converter maps true to the Visibility.Visible enumeration value and false to Visibility.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.

另请参阅See also