Общие сведения о привязке данных

В этом разделе показано, как привязать элемент управления (или другой элемент пользовательского интерфейса) к отдельному элементу или коллекции элементов в приложении универсальной платформы Windows (UWP). Кроме того, мы покажем, как управлять отрисовкой элементов, реализовывать представление сведений на основе выделения и преобразовывать данные для отображения. Дополнительные сведения см. в подробной статье о привязке данных.

Необходимые компоненты

В этом разделе предполагается, что вы знаете, как создать базовое приложение UWP. Инструкции по созданию первого приложения UWP см. в статье "Начало работы с приложениями Windows".

Создание проекта

Создайте проект пустого приложения (универсального приложения Windows). Назовите его "Краткое руководство".

Привязка к одному элементу

Каждая привязка состоит из целевого объекта привязки и источника привязки. Как правило, целевой объект — это свойство элемента управления или другого элемента пользовательского интерфейса, а источник — это свойство экземпляра класса (модель данных или модель представления). В этом примере показано, как привязать элемент управления к одному элементу. Целевой объект — это свойство TextBlock. Источник — это экземпляр простого класса с именем "Запись", который представляет звукозапись . Давайте рассмотрим класс первым.

Если вы используете C# или C++/CX, добавьте в проект новый класс и присвойте этому классу имя Recording.

Если вы используете C++/WinRT, добавьте в проект новые элементы MIDL-файла (.idl) с именем, как показано в приведенном ниже примере кода C++/WinRT. Замените содержимое этих новых файлов на код MIDL 3.0, показанный в описании, выполните сборку проекта, чтобы создать Recording.h, .cpp, RecordingViewModel.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"

Затем предоставьте исходный класс привязки из класса, представляющего страницу разметки. Для этого добавьте свойство type 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, чтобы выполнить сборку проекта.

Вот результат.

Binding a textblock

Привязка к коллекции элементов

Распространенный сценарий — привязка к коллекции бизнес-объектов. В C# и Visual Basic универсальный класс ObservableCollection T> является хорошим выбором коллекции для привязки данных, так как он реализует интерфейсы INotifyPropertyChanged и INotifyCollectionChanged<. Эти интерфейсы предоставляют уведомление об изменении привязок при добавлении или удалении элементов или свойстве самого списка. Если вы хотите, чтобы связанные элементы управления обновлялись с изменениями свойств объектов в коллекции, бизнес-объект также должен реализовать 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>

Мы еще не предоставили шаблон данных для класса Записи, поэтому лучше всего использовать платформу пользовательского интерфейса для вызова ToString для каждого элемента в ListView. Реализация ToString по умолчанию — возвращать имя типа.

Binding a list view 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>

Binding a list view 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>

Binding a list view 3

Дополнительные сведения о синтаксисе XAML см. в статье "Создание пользовательского интерфейса с помощью XAML". Дополнительные сведения о макете элемента управления см. в разделе "Определение макетов с помощью XAML".

Добавление представления сведений

Вы можете отобразить все сведения о объектах записи в элементах ListView. Но это занимает много места. Вместо этого можно отобразить достаточно данных в элементе, чтобы определить его, а затем, когда пользователь выбирает элемент, можно отобразить все сведения выбранного элемента в отдельном элементе пользовательского интерфейса, известном как представление сведений. Это расположение также называется представлением master/details или представлением списка и сведений.

Это можно сделать двумя способами. Представление сведений можно привязать к свойству SelectedItem объекта ListView. Вы можете также использовать класс CollectionViewSource: привязать как класс ListView, так и представление сведений к классу CollectionViewSource (который обеспечит обработку выбранного в настоящее время элемента). Оба способа описаны ниже. Они дают аналогичные результаты, показанные на картинке.

Если вы используете расширения компонентов C++/WinRT или Visual C++ (C++/CX), то для использования расширения разметки {Binding} необходимо добавить атрибут BindableAttribute в любой класс среды выполнения, к которому требуется привязаться. Чтобы использовать {x:Bind}, этот атрибут не требуется.

Важно!

Если вы используете C++/WinRT, то атрибут BindableAttribute доступен, если вы установили Windows SDK версии 10.0.17763.0 (Windows 10, версии 1809) или более поздней. Без этого атрибута необходимо реализовать интерфейсы ICustomPropertyProvider и ICustomProperty, чтобы иметь возможность использовать расширение разметки {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++, пользовательский интерфейс будет выглядеть не так, как показано ниже: визуализация свойства ReleaseDateTime отличается. Дополнительные сведения об этом приведены в следующем разделе.

Binding a list view 4

Форматирование или преобразование значений данных для отображения

С описанной выше визуализацией связана одна проблема. Свойство ReleaseDateTime — это не просто дата, а DateTime (если вы используете C++, то это Calendar). Таким образом, в C# оно отображается с большей точностью, чем требуется. И в C++ это свойство визуализируется как имя типа. Возможное решение проблемы — добавить строковое свойство в класс Recording, который возвращает эквивалент this.ReleaseDateTime.ToString("d"). Присвоение этому свойству имени ReleaseDate указывает, что оно возвращает дату, а не дату и время. Именование его ReleaseDateAsString будет также указывать на то, что он возвращает строку.

Более гибкое решение — использовать что-то известное как преобразователь значений. Ниже приведен пример создания собственного преобразователя значений. Если вы используете C#, добавьте приведенный ниже код в файл исходного кода Recording.cs. Если вы используете C++/WinRT, добавьте в проект новый элемент Файл MIDL (.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 является оптимальным, если не нужно добавлять члены экземпляра в StringFormatter, так как для вызова методов IValueConverter не потребуется QueryInterface. Кроме того, можно запросить создание интерфейса по умолчанию IStringFormatter, добавив заметку самого класса среды выполнения с помощью атрибута default_interface. Этот параметр является оптимальным при добавлении в StringFormatter членов экземпляра, которые вызываются чаще, чем методы IValueConverter, потому что для вызова членов экземпляра не потребуется QueryInterface.

Теперь можно добавить экземпляр StringFormatter как ресурс страницы и использовать его в привязке TextBlock, отображающего свойство ReleaseDateTime.

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

Как видно выше, чтобы повысить гибкость форматирования, мы используем разметку для передачи строки формата в преобразователь с помощью параметра преобразователя. В примерах кода, приведенных в этом разделе, только преобразователь значений C# использует этот параметр. Но можно легко передать строку формата в стиле C++ в качестве параметра преобразователя и использовать ее в преобразователе значений с функцией форматирования, например wprintf или swprintf.

Вот результат.

displaying a date with custom formatting

Примечание.

Начиная с Windows 10 версии 1607, платформа XAML предоставляет встроенный преобразователь Boolean в Visibility. Этот преобразователь сопоставляет значение true со значением перечисления Visibility.Visible, а значение false — со значением Visibility.Collapsed, поэтому можно осуществить привязку свойства Visibility к Boolean без создания преобразователя. Для использования встроенного преобразователя минимальная версия целевого пакета SDK вашего приложения должна быть 14393 или более поздней. Вы не сможете использовать преобразователь, если ваше приложение предназначено для более ранних версий Windows 10. Дополнительные сведения о целевых версиях см. в статье Адаптивный к версии код.

См. также