Поделиться через


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

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

Предпосылки

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

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

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

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

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

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

Если вы используете C++/WinRT, добавьте новые элементы Midl File (.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"

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

Связывание представления списка 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. Дополнительные сведения о макете элемента управления см. в статье Определение макетов с помощью XAML.

Добавление детализированного вида

Вы можете отобразить все сведения о объектах записи в элементах List View. Но это занимает много места. Вместо этого можно отобразить достаточно данных в элементе, чтобы определить его, а затем, когда пользователь выбирает элемент, можно отобразить все сведения выбранного элемента в отдельном элементе пользовательского интерфейса, известном как представление сведений. Такое расположение также называется представлением "основное и детали" или представлением "список и сведения".

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

Замечание

До сих пор в этом разделе мы использовали только расширение разметки {x:Bind} , но оба метода, которые мы рассмотрим ниже, требуют более гибкого (но менее производительного) расширения разметки {Binding} .

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

Это важно

Если вы используете C++/WinRT, то атрибут bindableAttribute доступен, если установлен пакет SDK для Windows версии 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 отличается. Дополнительные сведения см. в следующем разделе.

Привязка списка представлений 4

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

Возникла проблема с отрисовкой выше. Свойство ReleaseDateTime не является просто датой, это DateTime (если вы используете C++, то это Календарь). Таким образом, в C#он отображается с большей точностью, чем нам нужно. И в C++ он отображается как имя типа. Одним из решений является добавление строкового свойства в класс записи, возвращающий эквивалент 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 является оптимальным, если вы не собираетесь добавлять элементы экземпляра в 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.

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

отображение даты с пользовательским форматированием

Замечание

Начиная с Windows 10, версии 1607, XAML предоставляет встроенный логический преобразователь в Видимость. Преобразователь сопоставляет значение перечисления true с значением перечисления . Visible и false с видимости.Свернутым, чтобы можно было привязать свойство Видимости к логическому элементу без создания преобразователя. Чтобы использовать встроенный преобразователь, минимальная целевая версия пакета SDK приложения должна быть 14393 или более поздней. Его нельзя использовать, если приложение предназначено для более ранних версий Windows 10. Дополнительные сведения о целевых версиях см. в адаптивном коде версий .

См. также

  • привязка данных