내용 보기

작성자

관리자 (IP : 172.17.0.1)

날짜

2020-07-10 03:46

제목

[WPF] Converter를 static 하게 사용하기


xaml에서 사용할 Converter static 하게 사용하는 방법을 잠깐 떠들어 보자.


 

WPF Converter는 이거 생각보다 사용하기 불편하고 손이 많이 가는 객체 중에 하나다.

 

그냥 IValueConverter 요 인터페이스 하나만 구현하면 끝나는 것이긴 한데

(System.Windows.Data 네임스페이스에 있는 인터페이스)


실무에서는 xaml에서 Binding된 데이터의 변형을 요구할 때가 굉장히 많다.

 


그 때마다 기준이 되는 Model 값을 매번 ViewModel에 새로 만들어 주는 것은 낭비이므로

 

이렇게 IValueConverter를 구현한 Converter Binding에 엮어 주게 되는데

 



 Converter가 좀 불편한 것이 뭐냐면

 

DependencyObject 를 상속받는 것이 아니기 때문에

 

Binding 구문에서 DynamicResource 키워드로 사용할 수 없다는 단점이 있다.

 


무조건 StaticResource 를 이용한 접근만 허용되며

 

이는 곧 해당 xaml  Resource 안으로 편입 시켜야 한다는 얘기다.


 

그런데 만약 이 Converter가 한 솔루션 안에서 여러 프로젝트를 넘나들며 사용해야 한다면

 

다시 말해

 

이 프로젝트에서도 쓰이고 저 프로젝트에서도 쓰이고

 

 View에서도 쓰이고 저 View에서도 쓰이면

 

프로젝트 마다 View가 포함된 Resource 마다 Converter를 추가해 주어야 하는 불편함이 있다.


 

그리고 그러한 Converter가 늘어날수록 불편함은 커지게 되고 관리도 쉽지 않게 된다.

(Converter의 선언이 여러 Resource에 흩어지게 되므로)

 

그리고 Resource에 여러 번 등록하고 여러 프로젝트에서 각기 다른 Resource를 통해

 

Converter를 호출한다면 당연히 Converter도 여러 번 생성될 것이다.


 

그래서 아예 Converter만 따로 모아두고 이를 어디에서 누가 가져가 쓰든지

 

따로 Resource에 등록하지 않고 네임스페이스만 추가하여 바로 사용할 수 있게 하는 것은 물론

 

여러 번 생성되지 않도록 static 하게 만들 필요가 생겼다.

 




자 이거 어떻게 해야 할까?

 

 

이전 포스팅에서 다루었던 static 멤버와 MarkupExtension을 이용해 처리할 수 있다.

 

방법은

1.     Base가 되는 Converter MarkupExtension으로 재정의 한다.

2.      Base Converter가 IValueConverter abstract 으로 구현한다.

3.     Base Converter는 상속될 하위 Converter 타입을 static으로 생성하기 위한 멤버를 가진다.

4.     Base Converter 의 public override object ProvideValue(IServiceProvider serviceProvider) 는 하위 타입으로 생성했던 static Converter를 반환한다.

5.     따라서 이 Base Converter abstract 클래스가 되고 상속받는 하위 Converter 클래스에서 IValueConverter 구현부를 override 해야한다.

 


요정도로 보면 될 것 같다.




 

자 그럼 먼저 Base가 되는 Converter를 한 번 Markup으로 만들어 보자.

 

 

public abstract class ConverterMarkupExtension<T> : MarkupExtensionIValueConverter

         where T : classnew()

{

         private static Lazy<T> _converter = new Lazy<T>(() => new T());

 

         public override object ProvideValue(IServiceProvider serviceProvider)

         {

                  return _converter.Value;

         }

 

         public abstract object Convert(object valueType targetTypeobject parameterCultureInfo culture);

         public abstract object ConvertBack(object valueType targetTypeobject parameterCultureInfo culture);

}


 

중간에 필요한 네임스페이스는 모두 생략했다.


 

어쨌든 이런 식으로 base Converter Markup으로 재정의하고 나면


 

이제 실제로 필요한 Converter를 상속해 작성하면 된다.


 

이런 식으로 말이다.



 

public class BoolToVisibilityConverter : ConverterMarkupExtension<BoolToVisibilityConverter>

{

         public override object Convert(object valueType targetTypeobject parameterCultureInfo culture)

         {

                  try

                  {

                           var boolean = (bool)value;

                           return boolean ? Visibility.Visible : Visibility.Hidden;

                  }

                  catch (Exception ex)

                  {

                           Debug.WriteLine(ex);

                           return Visibility.Hidden;

                  }

         }

 

         public override object ConvertBack(object valueType targetTypeobject parameterCultureInfo culture)

         {

                  throw new NotImplementedException();

         }

}

 



뭐 아무것도 없다.

(ConverterBack은 그냥 굳이 구현할 필요를 못 느껴서 저렇게만 해뒀다단지 예제일 뿐이니까)



 

그냥 bool 이 들어왔을 때 true면 Visibility.Visible 을 반환하고 아니면

 

Visibility.Hidden 을 반환하는 흔하고 흔한 Converter 되겠다.

 


물론 .net 4.0에서는 BooleanToVisibilityConverter 라는 이름의

 

기본 converter를 내장하고 있다.


위 예제와 차이점이라면

 

기본 converter의 경우 value false이면 Visibility.Collapsed 를 반환한다는 정도?

 

그 외에는 위에서 떠들었던 IValueConverter 사용법으로 쓸 수 있는 녀석이다.

(위 예제는 말 그대로 그냥 예제를 위해 만든 거라고 생각하면 된다.)

 




자 이렇게

 

ConverterMarkupExtension<BoolToVisibilityConverter>를 상속받아

 

IValueConverter 구현부를 override 했으면

 


이제 직접 사용하면 된다.

 

이 녀석은 Resource에 따로 등록할 필요 없이

 

 converter가 위치한 프로젝트의 네임스페이스만 추가해서 Markup 에 바로 사용하면 된다.


 

예를 들어 위 BoolToVisibilityConverter 

 

CommonResource.Converters 라는 네임스페이스에 속해 있다면



 

뭐 이런 식으로


 

xmlns:c="clr-namespace:CommonResource.Converters;assembly=CommonResource"

 


이런 네임스페이스 추가를 해주고

 

실제 사용할 때에는


 

<Rectangle Visibility="{Binding IsVisibility, Converter={c:BoolToVisibilityConverter}, Fill="Red" Width="100" Height="100" />

 


이런 식으로 Converter 자리에 그냥 바로 넣어 주기만 하면 된다.

(그리고 당연히 이 Converter 프로젝트를 참조해야만 사용할 수 있다.)

 




 

… 깔끔하다.

 

 

 Resource 페이지에 x:Key를 줘가면서 추가할 필요도 없고

 

어떤 프로젝트에서 가져가서 사용하든 static 하게 하나만 생성될 것이니 메모리 낭비도 줄고

 

무엇보다 사용할 때 StaticResource DynamicResource 같은 거 없이

 

Converter를 직접 쓸 수 있게 되어 매우 편리해졌다.

 




사실 이 아이디어는 Dr.WPF에서 제시한 것을 변형한 것인데


Generic으로 수정하면서 만났던 이슈를 정리한 것이라고 보면 될 듯 하다.

(구글링 해보면 유사한 Converter 구현이 매우 많다그 중 하나를 정리한 것이다.)


출처1

https://blog.naver.com/vactorman/220552073364

출처2