Data Binding-② : 값 변환기(Value Converter)를 사용한 바인딩(Binding)
※ 참고 Site
1. https://www.wpf-tutorial.com/data-binding/value-conversion-with-ivalueconverter/
2. https://www.wpftutorial.net/ValueConverters.html
지난 시간에는 서로 같은 타입의 프로퍼티일때 바인딩하는 방법(DataContext 사용)에 대해서 확인해보았습니다.
ex) string → string / int → int
그런데 만약 서로 다른 타입의 프로퍼티일때는 바인딩을 어떻게 해야할까요??
ex) string → bool , bool → visibility 등등
바로 값 변환기(Value Converter)를 사용하면 됩니다.
1. Value Converter 기본 개념 및 인터페이스, 메서드
✅ 값 변환기(Value Converter) 관련 개념
- 서로 다른 타입의 두개의 프로퍼티를 바인딩하기 위해 사용
(두개 이상도 MultiValueConvert를 사용해서 바인딩이 가능하기는 하다.) - IValueConverter / IMultiValueConverter 인터페이스를 사용해서 구현
( 1 : 1 또는 多 : 1 관계의 바인딩만 가능, 多 : 多는 안되는듯 싶음)
→ 자세히 아는분은 의견점요!! - 일반적으로 Bool값을 Visibility 속성에 바인딩 하는 것을 많이 사용
✅ IValueConverter 인터페이스 사용 및 메서드 구현
System.Windows.Data 안에 있는 IValueConverter 인터페이스는 Convert와 ConvertBack 2개의 메서드로 구성되어 있다.
따라서 컨버팅을 수행하기 위해 별도로 사용자가 클래스 파일 하나를 생성하게 위 인터페이스를 상속받는다.
그리고 VS의 빠른작업을 지원받아서 2개의 인터페이스 메서드를 구현하고 사용자의 입맛에 맞게(?) 코드를 작성한다.
ConvertBack 메서드는 Binding Mode가 TwoWay일때 사용한다고 합니다.
저는 본 실습에서는 return null;로 두고 Convert 메서드만 사용하였습니다.
✅ Value Converter in XAML (네임스페이스 매핑 및 클래스 인스턴스화)
값 컨버터를 사용하기 위해서는 관련 인터페이스를 상속받은 클래스 파일을 작성하고,
해당 클래스 파일을 XAML 내에서 매핑시켜주어야 합니다.
그후에 Resources 부분에 해당 클래스를 인스턴스화 하는 과정을 수행해야 합니다.
<Window x:Class="WpfTutorial.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfTutorial"
mc:Ignorable="d"
Title="Value Converter .yyh"
Height="300"
Width="300">
<Window.Resources>
<local:IndexToImage x:Key="ImageConverter" />
<local:BoolToVisibility x:Key="VisibilityConverter" />
</Window.Resources>
저 같은 경우는 IValueConverter 인터페이스를 상속받은 클래스파일(IndexToImage, BoolToVisibility)이 xaml파일과 동일한네임스페이스에 위치해있기 때문에(local) 별도로 정의하지 않았고 바로 Resources에 정의하였습니다.
만약, 네임스페이스가 다를경우
상단에 아래와 같에 클래스가 속해있는 네임스페이스를 매핑해주고
xmlns:con="clr-namespace:[네임스페이스명]"
<Window.Resources>에는 아래와 같이 클래스 파일을 정의해줍니다
<con:IndexToImage x:Key = "OO" />
Resources에서는 값 컨버터의 인스턴스를 만들고 Key값을 지정해 줍니다.
그후에는 Key값을 가지고 각 Control의 프로퍼티에 위 컨버터를 사용해서 바인딩을 수행해주면 됩니다.
말로만 하면 어려우니 세부적으로는 아래의 예시를 참고해서 확인해 보도록 하겠습니다😊
2. IValueConverter 인터페이스 및 활용 예제
IValueConverter 인터페이스를 상속받아서 타겟 프로퍼티와 소스 프로퍼티간에 ValueConverter를 통해 1:1로 바인딩하는 간단한 실습을 진행해보겠습니다.
먼저 실습에 사용될 UI는 아래와 같습니다.
왼쪽의 ListBox에서 선택한 Index에 따라서 Image의 그림이 바뀌게 되고
우측의 CheckBox의 IsChecked의 값에 따라서 Image가 보이거나 안보이게 됩니다.
따라서 Value Converter를 수행하는 Class파일 2개를 만들고 2개 모두 Resources에 정의해두고 사용을 하게 됩니다.
(IMultiValueConverter를 수행하면 1개의 Class파일로도 가능할거 같군요..😗🤔)
⭕MainWindow.xaml
<Window x:Class="WpfTutorial.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfTutorial"
mc:Ignorable="d"
Title="Value Converter .yyh"
Height="300"
Width="300">
<Window.Resources>
<local:IndexToImage x:Key="ImageConverter" />
<local:BoolToVisibility x:Key="VisibilityConverter" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="100*" />
<RowDefinition Height="200*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0"
Orientation="Vertical">
<Label Content="-- User Interface --"
HorizontalContentAlignment="Center"
Margin="0,0,0,10" />
<DockPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150*" />
<ColumnDefinition Width="150*" />
</Grid.ColumnDefinitions>
<ListBox Name="ListBoxConverter"
Grid.Column="0">
<ListBoxItem Content="OK" />
<ListBoxItem Content="NG" />
</ListBox >
<CheckBox Name="CheckBoxConverter"
Grid.Column="1"
Content="Image Visibility"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</DockPanel>
</StackPanel>
<StackPanel Grid.Row="1"
Orientation="Vertical">
<Label Content="-- Image Display --"
HorizontalContentAlignment="Center"
Margin="0,0,0,10" />
<DockPanel>
<Grid>
<Image HorizontalAlignment="Center"
Height="100"
Width="100"
Visibility="{Binding ElementName=CheckBoxConverter, Path=IsChecked, Converter={StaticResource VisibilityConverter}}"
Source="{Binding ElementName=ListBoxConverter, Path=SelectedIndex, Converter={StaticResource ImageConverter}}" />
</Grid>
</DockPanel>
</StackPanel>
</Grid>
</Window>
여기서 중요한 부분은 <Window.Resources>에서 ValueConverter를 수행하는 2개의 Class파일을 정의해두고 하단의 Control에 각각 Name을 설정해서, Image의 Visibility와 Source 속성에대한 값에 바인딩을 설정했다는 것입니다.
✅ {Binding} 태그 확장
- Binding ElementName : Xaml내에서 Control의 Name을 지정하는 부분입니다.
바인딩을 수행하기 위한 원본 소스(컴포넌트)의 이름을 지정합니다. - Binding Path : 타겟과 바인딩 될 소스의 파라메터 Type을 명시하는 정도로 생각하면 될듯 싶습니다.
(Microsoft 문서에서는 "소스 속성에 바인딩할 속성에 대해 설명"이라고 정의해 두었습니다.) - Binding Converter : StaticResource에 정의해둔 ValueConverter Class의 Key값을 적어주면 됩니다.
바인딩 태그 확장에 대해 자세한 내용은 아래 문서를 참고 바랍니다.
https://learn.microsoft.com/ko-kr/windows/uwp/xaml-platform/binding-markup-extension
⭕ BoolToVisibility.cs / IndexToImage.cs
실제로 소스의 Value를 받아서 Target의 파라미터에 해당하는 값으로 변환(Convert)해주는 코드부입니다.
이는 사용자가 필요한대로 코드를 구성하면 되고
Visibility로 변환하는 코드와 같은 경우는 보편적으로 사용하는 내용이니 적어두고 참고하시면 좋을 것 같습니다.
🖐 잠깐! /Resources에 Image를 저장하는 방법
위의 내용들을 읽으셨다면 한가지 의문점이 발생할 것입니다.
return으로 Image.Source에 해당하는 경로를 반환해주는데 그럼 과연 저 경로에 어떻게 Image를 등록해주는 것일까요?
① Resources.resx에 사용하고자하는 Image를 등록해줍니다.
② 해당 Image파일의 속성에 들어가서 빌드작업을 Resource로 변경해 줍니다.
③ [ Resources/"이미지 이름"."확장자명" ]의 경로로 Image를 사용할 수 있게 된다.
⭕ 결과
CheckBox를 선택해주면 Image가 ListBox에 따라 다르게 보이고 CheckBox를 해제하면 Image가 보이지 않게 됩니다.
3. IMultiValueConverter 인터페이스 및 활용 예제
이번에는 위와 동일한 UI를 가지고 MultiValueConverter를 수행해보도록 하겠습니다.
Multi의 장점은 여러개의 프로퍼티 값을 한개의 메서드의 인자로 받아서 값 컨버터를 수행할 수 있다는 점입니다.
⭕ MainWindow.xaml
수정된 부분만 발췌하였습니다.
MultiConverter 클래스를 Resources에 정의한 부분은 생략하였으니 참고 바랍니다.
<Image HorizontalAlignment="Center"
Height="100"
Width="100"
Visibility="Visible">
<Image.Source>
<MultiBinding Converter="{StaticResource MultiConverterKey}">
<MultiBinding.Bindings>
<Binding ElementName="ListBoxConverter"
Path="SelectedIndex" />
<Binding ElementName="CheckBoxConverter"
Path="IsChecked" />
</MultiBinding.Bindings>
</MultiBinding>
</Image.Source>
</Image>
바뀐점은 Image 컨트롤에서 Visibility 태그를 없애고
Source 태그의 MultiBinding 속성에 Converter를 사용했다는 점입니다.
⭕ MultiConverter.cs
internal class MultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Count() > 1)
{
if ((bool)values[1] == true)
{
if ((int)values[0] == 0)
{
return new BitmapImage(new Uri("/Resources/ok.jpg", UriKind.RelativeOrAbsolute));
}
else if ((int)values[0] == 1)
{
ImageSourceConverter con = new ImageSourceConverter();
string projectPath = Directory.GetParent(Environment.CurrentDirectory).Parent.FullName;
string path = projectPath + "/Resources/ng.png";
return con.ConvertFromString(path);
}
else return null;
}
else return null;
}
else return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return null;
}
}
Visibility를 Hidden은 Source에 null값을 줌으로써 대체하였습니다.
한가지 특이한 점은 MultiBinding / MultiValueConverter로 image.source 프로퍼티에 바인딩을 하니
단순히 Resources에 저장된 이미지의 경로만 넣는것으로는 안되었습니다.
제가 사용한 방법은 2가지 입니다.
- BitmapImage
- ImageSourceConverter
→ 이 방법은 해당 프로젝트의 전체 경로도 알아야해서 조금 더 복잡합니다.
자세한건 아래의 링크를 보고 참고하였습니다.
https://social.msdn.microsoft.com/Forums/vstudio/en-US/e9c4b1db-4239-4e64-9fcc-e96b319cd751/multibinding-file-path-to-imagesource?forum=wpf
⭕ 결과
위 코드 디버깅 결과는 첫번째 결과(IValueConverter)와 동일하니 참고 바랍니다!!
댓글