데이터 바인딩 개요Data binding overview

이 항목에서는 UWP(유니버설 Windows 플랫폼) 앱에서 컨트롤(또는 다른 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 앱을 만드는 방법을 알고 있다고 가정합니다.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. 이름을 "Quickstart"로 지정합니다.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++/cx를 사용 C# 하는 경우 프로젝트에 새 클래스를 추가 하 고 클래스 기록의 이름을로 합니다.If you're using C# or C++/CX, then add a new class to your project, and name the class Recording.

C++/Winrt를 사용 하는 경우 아래 목록에 표시 된 C++것 처럼 새 Midl 파일 (.idl) 항목을 프로젝트에 추가 합니다.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 형식의 속성을 MainPage에 바인딩하면 됩니다.We do that by adding a property of type RecordingViewModel to MainPage.

C++/Winrt를 사용 하는 경우 먼저 MainPage.idl를 업데이트 합니다.If 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();
}

마지막 부분은 TextBlockViewModel.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:: 클릭 처리기 함수를 제거 해야 합니다.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.

Textblock 바인딩

항목 컬렉션에 바인딩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. 또한 바인딩된 컨트롤을 컬렉션에 있는 개체의 속성 변경 사항으로 업데이트하려면 비즈니스 개체에서 INotifyPropertyChanged를 구현해야 합니다.If 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.

다음 예제에서는 ListViewRecording 개체의 컬렉션에 바인딩합니다.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;
        };
    }
};

그런 다음 ListViewViewModel.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의 각 항목에 대해 ToString을 호출하는 것입니다.We 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.

목록 보기 바인딩

이를 해결 하려면 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>

목록 보기 바인딩

<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>

목록 보기 바인딩

XAML 구문에 대한 자세한 내용은 참조 XAML을 사용하여 UI 만들기를 참조하세요.For 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 및 Details 뷰를 모두 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.

/Winrt 또는 Visual C++ C++ component extensions (C++/cx)를 사용 하는 경우 {Binding} 태그 확장을 사용 하려면 바인딩하려는 모든 런타임 클래스에 대해 windows.ui.xaml.data.bindableattribute 특성을 추가 해야 합니다.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를 사용 하는 경우 Windows SDK 버전 10.0.17763.0 (Windows 10, 버전 1809) 이상을 설치한 경우에는 대해 windows.ui.xaml.data.bindableattribute 특성을 사용할 수 있습니다.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. 이 특성이 없으면 {Binding} 태그 확장을 사용할 수 있도록 ICustomPropertyProviderICustomProperty 인터페이스를 구현 해야 합니다.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>

그런 다음 CollectionViewSource를 사용하도록 ListView(더 이상 이름을 지정할 필요 없음)와 세부 정보 보기에 대한 바인딩을 조정합니다.And 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.

목록 보기 바인딩

표시할 데이터 값 필터링 또는 변환Formatting or converting data values for display

위의 렌더링과 관련 된 문제가 있습니다.There is an issue with the rendering above. Releasedatetime 속성은 날짜만 아니라 datetime 입니다 .를 사용 C++하는 경우에는 달력입니다.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")에 해당 하는 값을 반환 하는 기록 클래스에 문자열 속성을 추가 하는 것입니다.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. /Winrt를 사용 C++하는 경우 프로젝트에 새 Midl 파일 (.idl) 항목을 추가 하 고 아래 목록에 C++나와 있는 것 처럼 이름이 지정 된 프로젝트를 빌드하여 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. 또한 MainPage.h#include "StringFormatter.h"를 추가 합니다.Also 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.convert 를 기본 인터페이스로 선언 합니다.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. Ivalueconverter.convert 메서드를 호출 하는 데 QueryInterface가 필요 하지 않기 때문에 인스턴스 멤버를 stringformatter에 추가 하지 않는 경우 default 특성이 최적입니다.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. 이 옵션은 인스턴스 멤버를 호출 하는 데 QueryInterface가 필요 하지 않으므로 ivalueconverter.convert 의 메서드보다 더 자주 호출 되는 stringformatter 에 인스턴스 멤버를 추가 하는 경우에 가장 적합 합니다.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++변환기 매개 변수로 쉽게 전달 하 고 wprintf 또는 swprintf와 같은 서식 지정 함수를 사용 하 여 값 변환기에서 사용할 수 있습니다.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 를 표시 유형으로 매핑합니다 . 표시 되 는 열거형 값과 false를 표시 하려면 false로 설정 합니다. 이를 축소 하면 변환기를 만들지 않고도 표시 유형 속성을 부울에 바인딩할 수 있습니다.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. 기본 제공 변환기를 사용하려면 앱의 최소 대상 SDK 버전이 14393 이상이어야 합니다.To use the built in converter, your app's minimum target SDK version must be 14393 or later. 앱이 이전 버전의 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