본문 바로가기

.Net Technology/WPF

(5) XAML의 문법부터 이해하기

앞에서 언급했듯이 직접 손으로 XAML 코드를 작성하게 되지는 않을 것이다. WPF 애플리케이션애 개발할 때 UI 부분은 아마도 비주얼 스튜디오 2008이나 익스프레션 블렌드와 같은 툴을 이용해서 XAML을 만들게 되는 경우가 대부분일 것이다. 그럼에도 불구하고 XAML의 문법에 대해서 어느 정도 이해하고 있다면 XAML이 꼬이거나 특정 부분을 손으로 수정할 때 많은 도움을 받을 수 있을 것이다. 그리고 WPF를 더 깊게 이해할 수 있을 것이다. 그렇기 때문에 XAML의 기본적인 문법에 대해서 몇 가지 예제와 함께 살펴볼 것이다. 


XAML패드를 이용한 XAML 연습

XAML에 대해서 공부할 때 XAML패드를 이용하면 간단하고 빠르게 XAML에 대한 UI를 확인해 볼 수 있다. Microsoft Windows SDK를 설치하면 xamlpad.exe이란 유틸리티를 찾을 수 있을 것이다.

이상하게도 닷넷 프레임워크 3.5 SDK나 비주얼 스튜디오2008에서는 xamlpad.exe라는 툴을 제공하고 있지 않다. 그렇기 때문에 Windows SDK를 설치해서 이 툴을 이용할 수 있을 것이고 또한 마지막 부분에서 이 툴을 C#을 이용해서 직접 구현해 볼 것이다.



만약 Windows SDK를 다운로드 하였다면 [시작]->[모든 프로그램]->[Microsoft Windows SDK]->[Tools] 메뉴를 찾아서 이 프로그램을 실행할 수 있을 것이다. 다음 [그림10]은 XAML패드를 실행한 첫 화면을 보여주고 있고, Visual Tree Explorer가 활성화 되어 있는 것을 볼 수 있을 것이다. 

 
[그림10] XamlPad는 실시간으로 XAML의 마크업 언어를 보여준다.

XAML패드를 보면 아래에는 XAML을 넣을 수 있는 패널을 제공해주고 있고 그 위로 XAML의 결과를 보는 패널을 제공해주고 있다. 먼저 이 툴을 시작해보면 기본적으로 <Page>라는 요소가 설정되어 있는 것을 볼 수 있을 거이다. 

<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid>
    <!-- XAML의 추가 -->
  </Grid>
</Page>



XAML패드에 <Window> 엘리먼트가 추가되지 않았다 하더라도 <Page>를 <Window>로 변경해서 F5를 눌러보면 같은 화면이 보여지는 것을 볼 수 있다. 이것은 마크업에 있어서 <Page>와 <Window> 엘리먼트가 동일하다는 것이다. 

XAML패드는 코드를 표현하는 마크업 언어를 지원하지 않는다. 즉, 코드에서 생성한 엘리먼트를 지정한다거나 <Code>와 같은 엘리먼트를 지정하는 것을 허용하지 않는다는 것이다. 이러한 엘리먼트를 지정하면 에러가 발생하게 될 것이다. 

XAML패드를 이용해서 마크업 언어를 지정해 보면 인텔리센스가 부족하다는 것을 느낄 수 있을 것이다. 하지만 Show Visual Tree 버튼을 클릭해 보면 비주얼 스튜디오의 속성 창과 같은 창을 지원해주고 있기 때문에 쉽게 속성을 확인할 수 있을 것이다. 아쉬운 부분은 Visual Tree에서는 직접 XAML을 변경할 수 없고 그저 읽기만 가능하다는 것이다. 

또한 XAML패드는 *.xaml 파일을 자신이 원하는 위치에 저장할 수 있는 기능은 제공하고 있지 않다. 단지 자동적으로 XamlPad_Saved.xaml 파일에 마크업이 저장되고 이후에 툴을 다시 로드하면 이 파일을 읽어서 보여주게 된다. 


XAML의 네임스페이스와 키워드

이미 앞에서 살펴봤던 대로 XAML 파일의 최상위 루트에는(<Window>, <Page>, <Application>) 일반적으로 다음과 같은 네임스페이스들이 정의되어 있다. 

<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>
    </Grid>
</Page>



먼저 http://schemas.microsoft.com/winfx/2006/xaml/presentation 네임스페이스는 *.xaml 파일에서 사용할 WPF 네임스페이스들을 정의 하고 있다. (System.Windows, System.Windows.Controls, System.Windows.Data, System.Windows.Ink, System.Windows.Media, System.Windows.Navigation 등) 이렇게 1:다 방식으로 매핑되는 WPF 어셈블리들은(WindowsBase.dll, PresentationCore.dll, PresentationFramework.dll) 실제로 각각의 클래스에 [XmlnsDefinition]라는 애트리뷰트가 선언되어 있다. 만약 이러한 WPF 어셈블리를 Reflector를 이용해서 확인해 본다면 다음 [그림11]과 같이 직접 매핑되는 것을 확인할 수 있을 것이다. 
 

[그림11] http://schemas.microsoft.com/winfx/2006/xaml/presentation 네임스페이스와 WPF 기본 네임스페이스의 매핑


두 번째 네임스페이스인 http://schemas.microsoft.com/winfx/2006/xaml은 XAML에서 이용하는 특정한 키워드가 정의 되어 있을 뿐만 아니라 System.Windows.Markup에서 제공하는 클래스들을 정의하고 있다. 규칙에 맞게 XML 문서를 정의하기 위해서는 최상위 루트에 항상 하나의 XML 네임스페이스를 정의해야만 한다. 만약 최상위루트에서 추가적인 네임스페이스를 지정하고 싶다면 XamlSpecificStuff와 같은 유일한 접두어를 반드시 붙여 주어야 한다. 

<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:XamlSpecificStuff="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>
    </Grid>
</Page>


이렇게 접두어를 이용해서 XML 네임스페이스를 정의하게 되었을 때의 단점은 이 네임스페이스를 이용할 때 매번 XamlSpecificStuff을 선언해 주어야 한다는 것이다. 앞에서 C# 코드를 XAML에서 이용할 수 있는 예를 본적이 있을 것이다. 그와 같은 엘리먼트가 바로 http://schemas.microsoft.com/winfx/2006/xaml에서 지원하는 키워드 중에 하나인 것이다. 다른 XAML 키워드는 C#으로 정의한 클래스의 이름이다. 

만약 앞에서 만들었던 MyApp의 XAML에 XML 네임스페이스를 추가한다면 다음과 같이 코드를 수정해주어야 한다. 

<Application XamlSpecificStuff:Class="SimpleXamlApp.MyApp"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:XamlSpecificStuff ="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml" Exit ="AppExit">
  < XamlSpecificStuff:Code>
<![CDATA[
private void AppExit(object sender, ExitEventArgs e)
{
MessageBox.Show("App has exited");
}
]]>
  </XamlSpecificStuff:Code>
</Application>


XamlSpecificStuff라는 키워드는 상당히 길고 쓰기도 불편하기 때문에 대신 간단한 x: 라는 키워드를 이용하도록 하자.http://schemas.microsoft.com/winfx/2006/xaml 네임스페이스는 다음 [표9]에서처럼 클래스와 Code 키워드뿐만 아니라 추가적인 XAML 키워드들을 제공해주고 있다. 

XAML 키워드

설명

Array

닷넷의 Array 클래스를 XAML로 나타낸다.

ClassModifier

Class 키워드에 지정된 클래스의 접근 타입(internal, public)을 정의한다.

DynamicResource 

변경되었는지 모니터링 해야 하는 WPF 리소스들의 참조를 만든다.

FieldModifier 

루트의 자식 엘리먼트를 위해서 타입의 접근 타입(internal, public, private, protected)을 정의한다. (<Window> 엘리먼트에 포함된 <Button>이 바로 자식 엘리먼트가 된다.)

Key 

Dictionary 엘리먼트에 포함될 XAML Key값을 정의한다.

Name 

C#에서 사용할 XAML 엘리먼트의 이름을 정의한다

Null

null 참조를 나타낸다.

Static 

멤버를 스태틱 타입으로 만들고 싶을 때 정의한다.

StaticResource 

변경되었는지 모니터링 할 필요 없는 WPF 리소스들의 참조를 만든다.

Type 

C#의 typeof와 같은 의미로 사용된다.

TypeArguments 

제네릭 타입의 파라메터를 지정하는데 사용된다.

[표9]XAML의 키워드


이렇게 다양한 키워드이 존재하지만 간단하게 <Window>에서 ClassModifier와 FieldModifier, Name 그리고 Class 키워드를 사용한 예제를 살펴보도록 하겠다. XAML패드는 이러한 키워드를 사용할 수 없다고 앞에서 언급했었다. 

<!-- 이 클래스는 internal로 선언할 것이고 code 파일에서 이용한다면
Partial 클래스에서도 internal로 지정되어야 할 것이다. 
-->
<Window x:Class="MyWPFApp.MainWindow" x:ClassModifier ="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <!-- 이버튼은 *.g.cs 파일에서 public 으로 선언될 것이다. -->
  <Button x:Name ="myButton" x:FieldModifier ="public">
    OK
  </Button>
</Window>




모든 C#/XAML 타입들은 기본적으로 public으로 지정된다. 하지만 우리는 여기서 Window를 internal로 지정하였고 Button 타입을 public으로 지정하였기 때문에 다음과 같은 코드가 생성되는 것을 볼 수 있을 것이다. 

internal partial class MainWindow : System.Windows.Window,
System.Windows.Markup.IComponentConnector
{
    public System.Windows.Controls.Button myButton;
    ...
}



XAML의 요소와 애트리뷰트

우리는 지금까지 루트 엘리먼트와 XML 네임스페이스에 대해서 살펴 보았다. 이제는 루트 엘리먼트의 자식 엘리먼트에 대해서 살펴보도록 하겠다. 앞에서도 언급했듯이 실제로 WPF 애플리케이션은 단 하나의 패널을 가지게 되고 그 패널 안에 다양한 UI들이 존재하게 된다. 다음 자에서 이 패널들에 대해서 자세히 살펴볼 것이기 때문에 여기서는 간단하게 <Window>에 단 하나의 Button 엘리먼트를 포함시켜 보도록 하겠다. 

이미 이번 장을 살펴보았듯이 XAML 엘리먼트들은 닷넷에서 선언한 클래스나 구조체와 매핑되고 애트리뷰트는 속성들이나 이벤트들을 지정하게 된다. 그렇기 때문에 다음과 같이 XAML을 선언하여 사용할 수 있다. 

<Window x:Class="MyWPFApp.MainWindow" x:ClassModifier ="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <!-- 코드 파일에 myButton_Click와 같은 메서드가 존재한다는 가정으로
다음과 같은 이벤트를 선언한다.  -->
  <Button x:Name ="myButton" x:FieldModifier ="public"
  Height ="50" Width ="100" Click ="myButton_Click">
    OK
  </Button>
</Window>



이와 같은 코드를 프로그램에서는 다음과 같이 작성할 수 있을 것이다. 

Button myButton = new Button();
myButton.Height = 50;
myButton.Width = 100;
myButton.Content = "OK";
myButton.Click += new RoutedEventHandler(myButton_Click);



이번 장을 통해서 이 정도의 코드는 어느 정도 예상할 수 있을 것이다. 하지만 버튼의 Content 속성을 할당해 준 부분은 다시 한번 생각해볼 필요가 있다. 앞에서 설명했던 대로 WPF 컨트롤들은 ContentControl이라는 클래스를 상속받는다. 그렇기 때문에 버튼에 ScrollBar와 같은 아이템을 추가하는 것이 가능하다. 여기서 Content 속성은 “OK”라는 문자열을 <Button>과 </Button> 사이에 묵시적으로 지정해주었다. 또한 명시적으로 Content 속성을 이용해서 할당하는 것도 가능하다. 

<Button x:Name ="myButton"
Height ="50" Width ="100" Content = "OK">
</Button>



여기서 Content 속성을 어떻게 지정하든 크게 달라지는 것이 없기 때문에 개인적으로 더 좋아하는 방법대로 지정해주면 될 것이다. 더 흥미로운 부분은 XAML을 이용해서 버튼의 Content에 어떻게 다른 객체를 할당해 주는지에 대한 내용이다. 이미 앞에서 언급했듯이 속성 엘리먼트의 문법을 이용해서 해결할 수 있다. 


XAML 속성 엘리먼트의 문법의 이해

속성 엘리먼트의 문법은 복잡한 객체를 속성에 할당해줄 수 있게 하기 위해서 만들어진 문법이라고 보면 된다. 다음 코드는 스크롤이 가능한 버튼을 만드는 XAML 코드를 보여주고 있다. 

<Button x:Name ="myButton" Height ="100" Width ="100">
    <Button.Content>
        <ScrollBar Height = "50" Width = "20"/>
    </Button.Content>
</Button>


이 코드에서는 <Button.Content>라는 네임스페이스를 이용해서 ScrollBar 요소를 정의하였다. 속성 엘리먼트의 문법은 언제나 <개체이름.속성이름>과 같은 형식으로 지정해준다. 다음 [그림12]는 XAML패드를 이용해서 만들어본 스크롤이 가능한 버튼에 대한 예제를 보여주고 있다. 
 

[그림12] 속성 엘리먼트를 이용해서 객체의 할당

ContentControl을 상속받은 자식 엘리먼트는 자동적으로 Content 속성을 설정하게 된다고 했다. 그렇기 때문에 다음과 같이 선언하여 사용할 수 있다. 

<Button x:Name ="myButton" Height ="100" Width ="100">
    <ScrollBar Height = "50" Width = "20"/>
</Button>



속성 엘리먼트 문법은 Content 속성을 설정하는데 있어서 딱히 제한되는 부분이 없다. 오히려 XAML 문법은 복잡한 객체를 설정할 때마다 쉽게 이용할 수 있을 것이다. 예를 들어 버튼의 Background 속성을 설정한다고 가정해보자. 이 속성은 WPF에 있는 어떤 Brush라도 설정할 수 있어야 할 것이다. 만약 단색의 Brush를 할당해 주고 싶다면 다음 코드처럼 문자열을 이용해서 할당해 주면 자동적으로 해당 속성과 매핑하여 처리하게 된다. 

<!--“Green” 속성은 Brushes.Green으로 설정된다 -->
<Button x:Name ="myButton" Height ="100" Width ="100" Background ="Green">
  <Button.Content>
    <ScrollBar Height = "50" Width = "20"/>
  </Button.Content>
</Button>



하지만 만약에 LinearGradientBrush와 같이 복잡한 Brush를 만들어야 한다면 단순하게 이름/값 형식으로는 표현이 불가능 할 것이다. LinearGradientBrush를 표현하기 위해서는 다음과 같이 속성 엘리먼트 문법을 이용해서 값을 설정해 주어야 한다.

<Button x:Name ="myButton" Height ="100" Width ="100">
  <Button.Content>
    <ScrollBar Height = "50" Width = "20"/>
  </Button.Content>
  <Button.Background>
    <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
      <GradientStop Color="Blue" Offset="0" />
      <GradientStop Color="Yellow" Offset="0.25" />
      <GradientStop Color="Green" Offset="0.75" />
      <GradientStop Color ="Red" Offset="0.50" />
    </LinearGradientBrush>
  </Button.Background>
</Button>



LinearGradientBrush 클래스의 설정은 30장에서 살펴볼 내용이기 때문에 여기서 크게 신경 쓸 필요는 없다. 여기서는 간단하게 속성 엘리먼트 문법을 이용해서 Content 속성과 Background 속성을 설정해 줄 수 있다는 것을 알아두자. 다음 [그림13]은 이렇게 설정한 예제를 보여주고 있다. 
 

[그림13] 펜시버튼

속성 엘리먼트 문법은 LinearGradientBrush와 같은 객체를 할당할 때 거의 이용하기도 하지만 그냥 문자열과 같은 값을 할당할 때 또한 사용할 수 있다. 

<Button x:Name ="myButton" Height ="100" Width ="100">
  <Button.Content>
    <ScrollBar Height = "50" Width = "20"/>
  </Button.Content>
  <Button.Background>
    Pink
  </Button.Background>
</Button>



이렇게 지정한다고 해서 특별히 달라지는 것은 없다. 즉, 다음과 같이 선언한 것과 크게 다르지 않다는 것이다. 

<Button x:Name ="myButton" Height ="100" Width ="100" Background = "Pink">
  <Button.Content>
    <ScrollBar Height = "50" Width = "20"/>
  </Button.Content>
</Button>



첨부 속성의 이해

XAML은 속성 엘리먼트 문법에 추가적으로 첨부 속성이라는 문법을 제공해주고 있다. 첨부 속성은 굉장히 많이 사용하지만 첨부 속성을 사용하는 목적 중에 하나가 바로 자식들의 속성이 부모 엘리먼트에 따라서 다르게 지정될 수 있기 때문이다. 일반적으로 다양한 WPF 패널들(Grid, DockPanel 등) 중에 어떤 패널에 UI 엘리먼트를 위치시키기 위한 문법으로 많이 사용된다. 다음 장에서 이 패널들에 대해서 자세히 살펴 볼 것이다. 다음 첨부 속성의 예제코드를 보여주고 있다.

<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <DockPanel LastChildFill ="True">
    <!-- 첨부 속성을 이용하여 Dock의 설정 -->
    <Label DockPanel.Dock ="Top" Name="lblInstruction"
    FontSize="15">Enter Car Information</Label>
    <Label DockPanel.Dock ="Left" Name="lblMake">Make</Label>
    <Label DockPanel.Dock ="Right" Name="lblColor">Color</Label>
    <Label DockPanel.Dock ="Bottom" Name="lblPetName">Pet Name</Label>
    <Button Name="btnOK">OK</Button>
  </DockPanel>
</Page>



 
이 코드에서는 도킹 되는 Label의 위치를 설정들을 정의하였다. 첨부 속성들의 문법은 <부모타입.부모속성>과 같은 형식을 가지고 있다.  Button에는 특별히 도킹 영역을 지정할 수 없다는 것을 알아두자. 하지만 DockPanel의 LastChildFill 속성을 true로 지정해주면서 남은 영역이 버튼으로 채워지게 된다. 

첨부 속성에 알아야 할 몇 가지 내용들에 대해서 설명해 보겠다. 무엇보다도 무조건 부모의 속성이라고 해서 첨부 속성을 설정할 수 있는 것은 아니다. 예를 들어 다음과 같은 속성을 지정한다면 에러가 발생하게 될 것이다.  

<!-- 버튼의 Height 속성 -->
<Button x:Name ="myButton" Width ="100">
  <Button.Content>
    <ScrollBar Button.Height = "100" Height = "50" Width = "20"/>
  </Button.Content>
  <Button.Background>
    Pink
  </Button.Background>
</Button>


 
실제로 첨부속성은 의존성 속성(dependency property)의 한 부분이다. 의존성 속성은 다양한 입력들을 기반으로 그 값이 계산하게 된다. 그렇기 때문에 의존성속성과 첨부 속성은 또한 어떤 객체를 기반으로 그 속성을 설정하게 된다. 

WPF에서 사용되는 데이터 바인딩, 스타일, 테마, 애이메이션 서비스와 같은 여러 가지 기술들은 바로 의존성 속성을 기반으로 만들어 지게 된다. 또한 의존성 속성은 스스로 값의 유효성을 확인할 수 있는 기능을 제공하고 있다. 

사실상 의존성 속성의 값은 닷넷 프로퍼티에서 설정한 값과 다를 바가 없다. 그렇기 때문에 대부분의 경우 의존성 속성의 값을 설정하는 방법에 대해서 몰라도 크게 상관없다. 

하지만 의존성 속성을 구현하는 방법은 일반 속성과는 매우 다르다. 대부분의 WPF 어플리케이션에서는 커스텀 의존 프로퍼티를 구현하지 않을 것이다. 만약 커스텀 WPF 컨트롤을 만들었고 이 컨트롤의 위치를 설정하는 경우라면 아마도 의존 프로퍼티를 구현하게 될 것이다. 의존 프로퍼티에 대한 설명은 29장에서 다시 자세하게 살펴볼 것이다.


XAML 컨버터의 이해

애트리뷰트를 이용해서 값을 할당하거나(Background = "Pink") 아니면 앞뒤 태그 사이에 값을 할당한다고 할 경우(<Button>OK</Button>) 간단하게 문자열로 가정하여 설정할 것이다. 하지만 실제로는 문자열 타입이 아니라는 것이다. 예를 들어 버튼의 Background 속성은 다음과 같이 정의되어 있다. 

// System.Windows.Controls.Control.Background 속성
public Brush Background
{
    ...
}



여기서 확인할 수 있듯이 이 속성은 string이 아닌 Brush로 설정되어 있다. 이렇게 질문을 던질 수 있을 것이다. 그렇다면 과연 무엇이 Pink라는 문자열을 RGB값을 설정한 SolidColorBrush 객체로 변경하는 것일까? 다른 예를 살펴보자. 다음에는 원의 크기를 설정해 주었다. 

<Ellipse Fill = "Purple" Width = "100.5" Height = "87.4">
</Ellipse>


 
여기서 원의 Width와 Height의 값을 설정해 주었지만 이 값은 원래 string이 아닌 double 형을 가지고 있다.

내부적으로는 XAML 파서라는 녀석이 있어서 string 데이터를 원래의 객체로 변환해주는 역할을 하게 된다. 예를 들어 “Pink”라는 문자열 값을 설정하면 ColorConverter와 BrushConverter를 이용해서 해당 Brush를 설정하게 되는 것이다. 이밖에도 SizeConverter, RectConverter, VectorConverter와 같은 다양한 컨버터들이 존재한다. 

이러한 컨버터들의 이름과 상관없이 모든 컨버터들은 System.ComponentModel.TypeConverter라는 컨버터를 상속받게 된다. 이 클래스는 CanConvertTo(), ConvertTo(), CanConvertFrom(), ConvertFrom()와 같은 다양한 메서드들을 정의하고 있고 컨버터들은 이러한 메서드들을 구현하고 있다. 
대부분의 경우 객체에 지정한 string 데이터들이 어떠한 컨버터로 매핑되는지 알 필요는 없고 간단하게 XAML에서 지정한 background는 해당 타입으로 변환된다는 것 정도만 알아두도록 하자.


XAML이 새로운 기술들과 연관되어 있다 하더라도 컨버터의 개념은 닷넷 플랫폼이 발표되었을 때부터 이미 존재하고 있었다. 윈도우 폼과 GDI+ 또한 다양한 컨버터들이 존재한다. 예를 들어 GDI+의 System.Drawing 네임스페이스는 FontConverter라는 컨버터를 가지고 있고 만약 “Wingdings”라는 문자열을 지정하면 Wingdings라는 폰트로 변환해 주게 된다.



 
마크업 확장식의 이해

타입컨버터는 XAML에서 특별한 노력 없이도 쉽게 타입을 설정할 수 있는 흥미로운 기능을 제공해 주고 있다. 이 타입 컨버터는 *.xaml 파일을 처리할 때 내부적으로 사용된다. 하지만 이와 대조적으로 XAML에서는 마크업의 확장을 지원하고 있다. 타입컨버터와 같이 마크업의 확장을 이용하면 마크업 값을 간단한 런타임 객체로 변환해 줄 수 있다. 하지만 컨버터와 다른 부분은 특별하게 XAML 문법을 가지고 있다는 것이다. 

지금까지 이야기한 타입컨버터와 마크업의 확장을 살펴보면 동일한 목적으로 사용된다고 생각할 수 있을 것이다. 그렇다면 왜 이렇게 두 가지로 구분하여 정의한 것일까? 간단하게 마크업 확장식은 타입컨버터 보다 유연하게 설계되었고 새로운 기능을 확장하는 방법이 보다 수월하다. 

마크업 확장식을 이용하면 다른 타입의 스태틱 속성을 반환하는 것이 가능하다. 실제로 XAML의 Array, Null, Static, Type와 같은 키워드들은 내부적으로는 마크업 확장식을 이용한 것이다. 타입컨버터와 같이 마크업 확장식은 내부적으로 MarkupExtension을 상속받은 클래스인 것이다. 

마크업 확장식의 동작을 살펴보기 위해서 라벨의 Content 속성에 System.Environment라는 스태틱 메서드를 이용해서 컴퓨터의 정보를 보여주고 싶다고 가정하자. 이 때 다음과 같은 마크업 코드를 작성할 수 있다. 

<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CorLib="clr-namespace:System;assembly=mscorlib">
  <StackPanel>
    <Label Content ="{x:Static CorLib:Environment.MachineName}"/>
    <Label Content ="{x:Static CorLib:Environment.OSVersion}"/>
    <Label Content ="{x:Static CorLib:Environment.ProcessorCount}"/>
  </StackPanel>
</Page>



다른 것보다도 <Page>에 선언된 네임스페이스를 살펴보자. CorLib라는 접두사를 가진 네임스페이스가 새롭게 추가되어 있는 것을 볼 수 있다. (이 접두사의 이름은 임의로 정해지는 것이기 때문에 어떤 이름을 써도 상관은 없다.) XML 네임스페이스에 먼저 clr-namespace라는 토큰을 만들어 등록하였고 또 assembly라는 다른 토큰을 만들어서 등록하였다. 

이 XML 네임스페이스를 등록함으로써 Label 객체에 마크업 확장식을 이용해서 Environment의 스태틱 멤버를 등록할 수 있게 된다는 것을 알아두자. 여기 예제에서 보았듯이 마크업 확장식은 언제나 중괄호를 이용해서 나타내게 된다. 마크업 확장식은 두 개의 값을 받게 된다. 하나는 Static과 같이 마크업 확장식의 이름이고 하나는 CorLib:System.Environment.OSVersion와 같은 값을 할당해주게 된다. 

<!-- 'Static' 이라는 마크업 확장식과 Content에 할당할 값을 설정한다. -->
<Label Content ="{x:Static CorLib:Environment.OSVersion}"/>


 
그럼 마크업 확장식의 다른 예제를 살펴 보도록 하자. 여러 Label 객체를 추가하였고 다양한 값들을 설정해 보도록 하겠다. 이 예제 또한 다양한 마크업 확장식 타입을 이용해서 다음과 같은 코드 또한 사용할 수 있다. 

<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CorLib="clr-namespace:System;assembly=mscorlib">
  <StackPanel>
    <Label Content ="{x:Static CorLib:Environment.MachineName}"/>
    <Label Content ="{x:Static CorLib:Environment.OSVersion}"/>
    <Label Content ="{x:Static CorLib:Environment.ProcessorCount}"/>
    <Label Content ="{x:Type Label}" />
    <Label Content ="{x:Type Page}" />
    <Label Content ="{x:Type CorLib:Boolean}" />
    <Label Content ="{x:Type x:TypeExtension}" />
  </StackPanel>
</Page>


 
WPF의 Label 뿐만 아니라 Button에도 이와 같은 장식을 적용할 수 있다. 이번 예제에서는 mscorlib.dll에 존재하는 Boolean 데이터 타입을 출력해 보았고 또한 마크업 확장식의 타입을 출력해 보았다. 만약 XAML패드를 이용해서 이 내용을 출력해 보면 다음 [그림14]와 같은 그림이 출력되는 것을 볼 수 있을 것이다. 
 

[그림14] 마크업 확장식의 속성에 설정한 여러 멤버들과 타입의 정보


리소스와 데이터 바인딩 미리보기

마지막으로 소개할 XAML의 문법은 리소스와 데이터 바인딩에 대한 내용이다. 이번 예제에서는 Array 라는 마크업 확장식을 이용해볼 것이고 또한 간단한 데이터 바인딩에 대해서 살펴 볼 것이고 또한 WPF의 리소스(Resource)에 대해서 살펴볼 것이다. Array 마크업 확장식을 이용하면 속성에 Array 데이터를 할당해 줄 수 있다. Array와 같은 데이터를 XAML로 선언해 주려면 반드시 어떤 종류의 데이터 배열을 가지고 있는지 반드시 지정해 주어야 한다. 예를 들어 다음 코드를 살펴보도록 하자. 

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CorLib="clr-namespace:System;assembly=mscorlib">
<StackPanel>
<Label Content ="{x:Array Type = CorLib:String}"/>
</StackPanel>
</Page>



만약 XAML패드를 이용해서 이 내용을 확인해 본다면 System.String[] 이라는 값이 패널에 보여지는 것을 볼 수 있을 것이다. 여기서 중괄호를 이용해서는 Array 데이터를 보여줄 방법이 없다. 먼저 Array의 자식 엘리먼트를 이용해서 Array의 값들을 지정해줘야 한다. 다음과 같이 XAML 정의해 보도록 하자. 

<x:Array Type="CorLib:String">
    <CorLib:String>Sun Kil Moon</CorLib:String>
    <CorLib:String>Red House Painters</CorLib:String>
    <CorLib:String>Besnard Lakes</CorLib:String>
</x:Array>


 
이렇게 우리는 문자열 배열을 생성하였다. 즉, <x:Array> 안으로 문자열들을 생성한 후에 태그를 닫은 것이다. 이러한 XAML 마크업 문법이 올바른 설정이라 하더라도 이 값을 어디에 위치시켜야 하는지에 대한 의문이 들 수 있다. 만약 <Page> 엘리먼트 안에 직접 넣는다고 한다면 <Page>의 Content 속성에 값을 할당하는 것이 될 것이다. 그렇게 <Page>에 넣고 XAML패드를 이용해서 확인해보면 분명 System.String[]라는 문자열이 보여지게 될 것이다. 

<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CorLib="clr-namespace:System;assembly=mscorlib">
  <!-- Humm, we just set the Content property here! -->
  <x:Array Type="CorLib:String">
    <CorLib:String>Sun Kil Moon</CorLib:String>
    <CorLib:String>Red House Painters</CorLib:String>
    <CorLib:String>Besnard Lakes</CorLib:String>
  </x:Array>
</Page>


 
우리는 실제로 배열의 이름을 이용해서 ListBox와 같은 다른 곳에서 참조하여 사용하고 싶을 것이다. 만약 이 배열을 리소스(Resource) 엘리먼트 안에 넣는다면 그러한 방식으로 사용할 수 있을 것이다. 여기서 “리소스” 라는 말을 예전에 윈도우 프로그램에서 아이콘이나 이미지와 같은 파일들을 저장하던 리소스의 의미로 생각해서는 안 된다. WPF의 리소스들은 문자열 배열과 같은 마크업의 커스텀 데이터를 보여주기 위해서 주로 사용된다. (이 내용에 대해서는 30장에서 자세하게 살펴볼 것이다.)

그럼 마지막으로 <Page>안에 마크업 확장식을 이용하여 <StackPanel>에 리소스를 만들고 GoodMusic 라는 이름의 문자열 배열을 정의해보자. 그리고 ListBox 컨트롤의 ItemsSource 속성에 StaticResource 마크업 확장식을 이용하여 배열을 설정해주자. (여기서 Key의 이름과 반드시 매핑시켜 주어야 한다.)

<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CorLib="clr-namespace:System;assembly=mscorlib">
  <StackPanel>
    <StackPanel.Resources>
      <x:Array Type="CorLib:String" x:Key = "GoodMusic">
        <CorLib:String>Sun Kil Moon</CorLib:String>
        <CorLib:String>Red House Painters</CorLib:String>
        <CorLib:String>Besnard Lakes</CorLib:String>
      </x:Array>
    </StackPanel.Resources>
    <Label Content ="Really good music"/>
    <ListBox Width = "200" ItemsSource ="{StaticResource GoodMusic}"/>
  </StackPanel>
</Page>



 
코드를 살펴보면 <StackPanel.Resources> 엘리먼트를 선언하여 문자열 배열을 지정해주었다. 그리고 StaticResource 마크업 확장식으로 대입된 값은 초기 바인딩 이후에 값이 변하지 않는다는 것이다. 예를 들어 특정 색을 바인딩 하였지만 그 값이 향후에 변경된다 하더라도 StaticResource를 참조한 컨트롤의 값은 변하지 않는다는 것이다. 이것을 허용하기 위해서는 DynamicResource를 이용해야 한다. 다음 [그림15]는 위와 같은 코드를 XAML패드를 이용해서 본 화면을 보여주고 있다. 

 

[그림15]XAML패드로 살펴본 그림

Key와 StaticResource 마크업 확장에 x라는 접두사가 붙지 않는 이유는 이 속성들이 바로http://schemas.microsoft.com/winfx/2006/xaml/presentation라는 XML 네임스페이스에 선언되어 있기 때문이다. 

지금까지 XAML의 문법에 대해서 다양한 예제들과 설명들을 살펴보았다. XAML은 닷넷 객체를 트리로 표현하기 위해서 다양한 재미있는 기능들을 추가하였다. 이러한 기능들은 그래픽 UI를 설정할 때 굉장히 도움이 될 수 있지만 기억해야 할 것은 XAML은 기본적으로 생성자를 가지고 있는 추상적이지 않은 타입들을 표현할 수 있다는 것이다. 

이렇게 하여 WPF와 XAML의 기초적인 문법들을 살펴 보았다. 다음 장에서는 WPF 레이아웃 매니저들과 각각의 컨트롤들에 대해서 살펴볼 것이다. 하지만 그 전에 비주얼 스튜디오 2008에서 제공하는 WPF 프로젝트 템플릿과 익스프레션 블렌드에 대해서 살펴보도록 하겠다. 


이 내용은 C#.NET Platform 원서의 내용을 기반으로 작성되었습니다.