I posted how to use DataBinding with Behaviors in Silverlight 3 recently. However, it turns out, it's impossible or very limitted.

Follow code is what I expected to work.

<Grid x:Name="LayoutRoot" Background="White">
    <Button Content="Test" HorizontalAlignment="Right">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="Click">
                <hi:ShowMessageBoxAction x:Name="Action1"/>
            </i:EventTrigger>
        </i:Interaction.Triggers

        <i:Interaction.Behaviors>
            <hi:BindingProxyBehavior
                        TargetName="Action1"
                        TargetPropertyName="Message"
                        Binding="{Binding Path=MockString}" />
        </i:Interaction.Behaviors>
    </Button>
</Grid>

[It doesn't work, 'cause Behaviors are not FrameworkElements]

- ShowMessageBoxAction which named "Action1" by x:Name attribute has a property named Message.
- BindingProxyBehavior is a Behavior. And it figures out "Action1" by AssociatedObject(LayoutRoot).FindName method when it has been invoked.

But It's impossible, 'cause Behaviors are not Frameworks so they can't be found by FrameworkElement.FindName method.

It's OK, I can understand why is it impossible. However how do you think about code follow:

<DataTemplate x:Key="DataTemplate1">
    <Grid>
        <Button HorizontalAlignment="Left" Content="{Binding Mode=OneWay, Path=Title}">
            <i:Interaction.Triggers>
                <i:EventTrigger x:Name="Trriger1" EventName="Click">
                    <hi:ShowMessageBoxAction x:Name="ShowMessageAction"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>           

            <i:Interaction.Behaviors>
                <hi:BindingProxyBehavior
                    TargetName="ShowMessageAction"
                    TargetPropertyName="Message"
                    Binding="{Binding Path=Description}" />
            </i:Interaction.Behaviors>
        </Button>
    </Grid>
</DataTemplate>

[Does it work or not?]

It's similar to first one, except root Grid is in the DataTemplate.
Guess what? it works. Very well.
HOW COME!? Isn't it wierd?

Now I have to give up to do DataBinding with Behaviors in Silverlight 3 by that way.
It makes me depressed. "No binding, more work".
Anyway all I really want to ask Microsoft is, just allow us DataBinding with Behaviors, so we can create application rapidly, design-friendly, and even elegantly! Like follow code:

<Grid x:Name="LayoutRoot">
    <Button Content="Test">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="Click">
                <hi:ShowMessageBoxAction
                    x:Name="Action1"
                    Message="{Binding Path=Description}" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Button>
</Grid>

[Elegant DataBinding with Behaviors]

Here for sample project:

저작자 표시 동일 조건 변경 허락
신고
Posted by gongdo
Because Behaviors are not FrameworkElements, you can't use DataBinding with Behaviors. I posted this about it before.

<Button Content="Description">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Click">
            <local:ShowMessageBoxAction Message="{Binding Description}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</
Button>
[You can't use DataBinding with Behaviors like above]

Well, I'm still thinking about that it's the best way to use DataBinding with Behaviors. Why I can't do like that! However, if you really want to mix up DataBinding and Behaviors just like me, there are some work-arounds.

First, you can attach a 'Binder' class to FrameworkElement as an AttachedProperty. Morten Nielsen writed such a cool class, 'SurrogateBinder' which is general data binding helper. It could be attached to any FrameworkElement, and it passes through the value which from data source to actual target. Here's sample code:

<TextBox RenderTransformOrigin="0.5,0.5"
        Text="Hello Universe!"
        binders:SurrogateBind.Value="{Binding Path=MoreValues.Heading}" 
        binders:SurrogateBind.Target="RenderTransform.Children.Item[1].Angle" >
    <TextBox.RenderTransform>
        <TransformGroup>
            <ScaleTransform />
            <RotateTransform />
        </TransformGroup>
    </TextBox.RenderTransform>
</TextBox>

It works well with any property of FrameworkElement but not for AttachedProperty like Behaviors. So you must modify little bit to targeting AttachedProperty. Also SurrogateBinder can't be set on Blend, so most of designers might not want to use it. Blend-support is important for XAML design. So I don't prefer in-line AttachedProperty as a solution, even though it is cool & smart.

SecondPete Blois who is a program manager of Expression Blend introduced this article to show how Binding property could be used with Blend for binding to non-FrameworkElement, like Behaviors. As you know, we can't use DataBinding to properties on Behaviors, 'cause Behavior is not a FrameworkElement. But Blend seems to allow DataBinding to a property which is type of Binding. Hey! it's so tricky isn't it? :(

<i:Interaction.Behaviors>
    <id:DataStateBehavior Binding='{Binding IsOnline}' Value='True'/>
</i:Interaction.Behaviors>
[You can use DataBinding only on a property which is type of Binding. Could you guess that?]

It looks much cooler than other, but it's not a general solution. Imagine that you want to bind to 3rd party's Behavior, you nerver can do, of course it might has no Binding property! However, sample is just sample and actually it's not only for DataBinding with Behavior but also for Trigger which is raised by condition of data.

Thrid. Now, here's my approach.

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Click">
        <hi:ShowMessageBoxAction x:Name="ShowMessageAction"/>
    </i:EventTrigger>
</i:Interaction.Triggers>
<i:Interaction.Behaviors>
    <hi:BindingProxyBehavior
        TargetName="ShowMessageAction"
        TargetPropertyName="Message"
        Binding="{Binding Path=Description}" />
</i:Interaction.Behaviors>


Sigh, looks messy huh? But it based on standard Behavior, so it works well with Blend and you don't need to worry about writing these XAML code. Blend will generate XAML code as well as you write.
'BindingProxyBehavior' implements Behavior class, and it exposes  some properties which are needed to determine DataBinding & binding target. It focuses only one binding per an instance, but you can attach multiple instance of it, 'cause it's a standard Behavior.

Here's sample project. Enjoy it :)


Actually I'dont like any of them, but first code-block which is impossible currently. So I really want to say, why not with DataBinding? Please?
저작자 표시 동일 조건 변경 허락
신고
Posted by gongdo

The Binding object only can take DependencyProperty on the FrameworkElement as a binding target in Silverlight. ‘Course, all of methods and properties related to DataBinding is on the FrameworkElement. DataBinding model might be designed to bind a source data to target ‘UI Element’ at the first time, I guess. It’s reasonable, and it looks like flawless. Just Before Behaviors feature was added to Silverlight.

Oneday, I created a simple Silverlight 3 application to explain Behaviors for my coworkers with Silverlight 3. Me and my coworkers were quite familiar with DataBinding through expierence of Silverlight 2, so I wanted to show them a demo with DataBinding.

The behavior what I created was very simple, you might already know ‘ShowMessageBoxAction’ if you tried Silverlight 3 Behaviors. Here’s the code:

 public class ShowMessageBoxAction : TriggerAction<DependencyObject>
{
    public string Message { get; set; }

    protected override void Invoke(object parameter)
    {
        MessageBox.Show(Message);
    }
}

Yes it was a triggered action which shows MessageBox when Click event raised on Button. Nothing special, isn’t it? And then I wanted to mix it up DataBinding like:

<Button Content="Description">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Click">
            <local:ShowMessageBoxAction Message="{Binding Description}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</
Button>

If you are a developer who is familiar with DatatBinding, you might be aware of my mistake and it would never work. As I mentioned above, a target of DataBinding must be a DependencyProperty on FrameworkElement. I think I got out of my mind at that time. It’s absolutely my mistake because all the Triggers and Behaviors were not FrameworkElement so these were not parts of VisualTree.

However, was that approach bad or evil? Well, I don’t think so. Because it looks like natural. I mean, when we write some XAML codes, It’s not easy to be aware of whether an element(tag) is a FrameworkElement or just other AttachedProperty. And it’s almost impossible to explain it reasonably for designers. Imagine that how designers add some behaviors to an element by using Expression Blend, Behaviors will shown on ‘Object Tree Panel’, not a just Property Panel. Who could know it’s impossible to use DataBinding untill opened a property panel of a Behavior and realize that DataBinding property was disabled.

What do I want to say? Well, I’m understand why Behaviors can’t be binded using DataBinding. However I think DataBinding makes work easy and efficeint, so how about if DataBinding could reach through any DependencyObject or AttachedProperty element?


저작자 표시 동일 조건 변경 허락
신고
Posted by gongdo
간 마이크로소프트웨어 4월호에 기고한 내용이에요. 데이터 바인딩의 기초적인 원리와 어떻게 사용하는지에 대한 설명 및 소스가 있어요.
--------------------------

지난 글에서 실버라이트로 데이터 바인딩을 살짝 맛보았다이번에는실버라이트의 데이터 바인딩이 구체적으로 어떻게 이루어졌는지그리고 어떤 기능들을 제공하는지 알아보겠다

데이터 바인딩의 동작 원리 

데이터 바인딩은 기본적으로 UI 엘리먼트와 데이터를 연결하는 것을말한다이를 구현하는 것이 바로 Binding 클래스이고, ystem.Windows.Data 네임스페이스에서 찾을 수 있다지난글에서 우리가 사용했던 Text=”{Binding Path=Name}” 과 같은 바인딩 표현식에서도 Binding 지시자를 통해 UI 엘리먼트와 데이터가 연결되는 것을하였다이들 사이의 관계를 도식화 해보자면 다음과 같다.


[그림 1. 데이터 바인딩의 구조]

l  바인딩 엔진, Binding 클래스는 바인딩 원본과 바인딩 대상의 사이에서 서로의 값을 설정하거나 변화를 감시하는 역할을 수행한다.

l  바인딩 원본은 어떤 종류의 CLR 개체도 사용할 수 있으며 바인딩 원본의 바인딩 할 속성도 제한이 없다.

l  바인딩 대상은 반드시 FrameworkElement에서 파생된 클래스이어야 하고 바인딩 대상 속성은 반드시 DependencyProperty이어야 한다.

l  화살표는 바인딩의 방향을 나타내며 구성에 따라 단방향 혹은 양방향으로 연결할 수 있다.

l  바인딩에 의해 값이 변경될 때 ValueConverter가 지정되어 있을 경우 값이 적절하게 변환되어 설정된다.

 

바인딩은 다른 실버라이트 개체들과 마찬가지로 XAML과 코드로 선언할 수 있다.

[XAML]
<TextBlock Text="{Binding Id}" />

[C#]
TextBlock tb = new TextBlock();
Binding binding = new Binding("Id");
tb.SetBinding(TextBlock.TextProperty, binding);

 [리스트 1. 데이터의 Id속성을 UI Text속성에 바인딩하는 코드 조각]

이렇게 Binding 개체에 의해 연결된 바인딩은 명시적으로 데이터 바인딩 할 원본 개체의 인스턴스를 설정하지 않으면 기본적으로 DataContext 속성에 설정되어 있는 데이터 개체로부터 바인딩 할 원본 속성을 찾는다. DataContext는 실버라이트의 비주얼 트리를 타고 하위 개체로 전파되므로 공통적인 데이터 바인딩을 하는 UI 집합에서 편리하게 사용할 수 있다.


[그림 2. DataContext를 이용한 바인딩 원본 개체 설정]

바인딩의 기능과 표현식

 바인딩은 단순히 데이터와 UI 엘리먼트를 연결하는 기능 외에도 다양한 바인딩 시나리오를 위한 기능이 준비되어 있다. 각각의 기능과 표현식에 대해 알아보자. 아래에 소개한 기능과 표현식의 모든 소스 코드는 [DataBindingFeatures]의 각 폴더에서 찾아 볼 수 있다.

1.      바인딩 할 원본 개체의 속성 설정

앞서 소개한 것처럼 바인딩 할 원본 개체의 속성은 Binding 클래스의 Path 속성을 사용하여 지정한다. XAML에서 바인딩 표현식을 사용할 때에는 Path지시자를 생략하고 바인딩 할 원본 개체의 속성만 지정할 수 있다

또한 Path 속성을 아예 생략할 수도 있는데, 이 경우 바인딩 대상 속성에는 바인딩 원본 개체 그 자체가 바인딩 된다.

[XAML]
<TextBlock Text="{Binding Path=Name}" />
<!-- 또는 -->
<TextBlock Text="{Binding Name }" />
<!-- 또는 -->
<TextBlock Text="{Binding }" />

[리스트 2. 바인딩 경로를 설정하는 XAML 표현식]

2.      명시적인 데이터 원본 설정

데이터 원본은 Binding클래스의 Source 속성으로 설정할 수 있다. Source 속성을 생략할 경우 자동으로 DataContext에서 바인딩할 속성을 가져온다. 일반적인 경우 Source 속성을 생략하고 DataContext로부터 바인딩 받는 경우가 대부분이지만 때로 명시적인 DataContext가 아닌 명시적인 원본 설정이 필요할 때도 있다.

[XAML]
<TextBlock Text="{Binding Path=Id, Source={StaticResource MyProfile} }" />

[리스트 3. 명시적으로 바인딩 원본을 설정하는 XAML 표현식]

XAML에서 Source를 명시적으로 사용하기 위해서는 먼저 해당 엘리먼트를 포함하는 사용자 컨트롤이나 App.xaml에 반드시 바인딩할 원본을 리소스로 등록한 후 StaticResource 표현식을 사용하여 설정해야 한다. 위의 예제는 리소스로 등록된 MyResource 개체를 바인딩 원본으로 사용하여 MyResource.Id 속성을 Text 속성에 바인딩한다는 의미이다.

3.      데이터의 흐름 설정

Binding 클래스의 Mode 속성으로 원본을 대상에 어떤 방향으로 바인딩 할지 설정할 수 있다. 데이터 흐름에는 다음과 같은 세 종류가 있으며 Mode 속성이 생략 될 경우 자동으로 OneWay 방식이 선택된다.

l  OneTime
최초로 바인딩 원본이 설정될 때 단 한번만 바인딩 대상을 변경하는 모드.

l  OneWay
바인딩된 원본이 변경사항을 알려줄 때마다 자동으로 바인딩 대상도 변경하는 모드.

l  TwoWay
바인딩 대상이 변경되었을 때 역으로 바인딩 원본도 변경하는 양방향 모드.

[XAML]
<TextBlock Text="{Binding Id, Mode=OneTime}" />
<!-- 또는 -->
<TextBlock Text="{Binding Id, Mode=OneWay}" />
<!-- 또는 -->
<TextBlock Text="{Binding Id, Mode=TwoWay}" />

[리스트 4. 바인딩 흐름을 설정하는 XAML 표현식

, OneWay TwoWay 모드가 정상적으로 동작하기 위해서는 데이터 원본 오브젝트가 반드시 다음의 INotifyPropertyChanged 인터페이스를 구현해야 한다.

4.      데이터 원본의 변경 알림

위에서 본 데이터 바인딩의 OneWay혹은 TwoWay 모드는 데이터 원본이 변경되면 자동으로 바인딩 대상도 변경되어야 한다. 이를 위해 데이터 원본 개체는 자신의 데이터가 변경될 때 바인딩 개체에 그 사실을 알려줘야 한다.

이 과정은 모든 CLR 개체에서 자동으로 되는 것이 아니라 INotifyPropertyChanged라는 인터페이스를 상속받은 개체에서 코드를 통해 명시적으로 구현을 해야 한다.

[C#]
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
}

[리스트 5. INotifyPropertyChanged 인터페이스를 구현하는 원본 개체의 예제

이렇게 데이터 원본은 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가 가지고 있는 이벤트로 이벤트가 발생한 엘리먼트에서부터 시작하여 이벤트가 처리될때까지 상위 엘리먼트로 계속 라우팅되는 버블 이벤트이다.

[XAML]
<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;
}

[리스트 7. BindingValidationError를 이용한 데이터 유효성 검사 예제

지금까지 실버라이트 데이터 바인딩의 원리와 중요한 기능들을 살펴보았다. 물론 이것들은 데이터 바인딩의 가장 기초적인 부분이다. 보다 효과적인 사용을 위해서는 세심한 데이터 모델 설계와 UI의 구성, 그리고 무엇보다도 디자이너와의 협업이 중요하다. 다음 글에서는 데이터 바인딩을 보다 효과적으로 사용하는 방법과 디자이너와 협업하기 위한 방법에 대해 알아보도록 하겠다.

-----------------------------

다음호에 봐요 :D
저작자 표시 동일 조건 변경 허락
신고
Posted by gongdo


티스토리 툴바