Converter Manager

프로그래밍 2009.03.03 21:11 Posted by 아일레프

WPF Application을 만들다 보면 WPF의 강력한 기능인 DataBinding을 참 많이 사용하게 됩니다. 특히 MVC, MVP, MVVM 패턴 과같이 Model을 정의해 View를 구성하는 패턴을 사용하면 DataBinding은 정말 유용한 도구가 되죠. 그렇게 DataBinding을 사용하다 보면 자연히 많이 사용하게 되는 것이 바로 Converter입니다. 그런데 이 놈의 Converter가 너무 많아 절 속상하게 할 때가 너무 많이 있습니다.

지금 프로젝트 내에서도 많은 Converter가 있는데요, Enum값을 intg형으로 Convert, ConvertBack하는 EnumConverter, Bool값을 반대로 반환하는 BoolToOppsoiteBoolConverter, Double값을 int로 변환하는 DoubleToIntConverter 등 많은 Converter등이 있습니다. 절 가장 가슴 아프게 하는 부분은 단지 Converter가 많아서가 아니라, 특정 시점에 여러 개의 Converter가 동시에 존재하는 것입니다.

 

만약 Application 3개의 View가 존재하고 이 녀석들은 반드시 Singleton으로써 항상 메모리에 존재하게 된다고 가정해 봅시다. 그리고 모든 View가 3개의 공통적인 Converter가 필요하다고 가정해 보겠습니다.

 

<appLib:IntegerConverter x:Key="TimeIntegerConverter"/>

<appLib:BoolToOppositeBoolConverter x:Key="NegativeBoolConverter"/>

<appLib:IntegerToBooleanConverter x:Key="IntToBooleanConverter" />

모든 View는 위 녀석들을 사용하기 위해서 자신의 Resource에 위 Converter들을 정의해 놓고 있습니다.

<UserControl.Resources>

        <appLib:IntegerConverter x:Key="TimeIntegerConverter"/>

        <appLib:BoolToOppositeBoolConverter x:Key="NegativeBoolConverter"/>

        <appLib:IntegerToBooleanConverter x:Key="IntToBooleanConverter" />

</UserControl.Resources>

그리고 이 녀석들은 {Binding Path=TimeData.ZoneType, ElementName=My, Converter={StaticResource TimeIntegerConverter}} 와 같이 사용되게 됩니다.

문제는 메모리 상에 IntegerConverter라는 녀석은 필요한 시점에 단 하나만 있으면 좋을 텐데, 3개나 존재한다는 것입니다. 다음의 그림과 같을 것입니다.

 


 
안타깝지 않습니까? 아무리 메모리가 넘쳐나는 세상이더라도 위와 같은 현상을 가만히 지켜보기에는 너무 가슴이 아픕니다. 그리고 정말 귀찮은 일은 매번 View를 만들 때 마다 Resource Converter를 생성하는 Markup 코드를 Copy & Paste해 줘야 한다는 것입니다. 이젠 더 이상 참을 수 없습니다. 뭔가 해결 해야겠네요.


가장 먼저 생각나는 방법은 다음과 같았습니다. WPF Application StaticResource Markup Extend 구문은 Application Root Tree까지 Resource를 검색하니까 App.xaml에 위 Resource들을 정의해놓고 사용하는 것입니다.
그런데 이 방법은 문제가 있네요. 이 방법으로 실제로 프로그램을 실행하면 잘 되지만 Design Time WPF Designer App.xaml을 찾지 못하고 에러 메시지를 보여주게 됩니다. 이건 좀 아닌 것 같습니다. StaticResource말고 DynamicResource를 사용해 Converter에 대입 하라구요? 아쉽게도 Binding 객체의 Converter Property DependencyProperty가 아니기에 DynamicResource를 쓸 수 없습니다. 그리고 설사 DynamicResource확장 구문을 사용할 수 있다 하더라도 이 방법을 사용하기 망설여 지는 이유는 Compile Time에 에러를 찾아 낼 수 없다는 것입니다. IntegerConverter 키 이름을 실수로 IngetConverter로 써도 Compile Time에 에러를 보여주지 않습니다. 확인 하기 위해서는 직접 실행해 보고 결과를 체크해 볼 수 밖에 없지요.


결국 저에게 필요한 것은 Converter Manager입니다.

 
이 녀석이 해야 할 일은 다음과 같습니다.

1. Application 내에서 Converter를 Singleton으로 관리한다.

2. View에게 XAML상에서 Converter를 사용할 수 있는 방법을 제공한다.

3. Design Time에 잘못된 Converter 사용 요청이 들어오면 Exception을 발생시켜 사용자에게 알 수 있게 한다.

 

자 어떻게 하면 될까요? 저는 다음과 같은 방법을 생각했습니다.

먼저 ConverterManager라는 Static Class를 만듭니다.

 

public static class ConvertManager

    {

        private static KeyedByTypeCollection<IValueConverter> converterCollection; static ConvertManager(){

            converterCollection = new KeyedByTypeCollection<IValueConverter>();

        }

        public static IValueConverter GetConverter(string typeName)

        {

            return GetConverter(Type.GetType(typeName));

        }

        public static IValueConverter GetConverter(Type convertType)

        {

            if (convertType == null)

                throw new InvalidOperationException();

            if (converterCollection.Contains(convertType))

            {

                return converterCollection[convertType];

            }

            else

            {

                IValueConverter converter = Activator.CreateInstance(convertType) as IValueConverter;

                if (converter == null) throw new InvalidOperationException();

                converterCollection.Add(converter);

                return converter;

            }

        }

    }

ConvertManager Type 객체나 Type이름을 받아 Converter로 변환해 반환하는 GetConverter 메소드를 가지고 있습니다. 이 메소드는 결국 Type Parameter로 받은 후 해당 Type이 자신의 converterCollection에 있는지 확인 합니다. 없으면 객체를 만들어 converterCollection에 넣은 후 객체를 반환합니다. 있으면 해당 Converter를 반환하지요. 쉽지요?

 

그리고 XAML에서 쓸 수 있도록 MarkUpExtension을 정의 합니다. 다음과 같이 말이죠.

public class StaticConverterExtension : MarkupExtension

    {

        Type type;

        [ConstructorArgument("type")]

        public Type Type

        {

            get{return type;}

            set{

                type = value;

            }

        }

        public StaticConverterExtension() { }

        public StaticConverterExtension(Type type)

        {

            Type = type;

        }

        public override object ProvideValue(IServiceProvider serviceProvider)

        {

            return ConvertManager.GetConverter(Type);

        }

    }

 

이름이 구리지만 참아 주세요 ^^;; 녀석이 하는 일은 모두 이해하실 것이라 생각 하고 생략하겠습니다. 중요한 녀석은 녀석들을 별도의 Assembly 만들어야 한다는 것입니다. 공통된 Library Assembly 별도로 관리한 다는 의미도 있지만 다른 이유는 Microsoft 팀의 버그 때문입니다. 같은 Assembly안에 있으면 MarkUpExtension XAML Attribute 설정할 없는 버그입니다. (.)

 

그리고 View들은 Assembly 참조하고 다음과 같이 사용하면 됩니다.

<StackPanel>


<
Button Name="button" >123</Button>     

<
Button IsEnabled="{Binding ElementName=button, Path=IsEnabled, Converter={lib:StaticConverterExtension Type={x:Type lib:BoolToOppositeBoolConverter}}}">asdfasdf</Button>


<
Button Content="{Binding ElementName=button, Path=Content, Converter={lib:StaticConverterExtension {x:Type local:IntegerToBooleanConverter}}}"></Button>

 
</StackPanel>

 

BoolToOppositeBoolConverter true이면 false 반환하고, false이면 true 반환하는 Converter이고, IntegerToBooleanConverter 바인딩 Source Path int 변환할 있다면 true 반환하고 그렇지 않으면 false 반환하는 녀석입니다.

 

위와 같이 만들고 보니 디자이너가 다음과 같이 훌륭하게 결과를 표시해 주었습니다.

 

~ 뿌듯하네요. 이제 이상 Converter Resouce 정의해 필요도 없고 Memory 여기저기 존재하는 Converter 때문에 잠 못 잘 걱정도 사라졌습니다. 그리고 코드를 보면 Library Assembly 참조만 하고 있으면 어떤 Converter StaticConverterExtension으로 사용할 있음을 수있습니다.

 

탐구생활 : Converter 아니라 Application 여기저기 널려있는 StaticResource 여러분들을 속상하게 있습니다. 나름대로 해결방법을 생각해봅시다~

신고
TAG


 

티스토리 툴바