본문 바로가기

IT/c#

[WPF] WPF 데이터 바인딩에 대해서 알아보자 (기초)

 WPF(Windows® Presentation Foundation)를 사용하면 강력한 기능의 사용자 인터페이스를 쉽게 디자인할 수 있다는 사실은 독자 여러분도 이제 잘 알고 있으리라 생각합니다. 하지만 WPF가 강력한 데이터 바인딩 기능까지 제공한다는 사실은 잘 알려지지 않았습니다. WPF를 사용하면 Microsoft® .NET Framework 코드나 XAML, 또는 이 둘의 조합으로 데이터를 조작할 수 있습니다. 컨트롤, 공용 속성, XML 또는 개체에 바인딩할 수 있으므로 전례없이 빠르고 유연하며 손쉬운 데이터 바인딩이 가능합니다. 자, 그럼 컨트롤을 선택한 데이터 원본에 바인딩하는 과정을 어떻게 시작해야 할지 알아보겠습니다. 



데이터 바인딩 세부 사항


 WPF 데이터 바인딩을 사용하려면 항상 대상원본이 있어야 합니다. TextBox 컨트롤의 Text 속성과 같이 DependencyProperty에서 파생된 액세스 가능한 모든 속성이나 요소가 바인딩 대상이 될 수 있으며, 다른 컨트롤의 속성, CLR(공용 언어 런타임) 개체, XAML 요소, ADO.NET 데이터 집합, XML 조각 등이 바인딩 원본이 될 수 있습니다. WPF에는 사용자가 바인딩을 올바르게 구현할 수 있도록 XmlDataProviderObjectDataProvider라는 두 가지 특별한 공급자가 포함되어 있습니다.

 그럼 실질적인 사용 예를 통해 WPF 데이터 바인딩 방식이 어떻게 작동하는지 알아보도록 하겠습니다.



단순한 바인딩 만들기


 먼저 TextBlock의 Text 속성을 ListBox에서 선택한 항목에 바인딩하는 방법을 보여 주는 간단한 예를 살펴보겠습니다. 그림 1의 코드에는 선언된 ListBoxItem이 6개 있는 ListBox가 나와 있습니다. 코드 예제의 두 번째 TextBlock에는 <TextBlock.Text>라는 XML 하위 요소와 함께 XAML 속성 요소 구문에 지정된 Text라는 속성이 있습니다. 이 속성은 TextBlock에 사용할 텍스트를 포함합니다. Text 속성은 <Binding> 태그를 사용하여 ListBox의 선택된 항목에 대한 바인딩을 선언합니다.


 Binding 태그의 ElementName 특성은 TextBlock의 Text 속성이 바인딩되는 컨트롤의 이름을 나타냅니다. 그리고 Path 특성은 지금부터 우리가 직접 해볼 바인딩 작업의 대상이 되는 요소(ListBox)의 속성을 나타냅니다.


 이 코드를 실행하면 ListBox에서 색을 선택할 때 TextBlock에 색 이름이 표시됩니다.


그림 1의 코드를 약간 수정하면 단순한 구문으로 데이터 바인딩을 실행할 수 있습니다. 예로서 다음 코드 조각을 사용해 TextBlock의 <Binding> 태그를 바꾸어 보겠습니다.


<PRE class=clsCode>

<TextBlock Width="248" Height="24" Text="{Binding ElementName=lbColor, Path=SelectedItem.Content}" />

</PRE>


특성 구문이라고 하는 이 구문은 TextBlock의 Text 특성에 포함된 데이터 바인딩 코드를 간략하게 합니다. 기본적으로 Binding 태그는 특성과 함께 중괄호로 묶입니다.



바인딩 모드


 앞의 예를 좀 더 수정하여 TextBlock의 배경색을 ListBox에서 선택한 색으로 바인딩할 수 있습니다. 다음은 TextBlock에 Background 속성을 추가하고 특성 바인딩 구문을 사용하여 ListBox에서 선택한 항목의 값에 바인딩하는 코드입니다.


<PRE class=clsCode>

<TextBlock Width="248" Height="24" Text="{Binding ElementName=lbColor, Path=SelectedItem.Content, Mode=OneWay}" x:Name="tbSelectedColor" Background="{Binding ElementName=lbColor, Path=SelectedItem.Content, Mode=OneWay}"/>

</PRE>


 사용자가 ListBox에서 색을 선택하면 해당 색의 이름이 TextBlock에 표시되고 TextBlock의 배경색이 선택한 색으로 바뀝니다(그림 2 참조).




그림 2 하나의 원본을 2개의 대상에 바인딩


 앞서 소개한 예에는 Mode 특성을 OneWay로 설정하는 문이 있습니다. Mode 특성은 원본과 대상 간의 데이터 흐름 방식을 결정하는 바인딩 모드를 정의합니다. 이러한 바인딩 모드로는 OneWay 외에 OneTime, OneWayToSource, TwoWay의 세 가지가 더 있습니다.


 앞의 코드 조각에서 볼 수 있듯이 OneWay 바인딩을 사용하면 원본을 변경할 때마다 원본에서 대상으로 데이터 흐름이 발생합니다. 예에서는 이 바인딩 모드를 명시적으로 지정했지만 OneWay 바인딩은 TextBlock의 Text 속성에 대한 기본 바인딩 모드이므로 지정하지 않아도 됩니다. OneTime 바인딩도 원본에서 대상으로 데이터를 보낸다는 점은 OneWay 바인딩과 같지만 응용 프로그램이 시작되거나 DataContext가 변경될 때만 이러한 데이터 흐름이 발생한다는 차이가 있습니다. 따라서 원본의 변경 알림을 수신하지 않습니다.


 OneWay 바인딩이나 OneTime 바인딩과는 달리 OneWayToSource 바인딩은 대상에서 원본으로 데이터를 보냅니다. 마지막으로, TwoWay 바인딩은 원본 데이터를 대상으로 보내고 대상 속성의 값이 변경된 경우 변경 내용을 다시 원본으로 보냅니다.


 앞의 예에서는 ListBox 선택 항목이 변경될 때마다 원본(선택한 ListBoxItem)을 TextBlock으로 보내기 위해 OneWay 바인딩을 사용했습니다. TextBlock의 변경 내용을 다시 ListBox로 보낼 필요는 없었습니다. 사용자가 TextBlock을 편집할 수는 없기 때문입니다. 만약 TwoWay 바인딩을 사용하고자 했다면 이 코드에 TextBox를 추가하고 해당 텍스트와 배경색을 ListBox에 바인딩하고 Mode를 TwoWay로 설정했을 것입니다. 이 경우 사용자가 ListBox에서 색을 선택하면 TextBox에 선택한 색이 표시되고 배경색이 변경됩니다. 사용자가 TextBox에 Cyan과 같은 색을 입력하면 ListBox의 색 이름이 업데이트(대상에서 원본으로의 데이터 흐름)되고, ListBox가 업데이트됨에 따라 ListBox의 SelectedItem 속성에 바인딩된 모든 요소로 새 값이 전달됩니다. 즉, TextBlock도 색이 업데이트되고 텍스트 값이 새로운 색으로 설정됩니다(그림 3 참조).




그림 3 TwoWay 바인딩 실행



 TextBlock(OneWay)과 TextBox(TwoWay)를 ListBox에 바인딩하는 데 사용한 코드는 다음과 같습니다.


<PRE class=clsCode>

<TextBlock Width="248" Height="24" Text="{Binding ElementName=lbColor, Path=SelectedItem.Content, Mode=OneWay}" x:Name="tbSelectedColor" Background="{Binding ElementName=lbColor, Path=SelectedItem.Content, Mode=OneWay}"/><TextBox Width="248" Height="24" Text="{Binding ElementName=lbColor, Path=SelectedItem.Content, Mode=TwoWay}" x:Name="txtSelectedColor" Background="{Binding ElementName=lbColor, Path=SelectedItem.Content, Mode=OneWay}"/>

</PRE>


TwoWay 모드를 다시 OneWay로 바꾸면 사용자가 TextBox에서 색을 편집하더라도 변경된 값이 다시 ListBox로 전달되지 않습니다.

적합한 바인딩 모드를 선택하는 것은 중요합니다. 필자는 사용자에게 읽기 전용 데이터를 표시할 때 OneWay 바인딩을 주로 사용합니다. 그리고 사용자가 컨트롤의 데이터를 변경할 수 있고, 변경 내용이 데이터 원본(데이터 집합, 개체, XML 또는 기타 바인딩된 컨트롤)에 반영되도록 할 때 TwoWay 바인딩을 사용합니다. OneWayToSource는 사용자가 데이터 원본을 변경하더라도 데이터가 대상으로 바인딩되지는 않도록 할 때 적합합니다. 읽기 전용 컨트롤에서 화면이 로드되는 순간의 데이터 상태를 표시해야 하는 코드 개발 작업을 맡았을 때 OneTime 바인딩을 사용한 적이 있습니다. OneTime 바인딩으로 일련의 읽기 전용 컨트롤을 데이터에 바인딩하여, 사용자가 양식을 작성하고 데이터 원본의 값이 변경하더라도 바인딩된 컨트롤이 변경되지 않도록 했습니다. 이 경우 사용자는 변경 내용을 원본과 비교할 수 있습니다. OneTime 바인딩은 원본에 INotifyPropertyChanged가 구현되지 않은 경우에도 적합합니다.


바인딩 시점


 앞의 예에서 TextBox는 ListBox에서 선택한 ListBoxItem에 대한 TwoWay 바인딩을 가능하게 합니다. TextBox에서 ListBox로의 이러한 데이터 흐름은 포커스가 TextBox에서 다른 곳으로 이동할 때 발생합니다. 원본의 업데이트 시점을 정의하는 바인딩 속성인 UpdateSourceTrigger의 값을 지정하면 데이터를 다시 원본으로 보내는 이러한 데이터 흐름을 유발하는 이벤트를 변경할 수 있습니다. UpdateSourceTrigger에 대해서는 Explicit, LostFocus, PropertyChanged의 세 가지 값을 설정할 수 있습니다.


 UpdateSourceTrigger를 Explicit으로 설정하면 코드에서 BindingExpression.UpdateSource 메서드를 호출하지 않는 한 원본이 업데이트되지 않습니다. LostFocus 설정(TextBox 컨트롤의 기본값)은 포커스가 대상 컨트롤 밖으로 이동하면 원본이 업데이트됨을 나타냅니다. 또한 PropertyChanged 값은 대상 컨트롤의 바인딩된 속성이 변경될 때마다 원본이 업데이트됨을 나타냅니다. 이 설정은 바인딩이 발생하는 시점을 지시할 때 유용합니다.




XML로 바인딩


 XML이나 개체와 같은 데이터 원본에도 손쉽게 바인딩할 수 있습니다. 그림 4에는 데이터 원본으로 사용할 포함된 색 목록이 들어 있는 XmlDataProvider 샘플이 나와 있습니다. XmlDataProvider를 사용하면 XmlDataProvider 태그에 포함되거나 외부 위치를 참조하는 파일에 포함된 XML 문서 또는 코드 조각에 바인딩할 수 있습니다.


 포함된 XML 콘텐츠는 그림 4와 같이 XmlDataProvider 안에 <x:XData> 태그로 묶어서 작성해야 합니다. 또한 데이터 바인딩 대상이 XmlDataProvider를 참조할 수 있도록 XmlDataProvider에 x:Key 값을 제공해야 합니다. 그림에서 XPath 특성이 "/colors"로 설정되어 있는 것을 알 수 있습니다. 이 특성은 데이터 원본으로 사용할 XML 콘텐츠의 수준을 정의하는데, 파일이나 데이터베이스에 포함된 크기가 큰 XML 구조에 바인딩할 때 바인딩할 데이터가 루트 요소가 아닌 경우에 유용합니다.


 XmlDataProvider는 컨텍스트별 리소스에 넣을 수 있는 리소스입니다. 그림 4에서 보듯이 XmlDataProvider는 StackPanel의 컨텍스트 내에 리소스로 정의됩니다. 즉, StackPanel 내에 있는 모든 콘텐츠에서 XmlDataProvider를 사용할 수 있습니다. 리소스의 컨텍스트를 설정하면 적절한 영역에만 데이터 원본이 노출되도록 한정하기가 용이합니다. 따라서 페이지에 잘 정의되고 독립적인 컨트롤 및 지원 리소스를 만들어 가독성을 높일 수 있습니다.

리소스로 바인딩하는 구문은 요소로 바인딩하는 구문과는 조금 다릅니다. 컨트롤에 바인딩할 때는 Binding의 ElementName 속성과 Path 속성을 설정하지만 리소스에 바인딩할 때는 Source를 설정합니다. 그리고 예제의 경우 XmlDataProvider에 바인딩하므로 Binding의 XPath 속성도 설정합니다. 예를 들어 다음 코드는 ListBox의 항목을 MoreColors 리소스에 바인딩합니다. Source 속성은 리소스로 설정되며, MoreColors라는 StaticResource로 지정됩니다. XPath 속성은 항목이 XML 데이터 원본 안에 있는 <color> 요소의 name 특성에 바인딩됨을 나타냅니다.


<PRE class=clsCode>

<ListBox x:Name="lbColor" Width="248" Height="56" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Source={StaticResource MoreColors}, XPath=color/@name}"></ListBox>

</PRE>


 이 예에서는 XML이 변경되지 않으므로 StaticResource로 지정했습니다. 따라서 데이터 원본이 변경되더라도 대상으로 변경 내용이 전달되지 않습니다. DynamicResource 설정은 반대의 경우를 나타냅니다. 즉, 변경 내용이 전달되는 것입니다. 이는 시스템 테마, 세계화를 위한 언어 또는 글꼴을 참조할 때 유용합니다. DynamicResource를 사용하면 이러한 설정을 해당 설정에 바인딩된 전체 UI 요소로 동적으로 전파할 수 있습니다.


 XmlDataProvider는 XML 콘텐츠를 참조하기 위해 외부 원본을 가리킬 수도 있습니다. 예에서는 ListBox를 바인딩할 색 목록이 포함된 colors.xml이라는 파일이 사용되었습니다. 이 파일을 참조하기 위해 StackPanel에 XmlDataProvider 리소스를 하나 더 추가하고 XML 파일을 가리키도록 했습니다. 또한 Source 특성을 XML 파일의 이름으로 설정하고 x:Key를 Colors로 설정한 것을 알 수 있습니다.


<PRE class=clsCode>

<XmlDataProvider x:Key="Colors" Source="Colors.xml" XPath="/colors"/>

</PRE>


 두 XmlDataProvider는 동일한 StackPanel에 리소스로 추가되었습니다. 다음과 같이 StaticResource에 대해 설정된 이름을 변경하면 ListBox가 이 새 리소스에 자신을 바인딩하도록 지시할 수 있습니다.


<PRE class=clsCode>

<ListBox x:Name="lbColor" Width="248" Height="56" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Source={StaticResource Colors}, XPath=color/@name}"></ListBox>

</PRE>



개체 바인딩과 DataTemplate


 XmlDataProvider는 XML의 경우에 매우 유용하게 사용할 수 있지만 개체 목록이나 개체에 바인딩할 때에는 ObjectDataProvider를 리소스로 만드는 것이 좋습니다.


 ObjectDataProvider의 ObjectType은 데이터 바인딩 원본을 제공하는 개체를 지정하고 MethodName은 데이터를 가져오기 위해 호출되는 메서드를 나타냅니다. 예를 들어 PersonService라는 클래스가 있고 이 클래스에 List<Person>을 반환하는 GetPersonList 메서드가 있다면 ObjectDataProvider는 다음과 같이 됩니다.


<PRE class=clsCode>

<StackPanel.Resources><ObjectDataProvider x:Key="persons" ObjectType="{x:Type svc:PersonService}" MethodName="GetPersonList"></ObjectDataProvider></StackPanel.Resources>

</PRE>


 이 칼럼과 함께 제공되는 코드에서 PersonService 클래스와 Person 클래스는 물론, 다른 모든 샘플 코드 전체를 확인할 수 있습니다.


 ObjectDataProvider에 사용할 수 있는 속성은 이 외에도 많습니다. ConstructionParameters 속성을 사용하면 호출되는 클래스의 생성자에 매개 변수를 전달할 수 있습니다. 또한 MethodParameters 속성을 사용하여 매개 변수를 지정하거나 ObjectInstance 속성을 사용하여 개체의 기존 인스턴스를 원본으로 지정할 수도 있습니다.


 데이터를 비동기적으로 검색하려면 ObjectDataProvider의 IsAsynchronous 속성을 true로 설정하면 됩니다. 그러면 사용자가 ObjectDataProvider의 원본에 바인딩된 대상 컨트롤에 데이터가 채워지기를 기다리는 동안 화면에서 상호 작용할 수 있습니다.


 ObjectDataProvider를 추가할 때에는 data-source 클래스의 네임스페이스를 정규화해야 합니다. 이 예의 경우 <Window> 태그에 xmlns 특성을 추가하여 svc 바로 가기가 정규화되고 올바른 네임스페이스를 나타내도록 해야 합니다.


<PRE class=clsCode>

xmlns:svc="clr-namespace:DataBindingWPF"

</PRE>


 ObjectDataProvider를 통해 데이터 원본을 정의했으므로 이제 ListBox 컨트롤을 이 데이터에 바인딩합니다. 예제에서 필자는 각 ListBoxItem에 텍스트를 두 줄 표시하고자 했습니다. 첫째 줄에는 Person 인스턴스의 FullName 속성을 굵은 글꼴로 표시하고 둘째 줄에는 인스턴스의 Title과 City를 표시합니다. XAML에서는 재사용 가능한 데이터 시각화 전략을 정의할 수 있는 DataTemplate을 사용해 이를 간단히 구현할 수 있습니다.


 그림 5에는 지정한 레이아웃으로 Person 정보를 표시하도록 정의된 DataTemplate이 있는 완성된 XAML이 나와 있습니다. 그리고 DataTemplate의 DataType 속성을 설정하여 DataTemplate이 Person 클래스 형식을 참조한다는 사실을 나타냈습니다. 실제 바인딩은 DataTemplate에 지정하지 않고, ListBox 컨트롤에 지정합니다. Binding Source를 생략했기 때문에 범위 내의 현재 DataContext에 바인딩됩니다.


 그림 5에서 보듯이 ListBox의 서식은 변경하지 않으면서 ListBox에 데이터를 바인딩하기 위해 persons 리소스에 바인딩하도록 ListBox의 ItemsSource 속성을 설정했습니다. 또한 데이터가 올바르게 표시되도록 ItemTemplate 속성을 DataTemplate의 키 이름인 personLayout 리소스로 설정했습니다. 최종 결과로 그림 6과 같은 화면이 표시됩니다.