지난 글에서 실버라이트로 데이터 바인딩을 살짝 맛보았다. 이번에는실버라이트의 데이터 바인딩이 구체적으로 어떻게 이루어졌는지, 그리고 어떤 기능들을 제공하는지 알아보겠다.
데이터 바인딩의 동작 원리
데이터 바인딩은 기본적으로 UI 엘리먼트와 데이터를 연결하는 것을말한다. 이를 구현하는 것이 바로 Binding 클래스이고, System.Windows.Data 네임스페이스에서 찾을 수 있다. 지난글에서 우리가 사용했던 Text=”{Binding Path=Name}” 과 같은 바인딩 표현식에서도 Binding 지시자를 통해 UI 엘리먼트와 데이터가 연결되는 것을하였다. 이들 사이의 관계를 도식화 해보자면 다음과 같다.
[그림 1. 데이터 바인딩의 구조]
l 바인딩 엔진, 즉 Binding 클래스는 바인딩 원본과 바인딩 대상의 사이에서 서로의 값을 설정하거나 변화를 감시하는 역할을 수행한다.
l 바인딩 원본은 어떤 종류의 CLR 개체도 사용할 수 있으며 바인딩 원본의 바인딩 할 속성도 제한이 없다.
l 바인딩 대상은 반드시 FrameworkElement에서 파생된 클래스이어야 하고 바인딩 대상 속성은 반드시 DependencyProperty이어야 한다.
l 화살표는 바인딩의 방향을 나타내며 구성에 따라 단방향 혹은 양방향으로 연결할 수 있다.
l 바인딩에 의해 값이 변경될 때 ValueConverter가 지정되어 있을 경우 값이 적절하게 변환되어 설정된다.
바인딩은 다른 실버라이트 개체들과 마찬가지로 XAML과 코드로 선언할 수 있다.
[XAML] [C#]
<TextBlock Text="{Binding Id}" …/>
TextBlock tb = new TextBlock();
Binding binding = new Binding("Id");
tb.SetBinding(TextBlock.TextProperty, binding);
이렇게 Binding 개체에 의해 연결된 바인딩은 명시적으로 데이터 바인딩 할 원본 개체의 인스턴스를 설정하지 않으면 기본적으로 DataContext 속성에 설정되어 있는 데이터 개체로부터 바인딩 할 원본 속성을 찾는다. DataContext는 실버라이트의 비주얼 트리를 타고 하위 개체로 전파되므로 공통적인 데이터 바인딩을 하는 UI 집합에서 편리하게 사용할 수 있다.
[그림 2. DataContext를 이용한 바인딩 원본 개체 설정]
바인딩의 기능과 표현식
1. 바인딩 할 원본 개체의 속성 설정
앞서 소개한 것처럼 바인딩 할 원본 개체의 속성은 Binding 클래스의 Path 속성을 사용하여 지정한다. XAML에서 바인딩 표현식을 사용할 때에는 Path지시자를 생략하고 바인딩 할 원본 개체의 속성만 지정할 수 있다.
또한 Path 속성을 아예 생략할 수도 있는데, 이 경우 바인딩 대상 속성에는 바인딩 원본 개체 그 자체가 바인딩 된다.
[리스트 2. 바인딩 경로를 설정하는 XAML 표현식]
<TextBlock Text="{Binding Path=Name}" />
<!-- 또는 -->
<TextBlock Text="{Binding Name }" />
<!-- 또는 -->
<TextBlock Text="{Binding }" />
2. 명시적인 데이터 원본 설정
데이터 원본은 Binding클래스의 Source 속성으로 설정할 수 있다. Source 속성을 생략할 경우 자동으로 DataContext에서 바인딩할 속성을 가져온다. 일반적인 경우 Source 속성을 생략하고 DataContext로부터 바인딩 받는 경우가 대부분이지만 때로 명시적인 DataContext가 아닌 명시적인 원본 설정이 필요할 때도 있다.
[XAML]
[리스트 3. 명시적으로 바인딩 원본을 설정하는 XAML 표현식]
<TextBlock Text="{Binding Path=Id, Source={StaticResource MyProfile} }" />
XAML에서 Source를 명시적으로 사용하기 위해서는 먼저 해당 엘리먼트를 포함하는 사용자 컨트롤이나 App.xaml에 반드시 바인딩할 원본을 리소스로 등록한 후 StaticResource 표현식을 사용하여 설정해야 한다. 위의 예제는 리소스로 등록된 MyResource 개체를 바인딩 원본으로 사용하여 MyResource.Id 속성을 Text 속성에 바인딩한다는 의미이다.
3. 데이터의 흐름 설정
Binding 클래스의 Mode 속성으로 원본을 대상에 어떤 방향으로 바인딩 할지 설정할 수 있다. 데이터 흐름에는 다음과 같은 세 종류가 있으며 Mode 속성이 생략 될 경우 자동으로 OneWay 방식이 선택된다.
l OneTime
최초로 바인딩 원본이 설정될 때 단 한번만 바인딩 대상을 변경하는 모드.
l OneWay
바인딩된 원본이 변경사항을 알려줄 때마다 자동으로 바인딩 대상도 변경하는 모드.
l TwoWay
바인딩 대상이 변경되었을 때 역으로 바인딩 원본도 변경하는 양방향 모드.
[리스트 4. 바인딩 흐름을 설정하는 XAML 표현식]
<TextBlock Text="{Binding Id, Mode=OneTime}" />
<!-- 또는 -->
<TextBlock Text="{Binding Id, Mode=OneWay}" />
<!-- 또는 -->
<TextBlock Text="{Binding Id, Mode=TwoWay}" />
단, OneWay와 TwoWay 모드가 정상적으로 동작하기 위해서는 데이터 원본 오브젝트가 반드시 다음의 INotifyPropertyChanged 인터페이스를 구현해야 한다.
4. 데이터 원본의 변경 알림
위에서 본 데이터 바인딩의 OneWay혹은 TwoWay 모드는 데이터 원본이 변경되면 자동으로 바인딩 대상도 변경되어야 한다. 이를 위해 데이터 원본 개체는 자신의 데이터가 변경될 때 바인딩 개체에 그 사실을 알려줘야 한다.
이 과정은 모든 CLR 개체에서 자동으로 되는 것이 아니라 INotifyPropertyChanged라는 인터페이스를 상속받은 개체에서 코드를 통해 명시적으로 구현을 해야 한다.
[리스트 5. INotifyPropertyChanged 인터페이스를 구현하는 원본 개체의 예제]
public class Profile : INotifyPropertyChanged
{
private string _id;
public string Id
{
get { return _id; }
set
{
_id = value;
FirePropertyChanged("Id");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void FirePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
이렇게 데이터 원본은 PropertyChanged 이벤트를 발생시키고 바인딩 엔진은 그 이벤트를 받아 바인딩 대상 속성을 변경한다.
5. 데이터 컨버팅
때로는 바인딩 원본 속성의 타입과 바인딩 대상 속성의 타입이 일치하지 않을 수도 있다. 예를 들어 TextBlock의 Text 속성에 DateTime 타입의 값을 바인딩한다면 시스템의 로캘에 따라 “18/03/2009 PM 08:09:00”과 같이 원치 않는 형식으로 바인딩되는 것을 볼 수 있다. 이 때 IValueConverter 인터페이스를 상속하는 클래스를 하나 만들고 IValueConverter 인터페이스의 Convert 메서드와 ConvertBack 메서드를 구현하면 된다. 이렇게 구현한 ValueConverter는 먼저 XAML에 리소스로 등록을 한 후 Binding 클래스의 Converter속성에 리소스를 설정하면 된다. 또한 ConverterParameter 속성을 사용하면 값을 변환할 때 옵션을 부여할 수 있다.
[XAML]
<TextBox Text="{Binding Birthday,
Mode=TwoWay,
Converter={StaticResource DateConverter},
ConverterParameter=yyyy년 MM월 dd일}" />
[C#]
public class DateValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// 생략…
return …;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// 생략
return …;
}
#endregion
}
[리스트 6. IValueConverter 인터페이스를 구현하는 원본 개체의 예제]
Convert 메서드는 데이터 원본에서 바인딩 대상 방향으로 설정할 때 사용되고 ConvertBack 메서드는 반대로 바인딩 대상의 변화를 데이터 원본에 설정할 때 사용된다. 참고로 앞의 코드 조각에서 볼 수 있듯이 바인딩 표현식은 보기 좋게 정리하기 위해 표현식의 각 지시자 사이에 엔터를 포함한 공백을 넣을 수 있다.
6. 데이터 유효성 검사
TwoWay 바인딩이 설정된 TextBox에서 사용자가 문자열을 입력하면 입력된 문자열이 다시 데이터 원본 속성에 설정이 된다. 이 때 기본적으로 데이터 원본 속성의 설정자에서 사용자 입력의 유효성을 검사할 수도 있고 ValueConverter를 사용하여 유효한 형태로 변환할 수도 있다. 그러나 이렇게 할 경우 사용자가 잘못된 입력을 했다는 것을 상위 UI에 알려주려면 별도의 이벤트나 메서드를 사용해야 한다. 특히 복잡한 UI를 가진 입력 폼 등의 애플리케이션은 표준적인 입력 오류를 알려주는 수단이 필요하다.
Binding 클래스는 데이터 유효성 검사를 위해 ValidatesOnExceptions 속성과 NotifyOnValidationError 속성을 지원한다. 이 두 속성을 모두 True로 설정하면 데이터 원본의 속성이 바인딩 엔진에 의해 설정 중에 발생된 예외를 잡아서 BindingValidationError 이벤트로 전달한다. BindingValidationError 이벤트는 모든 FrameworkElement가 가지고 있는 이벤트로 이벤트가 발생한 엘리먼트에서부터 시작하여 이벤트가 처리될때까지 상위 엘리먼트로 계속 라우팅되는 버블 이벤트이다.
[리스트 7. BindingValidationError를 이용한 데이터 유효성 검사 예제]
<TextBox
Text="{Binding Path=Age,
NotifyOnValidationError=true,
ValidatesOnExceptions=true}" />
[C#]
public class Profile : INotifyPropertyChanged
{
private int _age;
public int Age
{
get { return _age; }
set
{
if (value < 0 || value > 150)
throw new Exception("나이는 0~150까지만 가능합니다.");
_age = value;
FirePropertyChanged("Age");
}
}
}
// 바인딩 에러 처리
void Page_BindingValidationError(object sender, ValidationErrorEventArgs e)
{
// 에러가 발생할 때 처리
if (e.Action == ValidationErrorEventAction.Added)
ErrorCaption.Text = e.Error.Exception.Message;
// 에러가 해결되었을 때 처리
else if (e.Action == ValidationErrorEventAction.Removed)
ErrorCaption.Text = "";
e.Handled = true;
}
지금까지 실버라이트 데이터 바인딩의 원리와 중요한 기능들을 살펴보았다. 물론 이것들은 데이터 바인딩의 가장 기초적인 부분이다. 보다 효과적인 사용을 위해서는 세심한 데이터 모델 설계와 UI의 구성, 그리고 무엇보다도 디자이너와의 협업이 중요하다. 다음 글에서는 데이터 바인딩을 보다 효과적으로 사용하는 방법과 디자이너와 협업하기 위한 방법에 대해 알아보도록 하겠다.
-----------------------------