資料繫結概觀Data binding overview

本主題說明如何在通用 Windows 平台 (UWP) 應用程式中將控制項 (或其他 UI 元素) 繫結到單一項目,或將項目控制項繫結到項目集合。This topic shows you how to bind a control (or other UI element) to a single item or bind an items control to a collection of items in a Universal Windows Platform (UWP) app. 此外,我們還會說明如何控制項目的呈現、根據選擇來實作詳細資料檢視、以及轉換資料以供顯示。In addition, we show how to control the rendering of items, implement a details view based on a selection, and convert data for display. 如需詳細資訊,請參閱深入了解資料繫結For more detailed info, see Data binding in depth.

必要條件Prerequisites

這個主題假設您知道如何建立基本的 UWP app。This topic assumes that you know how to create a basic UWP app. 如需建立第一個 UWP 應用程式的指示,請參閱 Windows 應用程式入門For instructions on creating your first UWP app, see Get started with Windows apps.

建立專案Create the project

建立新的 [空白應用程式 (Windows 通用)] 專案。Create a new Blank Application (Windows Universal) project. 將它命名為「快速入門」。Name it "Quickstart".

繫結到單一項目Binding to a single item

每個繫結是由繫結目標和繫結來源所組成。Every binding consists of a binding target and a binding source. 通常,目標是控制項或其他 UI 元素的屬性,來源是類別執行個體 (資料模型或檢視模型) 的屬性。Typically, the target is a property of a control or other UI element, and the source is a property of a class instance (a data model, or a view model). 這個範例示範如何將控制項繫結到單一項目。This example shows how to bind a control to a single item. 目標是 TextBlockText 屬性。The target is the Text property of a TextBlock. 來源是一個簡單類別 Recording 的執行個體,代表音訊錄製。The source is an instance of a simple class named Recording that represents an audio recording. 讓我們先看一下這個類別。Let's look at the class first.

如果您使用 C# 或 C++/CX,請在您的專案中加入一個新類別,並將類別命名為 RecordingIf you're using C# or C++/CX, then add a new class to your project, and name the class Recording.

如果您使用 C++/WinRT,請將新的 Midl 檔案 (.idl) 項目加入專案中,並如下列 C++/WinRT 程式碼範例所示命名。If you're using C++/WinRT, then add new Midl File (.idl) items to the project, named as shown in the C++/WinRT code example listing below. 以清單中顯示的 MIDL 3.0 程式碼取代那些新檔案的內容、建置要產生 Recording.h.cppRecordingViewModel.h.cpp 的專案,然後將程式碼加入產生的檔案,以符合清單。Replace the contents of those new files with the MIDL 3.0 code shown in the listing, build the project to generate Recording.h and .cpp and RecordingViewModel.h and .cpp, and then add code to the generated files to match the listing. 如需有關那些產生的檔案以及如何將其複製到專案中的詳細資訊,請參閱 XAML 控制項;繫結至一個 C++/WinRT 屬性For more info about those generated files and how to copy them into your project, see XAML controls; bind to a C++/WinRT property.

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

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

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

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

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

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

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

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

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

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

接著,從代表標記頁面的類別中公開繫結來源類別。Next, expose the binding source class from the class that represents your page of markup. 作法是將 RecordingViewModel 類型的屬性加入到 MainPageWe do that by adding a property of type RecordingViewModel to MainPage.

如果您是使用 C++/WinRT,請先更新 MainPage.idlIf you're using C++/WinRT, then first update MainPage.idl. 建立專案以重新產生 MainPage.h.cpp,並將這些產生檔案中的變更合併至專案中的檔案。Build the project to regenerate MainPage.h and .cpp, and merge the changes in those generated files into the ones in your project.

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

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

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

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

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

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

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

最後一步是將 TextBlock 繫結到 ViewModel.DefaultRecording.OneLiner 屬性。The last piece is to bind a TextBlock to the ViewModel.DefaultRecording.OneLiner property.

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

如果您使用 C++/WinRT,則必須移除 MainPage::ClickHandler 函式,才能建立專案。If you're using C++/WinRT, then you'll need to remove the MainPage::ClickHandler function in order for the project to build.

結果如下。Here's the result.

繫結文字方塊

繫結到項目集合Binding to a collection of items

常見的一個情況是繫結到商業物件的集合。A common scenario is to bind to a collection of business objects. 在 C# 和 Visual Basic 中,ObservableCollection<T> 泛型類別是適用於資料繫結的集合選擇,因為它實作 INotifyPropertyChangedINotifyCollectionChanged 介面。In C# and Visual Basic, the generic ObservableCollection<T> class is a good collection choice for data binding, because it implements the INotifyPropertyChanged and INotifyCollectionChanged interfaces. 當加入或移除項目,或清單本身的屬性變更時,這些介面提供變更通知給繫結。These interfaces provide change notification to bindings when items are added or removed or a property of the list itself changes. 如果您希望繫結控制項隨著集合中物件屬性的變更一起更新,那麼商業物件也應該實作 INotifyPropertyChangedIf you want your bound controls to update with changes to properties of objects in the collection, the business object should also implement INotifyPropertyChanged. 如需詳細資訊,請參閱深入了解資料繫結For more info, see Data binding in depth.

如果您使用 C++/WinRT,您可以在 XAML 項目控制項;繫結至一個 C++/WinRT 集合深入瞭解如何繫結至可觀察的集合。If you're using C++/WinRT, then you can learn more about binding to an observable collection in XAML items controls; bind to a C++/WinRT collection. 如果您先閱讀該主題,下面顯示的C++/WinRT 程式碼清單的目的將會更清楚。If you read that topic first, then the intent of the C++/WinRT code listing shown below will be clearer.

下一個範例將 ListView 繫結到 Recording 物件的集合。This next example binds a ListView to a collection of Recording objects. 首先讓我們將集合加入到檢視模型。Let's start by adding the collection to our view model. 將這些新成員加入到 RecordingViewModel 類別。Just add these new members to the RecordingViewModel class.

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

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

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

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

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

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

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

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

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

然後將 ListView 繫結到 ViewModel.Recordings 屬性。And then bind a ListView to the ViewModel.Recordings property.

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

我們還未提供資料範本給 Recording 類別,因此 UI 架構所能做的只是針對 ListView 中的每個項目,呼叫 ToStringWe haven't yet provided a data template for the Recording class, so the best the UI framework can do is to call ToString for each item in the ListView. ToString 的預設實作是傳回類型名稱。The default implementation of ToString is to return the type name.

繫結清單檢視 1

若要解決這個問題,我們可以覆寫 ToString 來傳回 OneLineSummary 的值,不然就是提供資料範本。To remedy this, we can either override ToString to return the value of OneLineSummary, or we can provide a data template. 資料範本選項是比較常見的解決辦法,而且更有彈性。The data template option is a more usual solution, and a more flexible one. 您可以使用內容控制項的 ContentTemplate 屬性或項目控制項的 ItemTemplate 屬性來指定資料範本。You specify a data template by using the ContentTemplate property of a content control or the ItemTemplate property of an items control. 以下是為 Recording 設計資料範本的兩種方式,同時提供結果的插圖。Here are two ways we could design a data template for Recording together with an illustration of the result.

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

繫結清單檢視 2

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

繫結清單檢視 3

如需 XAML 語法的詳細資訊,請參閱使用 XAML 建立 UIFor more information about XAML syntax, see Create a UI with XAML. 如需控制項配置的詳細資訊,請參閱使用 XAML 定義配置For more information about control layout, see Define layouts with XAML.

新增詳細資料檢視Adding a details view

您可以選擇在 ListView 項目中顯示 Recording 物件的所有詳細資料。You can choose to display all the details of Recording objects in ListView items. 但這會佔用大量空間。But that takes up a lot of space. 相反地,您可以在項目中顯示剛好足夠識別它的資料,然後當使用者做出選擇時,您可以在另一個稱為詳細資料檢視的 UI 中,顯示選定項目的所有詳細資料。Instead, you can show just enough data in the item to identify it and then, when the user makes a selection, you can display all the details of the selected item in a separate piece of UI known as the details view. 這種安排也稱為主要/詳細資料檢視,或清單/詳細資料檢視。This arrangement is also known as a master/details view, or a list/details view.

有兩種作法。There are two ways to go about this. 您可以將詳細資料檢視繫結到 ListViewSelectedItem 屬性。You can bind the details view to the SelectedItem property of the ListView. 或者,您可以使用 CollectionViewSource,將 ListView 和詳細資料檢視都繫結到 CollectionViewSource - 這麼做可為您處理目前選取的項目。Or you can use a CollectionViewSource, in which case you bind both the ListView and the details view to the CollectionViewSource (doing so takes care of the currently-selected item for you). 以下顯示這兩種技巧,且兩者的結果相同,如圖所示。Both techniques are shown below, and they both give the same results (shown in the illustration).

注意

本主題到目前為止,我們只使用 {x:Bind} 標記延伸,但以下我們將說明的兩種技巧需要更有彈性 (但效能較低) 的 {Binding} 標記延伸So far in this topic we've only used the {x:Bind} markup extension, but both of the techniques we'll show below require the more flexible (but less performant) {Binding} markup extension.

如果您使用 C++/WinRT 或 Visual C++ 元件延伸模組 (C++/CX),則需要將 BindableAttribute 屬性新增至您希望繫結至之任何執行階段類別,以使用 {Binding} 標記延伸。If you're using C++/WinRT or Visual C++ component extensions (C++/CX) then, to use the {Binding} markup extension, you'll need to add the BindableAttribute attribute to any runtime class that you want to bind to. 若要使用 {x:Bind},您不需要該屬性。To use {x:Bind}, you don't need that attribute.

重要

如果您使用的是 C++/WinRT,則可以使用 BindableAttribute 屬性,前提是您安裝了 Windows SDK 版本 10.0.17763.0 (Windows 10 版本 1809) 或更新版本。If you're using C++/WinRT, then the BindableAttribute attribute is available if you've installed the Windows SDK version 10.0.17763.0 (Windows 10, version 1809), or later. 如果沒有該屬性,則需要實作 ICustomPropertyProviderICustomProperty 介面,才能使用 {Binding} 標記延伸。Without that attribute, you'll need to implement the ICustomPropertyProvider and ICustomProperty interfaces in order to be able to use the {Binding} markup extension.

首先是 SelectedItem 技術。First, here's the SelectedItem technique.

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

其他只需變更標記。The only other change necessary is to the markup.

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

使用 CollectionViewSource 技術時,請先新增 CollectionViewSource 做為頁面資源。For the CollectionViewSource technique, first add a CollectionViewSource as a page resource.

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

然後,將 ListView (不再需要命名) 和詳細資料檢視上的繫結調整為使用 CollectionViewSourceAnd then adjust the bindings on the ListView (which no longer needs to be named) and on the details view to use the CollectionViewSource. 請注意,將詳細資料檢視直接繫結到 CollectionViewSource 時,就意味著您想要繫結至在集合本身找不到路徑之繫結中的目前項目。Note that by binding the details view directly to the CollectionViewSource, you're implying that you want to bind to the current item in bindings where the path cannot be found on the collection itself. 不需要指定 CurrentItem 屬性做為繫結的路徑 (但如果情況模稜兩可,您可以這樣做)。There's no need to specify the CurrentItem property as the path for the binding, although you can do that if there's any ambiguity).

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

以下是各種情況的相同結果。And here's the identical result in each case.

注意

如果您使用的是 C++,則您的 UI 看起來不會與下圖完全相同:ReleaseDateTime 屬性的轉譯結果不同。If you're using C++, then your UI won't look exactly like the illustration below: the rendering of the ReleaseDateTime property is different. 詳細討論請參閱下一節。See the following section for more discussion of this.

繫結清單檢視 4

格式化或轉換資料值以供顯示Formatting or converting data values for display

以上轉譯的結果有一個問題。There is an issue with the rendering above. ReleaseDateTime 屬性不只是日期,而是 DateTime (如果使用 C++,則是 Calendar)。The ReleaseDateTime property is not just a date, it's a DateTime (if you're using C++, then it's a Calendar). 因此,在 C# 中,其顯示會比我們所需的精確度更高。So, in C#, it's being displayed with more precision than we need. 而在 C++ 中,則會轉譯成類型名稱。And in C++ it's being rendered as a type name. 一種解決辦法是將字串屬性加入會傳回 this.ReleaseDateTime.ToString("d") 對等項目的 Recording 類別。One solution is to add a string property to the Recording class that returns the equivalent of this.ReleaseDateTime.ToString("d"). 將該屬性命名為 ReleaseDate 來表示只傳回日期,而不是日期和時間。Naming that property ReleaseDate would indicate that it returns a date, and not a date-and-time. 命名為 ReleaseDateAsString 進一步表示傳回字串。Naming it ReleaseDateAsString would further indicate that it returns a string.

更有彈性的解決辦法是使用所謂的「值轉換器」。A more flexible solution is to use something known as a value converter. 以下是如何撰寫您自己的值轉換器的範例。Here's an example of how to author your own value converter. 如果您使用的是 C#,請將下列程式碼加入您的 Recording.cs 原始程式碼檔案。If you're using C#, then add the code below to your Recording.cs source code file. 如果您使用 C++/WinRT,請將新的 Midl 檔案 (.idl) 項目加入專案中,如下列 C++/WinRT 程式碼範例所示命名,建立專案以產生 StringFormatter.h.cpp,將這些檔案加入專案,然後將程式碼清單貼入檔案中。If you're using C++/WinRT, then add a new Midl File (.idl) item to the project, named as shown in the C++/WinRT code example listing below, build the project to generate StringFormatter.h and .cpp, add those files to your project, and then paste the code listings into them. 也請將 #include "StringFormatter.h" 加入 MainPage.hAlso add #include "StringFormatter.h" to MainPage.h.

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

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

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

// StringFormatter.h
#pragma once

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

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

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

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

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

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

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

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

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

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

注意

針對上述 C++/WinRT 程式碼清單,在 StringFormatter.idl 中,我們使用預設屬性IValueConverter 宣告為預設介面。For the C++/WinRT code listing above, in StringFormatter.idl, we use the default attribute to declare IValueConverter as the default interface. 在清單中,StringFormatter 只有一個建構函式,沒有任何方法,因此不會為其產生任何預設介面。In the listing, StringFormatter has only a constructor, and no methods, so no default interface is generated for it. 如果您不會將實例成員加入 StringFormatterdefault 屬性是最佳選擇,因為不需要 QueryInterface 就能呼叫 IValueConverter 方法。The default attribute is optimal if you won't be adding instance members to StringFormatter, because no QueryInterface will be required to call the IValueConverter methods. 或者,您也可以提示要產生預設 IStringFormatter 介面,並使用 default_interface 屬性來標註執行階段類別本身來完成此動作。Alternatively, you can prompt a default IStringFormatter interface to be generated, and you do that by annotating the runtime class itself with the default_interface attribute. 如果您將實例成員加入 StringFormatter (被呼叫的頻率高於 IValueConverter 的方法),該選項就是最佳做法,因為這樣不需要 QueryInterface 就能呼叫實例成員。That option is optimal if you add instance members to StringFormatter that are called more often than the methods of IValueConverter are, because then no QueryInterface will be required to call the instance members.

現在我們可以加入 StringFormatter 的實例做為頁面資源,並在顯示 ReleaseDateTime 屬性的 TextBlock 的繫結中使用它。Now we can add an instance of StringFormatter as a page resource and use it in the binding of the TextBlock that displays the ReleaseDateTime property.

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

如您所見,為了讓格式化有彈性,我們會使用標記,透過轉換器參數將格式字串傳遞至轉換器。As you can see above, for formatting flexibility we use the markup to pass a format string into the converter by way of the converter parameter. 在本主題所顯示的程式碼範例中,只有 C# 值轉換器會使用該參數。In the code examples shown in this topic, only the C# value converter makes use of that parameter. 但是,您可以輕鬆傳遞 C++ 樣式的格式字串做為轉換器參數,然後在您的值轉換器中搭配格式化函數 (例如 wprintfswprintf) 來使用它。But you could easily pass a C++-style format string as the converter parameter, and use that in your value converter with a formatting function such as wprintf or swprintf.

結果如下。Here's the result.

顯示自訂格式的日期

注意

從 Windows 10 版本 1607 開始,XAML 架構針對可見度轉換器提供了內建布林值。Starting in Windows 10, version 1607, the XAML framework provides a built-in Boolean-to-Visibility converter. 轉換器會將 true 對應至 Visible.Visible 列舉值,並將 false 對應至 Visible.Collapsed,這樣您就可以將 Visibility 屬性繫結至布林值而不用建立轉換器。The converter maps true to the Visibility.Visible enumeration value and false to Visibility.Collapsed so you can bind a Visibility property to a Boolean without creating a converter. 若要使用內建轉換器,您 App 的最低目標 SDK 版本必須為 14393 或更新版本。To use the built in converter, your app's minimum target SDK version must be 14393 or later. 當您的 App 是以舊版 Windows 10 為目標時,您就無法使用它。You can't use it when your app targets earlier versions of Windows 10. 如需目標版本的相關詳細資訊,請參閱版本調適型程式碼For more info about target versions, see Version-adaptive code.

另請參閱See also