Gambaran umum pengikatan data

Topik ini menunjukkan kepada Anda cara mengikat kontrol (atau elemen UI lainnya) ke satu item atau mengikat kontrol item ke kumpulan item di aplikasi Platform Windows Universal (UWP). Selain itu, kami menunjukkan cara mengontrol penyajian item, menerapkan tampilan detail berdasarkan pilihan, dan mengonversi data untuk ditampilkan. Untuk informasi selengkapnya, lihat Pengikatan data secara mendalam.

Prasyarat

Topik ini mengasumsikan bahwa Anda tahu cara membuat aplikasi UWP dasar. Untuk petunjuk tentang membuat aplikasi UWP pertama Anda, lihat Mulai menggunakan aplikasi Windows.

Membuat proyek

Buat proyek Aplikasi Kosong (Windows Universal) baru. Beri nama "Mulai Cepat".

Mengikat ke satu item

Setiap pengikatan terdiri dari target pengikatan dan sumber pengikatan. Biasanya, target adalah properti kontrol atau elemen UI lainnya, dan sumbernya adalah properti instans kelas (model data, atau model tampilan). Contoh ini memperlihatkan cara mengikat kontrol ke satu item. Target adalah properti Teks dari TextBlock. Sumbernya adalah instans kelas sederhana bernama Rekaman yang mewakili rekaman audio. Mari kita lihat kelas terlebih dahulu.

Jika Anda menggunakan C# atau C++/CX, tambahkan kelas baru ke proyek Anda, dan beri nama kelas Rekaman.

Jika Anda menggunakan C++/WinRT, tambahkan item Midl File (.idl) baru ke proyek, bernama seperti yang ditunjukkan dalam contoh kode C++/WinRT yang tercantum di bawah ini. Ganti konten file baru tersebut dengan kode MIDL 3.0 yang ditunjukkan dalam daftar, buat proyek untuk menghasilkan Recording.h dan .cpp dan RecordingViewModel.h dan , .cpplalu tambahkan kode ke file yang dihasilkan agar sesuai dengan daftar. Untuk informasi selengkapnya tentang file yang dihasilkan dan cara menyalinnya ke proyek Anda, lihat kontrol XAML; mengikat properti 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"

Selanjutnya, ekspos kelas sumber pengikatan dari kelas yang mewakili halaman markup Anda. Kami melakukannya dengan menambahkan properti jenis RecordingViewModel ke MainPage.

Jika Anda menggunakan C++/WinRT, maka pertama-tama perbarui MainPage.idl. Buat proyek untuk meregenerasi MainPage.h dan .cpp, dan gabungkan perubahan dalam file yang dihasilkan ke dalam yang ada di proyek Anda.

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();
}

Bagian terakhir adalah mengikat TextBlock ke properti 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>

Jika Anda menggunakan C++/WinRT, maka Anda harus menghapus fungsi MainPage::ClickHandler agar proyek dapat dibangun.

Berikut hasilnya.

Binding a textblock

Pengikatan ke kumpulan item

Skenario umum adalah mengikat kumpulan objek bisnis. Di C# dan Visual Basic, kelas T ObservableCollection>< generik adalah pilihan koleksi yang baik untuk pengikatan data, karena mengimplementasikan antarmuka INotifyPropertyChanged dan INotifyCollectionChanged. Antarmuka ini menyediakan pemberitahuan perubahan pada pengikatan saat item ditambahkan atau dihapus atau properti daftar itu sendiri berubah. Jika Anda ingin kontrol terikat Anda diperbarui dengan perubahan pada properti objek dalam koleksi, objek bisnis juga harus menerapkan INotifyPropertyChanged. Untuk informasi selengkapnya, lihat Pengikatan data secara mendalam.

Jika Anda menggunakan C++/WinRT, maka Anda dapat mempelajari selengkapnya tentang pengikatan ke koleksi yang dapat diamati dalam kontrol item XAML; mengikat koleksi C++/WinRT. Jika Anda membaca topik tersebut terlebih dahulu, maka niat daftar kode C++/WinRT yang ditunjukkan di bawah ini akan lebih jelas.

Contoh berikutnya ini mengikat ListView ke kumpulan Recording objek. Mari kita mulai dengan menambahkan koleksi ke model tampilan kita. Cukup tambahkan anggota baru ini ke kelas 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;
        };
    }
};

Lalu ikat ListView ke properti ViewModel.Recordings.

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

Kami belum menyediakan templat data untuk kelas Perekaman, jadi yang terbaik yang dapat dilakukan kerangka kerja UI adalah memanggil ToString untuk setiap item di ListView. Implementasi default ToString adalah mengembalikan nama jenis.

Binding a list view 1

Untuk memperbaiki hal ini, kita dapat mengambil alih ToString untuk mengembalikan nilai OneLineSummary, atau kita dapat menyediakan templat data. Opsi templat data adalah solusi yang lebih biasa, dan yang lebih fleksibel. Anda menentukan templat data dengan menggunakan properti ContentTemplate kontrol konten atau properti ItemTemplate kontrol item. Berikut adalah dua cara kita dapat merancang templat data untuk Perekaman bersama dengan ilustrasi hasilnya.

<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

Untuk informasi selengkapnya tentang sintaks XAML, lihat Membuat UI dengan XAML. Untuk informasi selengkapnya tentang tata letak kontrol, lihat Menentukan tata letak dengan XAML.

Menambahkan tampilan detail

Anda dapat memilih untuk menampilkan semua detail objek Rekaman di item ListView. Tapi itu membutuhkan banyak ruang. Sebagai gantinya, Anda dapat menampilkan cukup data dalam item untuk mengidentifikasinya dan kemudian, ketika pengguna membuat pilihan, Anda dapat menampilkan semua detail item yang dipilih dalam bagian terpisah dari UI yang dikenal sebagai tampilan detail. Pengaturan ini juga dikenal sebagai tampilan master/detail, atau tampilan daftar/detail.

Ada dua cara untuk membahas hal ini. Anda dapat mengikat tampilan detail ke properti SelectedItem dari ListView. Atau Anda dapat menggunakan CollectionViewSource, dalam hal ini Anda mengikat ListView dan tampilan detail ke CollectionViewSource (melakukannya mengurus item yang saat ini dipilih untuk Anda). Kedua teknik ditunjukkan di bawah ini, dan keduanya memberikan hasil yang sama (ditunjukkan dalam ilustrasi).

Catatan

Sejauh ini dalam topik ini kami hanya menggunakan ekstensi markup {x:Bind}, tetapi kedua teknik yang akan kami tampilkan di bawah ini memerlukan ekstensi markup {Binding} yang lebih fleksibel (tetapi kurang berkinerja).

Jika Anda menggunakan ekstensi komponen C++/WinRT atau Visual C++ (C++/CX) maka, untuk menggunakan ekstensi markup {Binding} , Anda harus menambahkan atribut BindableAttribute ke kelas runtime apa pun yang ingin Anda ikat. Untuk menggunakan {x:Bind}, Anda tidak memerlukan atribut tersebut.

Penting

Jika Anda menggunakan C++/WinRT, atribut BindableAttribute tersedia jika Anda telah menginstal Windows SDK versi 10.0.17763.0 (Windows 10, versi 1809), atau yang lebih baru. Tanpa atribut tersebut , Anda harus mengimplementasikan antarmuka ICustomPropertyProvider dan ICustomProperty agar dapat menggunakan ekstensi markup {Binding} .

Pertama, berikut adalah teknik 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
{
    ...
};

Satu-satunya perubahan lain yang diperlukan adalah 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>

Untuk teknik CollectionViewSource, pertama-tama tambahkan CollectionViewSource sebagai sumber daya halaman.

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

Lalu sesuaikan pengikatan pada ListView (yang tidak perlu dinamai lagi) dan pada tampilan detail untuk menggunakan CollectionViewSource. Perhatikan bahwa dengan mengikat tampilan detail langsung ke CollectionViewSource, Anda menyiratkan bahwa Anda ingin mengikat item saat ini dalam pengikatan di mana jalur tidak dapat ditemukan pada koleksi itu sendiri. Tidak perlu menentukan properti CurrentItem sebagai jalur untuk pengikatan, meskipun Anda dapat melakukannya jika ada ambiguitas).

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

Dan inilah hasil yang identik dalam setiap kasus.

Catatan

Jika Anda menggunakan C++, UI Anda tidak akan terlihat persis seperti ilustrasi di bawah ini: penyajian properti ReleaseDateTime berbeda. Lihat bagian berikut untuk diskusi selengkapnya tentang hal ini.

Binding a list view 4

Memformat atau mengonversi nilai data untuk ditampilkan

Ada masalah dengan penyajian di atas. Properti ReleaseDateTime bukan hanya tanggal, ini adalah DateTime (jika Anda menggunakan C++, maka itu adalah Kalender). Jadi, di C#, itu ditampilkan dengan lebih presisi daripada yang kita butuhkan. Dan di C++ itu sedang dirender sebagai nama jenis. Salah satu solusinya adalah menambahkan properti string ke kelas Rekaman yang mengembalikan nilai yang setara dengan this.ReleaseDateTime.ToString("d"). Penamaan properti ReleaseDate akan menunjukkan bahwa properti mengembalikan tanggal, dan bukan tanggal dan waktu. Menamainya ReleaseDateAsString selanjutnya akan menunjukkan bahwa ia mengembalikan string.

Solusi yang lebih fleksibel adalah menggunakan sesuatu yang dikenal sebagai pengonversi nilai. Berikut adalah contoh cara menulis pengonversi nilai Anda sendiri. Jika Anda menggunakan C#, tambahkan kode di bawah ini ke file kode sumber Anda Recording.cs . Jika Anda menggunakan C++/WinRT, tambahkan item Midl File (.idl) baru ke proyek, bernama seperti yang ditunjukkan dalam daftar contoh kode C++/WinRT di bawah ini, buat proyek untuk menghasilkan StringFormatter.h dan .cpp, tambahkan file tersebut ke proyek Anda, lalu tempelkan daftar kode ke dalamnya. #include "StringFormatter.h" Tambahkan juga ke 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();
    }
};
...

Catatan

Untuk daftar kode C++/WinRT di atas, di StringFormatter.idl, kami menggunakan atribut default untuk mendeklarasikan IValueConverter sebagai antarmuka default. Dalam daftar, StringFormatter hanya memiliki konstruktor, dan tidak ada metode, sehingga tidak ada antarmuka default yang dihasilkan untuk itu. Atribut default ini optimal jika Anda tidak akan menambahkan anggota instans ke StringFormatter, karena tidak ada QueryInterface yang diperlukan untuk memanggil metode IValueConverter . Atau, Anda dapat meminta antarmuka IStringFormatter default untuk dihasilkan, dan Anda melakukannya dengan menganotasi kelas runtime itu sendiri dengan atribut default_interface. Opsi itu optimal jika Anda menambahkan anggota instans ke StringFormatter yang dipanggil lebih sering daripada metode IValueConverter, karena tidak ada QueryInterface yang diperlukan untuk memanggil anggota instans.

Sekarang kita dapat menambahkan instans StringFormatter sebagai sumber daya halaman dan menggunakannya dalam pengikatan TextBlock yang menampilkan properti ReleaseDateTime .

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

Seperti yang Anda lihat di atas, untuk fleksibilitas pemformatan, kami menggunakan markup untuk meneruskan string format ke pengonversi melalui parameter pengonversi. Dalam contoh kode yang ditampilkan dalam topik ini, hanya pengonversi nilai C# yang menggunakan parameter tersebut. Tetapi Anda dapat dengan mudah meneruskan string format C++-style sebagai parameter pengonversi, dan menggunakannya di pengonversi nilai Anda dengan fungsi pemformatan seperti wprintf atau swprintf.

Berikut hasilnya.

displaying a date with custom formatting

Catatan

Mulai windows 10, versi 1607, kerangka kerja XAML menyediakan konverter Boolean-to-Visibility bawaan. Pengonversi memetakan true ke nilai enumerasi Visibility.Visible dan false ke Visibility.Collapsed sehingga Anda dapat mengikat properti Visibilitas ke Boolean tanpa membuat pengonversi. Untuk menggunakan konverter bawaan, versi SDK target minimum aplikasi Anda harus 14393 atau yang lebih baru. Anda tidak dapat menggunakannya saat aplikasi menargetkan versi Windows 10 yang lebih lama. Untuk informasi selengkapnya tentang versi target, lihat Kode adaptif versi.

Baca juga