WPF의 데이터 바인딩 개요

WPF(Windows Presentation Foundation)의 데이터 바인딩은 앱이 데이터를 제공하고 상호 작용할 수 있는 간단하고 일관된 방법을 제공합니다. 다양한 데이터 소스에서 .NET 개체 및 XML의 형식으로 데이터에 요소를 바인딩할 수 있습니다. 모든 ContentControl(예: Button) 및 모든 ItemsControl(예: ListBoxListView)에는 단일 데이터 항목이나 데이터 항목 수집의 스타일을 유연하게 지정할 수 있는 기본 제공 기능이 있습니다. 데이터를 기반으로 정렬, 필터 및 그룹 보기를 생성할 수 있습니다.

WPF의 데이터 바인딩 기능은 광범위한 속성을 통한 기본적인 데이터 바인딩 지원, 데이터의 유연한 UI 표현 및 UI와 비즈니스 논리의 분명한 분리와 같은 기존 모델에 비해 여러 가지 이점이 있습니다.

이 문서에서는 먼저 WPF 데이터 바인딩의 기초 개념을 살펴본 다음, Binding 클래스 및 기타 데이터 바인딩 기능을 사용하는 방법을 다룹니다.

데이터 바인딩이란?

데이터 바인딩은 앱 UI와 해당 UI가 표시하는 데이터를 연결하는 프로세스입니다. 바인딩 설정이 올바르고 데이터가 적절한 알림을 제공하는 경우 데이터 값이 변경될 때 데이터에 바인딩된 요소에 변경 사항이 자동으로 반영됩니다. 또한 요소에서 데이터의 외부 표현이 변경되면 내부 데이터가 자동으로 업데이트되어 변경 내용이 반영될 수 있습니다. 예를 들어 사용자가 TextBox 요소의 값을 편집하면 내부 데이터 값이 자동으로 업데이트되어 해당 변경 내용이 반영됩니다.

데이터 바인딩은 일반적으로 서버 또는 로컬 구성 데이터를 양식이나 기타 UI 컨트롤에 배치하는 데 사용됩니다. WPF에서 이 개념은 광범위한 속성을 다양한 데이터 소스에 바인딩하는 기능을 포함하도록 확장됩니다. WPF에서 요소의 종속성 속성은 .NET 개체(ADO.NET 개체 또는 웹 서비스와 웹 속성에 연결된 개체 포함) 및 XML 데이터에 바인딩될 수 있습니다.

데이터 바인딩의 예는 경매 항목 목록을 나타내는 Data Binding Demo(데이터 바인딩 데모)에서 다음 앱 UI를 살펴봅니다.

Data binding sample screenshot

앱은 데이터 바인딩의 다음 기능을 보여 줍니다.

  • ListBox의 콘텐츠는 AuctionItem 개체 컬렉션에 바인딩됩니다. AuctionItem 개체에는 Description, StartPrice, StartDate, Category, SpecialFeatures 등의 속성이 있습니다.

  • ListBox에 표시된 데이터(AuctionItem 개체)는 각 항목에 관한 설명과 현재 위치가 표시되도록 템플릿으로 만들어집니다. 템플릿은 DataTemplate을 사용하여 만듭니다. 또한 각 항목의 모양은 표시되는 AuctionItemSpecialFeatures 값에 따라 달라집니다. AuctionItemSpecialFeatures 값이 Color이면 항목에는 파란색 테두리가 포함됩니다. Highlight 값이면 항목에는 주황색 테두리와 별모양이 포함됩니다. 데이터 템플릿 섹션에서는 데이터 템플릿을 살펴봅니다.

  • 사용자는 제공된 CheckBoxes를 사용하여 데이터를 그룹화, 필터링 또는 정렬할 수 있습니다. 위 이미지에는 Group by categorySort by category and dateCheckBoxes가 선택되어 있습니다. 데이터가 제품 범주에 따라 그룹화되고 범주 이름이 사전순으로 표시되어 있음을 알 수 있습니다. 그림에서 확인하기는 어렵지만 항목은 각 범주 내에서 시작 날짜별로 정렬됩니다. 정렬에는 ‘컬렉션 뷰’가 사용됩니다. 컬렉션에 바인딩 섹션에서는 컬렉션 뷰를 살펴봅니다.

  • 사용자가 항목을 선택하면 ContentControl은 선택한 항목의 세부 정보를 표시합니다. 이 환경을 ‘마스터-세부 시나리오’라고 합니다. 마스터-세부 시나리오 섹션에서는 이 바인딩 유형을 살펴봅니다.

  • StartDate 속성 형식은 밀리초까지 시간이 포함된 날짜를 반환하는 DateTime입니다. 이 앱에는 더 짧은 날짜 문자열이 표시되도록 사용자 지정 변환기가 사용되었습니다. 데이터 변환 섹션에서는 변환기를 살펴봅니다.

사용자가 Add Product 단추를 클릭하면 다음 양식이 표시됩니다.

Add Product Listing page

사용자는 양식에 있는 필드를 편집하고, 간단한 미리 보기 및 자세한 미리 보기 창을 사용하여 제품 목록을 미리 보고, Submit을 선택하여 새 제품 목록을 추가할 수 있습니다. 기존 그룹화, 필터링 및 정렬 설정이 새 항목에 적용됩니다. 이 경우 위 그림에 입력된 항목은 Computer 범주에서 두 번째 항목으로 표시됩니다.

Start DateTextBox에 제공된 유효성 검사 논리는 이 이미지에 표시되지 않습니다. 사용자가 잘못된 날짜(형식이 잘못되거나 지난 날짜)를 입력하면 ToolTip이 표시되고 TextBox 옆에 빨간색 느낌표가 나타납니다. 데이터 유효성 검사 섹션에서는 유효성 검사 논리를 만드는 방법을 설명합니다.

위에서 간략히 설명한 데이터 바인딩의 다른 기능을 살펴보기 전에 먼저 WPF 데이터 바인딩 이해에 필수적인 기본 개념을 설명합니다.

기본 데이터 바인딩 개념

바인딩할 요소 및 데이터 소스의 특성에 관계없이 각 바인딩은 항상 다음 그림에 나와 있는 모델을 따릅니다.

Diagram that shows the basic data binding model.

그림과 같이 데이터 바인딩은 기본적으로 바인딩 대상과 바인딩 소스를 연결합니다. 그림에서는 다음 기본 WPF 데이터 바인딩 개념을 보여 줍니다.

  • 일반적으로 각 바인딩에는 다음 네 가지 구성 요소가 있습니다.

    • 바인딩 대상 개체.
    • 대상 속성.
    • 바인딩 소스.
    • 사용할 바인딩 소스에 있는 값 경로.

    예를 들어 TextBox의 콘텐츠를 Employee.Name 속성에 바인딩하려면 대상 개체는 TextBox이고, 대상 속성은 Text 속성이고, 사용할 값은 Name이고, 소스 개체는 Employee 개체입니다.

  • 대상 속성은 종속성 속성이어야 합니다. 대부분 UIElement 속성은 종속성 속성이고 읽기 전용 속성을 제외한 대부분 종속성 속성은 기본적으로 데이터 바인딩을 지원합니다. DependencyObject에서 파생된 형식만 종속성 속성을 정의할 수 있으며 모든 UIElement 형식은 DependencyObject에서 파생됩니다.

  • 그림에는 표시되지 않았지만 바인딩 소스 개체는 사용자 지정 .NET 개체로 제한되지 않습니다. WPF 데이터 바인딩은 .NET 개체 및 XML 형식의 데이터를 지원합니다. 예를 들어 바인딩 소스는 UIElement, 목록 개체, ADO.NET 개체, Web Services 개체 또는 XML 데이터가 포함된 XmlNode일 수 있습니다. 자세한 내용은 바인딩 소스 개요를 참조하세요.

바인딩을 설정하고 있다면 바인딩 소스’에’ 바인딩 대상을 바인딩하고 있다는 것을 기억하세요. 예를 들어 데이터 바인딩을 사용하여 몇몇 기본 XML 데이터를 ListBox에 표시하고 있다면 XML 데이터에 ListBox를 바인딩하는 것입니다.

바인딩을 설정하려면 Binding 개체를 사용합니다. 이 문서의 나머지 부분에서는 Binding 개체와 연결된 다양한 개념과 개체의 일부 속성 및 사용법을 살펴봅니다.

데이터 흐름 방향

이전 그림에서 화살표가 가리키는 대로 바인딩의 데이터 흐름은 바인딩 대상에서 바인딩 소스로 이동하거나(예: 사용자가 TextBox를 편집하면 소스 값이 변경됨), 바인딩 소스가 적절한 알림을 제공할 경우 바인딩 소스에서 바인딩 대상으로 이동합니다(예: 바인딩 소스가 변경되면 TextBox 콘텐츠가 업데이트됨).

앱에서 사용자가 데이터를 변경하고 다시 소스 개체에 전파할 수 있도록 해야 할 수 있습니다. 또는 사용자가 소스 데이터를 업데이트할 수 없도록 해야 할 수 있습니다. Binding.Mode를 설정하여 데이터 흐름을 제어할 수 있습니다.

이 그림은 다양한 유형의 데이터 흐름을 보여 줍니다.

Data binding data flow

  • OneWay 바인딩을 사용하면 소스 속성이 변경될 경우 대상 속성이 자동으로 업데이트되지만 대상 속성이 변경될 경우 변경 내용이 다시 소스 속성으로 전파되지 않습니다. 이 바인딩 유형은 바인드되는 컨트롤이 암시적으로 읽기 전용인 경우에 적합합니다. 예를 들어 주식 시세표시기와 같은 소스에 바인딩할 수 있거나 대상 속성에는 테이블의 데이터 바인딩된 배경색과 같이 변경을 위해 제공된 컨트롤 인터페이스가 없을 수 있습니다. 대상 속성의 변경 내용을 모니터링할 필요가 없는 경우 OneWay 바인딩 모드를 사용하면 TwoWay 바인딩 모드의 오버헤드가 방지됩니다.

  • TwoWay 바인딩으로 인해 소스 속성 또는 대상 속성이 변경되어 다른 항목이 자동으로 업데이트됩니다. 이 바인딩 유형은 편집 가능한 형식이나 기타 완전한 대화형 UI 시나리오에 적합합니다. 대부분의 속성은 기본적으로 OneWay 바인딩으로 설정되지만, 일부 종속성 속성(일반적으로 TextBox.TextCheckBox.IsChecked와 같이 사용자가 편집할 수 있는 컨트롤의 속성)은 기본적으로 TwoWay 바인딩으로 설정됩니다. 종속성 속성이 기본적으로 단방향 또는 양방향으로 바인딩되는지를 프로그래밍 방식으로 결정하려면 DependencyProperty.GetMetadata를 사용하여 속성 메타데이터를 가져온 후 FrameworkPropertyMetadata.BindsTwoWayByDefault 속성의 부울 값을 확인합니다.

  • OneWayToSourceOneWay 바인딩의 반대로, 대상 속성이 변경되면 소스 속성을 업데이트합니다. UI에서 소스 값을 다시 평가하면 되는 경우가 한 가지 예제 시나리오입니다.

  • 그림에는 나타나지 않지만 OneTime 바인딩을 사용하면 소스 속성이 대상 속성을 초기화하지만 이후 변경 내용을 전파하지 않습니다. 데이터 컨텍스트가 변경되거나 데이터 컨텍스트의 개체가 변경될 경우 변경 내용이 대상 속성에 반영되지 ‘않습니다’. 이 바인딩 유형은 현재 상태의 스냅샷이 적절하거나 데이터가 실제로 정적인 경우에 적합합니다. 또한 이 바인딩 유형은 원본 속성의 일부 값으로 대상 속성을 초기화하려고 하며 데이터 컨텍스트가 사전에 알려지지 않은 경우에도 유용합니다. 이 모드는 기본적으로 소스 값이 변경되지 않은 경우에 더 나은 성능을 제공하는 OneWay 바인딩의 더 간단한 형식입니다.

소스 변경 내용을 검색하려면(OneWayTwoWay 바인딩에 적용 가능) 소스에서 INotifyPropertyChanged와 같은 적절한 속성 변경 알림 메커니즘을 구현해야 합니다. 방법: 속성 변경 알림 구현에서 INotifyPropertyChanged 구현의 예제를 참조하세요.

Binding.Mode 속성은 바인딩 모드에 관한 자세한 설명과 바인딩 방향을 지정하는 방법의 예제를 제공합니다.

소스 업데이트를 트리거하는 항목

TwoWay 또는 OneWayToSource인 바인딩은 대상 속성의 변경 내용을 수신하고 다시 소스에 전파합니다. 이를 소스 업데이트라고 합니다. 예를 들어 TextBox의 텍스트를 편집하여 기본 소스 값을 변경할 수 있습니다.

하지만 텍스트를 편집하는 동안이나 텍스트 편집을 마치고 컨트롤이 포커스를 잃은 후 소스 값이 업데이트될까요? Binding.UpdateSourceTrigger 속성은 소스 업데이트를 트리거하는 항목을 결정합니다. 다음 그림에서 오른쪽 화살표의 점은 Binding.UpdateSourceTrigger 속성의 역할을 보여 줍니다.

Diagram that shows the role of the UpdateSourceTrigger property.

UpdateSourceTrigger 값이 UpdateSourceTrigger.PropertyChanged이면 대상 속성이 변경되는 즉시 TwoWay 또는 OneWayToSource 바인딩의 오른쪽 화살표가 가리키는 값이 업데이트됩니다. 하지만 UpdateSourceTrigger 값이 LostFocus이면 대상 속성이 포커스를 잃을 경우에만 이 값이 새 값으로 업데이트됩니다.

Mode 속성과 비슷하게 종속성 속성에 따라 기본 UpdateSourceTrigger 값이 다릅니다. 대부분의 종속성 속성에 대한 기본값이 PropertyChanged인 반면 TextBox.Text 속성의 기본값은 LostFocus입니다. PropertyChanged는 대개 대상 속성이 변경될 때마다 소스 업데이트가 수행됨을 의미합니다. 즉각적인 변경 내용은 CheckBoxes 및 기타 간단한 컨트롤에 적합합니다. 하지만 텍스트 필드의 경우 키 입력이 있을 때마다 업데이트하면 성능이 저하될 수 있고 새 값으로 커밋하기 전에 사용자가 백스페이스 키를 누르고 입력 오류를 수정할 수 있는 기회가 사라집니다.

종속성 속성의 기본값을 찾는 방법에 관한 자세한 내용은 UpdateSourceTrigger 속성 페이지를 참조하세요.

다음 표에는 TextBox를 예제로 사용하는 각 UpdateSourceTrigger 값의 예제 시나리오가 나와 있습니다.

UpdateSourceTrigger 값 소스 값이 업데이트될 때 TextBox의 예제 시나리오
LostFocus(TextBox.Text의 기본값) TextBox 컨트롤이 포커스를 잃을 때. 유효성 검사 논리와 연결된 TextBox(아래 데이터 유효성 검사 참조).
PropertyChanged TextBox에 입력할 때. 대화방 창의 TextBox 컨트롤.
Explicit 앱이 UpdateSource를 호출할 때. 편집 가능한 양식의 TextBox 컨트롤(사용자가 제출 단추를 클릭할 경우에만 소스 값 업데이트).

예는 방법: TextBox 텍스트의 소스를 업데이트하는 시점 제어를 참조하세요.

바인딩 만들기

이전 섹션에서 설명한 일부 개념을 다시 설명하면, Binding 개체를 사용하여 바인딩을 설정하고 각 바인딩에는 일반적으로 네 가지 구성 요소인 바인딩 대상, 대상 속성, 바인딩 소스 및 사용할 소스 값이 포함됩니다. 이 섹션에서는 바인딩을 설정하는 방법을 설명합니다.

바인딩 소스 개체가 SDKSample 네임스페이스에 정의된 MyData 클래스인 다음 예제를 살펴볼 수 있습니다. 설명을 위해 MyData에는 값이 “Red”로 설정된 ColorName 문자열 속성이 있습니다. 따라서 이 예제에서는 빨간색 배경이 있는 단추를 생성합니다.

<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:c="clr-namespace:SDKSample">
    <DockPanel.Resources>
        <c:MyData x:Key="myDataSource"/>
    </DockPanel.Resources>
    <DockPanel.DataContext>
        <Binding Source="{StaticResource myDataSource}"/>
    </DockPanel.DataContext>
    <Button Background="{Binding Path=ColorName}"
            Width="150" Height="30">
        I am bound to be RED!
    </Button>
</DockPanel>

바인딩 선언 구문에 관한 자세한 설명과 코드에서 바인딩을 설정하는 방법의 예제는 바인딩 선언 개요를 참조하세요.

이 예제를 기본 다이어그램에 적용하면 결과 그림은 다음과 같이 표시됩니다. Background 속성이 기본적으로 OneWay 바인딩을 지원하므로 이 그림에서는 OneWay 바인딩을 설명합니다.

Diagram that shows the data binding Background property.

Background 속성이 Brush 형식인데 ColorName 속성이 형식 문자열인 경우에도 이 바인딩이 작동하는 이유가 궁금할 수 있습니다. 이 바인딩은 기본 형식 변환을 사용하며 이 기능은 데이터 변환 섹션에서 설명합니다.

바인딩 소스 지정

이전 예제에서는 DockPanel.DataContext 속성을 설정하여 바인딩 소스를 지정합니다. 그런 다음, Button은 부모 요소인 DockPanel에서 DataContext 값을 상속합니다. 다시 말하지만, 바인딩 소스 개체는 바인딩의 네 가지 필수 구성 요소 중 하나입니다. 따라서 바인딩 소스 개체를 지정하지 않으면 바인딩은 아무 작업도 하지 않습니다.

바인딩 소스 개체를 지정하는 여러 가지 방법이 있습니다. 여러 속성을 같은 소스에 바인딩할 경우 부모 요소에서 DataContext 속성을 사용하는 방법이 유용합니다. 하지만 개별 바인딩 선언에서 바인딩 소스를 지정하는 방법이 더 적절한 경우도 있습니다. 이전 예제에서는 DataContext 속성을 사용하지 않고 다음 예제와 같이 단추의 바인딩 선언에서 직접 Binding.Source 속성을 설정하여 바인딩 소스를 지정할 수 있습니다.

<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:c="clr-namespace:SDKSample">
    <DockPanel.Resources>
        <c:MyData x:Key="myDataSource"/>
    </DockPanel.Resources>
    <Button Background="{Binding Source={StaticResource myDataSource}, Path=ColorName}"
            Width="150" Height="30">
        I am bound to be RED!
    </Button>
</DockPanel>

요소에서 직접 DataContext 속성을 설정하지 않고, 상위 항목(예: 첫 번째 예제의 단추)에서 DataContext 값을 상속하고 바인딩에서 Binding.Source 속성(예: 마지막 예제의 단추)을 설정하여 바인딩 소스를 명시적으로 지정하면 Binding.ElementName 속성이나 Binding.RelativeSource 속성을 사용하여 바인딩 소스를 지정할 수도 있습니다. 슬라이더를 사용하여 단추 너비를 조정하는 경우와 같이 앱에서 다른 요소에 바인딩할 경우에는 ElementName 속성이 유용합니다. 바인딩이 ControlTemplate 또는 Style에서 지정되는 경우 RelativeSource 속성이 유용합니다. 자세한 내용은 방법: 바인딩 소스 지정을 참조하세요.

값 경로 지정

바인딩 소스가 개체이면 Binding.Path 속성을 사용하여 바인딩에 사용할 값을 지정합니다. XML 데이터에 바인딩할 경우에는 Binding.XPath 속성을 사용하여 값을 지정합니다. 경우에 따라 데이터가 XML이더라도 Path 속성을 사용하는 것이 좋을 수 있습니다. 예를 들어 XPath 쿼리의 결과로 반환된 XmlNode의 Name 속성에 액세스하려면 XPath 속성 외에 Path 속성을 사용해야 합니다.

자세한 내용은 PathXPath 속성을 참조하세요.

사용할 값의 Path가 네 가지 필수 구성 요소 중 하나임을 강조했지만, 전체 개체에 바인딩하려는 시나리오에서는 사용할 값이 바인딩 소스 개체와 같습니다. 이 경우에는 Path를 지정하지 않는 것이 좋을 수 없습니다. 다음 예제를 살펴보십시오.

<ListBox ItemsSource="{Binding}"
         IsSynchronizedWithCurrentItem="true"/>

위의 예제에서는 빈 바인딩 구문인 {Binding}을 사용합니다. 이 경우 ListBox는 부모 DockPanel 요소(이 예제에는 표시되지 않음)에서 DataContext를 상속합니다. 경로를 지정하지 않으면 기본적으로 전체 개체에 바인딩됩니다. 즉, 이 예제에서는 ItemsSource 속성을 전체 개체에 바인딩하므로 경로가 비어 있습니다. 자세한 내용은 컬렉션에 바인딩 섹션을 참조하세요.

컬렉션에 바인딩 외에 개체의 단일 속성만이 아닌 전체 개체에 바인딩하려는 경우에도 이 시나리오가 유용합니다. 예를 들면 소스 개체가 String 형식이고 문자열 자체에 바인딩하려는 경우일 수 있습니다. 또 다른 일반적인 시나리오는 여러 속성이 있는 개체에 요소를 바인딩하려는 경우입니다.

바인딩된 대상 속성에서 데이터가 의미를 가지려면 사용자 지정 논리를 적용해야 할 수 있습니다. 기본 형식 변환이 없는 경우 사용자 지정 논리는 사용자 지정 변환기 형식일 수 있습니다. 변환기에 관한 자세한 내용은 데이터 변환을 참조하세요.

Binding 및 BindingExpression

데이터 바인딩의 다른 기능과 사용법을 살펴보기 전에 BindingExpression 클래스를 알아보는 것이 도움이 됩니다. 이전 섹션에서 살펴본 대로 Binding 클래스는 바인딩 선언의 상위 수준 클래스입니다. 이 클래스는 바인딩의 특징을 지정할 수 있는 다양한 속성을 제공합니다. 관련 클래스인 BindingExpression은 소스와 대상 간에 연결을 유지 관리하는 기본 개체입니다. 바인딩에는 여러 바인딩 식에서 공유될 수 있는 모든 정보가 포함됩니다. BindingExpression은 공유될 수 없는 인스턴스 식이고 Binding의 모든 인스턴스 정보를 포함합니다.

다음 예제를 참조하세요. 여기서 myDataObjectMyData 클래스의 인스턴스이고, myBinding은 소스 Binding 개체이며, MyDataColorName 문자열 속성을 포함하는 정의된 클래스입니다. 이 예제에서는 TextBlock의 인스턴스인 myText의 텍스트 콘텐츠를 ColorName에 바인딩합니다.

// Make a new source
var myDataObject = new MyData();
var myBinding = new Binding("ColorName")
{
    Source = myDataObject
};

// Bind the data source to the TextBox control's Text dependency property
myText.SetBinding(TextBlock.TextProperty, myBinding);
' Make a New source
Dim myDataObject As New MyData
Dim myBinding As New Binding("ColorName")
myBinding.Source = myDataObject

' Bind the data source to the TextBox control's Text dependency property
myText.SetBinding(TextBlock.TextProperty, myBinding)

같은 myBinding 개체를 사용하여 다른 바인딩을 만들 수 있습니다. 예를 들어 myBinding 개체를 사용하여 확인란의 텍스트 콘텐츠를 ColorName에 바인딩할 수 있습니다. 이 시나리오에는 myBinding 개체를 공유하는 두 개의 BindingExpression 인스턴스가 있습니다.

BindingExpression 개체는 데이터에 바인딩된 개체에서 GetBindingExpression을 호출하여 반환됩니다. 다음 문서에서는 BindingExpression 클래스를 사용하는 몇 가지 방법을 설명합니다.

데이터 변환

바인딩 만들기 섹션에서는 Background 속성이 값이 "Red"인 문자열 속성에 바인딩되므로 단추가 빨간색입니다. 이 문자열 값은 문자열 값을 Brush로 변환하는 Brush 형식에 대한 형식 변환기가 있기 때문에 가능합니다.

이 정보를 그림에 추가하면 바인딩 만들기 섹션은 다음과 같이 표시됩니다.

Diagram that shows the data binding Default property.

하지만 바인딩 소스 개체에 문자열 형식의 속성이 아닌 Color 형식의 Color 속성이 있으면 어떻게 될까요? 이 경우 바인딩을 적용하려면 먼저 Color 속성 값을 Background 속성이 사용하는 어떤 것으로 변환해야 합니다. 다음 예제와 같이 IValueConverter 인터페이스를 구현하여 사용자 지정 변환기를 만들어야 합니다.

[ValueConversion(typeof(Color), typeof(SolidColorBrush))]
public class ColorBrushConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Color color = (Color)value;
        return new SolidColorBrush(color);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return null;
    }
}
<ValueConversion(GetType(Color), GetType(SolidColorBrush))>
Public Class ColorBrushConverter
    Implements IValueConverter
    Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.Convert
        Dim color As Color = CType(value, Color)
        Return New SolidColorBrush(color)
    End Function

    Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
        Return Nothing
    End Function
End Class

자세한 내용은 IValueConverter를 참조하세요.

이제 기본 변환 대신 사용자 지정 변환기가 사용되고 다이어그램이 다음과 같이 표시됩니다.

Diagram that shows the data binding custom converter.

다시 말하지만, 바인딩되는 형식에 있는 형식 변환기 때문에 기본 변환을 사용할 수 있습니다. 이 동작은 대상에서 사용할 수 있는 형식 변환기에 따라 결정됩니다. 확실하지 않으면 사용자 지정 변환기를 만듭니다.

데이터 변환기를 구현하는 것이 좋은 몇 가지 일반적인 시나리오는 다음과 같습니다.

  • 데이터를 문화권에 따라 다르게 표시해야 하는 경우. 예를 들어 특정 문화권에서 사용되는 규칙에 따라 통화 변환기 또는 달력 날짜/시간 변환기를 구현해야 할 수 있습니다.

  • 사용되는 데이터가 반드시 속성의 텍스트 값을 변경하는 데 사용될 필요는 없지만, 이미지의 소스 또는 표시 텍스트의 색이나 스타일과 같은 몇 가지 다른 값을 변경하는 데 사용되는 경우. 텍스트 필드를 표 셀의 Background 속성에 바인딩하는 것과 같이 적절해 보이지 않는 속성의 바인딩을 변환하는 방식으로 이 인스턴스에서 변환기를 사용할 수 있습니다.

  • 두 개 이상의 컨트롤 또는 컨트롤의 여러 속성이 같은 데이터에 바인딩됩니다. 이 경우 기본 바인딩은 텍스트만 표시할 수 있는 반면, 기타 바인딩은 특정 표시 문제를 처리하지만 소스 정보와 같은 바인딩을 사용합니다.

  • 대상 속성에는 MultiBinding이라는 바인딩 컬렉션이 있습니다. MultiBinding의 경우 사용자 지정 IMultiValueConverter를 사용하여 바인딩의 값에서 최종 값을 생성합니다. 예를 들어 빨간색, 파란색 및 녹색 값을 기준으로 색을 계산할 수 있고 이러한 값은 같거나 다른 바인딩 소스 개체의 값일 수 있습니다. 예제와 자세한 내용은 MultiBinding을 참조하세요.

컬렉션에 바인딩

바인딩 소스는 속성에 데이터가 포함된 단일 개체로 처리되거나 종종 함께 그룹화되는 다형 개체의 데이터 수집(예: 데이터베이스에 대한 쿼리의 결과)으로 처리될 수 있습니다. 지금까지 단일 개체에 대한 바인딩만 설명했습니다. 그러나 일반적인 시나리오는 데이터 수집에 대한 바인딩입니다. 예를 들어 일반적인 시나리오는 ListBox, ListView 또는 TreeView와 같은 ItemsControl을 사용하여 데이터 바인딩이란? 섹션에 나와 있는 앱과 같이 데이터 수집을 표시하는 것입니다.

다행히도 기본 다이어그램이 적용됩니다. ItemsControl을 컬렉션에 바인딩할 경우 다이어그램은 다음과 같이 표시됩니다.

Diagram that shows the data binding ItemsControl object.

이 다이어그램과 같이 ItemsControl을 컬렉션 개체에 바인딩하는 데 사용할 속성은 ItemsControl.ItemsSource 속성입니다. ItemsSourceItemsControl의 콘텐츠로 간주할 수 있습니다. ItemsSource 속성은 기본적으로 OneWay 바인딩을 지원하므로 바인딩은 OneWay입니다.

컬렉션을 구현하는 방법

IEnumerable 인터페이스를 구현하는 컬렉션을 열거할 수 있습니다. 그러나 컬렉션에서 삽입 또는 삭제가 발생할 때마다 UI가 자동으로 업데이트되도록 동적 바인딩을 설정하려면 컬렉션이 INotifyCollectionChanged 인터페이스를 구현해야 합니다. 이 인터페이스는 기본 컬렉션이 변경될 때마다 발생해야 하는 이벤트를 노출합니다.

WPF는 ObservableCollection<T> 클래스를 제공하고 이 클래스는 INotifyCollectionChanged 인터페이스를 노출하는 데이터 수집의 기본 구현입니다. 소스 개체에서 대상으로 데이터 값을 전송하는 기능을 완전히 지원하려면 바인딩 가능 속성을 지원하는 컬렉션의 각 개체도 INotifyPropertyChanged 인터페이스를 구현해야 합니다. 자세한 내용은 바인딩 소스 개요를 참조하세요.

고유한 컬렉션을 구현하기 전에 ObservableCollection<T>을 사용하거나 List<T>, Collection<T>BindingList<T>와 같은 기존 컬렉션 클래스 중 하나를 사용하는 것이 좋습니다. 고급 시나리오가 있고 사용자 지정 컬렉션을 구현하려면 인덱스를 통해 개별적으로 액세스할 수 있는 제네릭이 아닌 컬렉션을 제공하므로 최고의 성능을 제공하는 IList를 사용하는 것이 좋습니다.

컬렉션 뷰

ItemsControl이 데이터 수집에 바인딩된 후 데이터를 정렬, 필터링 또는 그룹화해야 할 수 있습니다. 이 작업을 수행하는 데는 ICollectionView 인터페이스를 구현하는 클래스인 컬렉션 뷰를 사용합니다.

컬렉션 뷰란?

컬렉션 뷰는 기본 소스 컬렉션 자체를 변경할 필요 없이 정렬, 필터 및 그룹화 쿼리에 따라 소스 컬렉션을 탐색하고 표시할 수 있는 바인딩 소스 컬렉션의 최상위에 있는 레이어입니다. 컬렉션 뷰에서는 컬렉션의 현재 항목에 대한 포인터도 유지 관리합니다. 소스 컬렉션이 INotifyCollectionChanged 인터페이스를 구현하면 CollectionChanged 이벤트를 통해 변경된 내용이 뷰에 전파됩니다.

뷰는 기본 소스 컬렉션을 변경하지 않으므로 각 소스 컬렉션에는 연결된 여러 뷰가 있을 수 있습니다. 예를 들어 Task 개체의 컬렉션이 있을 수 있습니다. 뷰를 사용하여 같은 데이터를 다양한 방식으로 표시할 수 있습니다. 예를 들어 페이지 왼쪽에는 우선 순위별로 정렬된 작업을 표시하고 오른쪽에는 영역별로 그룹화된 작업을 표시해야 할 수 있습니다.

뷰를 만드는 방법

뷰를 만들고 사용하는 한 가지 방법은 뷰 개체를 인스턴스화하고 이를 바인딩 소스로 사용하는 것입니다. 예를 들어 데이터 바인딩이란? 섹션에 나와 있는 Data binding demo(데이터 바인딩 데모) 앱을 고려합니다. 이 앱은 ListBox가 데이터 수집에 직접 바인딩되지 않고 데이터 수집을 통해 뷰에 바인딩되도록 구현됩니다. 다음 예제는 Data binding demo(데이터 바인딩 데모) 앱에서 추출됩니다. CollectionViewSource 클래스는 CollectionView에서 상속되는 클래스의 XAML 프록시입니다. 이 특정 예제에서 뷰의 Source는 현재 앱 개체의 AuctionItems 컬렉션(ObservableCollection<T> 형식)에 바인딩됩니다.

<Window.Resources>
    <CollectionViewSource 
      Source="{Binding Source={x:Static Application.Current}, Path=AuctionItems}"   
      x:Key="listingDataView" />
</Window.Resources>

리소스 listingDataView는 앱에 있는 ListBox와 같은 요소의 바인딩 소스로 사용됩니다.

<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8" 
         ItemsSource="{Binding Source={StaticResource listingDataView}}" />

같은 컬렉션에 대한 또 다른 뷰를 만들려면 또 다른 CollectionViewSource 인스턴스를 만들고 다른 x:Key 이름을 지정할 수 있습니다.

다음 표에서는 기본 컬렉션 뷰로 만들어지거나 소스 컬렉션 형식에 따라 CollectionViewSource를 통해 만들어지는 뷰 데이터 형식을 보여 줍니다.

소스 컬렉션 형식 컬렉션 뷰 형식 참고
IEnumerable CollectionView를 기반으로 하는 내부 형식 항목을 그룹화할 수 없습니다.
IList ListCollectionView 가장 빠릅니다.
IBindingList BindingListCollectionView

기본 뷰 사용

컬렉션 뷰를 만들고 사용하는 한 가지 방법은 컬렉션 뷰를 바인딩 소스로 지정하는 것입니다. WPF에서도 바인딩 소스로 사용되는 모든 컬렉션에 대한 기본 컬렉션 뷰를 만듭니다. 컬렉션에 직접 바인딩할 경우 WPF는 기본 뷰에 바인딩됩니다. 같은 컬렉션에 대한 모든 바인딩이 이 기본 뷰를 공유하므로 하나의 바인딩된 컨트롤이나 코드(예: 뒷부분에 설명되는 현재 항목 포인터에 대한 정렬 또는 변경)를 통해 기본 뷰에 적용된 변경 내용은 같은 컬렉션에 대한 모든 다른 바인딩에 반영됩니다.

기본 뷰를 가져오려면 GetDefaultView 메서드를 사용합니다. 예제를 보려면 데이터 수집의 기본 뷰 가져오기를 참조하세요.

ADO.NET DataTable이 있는 컬렉션 뷰

성능을 개선하기 위해 ADO.NET DataTable 또는 DataView 개체의 컬렉션 뷰는 정렬 및 필터링을 DataView에 위임하며, 이로 인해 정렬 및 필터링이 데이터 소스의 모든 컬렉션 뷰에서 공유됩니다. 각 컬렉션 뷰를 개별적으로 정렬 및 필터링하려면 자체 DataView 개체를 사용하여 각 컬렉션 뷰를 초기화합니다.

정렬

앞에서 설명한 대로 뷰에서는 컬렉션에 정렬 순서를 적용할 수 있습니다. 기본 컬렉션에 있는 데이터에는 관련 상속 순서가 있거나 없을 수도 있습니다. 컬렉션에 대한 뷰를 통해 제공한 비교 기준에 따라 순서를 적용하거나 기본 순서를 변경할 수 있습니다. 이는 데이터의 클라이언트 기반 뷰이므로 일반적으로 열과 일치하는 값에 따라 표 형식 데이터의 열을 정렬해야 할 수 있습니다. 뷰를 사용하면 기본 컬렉션을 변경하거나 컬렉션 콘텐츠를 다시 쿼리할 필요 없이 이 사용자 기반 정렬을 적용할 수 있습니다. 예제를 보려면 머리글을 클릭할 때 GridView 열 정렬을 참조하세요.

다음 예제에서는 데이터 바인딩이란? 섹션에 있는 앱 UI의 "Sort by category and date" CheckBox에 관한 정렬 논리를 보여 줍니다.

private void AddSortCheckBox_Checked(object sender, RoutedEventArgs e)
{
    // Sort the items first by Category and then by StartDate
    listingDataView.SortDescriptions.Add(new SortDescription("Category", ListSortDirection.Ascending));
    listingDataView.SortDescriptions.Add(new SortDescription("StartDate", ListSortDirection.Ascending));
}
Private Sub AddSortCheckBox_Checked(sender As Object, e As RoutedEventArgs)
    ' Sort the items first by Category And then by StartDate
    listingDataView.SortDescriptions.Add(New SortDescription("Category", ListSortDirection.Ascending))
    listingDataView.SortDescriptions.Add(New SortDescription("StartDate", ListSortDirection.Ascending))
End Sub

필터링

뷰에서 컬렉션에 필터를 적용할 수 있으므로 뷰에는 전체 컬렉션의 특정 하위 집합만 표시됩니다. 데이터에서 조건에 따라 필터링할 수 있습니다. 예를 들어 데이터 바인딩이란? 섹션의 앱이 수행하는 것처럼 "Show only bargains" CheckBox에는 가격이 $25 이상인 항목을 필터링하는 논리가 포함됩니다. 다음 코드는 Filter를 선택할 때 ShowOnlyBargainsFilterCheckBox 이벤트 처리기로 설정하기 위해 실행됩니다.

private void AddFilteringCheckBox_Checked(object sender, RoutedEventArgs e)
{
    if (((CheckBox)sender).IsChecked == true)
        listingDataView.Filter += ListingDataView_Filter;
    else
        listingDataView.Filter -= ListingDataView_Filter;
}
Private Sub AddFilteringCheckBox_Checked(sender As Object, e As RoutedEventArgs)
    Dim checkBox = DirectCast(sender, CheckBox)

    If checkBox.IsChecked = True Then
        AddHandler listingDataView.Filter, AddressOf ListingDataView_Filter
    Else
        RemoveHandler listingDataView.Filter, AddressOf ListingDataView_Filter
    End If
End Sub

ShowOnlyBargainsFilter 이벤트 처리기에는 다음과 같은 구현이 있습니다.

private void ListingDataView_Filter(object sender, FilterEventArgs e)
{
    // Start with everything excluded
    e.Accepted = false;

    // Only inlcude items with a price less than 25
    if (e.Item is AuctionItem product && product.CurrentPrice < 25)
        e.Accepted = true;
}
Private Sub ListingDataView_Filter(sender As Object, e As FilterEventArgs)

    ' Start with everything excluded
    e.Accepted = False

    Dim product As AuctionItem = TryCast(e.Item, AuctionItem)

    If product IsNot Nothing Then

        ' Only include products with prices lower than 25
        If product.CurrentPrice < 25 Then e.Accepted = True

    End If

End Sub

CollectionViewSource 대신 CollectionView 클래스 중 하나를 사용하는 경우 Filter 속성을 사용하여 콜백을 지정합니다. 예제를 보려면 뷰에서 데이터 필터링을 참조하세요.

그룹화

IEnumerable 컬렉션을 보는 내부 클래스를 제외하고 모든 컬렉션 뷰는 사용자가 컬렉션 뷰의 컬렉션을 논리 그룹으로 분할할 수 있는 ‘그룹화’를 지원합니다. 그룹은 사용자가 그룹 목록을 제공하는 경우 명시적 그룹이고 데이터에 따라 그룹이 동적으로 생성되는 경우 암시적 그룹입니다.

다음 예제에서는 "Group by category" CheckBox의 논리를 보여 줍니다.

// This groups the items in the view by the property "Category"
var groupDescription = new PropertyGroupDescription();
groupDescription.PropertyName = "Category";
listingDataView.GroupDescriptions.Add(groupDescription);
' This groups the items in the view by the property "Category"
Dim groupDescription = New PropertyGroupDescription()
groupDescription.PropertyName = "Category"
listingDataView.GroupDescriptions.Add(groupDescription)

다른 그룹화 예제를 보려면 GridView를 구현하는 ListView의 항목 그룹화를 참조하세요.

현재 항목 포인터

뷰는 현재 항목의 개념도 지원합니다. 컬렉션 뷰에서 개체를 탐색할 수 있습니다. 탐색할 때 컬렉션의 특정 위치에 있는 개체를 검색할 수 있는 항목 포인터를 이동합니다. 예제를 보려면 데이터 CollectionView의 개체 탐색을 참조하세요.

WPF는 뷰(지정한 뷰 또는 컬렉션의 기본 뷰)를 통해 컬렉션에 바인딩되므로 컬렉션에 대한 모든 바인딩에는 현재 항목 포인터가 포함됩니다. 뷰에 바인딩할 때 Path 값의 슬래시("/") 문자는 뷰의 현재 항목을 지정합니다. 다음 예제에서 데이터 컨텍스트는 컬렉션 뷰입니다. 첫 줄은 컬렉션에 바인딩됩니다. 두 번째 줄은 컬렉션의 현재 항목에 바인딩됩니다. 세 번째 줄은 컬렉션에 있는 현재 항목의 Description 속성에 바인딩됩니다.

<Button Content="{Binding }" />
<Button Content="{Binding Path=/}" />
<Button Content="{Binding Path=/Description}" />

컬렉션 계층 구조를 트래버스하도록 슬래시와 속성 구문을 쌓을 수도 있습니다. 다음 예제에서는 소스 컬렉션의 현재 항목 속성인 Offices라는 컬렉션의 현재 항목에 바인딩합니다.

<Button Content="{Binding /Offices/}" />

컬렉션에 적용되는 정렬이나 필터링이 현재 항목 포인터에 영향을 미칠 수 있습니다. 정렬은 현재 항목 포인터를 선택된 마지막 항목에 유지하지만 이제 컬렉션 뷰는 포인터를 중심으로 재구성됩니다. 이전에는 선택된 항목이 목록의 시작 부분에 있었을 수 있지만 이제 선택된 항목이 가운데 부분에 있을 수 있습니다. 필터링은 필터링한 후 선택 항목이 뷰에 남아 있으면 선택된 항목을 유지합니다. 남아 있지 않으면 현재 항목 포인터는 필터링된 컬렉션 뷰의 첫 번째 항목으로 설정됩니다.

마스터-세부 바인딩 시나리오

현재 항목의 개념은 컬렉션에서 항목을 탐색하는 경우뿐 아니라 마스터-세부 바인딩 시나리오에도 유용합니다. 다시 데이터 바인딩이란? 섹션의 앱 UI를 살펴보겠습니다. 해당 앱에서 ListBox 내의 선택 항목에 따라 ContentControl에 표시되는 콘텐츠가 결정됩니다. 다르게 설명하자면, ListBox 항목이 선택될 때 ContentControl은 선택된 항목의 세부 정보를 표시합니다.

간단히 두 개 이상의 컨트롤을 같은 뷰에 바인딩하여 마스터-세부 시나리오를 구현할 수 있습니다. 의 다음 예제에서 데이터 바인딩 데모데이터 바인딩이란? 섹션의 앱 UI에 표시되는 ListBoxContentControl의 태그를 보여 줍니다.

<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8" 
         ItemsSource="{Binding Source={StaticResource listingDataView}}" />
<ContentControl Name="Detail" Grid.Row="3" Grid.ColumnSpan="3"
                Content="{Binding Source={StaticResource listingDataView}}"
                ContentTemplate="{StaticResource detailsProductListingTemplate}" 
                Margin="9,0,0,0"/>

컨트롤이 둘 다 같은 소스인 listingDataView 정적 리소스에 바인딩되어 있습니다(뷰를 만드는 방법 섹션에서 이 리소스의 정의 참조). 이 바인딩은 singleton 개체(이 경우 ContentControl)가 컬렉션에 바인딩될 경우 자동으로 뷰의 CurrentItem에 바인딩되기 때문에 가능합니다. CollectionViewSource 개체는 통화 및 선택 항목을 자동으로 동기화합니다. 이 예제에서 목록 컨트롤이 CollectionViewSource 개체에 바인딩되지 않은 경우에는 IsSynchronizedWithCurrentItem 속성을 true로 설정해야 이 기능이 작동합니다.

다른 예제를 보려면 선택에 따라 컬렉션 및 표시 정보에 바인딩계층적 데이터에 마스터-세부 패턴 사용을 참조하세요.

위 예제에서는 템플릿을 사용합니다. 실제로 템플릿(ContentControl에서 명시적으로 사용되는 템플릿 및 ListBox에서 암시적으로 사용되는 템플릿)을 사용하지 않으면 데이터가 원하는 방식으로 표시되지 않습니다. 이제 다음 섹션에서 데이터 템플릿을 살펴보겠습니다.

데이터 템플릿

데이터 템플릿을 사용하지 않으면 데이터 바인딩이란? 섹션의 앱 UI가 다음과 같이 표시됩니다.

Data Binding Demo without Data Templates

이전 섹션의 예제와 같이 ListBox 컨트롤과 ContentControl이 둘 다 AuctionItem의 전체 컬렉션 개체(또는 더 구체적으로는 컬렉션 개체에 대한 뷰)에 바인딩됩니다. 데이터 수집을 표시하는 방법에 대한 구체적인 명령이 없으면 ListBox는 기본 컬렉션에 있는 각 개체의 문자열 표현을 표시하고 ContentControl은 바인딩된 개체의 문자열 표현을 표시합니다.

이 문제를 해결하기 위해 앱은 DataTemplates를 정의합니다. 이전 섹션의 예제와 같이 ContentControldetailsProductListingTemplate 데이터 템플릿을 명시적으로 사용합니다. ListBox 컨트롤은 컬렉션의 AuctionItem 개체를 표시할 때 다음 데이터 템플릿을 암시적으로 사용합니다.

<DataTemplate DataType="{x:Type src:AuctionItem}">
    <Border BorderThickness="1" BorderBrush="Gray"
            Padding="7" Name="border" Margin="3" Width="500">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="20"/>
                <ColumnDefinition Width="86"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>

            <Polygon Grid.Row="0" Grid.Column="0" Grid.RowSpan="4"
                     Fill="Yellow" Stroke="Black" StrokeThickness="1"
                     StrokeLineJoin="Round" Width="20" Height="20"
                     Stretch="Fill"
                     Points="9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7"
                     Visibility="Hidden" Name="star"/>

            <TextBlock Grid.Row="0" Grid.Column="1" Margin="0,0,8,0"
                       Name="descriptionTitle"
                       Style="{StaticResource smallTitleStyle}">Description:</TextBlock>
            
            <TextBlock Name="DescriptionDTDataType" Grid.Row="0" Grid.Column="2"
                       Text="{Binding Path=Description}"
                       Style="{StaticResource textStyleTextBlock}"/>

            <TextBlock Grid.Row="1" Grid.Column="1" Margin="0,0,8,0"
                       Name="currentPriceTitle"
                       Style="{StaticResource smallTitleStyle}">Current Price:</TextBlock>
            
            <StackPanel Grid.Row="1" Grid.Column="2" Orientation="Horizontal">
                <TextBlock Text="$" Style="{StaticResource textStyleTextBlock}"/>
                <TextBlock Name="CurrentPriceDTDataType"
                           Text="{Binding Path=CurrentPrice}" 
                           Style="{StaticResource textStyleTextBlock}"/>
            </StackPanel>
        </Grid>
    </Border>
    <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding Path=SpecialFeatures}">
            <DataTrigger.Value>
                <src:SpecialFeatures>Color</src:SpecialFeatures>
            </DataTrigger.Value>
            <DataTrigger.Setters>
                <Setter Property="BorderBrush" Value="DodgerBlue" TargetName="border" />
                <Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
                <Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
                <Setter Property="BorderThickness" Value="3" TargetName="border" />
                <Setter Property="Padding" Value="5" TargetName="border" />
            </DataTrigger.Setters>
        </DataTrigger>
        <DataTrigger Binding="{Binding Path=SpecialFeatures}">
            <DataTrigger.Value>
                <src:SpecialFeatures>Highlight</src:SpecialFeatures>
            </DataTrigger.Value>
            <Setter Property="BorderBrush" Value="Orange" TargetName="border" />
            <Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
            <Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
            <Setter Property="Visibility" Value="Visible" TargetName="star" />
            <Setter Property="BorderThickness" Value="3" TargetName="border" />
            <Setter Property="Padding" Value="5" TargetName="border" />
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>

DataTemplates를 둘 다 사용하면 결과 UI는 데이터 바인딩이란? 섹션의 UI와 같이 표시됩니다. 스크린샷에 표시된 것처럼 DataTemplates를 사용하면 컨트롤에 데이터를 배치할 수 있을 뿐 아니라 데이터에 대한 눈에 띄는 시각적 개체를 정의할 수도 있습니다. 예를 들어 DataTrigger는 위의 DataTemplate에서 사용되므로 HighLight 값이 SpecialFeaturesAuctionItem에는 주황색 테두리와 별모양이 함께 표시됩니다.

데이터 템플릿에 관한 자세한 내용은 데이터 템플릿 개요를 참조하세요.

데이터 유효성 검사

사용자 입력을 사용하는 대부분 앱에는 사용자가 필요한 정보를 입력했는지 확인할 수 있는 유효성 검사 논리가 있어야 합니다. 유효성 검사는 형식, 범위, 서식 또는 기타 앱별 요구 사항에 따라 달라질 수 있습니다. 이 섹션에서는 WPF에서 데이터 유효성 검사를 사용하는 방법을 설명합니다.

유효성 검사 규칙과 바인딩 연결

WPF 데이터 바인딩 모델을 사용하면 ValidationRulesBinding 개체와 연결할 수 있습니다. 예를 들어 다음 예제에서는 TextBoxStartPrice 속성에 바인딩하고 ExceptionValidationRule 개체를 Binding.ValidationRules 속성에 추가합니다.

<TextBox Name="StartPriceEntryForm" Grid.Row="2"
         Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
    <TextBox.Text>
        <Binding Path="StartPrice" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <ExceptionValidationRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

ValidationRule 개체는 속성 값이 유효한지 확인합니다. WPF에는 두 가지 형식의 기본 제공 ValidationRule 개체가 있습니다.

ValidationRule 클래스에서 파생시키고 Validate 메서드를 구현하는 방식으로 자체 유효성 검사 규칙을 만들 수도 있습니다. 다음 예제에서는 데이터 바인딩이란? 섹션의 Add Product Listing "Start Date" TextBox에서 사용되는 규칙을 보여 줍니다.

public class FutureDateRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        // Test if date is valid
        if (DateTime.TryParse(value.ToString(), out DateTime date))
        {
            // Date is not in the future, fail
            if (DateTime.Now > date)
                return new ValidationResult(false, "Please enter a date in the future.");
        }
        else
        {
            // Date is not a valid date, fail
            return new ValidationResult(false, "Value is not a valid date.");
        }

        // Date is valid and in the future, pass
        return ValidationResult.ValidResult;
    }
}
Public Class FutureDateRule
    Inherits ValidationRule

    Public Overrides Function Validate(value As Object, cultureInfo As CultureInfo) As ValidationResult

        Dim inputDate As Date

        ' Test if date is valid
        If Date.TryParse(value.ToString, inputDate) Then

            ' Date is not in the future, fail
            If Date.Now > inputDate Then
                Return New ValidationResult(False, "Please enter a date in the future.")
            End If

        Else
            ' // Date Is Not a valid date, fail
            Return New ValidationResult(False, "Value is not a valid date.")
        End If

        ' Date is valid and in the future, pass
        Return ValidationResult.ValidResult

    End Function

End Class

다음 예제와 같이 StartDateEntryFormTextBox은 이 FutureDateRule을 사용합니다.

<TextBox Name="StartDateEntryForm" Grid.Row="3"
         Validation.ErrorTemplate="{StaticResource validationTemplate}" 
         Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
    <TextBox.Text>
        <Binding Path="StartDate" UpdateSourceTrigger="PropertyChanged" 
                 Converter="{StaticResource dateConverter}" >
            <Binding.ValidationRules>
                <src:FutureDateRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

UpdateSourceTrigger 값은 PropertyChanged이므로 바인딩 엔진은 키 입력이 있을 때마다 소스 값을 업데이트합니다. 즉, 키 입력이 있을 때마다 ValidationRules 컬렉션에서 모든 규칙을 확인합니다. 이 내용은 유효성 검사 프로세스 섹션에서 자세히 설명합니다.

시각적 피드백 제공

사용자가 잘못된 값을 입력할 경우 앱 UI에 오류에 관한 피드백을 표시해야 할 수 있습니다. 해당 피드백을 제공하는 한 가지 방법은 Validation.ErrorTemplate 연결된 속성을 ControlTemplate으로 설정하는 것입니다. 이전 하위 섹션과 같이 StartDateEntryFormTextBoxvalidationTemplate이라는 ErrorTemplate을 사용합니다. 다음 예제에서는 validationTemplate의 정의를 보여 줍니다.

<ControlTemplate x:Key="validationTemplate">
    <DockPanel>
        <TextBlock Foreground="Red" FontSize="20">!</TextBlock>
        <AdornedElementPlaceholder/>
    </DockPanel>
</ControlTemplate>

AdornedElementPlaceholder 요소는 표시되는 컨트롤을 배치할 위치를 지정합니다.

또한 ToolTip을 사용하여 오류 메시지를 표시할 수도 있습니다. StartDateEntryFormStartPriceEntryFormTextBox는 둘 다 오류 메시지를 표시하는 ToolTip을 만드는 textStyleTextBox 스타일을 사용합니다. 다음 예제에서는 textStyleTextBox의 정의를 보여 줍니다. 바인딩된 요소의 속성에 대한 바인딩 중 하나 이상에서 오류가 발생할 경우 연결된 속성 Validation.HasErrortrue입니다.

<Style x:Key="textStyleTextBox" TargetType="TextBox">
    <Setter Property="Foreground" Value="#333333" />
    <Setter Property="MaxLength" Value="40" />
    <Setter Property="Width" Value="392" />
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
            <Setter Property="ToolTip" 
                    Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
        </Trigger>
    </Style.Triggers>
</Style>

사용자 지정 ErrorTemplateToolTip을 사용하면 StartDateEntryFormTextBox은 유효성 검사 오류가 있는 경우 다음과 같이 표시됩니다.

Data binding validation error

Binding에 연결된 유효성 검사 규칙이 있지만 바인딩된 컨트롤에서 ErrorTemplate을 지정하지 않으면 유효성 검사 오류가 있을 경우 이를 사용자에게 알리는 데 기본 ErrorTemplate이 사용됩니다. 기본 ErrorTemplate은 표시기 레이어에서 빨간색 테두리를 정의하는 컨트롤 템플릿입니다. 기본 ErrorTemplateToolTip을 사용하면 StartPriceEntryFormTextBox의 UI는 유효성 검사 오류가 발생하는 경우 다음과 같이 표시됩니다.

Data binding validation error default

대화 상자에서 모든 컨트롤의 유효성을 검사하는 논리를 제공하는 방법의 예제를 보려면 대화 상자 개요에서 사용자 지정 대화 상자 섹션을 참조하세요.

유효성 검사 프로세스

유효성 검사는 대개 대상 값이 바인딩 소스 속성에 전송될 때 수행됩니다. 이 전송은 TwoWayOneWayToSource 바인딩에서 발생합니다. 다시 말하지만, 소스 업데이트를 트리거하는 항목 섹션에서 설명한 대로 소스 업데이트는 UpdateSourceTrigger 속성의 값에 따라 결정됩니다.

다음 항목은 ‘유효성 검사’ 프로세스를 설명합니다. 이 프로세스 중에 유효성 검사 오류나 기타 오류가 발생하면 프로세스가 중단됩니다.

  1. 바인딩 엔진은 해당 Binding에 대해 ValidationStepRawProposedValue로 설정된 사용자 지정 ValidationRule 개체가 정의되어 있는지 확인합니다. 이 경우 개체 중 하나에서 오류가 발생하거나 모든 개체가 통과할 때까지 각 ValidationRule에서 Validate 메서드를 호출합니다.

  2. 그런 다음 바인딩 엔진은 변환기를 호출합니다(있는 경우).

  3. 변환기가 성공하면 바인딩 엔진은 해당 Binding에 대해 ValidationStepConvertedProposedValue로 설정된 사용자 지정 ValidationRule 개체가 정의되어 있는지 확인합니다. 이 경우 개체 중 하나에서 오류가 발생하거나 모든 개체가 통과할 때까지 ValidationStepConvertedProposedValue로 설정된 각 ValidationRule에서 Validate 메서드를 호출합니다.

  4. 바인딩 엔진은 소스 속성을 설정합니다.

  5. 바인딩 엔진은 해당 Binding에 대해 ValidationStepUpdatedValue로 설정된 사용자 지정 ValidationRule 개체가 정의되어 있는지 확인합니다. 이 경우 개체 중 하나에서 오류가 발생하거나 모든 개체가 통과할 때까지 ValidationStepUpdatedValue로 설정된 각 ValidationRule에서 Validate 메서드를 호출합니다. DataErrorValidationRule이 바인딩과 연결되고 해당 ValidationStep이 기본값 UpdatedValue로 설정된 경우 이때 DataErrorValidationRule이 확인됩니다. 이때 ValidatesOnDataErrorstrue로 설정된 모든 바인딩이 선택됩니다.

  6. 바인딩 엔진은 해당 Binding에 대해 ValidationStepCommittedValue로 설정된 사용자 지정 ValidationRule 개체가 정의되어 있는지 확인합니다. 이 경우 개체 중 하나에서 오류가 발생하거나 모든 개체가 통과할 때까지 ValidationStepCommittedValue로 설정된 각 ValidationRule에서 Validate 메서드를 호출합니다.

이 프로세스 중에 ValidationRule이 통과하지 못하면 바인딩 엔진은 ValidationError 개체를 만들고 바인딩된 요소의 Validation.Errors 컬렉션에 추가합니다. 바인딩 엔진은 특정 단계에서 ValidationRule 개체를 실행하기 전에 해당 단계 중에 바인딩된 요소의 Validation.Errors 연결된 속성에 추가된 모든 ValidationError를 제거합니다. 예를 들어 ValidationStepUpdatedValue로 설정된 ValidationRule이 실패한 경우 다음에 유효성 검사 프로세스가 수행될 때 바인딩 엔진은 ValidationStepUpdatedValue로 설정된 ValidationRule을 호출하기 직전에 해당 ValidationError를 제거합니다.

Validation.Errors가 비어 있지 않으면 요소의 Validation.HasError 연결된 속성은 true로 설정됩니다. 또한 BindingNotifyOnValidationError 속성이 true로 설정된 경우 바인딩 엔진은 요소에서 Validation.Error 연결된 이벤트를 발생시킵니다.

한쪽 방향으로(대상에서 소스로 또는 소스에서 대상으로) 유효한 값이 전송되면 Validation.Errors 연결된 속성이 지워집니다.

바인딩에 연결된 ExceptionValidationRule이 있거나 ValidatesOnExceptions 속성이 true로 설정되고 바인딩 엔진이 소스를 설정할 때 예외가 throw된 경우 바인딩 엔진은 UpdateSourceExceptionFilter가 있는지 확인합니다. UpdateSourceExceptionFilter 콜백을 사용하여 예외를 처리하기 위한 사용자 지정 처리기를 제공하는 옵션이 있습니다. UpdateSourceExceptionFilterBinding에 지정되지 않은 경우 바인딩 엔진은 예외가 있는 ValidationError를 만들고 바인딩된 요소의 Validation.Errors 컬렉션에 추가합니다.

디버깅 메커니즘

바인딩 관련 개체에서 PresentationTraceSources.TraceLevel 연결된 속성을 설정하여 특정 바인딩 상태에 대한 정보를 받을 수 있습니다.

참조