数据绑定概述

本主题演示如何将控件(或其他 UI 元素)绑定到单个项,或将项控件绑定到通用 Windows 平台(UWP)应用中的项集合。 此外,我们还演示如何控制项的呈现、基于所选内容实现详细信息视图,以及转换要显示的数据。 如需了解更多详细信息,请参阅 数据绑定深入

先决条件

本主题假定你知道如何创建基本的 UWP 应用。 有关创建第一个 UWP 应用的说明,请参阅 Windows 应用入门。

创建项目

创建新的 空白应用程序(Windows 通用) 项目。 将其命名为“快速入门”。

绑定到单个项目

每个绑定都由绑定目标和绑定源组成。 通常,目标是控件或其他 UI 元素的属性,源是类实例(数据模型或视图模型)的属性。 此示例演示如何将控件绑定到单个项。 目标是 TextBlockText 属性。 源是一个名为 录音 的简单类的实例,代表音频录制。 让我们先看一下类。

如果使用 C# 或 C++/CX,请将新类添加到项目中,并将类命名为 录制

如果使用 C++/WinRT,则将新的 Midl 文件(.idl) 项添加到项目中,如下面的 C++/WinRT 代码示例所示。 将这些新文件的内容替换为列表中显示的 MIDL 3.0 代码,生成项目以生成 Recording.h.cppRecordingViewModel.h.cpp,然后将代码添加到生成的文件中以匹配列表。 有关这些生成的文件以及如何将它们复制到项目中的详细信息,请参阅 XAML 控件;绑定到 C++/WinRT 属性

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"

接下来,从表示您页面代码的类中公开绑定源类。 为此,我们将 RecordingViewModel 类型的属性添加到 MainPage

如果使用 C++/WinRT,则首先更新 MainPage.idl。 构建项目以重新生成 MainPage.h.cpp,并将那些生成文件中的更改合并到项目中的相应文件中。

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.OneLineSummary 属性。

<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 函数才能构建项目。

下面是结果。

绑定文本块

绑定到项目集合

常见方案是绑定到业务对象的集合。 在 C# 和 Visual Basic 中,泛型 ObservableCollection<T> 类是数据绑定的良好集合选择,因为它实现了 INotifyPropertyChangedINotifyCollectionChanged 接口。 当添加或删除项或列表本身的属性发生更改时,这些接口会向绑定提供更改通知。 如果希望使绑定控件能够更新集合中对象的属性更改,则业务对象还应实现 INotifyPropertyChanged。 有关更多信息,请参阅 数据绑定深度分析

如果您使用 C++/WinRT,那么您可以详细了解如何在 XAML 项控件中绑定到可观察集合,并绑定到 C++/WinRT 集合。 如果首先阅读该主题,则下面所示的 C++/WinRT 代码列表的意图将更加清晰。

下一个示例将 ListView 绑定到 Recording 对象的集合。 首先,将集合添加到视图模型。 只需将这些新成员添加到 RecordingViewModel 类。

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 属性。

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

我们尚未为 录制 类提供数据模板,因此 UI 框架所能做到的最佳操作是为 ListView 中的每个项调用 ToStringToString 的默认实现是返回类型名称。

绑定列表视图 1

若要解决此问题,我们可以重写 ToString 以返回 OneLineSummary的值,或者我们可以提供一个数据模板。 数据模板选项是更常见的解决方案,也是更灵活的解决方案。 通过使用内容控件的 ContentTemplate 属性或项控件的 ItemTemplate 属性来指定数据模板。 下面是两种方法,我们可以设计用于 录制 的数据模板以及结果图示。

<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创建 UI。 有关控件布局的详细信息,请参阅 使用 XAML定义布局。

添加详情视图

可以选择在 ListView 项中显示 记录 对象的所有详细信息。 但这占用了很多空间。 相反,你可以在项目中显示足够的数据来标识它,然后在用户做出选择时,可以在称为详细信息视图的单独 UI 中显示所选项的所有详细信息。 这种排列方式也称为主视图/详细信息视图或列表/详细信息视图。

有两种方法可以解决此问题。 可以将详细信息视图绑定到 ListViewSelectedItem 属性。 或者,可以使用 CollectionViewSource,在这种情况下,将 ListView 和详细信息视图绑定到 CollectionViewSource(这样做会为你处理当前选定的项)。 下面显示了这两种技术,它们都给出了相同的结果(如图所示)。

注释

到目前为止,在本主题中,我们只使用了 {x:Bind} 标记扩展,但下面显示的两种技术都需要更灵活(但性能较低的){Binding} 标记扩展

如果使用 C++/WinRT 或 Visual C++ 组件扩展(C++/CX),则若要使用 {Binding} 标记扩展,则需要将 BindableAttribute 属性添加到要绑定到的任何运行时类。 若要使用 {x:Bind},则不需要该属性。

重要

如果使用 C++/WinRT,则已安装 Windows SDK 版本 10.0.17763.0(Windows 10 版本 1809)或更高版本时,BindableAttribute 属性可用。 如果没有该属性,则需要实现 ICustomPropertyProviderICustomProperty 接口才能使用 {Binding} 标记扩展。

首先,下面是 SelectedItem 技术。

// 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
{
    ...
};

唯一还需要更改的是标记。

<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 添加为页面资源。

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

然后调整 ListView(不再需要命名)和详细信息视图上的绑定,以便使用 CollectionViewSource。 请注意,将详细信息视图直接绑定到 CollectionViewSource意味着,当在集合中找不到该路径时,你希望绑定到绑定中的当前项。 无需将 CurrentItem 属性指定为绑定的路径,不过,如果存在任何歧义,则可以执行此操作。

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

下面是每个案例的相同结果。

注释

如果使用 C++,则 UI 看起来与下图不完全相同:ReleaseDateTime 属性的呈现不同。 有关此内容的详细信息,请参阅以下部分。

绑定列表视图 4

设置或转换要显示的数据值的格式

上述渲染存在问题。 ReleaseDateTime 属性不仅仅是日期,它是 DateTime(如果使用C++,则它是 日历)。 因此,在 C# 中,它显示的精度比我们需要的要高。 在 C++ 中,它被视为类型名称。 一种解决方案是为 Recording 类添加一个字符串属性,以返回与 this.ReleaseDateTime.ToString("d")等效的值。 ReleaseDate 命名该属性将指示它返回日期而不是日期和时间。 将其命名 ReleaseDateAsString 将进一步指示它返回字符串。

更灵活的解决方案是使用称为值转换器的东西。 下面是有关如何创作自己的值转换器的示例。 如果使用 C#,请将下面的代码添加到 Recording.cs 源代码文件中。 如果使用 C++/WinRT,则将新的 Midl File (.idl) 项添加到项目中,如下面的 C++/WinRT 代码示例所示,生成项目以生成 StringFormatter.h.cpp,将这些文件添加到项目中,然后将代码列表粘贴到项目中。 此外,将 #include "StringFormatter.h" 添加到 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();
    }
}
// pch.h
...
#include <winrt/Windows.Globalization.h>

// 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 作为默认接口。 在列表中,StringFormatter 只有构造函数,并且没有方法,因此不会为其生成默认接口。 如果您不打算向 default添加实例成员,那么 属性是最优选择,因为调用 IValueConverter 方法不需要 QueryInterface。 或者,可以提示生成默认 IStringFormatter 接口,并通过使用 default_interface 属性注释运行时类本身来执行此操作。 如果将实例成员添加到 StringFormatter,并且实例成员的调用频率比 IValueConverter 方法的调用频率更高,那么此选项是最佳选择,因为这样不需要通过 QueryInterface 来调用实例成员。

现在,我们可以将 StringFormatter 实例添加为页面资源,并在显示 ReleaseDateTime 属性的 TextBlock 绑定中使用它。

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

如上所示,为了灵活地设置格式,我们使用标记通过转换器参数将格式字符串传递到转换器。 在本主题所示的代码示例中,只有 C# 值转换器才使用该参数。 但是,你可以轻松地将C++样式的格式字符串作为转换器参数传递,并在值转换器中使用格式设置函数,例如 wprintfswprintf

下面是结果。

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

注释

从 Windows 10 版本 1607 开始,XAML 框架提供内置的布尔到可见性转换器。 转换器将 true 映射到 Visibility.Visible 枚举值,将 false 映射到 Visibility.Collapsed,以便您无需创建转换器即可将 Visibility 属性绑定到布尔值。 若要使用内置转换器,应用的最低目标 SDK 版本必须为 14393 或更高版本。 当你的应用面向 Windows 10 的早期版本时,你不能使用它。 有关目标版本的详细信息,请参阅 版本自适应代码

另请参阅