'프로그래밍/C#/WPF'에 해당되는 글 2건

  1. 2015.06.21 WPF 데이터 바인딩의 기초 - 2
  2. 2015.06.16 WPF 데이터 바인딩의 기초 - 1 (1)
프로그래밍/C#/WPF2015.06.21 17:51

1. 서로 다른 데이터 타입 사이에서의 데이터 바인딩

앞의 예제(http://lyb1495.tistory.com/108)에서 로그인 처리를 위해 사용자의 아이디와 비밀번호를 TextBoxText 속성을 통해 입력 받고, 이를 LoginViewModelLoginID, LoginPasswd 프로퍼티에 바인딩하는 방법에 대해 알아보았다사실 지금까지 별도의 언급은 없었지만 바인딩 대상의 속성 타입과(TextBoxText 속성), 바인딩 소스의 데이터 타입(LoginViewModelLoginID, LoginPasswd 프로퍼티)이 동일한가의 여부는 중요한 문제다.


앞의 예제에서는 TextBoxText 속성 타입과 LoginID, LoginPasswd 프로퍼티 타입 모두 string으로 동일한 데이터 타입을 지니고 있기 때문에 별다른 문제 없이 데이터 바인딩이 가능했던 것이다. 그렇다면 바인딩 대상의 속성과 바인딩 소스의 데이터 타입이 다르다면 과연 데이터 바인딩은 어떻게 해야 할까?


예를 들어 다음과 같은 상황을 가정해보자ViewModel에서는 bool 타입의 특정 프로퍼티를 제공하고 있고, 이 프로퍼티에 UI 요소의 화면 출력 여부를 결정하는 Visibility 속성을 바인딩 하려고 한다. bool 타입과 Visibility 타입은 명백히 다른 데이터 타입을 지닌다. (Visibility에 대해 좀 더 자세히 설명하자면, UI 요소의 화면 출력을 결정하기 위한 enum 데이터 타입이다.)

namespace System.Windows
{
    // 요약:
    //     요소의 표시 상태를 지정합니다.
    public enum Visibility
    {
        // 요약:
        //     요소를 표시합니다.
        Visible = 0,
        //
        // 요약:
        //     요소를 표시하지 않지만 레이아웃에 요소를 위한 공간을 남겨 둡니다.
        Hidden = 1,
        //
        // 요약:
        //     요소를 표시하지 않고 레이아웃에 요소를 위한 공간을 남겨 두지 않습니다.
        Collapsed = 2,
    }
}

먼저 다음과 같이 버튼3개를 지니는 간단한 XAML을 작성해 보자. 각 버튼은 버튼의 화면 출력여부를 결정하기 위해 Visibility 속성에 ViewModel에서 제공하는 bool 타입의 프로퍼티를 바인딩 한다. (참고로 TextBoxVisibility는 기본값으로 OneWay 바인딩을 사용한다.)

<Window x:Class="WpfApplication1.VisibilityBidingWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="VisibilityBidingWindow" Height="300" Width="400">

    <StackPanel>
        <Button Content="Button1" Visibility="{Binding VisibilityForButton1}"/>
        <Button Content="Button2" Visibility="{Binding VisibilityForButton2}"/>
        <Button Content="Button3" Visibility="{Binding VisibilityForButton3}"/>
    </StackPanel>

</Window>

그리고 위 XAMLCode Behind는 다음과 같이 간단하게 DataContextViewModel을 설정하는 것으로 끝이 난다.

using System.Windows;
using WpfApplication1.ViewModels;

namespace WpfApplication1
{
    public partial class VisibilityBidingWindow : Window
    {
        public VisibilityBidingWindow()
        {
            InitializeComponent();
            this.DataContext = new VisibilityBidingViewModel(true, false, false);
        }
    }
}

계속해서 VisibilityBidingViewModel의 코드를 확인해보자. VisibilityBidingViewModel은 각각의 버튼 Visibility 속성에 바인딩 될 3개의 bool 타입의 프로퍼티를 지닌다. VisibilityBidingViewModel의 생성자는 이들 3개의 bool 타입 프로퍼티를 초기화하기 위한 매개변수를 받도록 한다. (위에서 살펴본 XAMLCode Behind는 매개변수로 true, false, false를 입력하고 있으므로 첫 번째 버튼만 화면에 출력되고 나머지 2개 버튼은 화면에 출력되지 않는 것이 올바른 결과가 된다.)


마지막으로 INotifyPropertyChanged 인터페이스 구현을 추가해 OneWay 또는 TwoWay 바인딩 방식에서 바인딩 소스(ViewModel)의 변화를 바인딩 대상(UI 요소)으로 올바르게 전파하도록 한다.

using System.ComponentModel;

namespace WpfApplication1.ViewModels
{
    public class VisibilityBidingViewModel : INotifyPropertyChanged
    {
        private bool _visibilityForButton1;
        public bool VisibilityForButton1
        {
            get { return _visibilityForButton1; }
            set
            {
                _visibilityForButton1 = value;
                OnPropertyUpdate("VisibilityForButton1");
            }
        }

        private bool _visibilityForButton2;
        public bool VisibilityForButton2
        {
            get { return _visibilityForButton2; }
            set
            {
                _visibilityForButton2 = value;
                OnPropertyUpdate("VisibilityForButton2");
            }
        }

        private bool _visibilityForButton3;
        public bool VisibilityForButton3
        {
            get { return _visibilityForButton3; }
            set
            {
                _visibilityForButton3 = value;
                OnPropertyUpdate("VisibilityForButton3");
            }
        }

        public VisibilityBidingViewModel(bool visibilityForButton1, bool visibilityForButton2, bool visibilityForButton3)
        {
            _visibilityForButton1 = visibilityForButton1;
            _visibilityForButton2 = visibilityForButton2;
            _visibilityForButton3 = visibilityForButton3;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyUpdate(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

소스 코드를 빌드하고 실행해보면 아래와 같은 화면을 볼 수 있을 것이다.



Code Behind는 매개변수로 true, false, false를 입력하고 있으므로 첫 번째 버튼만 화면에 출력되고 나머지 2개 버튼은 화면에 출력되지 않아야 하지만 3개의 버튼이 모두 보인다. 바인딩에 무엇인가 문제가 있다는 뜻 이다. 디버그 모드에서 출력되는 정보를 확인해보면 다음과 같은 메시지를 확인할 수 있다.


 System.Windows.Data Error: 1 : Cannot create default converter to perform 'one-way' conversions between types 'System.Boolean' and 'System.Windows.Visibility'. Consider using Converter property of Binding. BindingExpression:Path=VisibilityForButton1; DataItem='VisibilityBidingViewModel' (HashCode=46591244); target element is 'Button' (Name=''); target property is 'Visibility' (type 'Visibility')


System.Windows.Data Error: 5 : Value produced by BindingExpression is not valid for target property.; Value='True' BindingExpression:Path=VisibilityForButton1; DataItem='VisibilityBidingViewModel' (HashCode=46591244); target element is 'Button' (Name=''); target property is 'Visibility' (type 'Visibility')


, bool 타입과 Visibility 타입이 서로 다르기 때문에 바인딩을 정상적으로 처리할 수 없다는 메시지 이다.



2. IValueConverter

서로 다른 데이터 타입 사이의 데이터 바인딩을 처리하기 위해 사용할 수 있는 것이 바로 IValueConverter이다. IvalueConverterConvert()ConvertBack() 이라는 2개의 메소드를 제공하는데 OneWay 바인딩 방식에서는 Convert() 메소드만 사용한다. , Convert() 메소드는 바인딩 소스의 데이터 타입으로부터 바인딩 대상 속성의 데이터 타입으로 변환을 처리하며, ConvertBack() 메소드는 이와 반대로 바인딩 대상 속성 데이터 타입으로부터 바인딩 소스 데이터 타입으로 변환을 처리한다. (ConvertBack() 메소드의 구현은 Convert() 메소드 구현의 역 과정이라고도 할 수 있다.)

Object Convert(
	Object value, // 바인딩 소스에서 생성 되는 값입니다
	Type targetType, // 바인딩 대상 속성의 형식입니다.
	Object parameter, // 사용할 변환기 매개 변수입니다.
	CultureInfo culture // 변환기에서 사용할 문화권입니다.
)

Object ConvertBack(
	Object value, // 바인딩 대상에서 생성 되는 값입니다.
	Type targetType, // 변환할 대상 형식입니다.
	Object parameter, // 사용할 변환기 매개 변수입니다.
	CultureInfo culture // 변환기에서 사용할 문화권입니다.
)

그럼 IvalueConverter를 이용해 bool 타입과 Visibility 타입 사이의 데이터 바인딩을 위한 타입 변환기를 작성해 보자. 먼저 바인딩 소스(ViewModel)에서 제공하는 데이터 타입 boolUI 요소의 Visibility 속성에 적용 시키기 위해 Convert() 메소드를 작성한다. 간단하게 bool 변수 값이 true 라면 Visibility.Visible를 반환해 화면에 UI 요소가 출력되도록 하고, false 라면 Visibility.Hidden를 반환해 UI 요소가 출력되지 않도록 한다.

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace WpfApplication1.Converters
{
    public class BooleanToVisibilityConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            bool flag = (value is bool) ? (bool)value : false;
            return flag ? Visibility.Visible : Visibility.Hidden;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }
}

UI 요소의 Visibility 속성은 OneWay 바인딩 방식을 사용하므로 ConvertBack() 메소드는 사용하지 않는다.


마지막으로 XAML에 위에서 작성한 Converter를 선언하고 바인딩 구문을 다음과 같이 변경한다.

<Window x:Class="WpfApplication1.VisibilityBidingWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:cvts="clr-namespace:WpfApplication1.Converters"
        Title="VisibilityBidingWindow" Height="300" Width="400">

    <Window.Resources>
        <cvts:BooleanToVisibilityConverter x:Key="boolToVisibility"/>
    </Window.Resources>
    <StackPanel>
        <Button Content="Button1" Visibility="{Binding VisibilityForButton1, Converter={StaticResource boolToVisibility}}"/>
        <Button Content="Button2" Visibility="{Binding VisibilityForButton2, Converter={StaticResource boolToVisibility}}"/>
        <Button Content="Button3" Visibility="{Binding VisibilityForButton3, Converter={StaticResource boolToVisibility}}"/>
    </StackPanel>

</Window>

애플리케이션을 다시 빌드 한 후 실행해보면 첫 번째 버튼만 화면에 출력되는 것을 확인할 수 있다.



3. 데이터 변환 파라미터 사용

XAMLCode Behind에서 VisibilityBidingViewModel를 생성할 때 bool 값을 true, false, true 순으로 지정해보자. 이것은 아래 그림처럼 두 번째 버튼을 제외하고 첫 번째와 세 번째 버튼만 화면에 출력하도록 한다.



두 번째 버튼의 공간이 비어있는 것을 확인할 수 있는데, 이것은 BooleanToVisibilityConverter 구현시 bool 값이 false라면 Visibility.Hidden를 반환하도록 했기 때문이다. (Visibility.HiddenUI 요소가 화면에 출력되지는 않지만 공간을 점유한다. 화면에 출력되지 않고 공간도 차지하지 않게 하려면 Visibility.Collapsed를 반환하도록 한다.) 필요에 따라 bool 값이 false라면 Visibility.Hidden이 아닌 Visibility.Collapsed를 반환하도록 하려면 어떻게 해야 할까? 한 가지 방법은 Convert(), ConvertBack() 메소드의 매개 변수 중 하나인 parameter를 활용하는 것이다.

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace WpfApplication1.Converters
{
    public class BooleanToVisibilityConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            bool flag = (value is bool) ? (bool)value : false;
            bool collapsed = (parameter as string) == "collapsed";
            if (flag)
                return Visibility.Visible;
            return collapsed ? Visibility.Collapsed : Visibility.Hidden;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }
}

변경 된 Convert() 메소드를 보면 bool 값이 false일때, 매개변수 parameterstring 타입이고 그 값이 “collapsed” 라면 반환 값으로 Visibility.Collapsed를 지정했다. 그 외에는 이전 구현 대로 Visibility.Hidden를 반환하도록 한다그리고 XAML의 바인딩 구문을 아래와 같이 변경한다. 이것은 ConverterParameter를 통해 Convert() 메소드의 매개변수 중 하나인 parameter에 “collapsed” 값을 지니는 string을 넘기는 방법을 보여준다.


<Button Content="Button2" Visibility="{Binding VisibilityForButton2, Converter={StaticResource boolToVisibility}, ConverterParameter=collapsed}"/>




애플리케이션을 다시 빌드 하고 실행하면 아래 그림처럼 두 번째 버튼의 공간까지 사라지는 것을 확인할 수 있다.




4. 다중 입력값 처리를 위한 IMultiValueConverter

IMultiValueConverterMultiBinding을 통해 2개 이상의 입력 값이 존재하고 이들을 특정 데이터 타입으로 변환해야 할 때 사용할 수 있다. IMultiValueConverterIValueConverter처럼 Convert()ConvertBack() 이라는 2개의 메소드를 제공하며 사용법은 IvalueConverter와 큰 차이가 없다. 다만 Convert(), ConvertBack() 메소드 시그니처가 IValueConverter와는 조금 다르다는 것에 주의하도록 한다.

Object Convert(
	Object[] values, // 배열 값의 소스 바인딩에 MultiBinding 생성 합니다. 값 UnsetValue 소스 바인딩 값에 대 한 변환을 제공 합니다 있음을 나타냅니다. 
	Type targetType, // 바인딩 대상 속성의 형식입니다.
	Object parameter, // 사용할 변환기 매개 변수입니다.
	CultureInfo culture // 변환기에서 사용할 문화권입니다.
)

Object[] ConvertBack(
	Object value, // 바인딩 대상에서 생성 하는 값입니다.
	Type[] targetTypes, // 배열 형식으로 변환 합니다. 배열 길이 수와의 반환 방법에 대해 제안 된 값을 나타냅니다. 
	Object parameter, // 사용할 변환기 매개 변수입니다.
	CultureInfo culture // 변환기에서 사용할 문화권입니다.
)


5. 참고

https://msdn.microsoft.com/ko-kr/library/system.windows.data.ivalueconverter.convertback(v=vs.110).aspx

https://msdn.microsoft.com/ko-kr/library/system.windows.data.imultivalueconverter.convertback(v=vs.110).aspx


저작자 표시 비영리 변경 금지
신고

'프로그래밍 > C#/WPF' 카테고리의 다른 글

WPF 데이터 바인딩의 기초 - 2  (0) 2015.06.21
WPF 데이터 바인딩의 기초 - 1  (1) 2015.06.16
Posted by devop
프로그래밍/C#/WPF2015.06.16 17:56

1. 데이터 바인딩 배경

WPF에서 데이터 바인딩이란 XAML로 표현되는 UI 요소와 ViewModel로 표현되는 데이터 사이에 관계를 맺는 기술을 의미한다. 이를 통해 UI 부분과 데이터 부분을 서로 독립적으로 다룰 수 있으며, 이것은 곧 UI 디자이너와 개발자의 역할을 보다 분명하게 정의하고 협업을 효율적으로 할 수 있게 한다.

또한, 굳이 UI 디자이너와 개발자 사이의 협업 관계를 고려하지 않는다 해도 애플리케이션 로직(데이터)과 프레젠테이션(UI) 부분을 분명하게 분리한다는 것은 개발 생산성이나 추후 유지보수성을 고려해보았을 때 매우 바람직한 시도라 할 수 있다. 실제로 이러한 시도의 일환으로 과거부터 존재했던 수 많은 MVC 프레임워크를 일일이 논하지 않아도 많은 개발자들은 이미 애플리케이션 로직과 프레젠테이션 부분을 분리하는데 많은 관심과 노력을 기울이고 있다는 것을 알 수 있다.



2. 데이터 바인딩 구조

WPF 데이터 바인딩을 구성하는 요소는 크게 3가지로 생각해 볼 수 있다. UI 요소를 의미하는 바인딩 대상(보다 정확하게는 UI요소의 DependencyObject 속성), 데이터를 의미하는 바인딩 소스 그리고 이 둘 사이의 관계를 맺어주는 바인딩 개체가 바로 그것이다.

바인딩 개체가 제공하는 바인딩 방식은 4가지가 있다.


첫 번째, OneWay방식은 바인딩 소스에서 바인딩 대상 방향으로만 데이터 바인딩을 제공한다. 예를 들어 TextBox의 Text 속성(바인딩 대상)에 바인딩 된 string 객체(바인딩 소스)가 있다면, string 객체를 수정했을 때 수정 된 값이 TextBox에 반영된다. 그러나 반대로 TextBox의 Text속성이 변경되어도 여기에 바인딩 된 string 객체는 변경되지 않는 않는다.


두 번째, TwoWay는 바인딩 소스와 바인딩 대상 양방향 모두 데이터 바인딩을 제공한다. 즉, OneWay에서 TextBox의 Text 속성을 변경하면 여기에 바인딩 된 string 객체의 값도 함께 변경된다는 것을 의미한다. 대부분의 UI 요소는 기본값으로 OneWay 바인딩을 사용하지만 TextBox의 Text속성, CheckBox의 IsChecked 속성 등은 TwoWay 바인딩을 기본값으로 한다.


세 번째, OneWayToSoruce는 OneWay방식의 반대로 동작한다.

네 번째, 아래 그림에서는 표현되어 있지 않지만 OneTime 방식으로 최초 바인딩 소스 값이 바인딩 대상 속성 값을 초기화 하지만 그 이후는 어떤 변환도 바인딩 대상, 바인딩 소스 모두에 반영되지 않는 방식이다.


위 4가지 데이터 바인딩 방식에서 조금 더 관심을 가져야 하는 부분은 TwoWay 바인딩 방식이다.


TwoWay 바인딩은 바인딩 대상의 속성 값이 변경되면, 해당 변경 내용을 다시 바인딩 소스로 전파하는데 이 과정을 UpdateSourceTrigger라 한다. (OneWayToSource 바인딩도 UpdateSourceTrigger를 정의할 수 있다.)


UpdateSourceTrigger

설명

LostFocus

UI 요소가 포커스를 잃었을 때 바인딩 소스를 업데이트한다.

) TextBox가 포커스를 잃었을 때 TextBoxText 속성 값을 여기에 바인딩 된 string 객체로 전파한다.

PropertyChanged

UI요소의 바인딩 된 속성값이 변경될 때 바인딩 소스를 업데이트한다.

) TextBoxText 속성값이 변경되면 여기에 바인딩 된 string 객체로 전파한다.

Explicit

애플리케이션에서 명시적으로 UpdateSource를 호출할 때

) 사용자가 특정 버튼을 클릭했을 때 UpdateSoruce를 실행해 TextBoxText 속성값을 여기에 바인딩 된 string 객체로 전파한다.



3. 예제

먼저 데이터 바인딩을 사용하지 않을 경우 나타날 수 있는 애플리케이션 코드를 살펴보자.


아래  XAML은 사용자 로그인을 처리하기 위해 아이디와 비밀번호를 입력 받고, 로그인 처리를 실행하기 위한 1개의 버튼을 정의하고 있다. 2개의 TextBox와 1개의 버튼은 x:Name을 통해 고유 식별자를 정의하고 있는데 이것은 XAML의 Code Behind(XAML파일명에 .cs확장자를 더한 클래스 파일이다. 예를 들어 MainWindow.xaml은 MainWindow.xaml.cs라는 Code Behind 파일을 지니게 된다.)에서 해당 UI 요소를 접근하는데 사용된다.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="300" Width="400">

    <StackPanel>
        <StackPanel.Resources>
            <Style TargetType="StackPanel">
                <Setter Property="Margin" Value="5"/>
            </Style>
            <Style TargetType="Label">
                <Setter Property="Width" Value="80"/>
                <Setter Property="VerticalAlignment" Value="Center"/>
            </Style>
            <Style TargetType="TextBox">
                <Setter Property="Width" Value="120"/>
            </Style>
            <Style TargetType="Button">
                <Setter Property="Width" Value="50"/>
                <Setter Property="Height" Value="25"/>
            </Style>
        </StackPanel.Resources>

        <StackPanel Orientation="Horizontal">
            <Label Content="아이디"/>
            <TextBox x:Name="ID"/>
        </StackPanel>

        <StackPanel Orientation="Horizontal">
            <Label Content="비밀번호"/>
            <TextBox x:Name="Passwd"/>
        </StackPanel>

        <StackPanel Orientation="Horizontal">
            <Button x:Name="LoginBtn" Content="로그인"/>
        </StackPanel>
    </StackPanel>

</Window>

아래 코드는 위 XAML의 Code Behind 내용 이다. x:Name으로 명명된 식별자를 통해 TextBox의 Text 속성의 값을 읽거나 설정할 수 있다. Button 역시 x:Name으로 명명된 식별자를 통해 Click 이벤트 등을 제어할 수 있다.


로그인 화면 구성이 비교적 간단하고, 입력 값 검증 규칙이 복잡하지 않기 때문에 Code Behind에 UI 요소와 데이터를 처리하는 로직이 뒤섞여 있어도 불편함이 크게 들어나지는 않는다. 그러나 화면을 구성하는 UI 요소의 개수가 증가하고, 입력 값 검증 로직이 복잡해짐에 따라 Code Behind의 복잡도도 크게 증가하게 될 것을 쉽게 예상할 수 있다.

using System.Windows;
using System.Windows.Input;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        private string LoginID { get; set; }
        private string LoginPasswd { get; set; }

        public MainWindow()
        {
            InitializeComponent();
            this.Loaded += OnLoaded;
            this.LoginBtn.Click += LoginButtonClick;
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            Keyboard.Focus(this.ID);
        }

        private void LoginButtonClick(object sender, RoutedEventArgs e)
        {
            LoginID = this.ID.Text;
            LoginPasswd = this.Passwd.Text;
            if (string.IsNullOrEmpty(LoginID))
            {
                MessageBox.Show("아이디를 입력해주세요.");
                Keyboard.Focus(this.ID);
                return;
            }
            if (string.IsNullOrEmpty(LoginPasswd))
            {
                MessageBox.Show("비밀번호를 입력해주세요.");
                Keyboard.Focus(this.Passwd);
                return;
            }
            doLogin();
        }

        private bool doLogin()
        {
            //-- 로그인 처리
            MessageBox.Show(string.Format("아이디={0}, 비밀번호={1}", LoginID, LoginPasswd));
            return true;
        }
    }
}

위 코드를 데이터 바인딩을 적용해 UI 부분과 데이터 부분을 유연하게 분리해 보자.


먼저 변경 된 XAML은 아래와 같다앞서 살펴본 XAML과는 크게 차이가 나지 않지만 TextBoxText 속성에 바인딩 구문이 사용되고 있음을 주목하자.

<Window x:Class="WpfApplication1.LoginWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="LoginWindow" Height="300" Width="400">

    <StackPanel>
        <StackPanel.Resources>
            <Style TargetType="StackPanel">
                <Setter Property="Margin" Value="10"/>
            </Style>
            <Style TargetType="Label">
                <Setter Property="Width" Value="80"/>
                <Setter Property="VerticalAlignment" Value="Center"/>
            </Style>
            <Style TargetType="TextBox">
                <Setter Property="Width" Value="120"/>
            </Style>
            <Style TargetType="Button">
                <Setter Property="Width" Value="50"/>
                <Setter Property="Height" Value="25"/>
            </Style>
        </StackPanel.Resources>

        <StackPanel Orientation="Horizontal">
            <Label Content="아이디"/>
            <TextBox x:Name="ID" Text="{Binding LoginID, UpdateSourceTrigger=PropertyChanged}"/>
        </StackPanel>

        <StackPanel Orientation="Horizontal">
            <Label Content="비밀번호"/>
            <TextBox x:Name="Passwd" Text="{Binding LoginPasswd, UpdateSourceTrigger=PropertyChanged}"/>
        </StackPanel>

        <StackPanel Orientation="Horizontal">
            <Button x:Name="LoginBtn" Content="로그인"/>
        </StackPanel>
    </StackPanel>

</Window>

Code Behinde의 내용은 다음과 같다. this.DataContext에 LoginViewModel을 설정하고 있는 부분이 앞서 살펴본 Code Behinde와 차이가 있음을 알 수 있다. DataContext는 XAML의 UI 요소의 바인딩 구문에 사용될 수 있는 데이터를 가리킨다. 예를 들어 x:Name이 “ID”인 TextBox의 Text는 LoginID라는 항목에 바인딩 되어 있는데 이것은 바로 DataContext로 지정한 LoginViewModel의 LoginID 프로퍼티를 가리키는 것이다. 이와 비슷하게 LoginPasswd는 LoginViewModel의 LoginPasswd 프로퍼티를 가리킨다. 앞에서 알아본 내용에 따르면 TextBox의 Text 속성은 기본값으로 TwoWay 바인딩을 사용하므로 LoginViewModel의 LoginID 값을 변경하면 변경 된 값이 TextBox의 Text속성에 반영이 되거나 또는 사용자가 TextBox에 직접 타이핑해 넣은 값이 LoginViewModel의 LoginID에 저장될 것을 기대할 수 있다.

using System.Windows;
using System.Windows.Input;
using WpfApplication1.ViewModels;

namespace WpfApplication1
{
    public partial class LoginWindow : Window
    {
        public LoginWindow()
        {
            InitializeComponent();

            this.Loaded += OnLoaded;
            this.LoginBtn.Click += LoginButtonClick;
            this.DataContext = new LoginViewModel();
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            Keyboard.Focus(this.ID);
        }

        private void LoginButtonClick(object sender, RoutedEventArgs e)
        {
            var viewModel = this.DataContext as LoginViewModel;
            if (string.IsNullOrEmpty(viewModel.LoginID))
            {
                MessageBox.Show("아이디를 입력해주세요.");
                Keyboard.Focus(this.ID);
                return;
            }
            if (string.IsNullOrEmpty(viewModel.LoginPasswd))
            {
                MessageBox.Show("비밀번호를 입력해주세요.");
                Keyboard.Focus(this.Passwd);
                return;
            }
            doLogin();
        }

        private bool doLogin()
        {
            //-- 로그인 처리
            var viewModel = this.DataContext as LoginViewModel;
            MessageBox.Show(string.Format("아이디={0}, 비밀번호={1}", viewModel.LoginID, viewModel.LoginPasswd));
            return true;
        }
    }
}

실제로 아이디와 비밀번호에 값을 입력하고 로그인 버튼을 클릭하면 아래 그림과 같은 결과를 확인할 수 있다. 즉, 바인딩 대상(UI 요소)의 변경이 바인딩 소스(ViewModel)로 올바르게 전파되고 있는 것을 확인한 것이다. 이러한 변경의 전파는 UpdateSourceTrigger 값으로 PropertyChanged를 사용하고 있기 때문에 TextBox의 Text 속성 값이 변경될 때마다 발생하게 된다.


그럼 반대로 바인딩 소스(ViewModel)의 변경이 바인딩 대상(UI 요소)으로 전파되는지도 확인해보자. 이를 위해 간단하게 로그인 버튼 옆에 자동입력 버튼을 하나 추가한다.


 <StackPanel Orientation="Horizontal">

   <Button x:Name="AutoBtn" Content="자동입력"/>

   <Button x:Name="LoginBtn" Content="로그인" Margin="8,0,0,0"/>

 </StackPanel>


그리고 Code Behinde에는 다음과 같은 자동입력 버튼을 클릭했을 때 실행 될 핸들러를 하나 추가한다.


 private void AutoButtonClick(object sender, RoutedEventArgs e)

 {

     var viewModel = this.DataContext as LoginViewModel;

     viewModel.LoginID = "myid2";

     viewModel.LoginPasswd = "mypassword2";

 }


애플리케이션을 다시 빌드하고 실행한다. 그리고 자동입력 버튼을 클릭한 다음 바로 로그인 버튼을 클릭해보자. 그러면 아래 그림과 같이 조금은 이상한 결과를 얻게 된다.



AutoButtonClick 핸들러를 통해 LoginViewModel의 LoginID와 LoginPasswd 프로퍼티의 값을 변경 했으나 변경의 전파가 UI 요소로 올바르게 이루어 지지 않았다는 것을 알 수 있다. (Alert 윈도우를 통해 LoginViewModel의 LoginID와 LoginPasswd 프로퍼티는 값이 올바르게 변경되었다는 것을 확인할 수 있다.)


사실 이것은 ViewModel을 구현하는데 있어서 INotifyPropertyChanged 인터페이스의 필요성을 설명하기 위해 의도한 결과이다. OneWay나 TwoWay 바인딩 방식에서 바인딩 소스(ViewModel)의 변화를 바인딩 대상(UI 요소)으로 올바르게 전파하기 위해서는 INotifyPropertyChanged 인터페이스 구현이 반드시 필요하다.

using System.ComponentModel;

namespace WpfApplication1.ViewModels
{
    public class LoginViewModel : INotifyPropertyChanged
    {
        private string _loginID;
        public string LoginID
        {
            get { return _loginID; }
            set
            {
                _loginID = value;
                OnPropertyUpdate("LoginID");
            }
        }

        private string _loginPasswd;
        public string LoginPasswd
        {
            get { return _loginPasswd; }
            set
            {
                _loginPasswd = value;
                OnPropertyUpdate("LoginPasswd");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyUpdate(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

INotifyPropertyChanged 인터페이스는 PropertyChangedEventHandler라는 이벤트 객체를 하나 포함하고 있는데, 이 이벤트 객체를 통해 프로퍼티 값이 변경되었다는 것을 UI 요소에 알리게 된다. 이제 다시 애플리케이션을 빌드하고 실행해보자. 그리고 자동입력 버튼을 클릭하면 아이디와 비밀번호 TextBox에 LoginViewModel을 통해 변경된 값이 반영되는 것을 확인할 수 있다.



참고

https://msdn.microsoft.com/ko-kr/library/ms752347(v=vs.110).aspx



저작자 표시 비영리 변경 금지
신고

'프로그래밍 > C#/WPF' 카테고리의 다른 글

WPF 데이터 바인딩의 기초 - 2  (0) 2015.06.21
WPF 데이터 바인딩의 기초 - 1  (1) 2015.06.16
Posted by devop