Search

'Markup Extension'에 해당되는 글 1건

  1. 2008.11.13 XAML Markup Extension

XAML Markup Extension

프로그래밍 2008.11.13 19:53 Posted by 아일레프

Dependency Property에 이어서 Markup Extension에 대한 글을 써 보려 합니다. 아래 적은 내용은 모두 MSDN에서 제공하는 내용을 단지 쉽게(?) 풀어 쓴 것에 지나지 않습니다.

사실 XAML Markup Extension은 XAML에서 특정 Object의 Property의 값을 대입하는 하나의 방법에 지나지 않습니다. 이 방법에는 크게 3가지가 있는데 다음과 같습니다.

  1. Using Property Element

<Button>
  <Button.Background>
   
<SolidColorBrush Color="White" />
 
</Button.Background>
</Button>

       2.   Using XML Attribute

<Button Background="White" />

       3.    Using Markup Extension

<Button Background="{StaticResource MyBrush}" />

1번의 경우 Button의 Background Property에 SolidColorBrush Object를 생성해 대입하는 것을 알 수 있습니다. Button button = new Button { Background = new SolidColorBrush(Colors.White) }; 와 같이 처리가 되겠네요.

2번의 경우는 Background에 "White"라는 문자열이 대입되었습니다. Background는 Brush Type인데 String Type에 해당하는 문자열이 대입되었기에 XAML Processor는 다음과 같은 처리 방법에 따라 Property에 적당한 값을 대입하게 됩니다.

  1. Property의 Type이 primitive Type인가? - primitiveType.Parse(string)을 호출 해 대입
  2. Property의 Type이 Enumeration Type인가? – Enum.Parse(typeof(EnumerationType, string)을 호출해 대입
  3. Property의 Type에 대응되는 TypeConverter가 존재하는가? – TypeConverter.ConvertFrom(string)을 호출해 대입

Using Property Element 와 XML Attribute를 사용해 Property의 값을 대입하는 방식에는 커다란 공통점이 있습니다. 만약 Property의 Type이 primitive Type이 아니라면 "생성"과정, 즉 내부적으로 "new" 키워드를 부르는 연산을 수행한다는 것입니다. 그런데 만약 "생성"과정이 아닌, 이미 존재하는 Object에 대한 참조를 Property에 대입하고 싶다면? 아쉽게도 1번과 2번의 방식으로는 결코 Object에 대한 참조를 대입할 수 없고 이런 방식을 고안 할 수 도 없습니다.

  • 기존에 존재하는 Object의 참조자를 Property에 대입하고 싶을 때 기존의 XML문법으로는 이 방법을 고안하기가 쉽지 않다.(적어도 WPF Team은 찾지 못했다.) 이 용도로 사용되는 XAML MarkupExtension의 가장 큰 예는 StaticResource, DynamicResource, TemplateBinding 등이 있습니다.

또 Property에 대입할 Object가 여러 개의 Constructor Argument를 필요로 하거나 혹은 Property 값 설정을 필요로 한다면? 즉 이런 경우입니다.

Button button = new Button { Background = new MyBrush(parameter1, parameter2) }; 혹은

Button button = new Button {Background = new MyBrush { Color=Colors.White, Offset=1}; ( 물론 MyBrush는 Brush를 상속받은 객체이겠지요)

이 경우는 Property Element를 사용하면 해결할 수 있을 수도 있어 보입니다.

<Button>
 
<Button.Background>
    
<my:MyBrush Color="White" Offset="1"/>
 
</Button.Background>
</Button>

 

그러나 XML Attribute 만을 사용해서는 이런 동작을 기존의 XML 문법만을 이용해서 구현하기는 힘들 것 같군요.

  • Property에 대응되는 Object가 하나 이상의 생성 parameter를 필요로 하거나 Property 설정을 필요로 할 때 기존의 XML의 Attribute문법으로는 이 방법을 고안하기 쉽지 않다. 이 용도로 사용되는 XAML MarkupExtension의 가장 큰 예는 Binding이 있습니다.

결국 Microsoft는 기존의 XML 문법을 확장할 수 밖에 없게 되었는데요 그것이 바로 XAML Markup Extension입니다. Extension에는 XML문법에서 확장했다는 의미가 포함 되어 있고 '{'로 시작하고 '}'로 끝나게 됩니다. 이 Markup Extension은 MarkupExtension클래스를 상속받고 ProvideValues라는 abstract Method를 Override해주는 것으로 구현됩니다.

<Button Background="{StaticResource MyBrush}" />
위의 XAML 은 코드는 Button Button button = new Button{Background = new StaticResourceExtension("MyBrush").ProvideValues()}; 와 같은 느낌으로 해석될 수 있습니다.

자주 사용하는 "{x:Null}" Extension은 어떨까요?

<Button Background="{x:Null}">

위 XAML은 Button button= new Button { Background = new NullExtension().ProvideValues() };로 해석될 수 있겠네요. 쉽지요? ^^;;

{x:Null}은 단순히 null을 return하기에 다음과 같이 직접 NullExtension 을 구현할 수도 있습니다.

public class NullExtension : MarkupExtension
{
  
public override object ProvideValue(IServiceProvider serviceProvider)
  
{
      
return null
   
}
}

정말 쉽네요.

StaticResourceExtension은 내부적으로 FindResource를 부르기 때문에 아마도 다음과 같이 구현될 것 같습니다.

public class StaticResourceExtension : MarkupExtension{
   
private object resourceKey;
   
public StaticResourceExtension(object key){
        
resourceKey= key; 
    
}
    public override object ProvideValue(IServiceProvider serviceProvider){ 
       
FindResource(resourceKey); //이 코드는 동작하지 않습니다. 단지 이런 느낌일 것 같다는 추측;;
    }
    public object ResourceKey
   
{
       
get { return resourceKey;} 
        
set { resourceKey =value;}
    
}
}

위에 Constructor Parameter를 사용하고 있다는 것을 확인해 주시기 바랍니다. 만약 {StaticResource MyBrush}로 사용되었다면 MyBrush가 Constructor Parameter가 되는 것입니다. 또한 StaticResourceExtension은 ResourceKey라는 Property도 가지고 있습니다. 이 Property를 통해서도 StaticResoure를 사용할 수 있는데 {StaticResource MyBrush}는 {{StaticResource ResourceKey=MyBrush}와 동일한 의미를 가지는 XAML 코드입니다.

MarkupExtension은 Object이기 때문에 Property Element를 사용해서 XAML을 구성할 수도 있습니다. <Button Background="{StaticResource MyBrush}" />은 다음과 같이 사용할 수도 있다는 것이죠.

<Button >
   
<Button.Background>
         
<StaticResource ResourceKey="MyBrush" /> 
    
</Button.Background>
</Button>

단 보통 Propery Element를 사용한 것의 차이점은 Background Property에 StaticResourceExtension Object가 대입되는 것이 아니라 ProvideValues() 메소드가 불린 결과가 대입된다는 것입니다. , 즉 Background = new StaticResource{ResourceKey="MyBrush"};로 Processing되는 것이 아니라 Background = new StaticResource{ResourceKey="MyBrush"}.ProvideValues()로 Processing된다는 것입니다.

 

기존의 Microsoft가 제공하는 XAML의 기능에 한계를 느낀다면 직접 MarkupExtension을 구현해보는 것도 재미있는 일이될 것 같습니다.

 

사족 : ProvidesValue의 함수에는 IServiceProvider라는 parameter가 들어가는 데요, 이 것으로 여러가지 Service를 사용할 수 있습니다. (저도 자세히 모르겠어요 ㅜ.ㅜ) 가령 앞서 구현한 StaticResourceExtension클래스를 보면 ProvideValue내에서는 FindResource를 사용할 수 없습니다. (FindResource method를 멤버 method로 가지고 있지 않기때문이죠) 이럴 경우에 serviceProvider를 사용하면 여러 메소드를 사용할수 있습니다. 거듭 말씀드리지만 이부분은 저도 잘 몰라서 혹시 이 부분에 대한 지식이 있으신 분들은 공유해주셨으면 합니다.

 

감사합니다.

신고