[WPF_01] 도대체 XAML이 뭐냐?

.NET Framework 3.0이 소개되면서 각종 세미나 및 코드 샘플을 통해
어느덧(?) 익숙해져 버린 단어가 되어버렸지만,
그 본질, 특징을 제대로 알고 쓰자는 의미에게 강좌를 마련했습니다.

XAML은 Extensible Application Markup Language의 약자로 Back-end와 Front-end를 구분지으며, 개발자와 디자이너의 협업을 원활하게 하도록 만들어진 언어입니다.
한국에서는 '자믈'이라고 읽히는 경우가 있는데, 외국 스크린캐스트를 보면 거의 '재믈'이라고 발음을 합니다.

Static한 화면의 디자인 구성부터 시작해서, Story Board(애니메이션), Control등의 Contents에 Binding, 3D표현 등등의 아주 많은 작업을 할 수 있습니다.
단, 동적으로 Control을 생성해서 구성하는 경우나 이벤트 핸들러 등등을 추가하여 별도의 인터렉션이 필요할 때는 당연히 C#코드나 VB .Net 코드가 필요하겠지요.

그러면 본격적으로 XAML을 살펴보도록 합시다.

<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
    
<Button.Content>
    WPF TEST 
    
</Button.Content>
</Button> 

XAML은 .NET Framework 3.0이상이 깔린 환경에서 XamlPad나 IE에서 보면 보입니다.

XamlPad

위의 코드는 아래와 같습니다.

<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Content="WPF TEST">
</Button>  

<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
   WPF TEST
</Button>   

위와 같은 코드가 동일한 결과물을 낼수 있는 것은 강력한 TypeConverter가 존재하기 때문입니다. 경우에 따라서 단순한 문자열이 되기도 하고, 정수가 되기도 하고, Collection이 되기도 합니다.


그렇다면 만약 아래와 같은 코드는 어떨까요?

<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
   <Content>
   "WPF TEST2"
   
</Content>

</Button>  

언뜻봐서는 될 것 같지만,

"XML 네임스페이스 'http://schemas.microsoft.com/winfx/2006/xaml/presentation'에 'Content' 태그가 없습니다. '2' 줄 '5' 위치입니다." 라는 에러 메세지가 나옵니다.
이 에러의 의미를 살펴보겠습니다.
"Content"는 XML이 참조하는 해당 네임스페이스에 정의가 되어 있지 않습니다.
왜나하면 "Content"는 "Button"(또는 다른 Control)이 가지고 있는 속성일 뿐, "Button"처럼 그 자체가 객체로서 표현될 수 있는 성질의 것이 아니기 때문입니다.
그렇기 때문에 혼자 XML의 Element가 될 수는 없습니다. 속성을 표현하기 위해서는 <Button Content=""> 와 같이 Button 객체의 Attribute로 들어가던지, <Button.Content>처럼 Button이라는 타입을 명시하고 그의 속성임을 분명히 밝혀야 XML의 Element로 사용할 수 있습니다.
(한가지 Tip을 드리자면 저 Content라는 놈은 WPF에서 무시무시한 역할을 하는 놈입니다. 단순한 string 만을 가지는 것이 아니라 다양한 그래픽 요소(2D, 3D)뿐 아니라 동영상까지 짊어질 수 있습니다.)

이쯤되면 궁금해지는 것이 하나이상 생기실 겁니다.
바로 네임스페이스!
http://schemas.microsoft.com/winfx/2006 ··· entation 이게 과연 무엇일까?
(링크를 타고 들어가봤자, 404 error response 뿐입니다.)
(팁, Silverlight은 http://schemas.microsoft.com/client/2007 을 사용하지요.)
XAML에서 Root Object Element는 적어도 하나 이상의 네임스페이스를 가져야 합니다. 그렇지 않고는 XAML 파서가 Button이라고 했을 때, 그 버튼이 .NET 모듈에 있는 버튼인지 아니면 개발자가 만든 이상한 모양의 컨트롤을 Button이라고 이름짓고 그것을 사용하려고 하는 판단할 수 가 없는 노릇이지요.

<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"~>흔히들 이렇게 기본 네임스페이스로 사용하고 있습니다. 그러나 아래와 같이 사용하셔도 됩니다. 

<MyNameSpace:Button 
   xmlns:MyNameSpace
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"~>

root Element에 네임스페이스를 지정했다면 그 하위의 Element는 그 네임스페이스를 상속 받습니다.  만약에 추가로 다른 네임스페이스를 사용한다면 위의 MyNameSpace와 같이 Prefix를 명시적으로 적어주어야 합니다. 예를 들자면 자주 사용하는  xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml 처럼요.

사실 XAML은 .NET API를 사용하기 위한 하나의 방법일 뿐입니다.
그렇다면 그 API는 어떻게 있는 API일까요?

WPF http://schemas.microsoft.com/winfx/2006/xaml/presentation  네임스페이스는 아래와 대응됩니다.

System.Windows
System.Windows.Automation
System.Windows.Controls
System.Windows.Controls.Primitives
System.Windows.Data
System.Windows.Documents
System.Windows.Forms.Integration
System.Windows.Ink
System.Windows.Input
System.Windows.Media
System.Windows.Media.Animation
System.Windows.Media.Effects
System.Windows.Media.Imaging
System.Windows.Media.Media3D
System.Windows.Media.TextFormatting
System.Windows.Navigation
System.Windows.Shapes

 

C#에서 using 지시어를 사용하여 네임스페이스를 잡을 때는 당연히 위의 것들을 사용해야 합니다.

(팁, 만약 위의 네임스페이스 이외의 것을 사용하고자 할 때는 Visual Studio에서 프로젝트의 References에 해당 dll을 Add Reference하시고 XAML 에디터에서 xmlns:를 치는 순간 Visual Studio에서 Reference들을 체크하여 네임스페이스를 선택할 수 있도록 도와줍니다.

 
예를 들면
xmlns:my="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms" 형태입니다.)

 
그럼 이제 Visual Studio 에서 WPF Application 프로젝트를 하나 만들어보겠습니다.

<Window x:Class="WPFApplication1.Window1"
    xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
    Title
="Window1" Height="300" Width="300">
  
</Window>  
namespace WPFApplication1
{
    
/// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>

    
public partial class Window1 : Window
    {
        
public Window1()
        {
            InitializeComponent()
;
        
}
    }
}

위와 같이 자동으로 생성됩니다.

그럼 여기서 더해지는 궁금증!
XAML이 어떻게 로드되는거지?
기본으로 생성되는 Windows1.xaml에 따라 다니는 cs 파일이 있을 겁니다. xaml파일의 이름이 바뀌면 덩달아 자신의 이름이 [XAML파일].xaml.cs로 바뀌는 놈이지요. 그 파일에 InitializeComponent() 라는 함수가 그 일을 해주는 놈입니다. 그 함수 내부는 아래와 같은 형식입니다.

private bool _contentLoaded;

public void 
InitializeComponent() {
   
if (_contentLoaded) {
        
return;
   
}
   _contentLoaded 
= true;
   
System.Uri resourceLocater = new System.Uri("/WPFApplication1;component/window1.xaml", System.UriKind.Relative);
   
System.Windows.Application.LoadComponent(this, resourceLocater);
}

참고로 Silverlight은 저런 코드를 명시적으로 적어주어야 하지요.
XAML과 C#코드와의 연동이 어떻게 이루어지는지는 뒤에서 살펴보도록 합시다.

그렇다면 이번엔 위 프로젝트에
x:Class="WPFApplication1.Window1"
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml 와 같은 부분이 있는데요.
x:Class 는 대충 코드에서 냄새가 나지만, 그 이외의 Attribute 키워드에는 어떤 것이 있는지 살펴보면,

x:Class
x:ClassModifier
x:Code
x:FieldModifier
x:Key
x:Name
x:Shared
x:Subclass
x:TypeArguments
x:Uid
x:XData


그리고 키워드 같지만, System.Windows.Markup 네임스페이스에 정의되어 있는 Markup Extension을 살펴보면 아래와 같습니다.

x:Array
x:Null
x:Static
x:Type


위의 내용들은 http://msdn2.microsoft.com/en-us/library/ms753327.aspx 에 설명되어 있습니다. (저는 해당 내용이 필요할 때마다 언급하겠습니다.)
자세히 읽어보시면 각각의 Attribute나 Markup Extension을 사용할 때 주의점(제약사항)이 나와있습니다.

예를 들면 x:Array를 사용할 때는 반드시 x:Type을 명시하라고 되어있습니다.

<x:Array Type="sys:String" 
   xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:sys
="clr-namespace:System;assembly=mscorlib"> 
   
<sys:String>Hello</sys:String> 
   
<sys:String>World</sys:String> 
</x:Array> 

당연한 이야기겠지만 위와 같이 Array안에 들어가는 아이템의 Type을 명시적으로 정해주어야 합니다.

또 다른 예를 들면 x:Code를 사용할 때는 CDATA 타입을 사용하라고 되었습니다. 왜냐하면 코드 속에 <, >가 들어가 있다면 parser가 parsing을 할 때 에러를 내기 때문입니다.

<Window x:Class="WPFApplication1.Window1"
    xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
    Title
="Window1" Height="300" Width="300">
    
<Button Click="button_Click">Ok</Button>
    
<x:Code>
        
<![CDATA[
        void
 button_Click(object sender, RoutedEventArgs e)
        {
            System.Windows.Forms.MessageBox.Show(
"Click!!");
        }
        ]]
>
    
</x:Code>
</Window>

위의 코드는 테스트삼아 만들어봤는데 XAML을 사용하는 이유가 프로그램 코드와 디자인을 분리시킨다는 장점이 있기 때문이라면, 위의 코드는 그닥 좋아보이지 않습니다. 참고로 XamlPad에서 위의 코드는 돌아가지 않습니다. x:Code를 사용하면 그 코드 부분 때문에 반드시 컴파일이 되어야 하기 때문이지요.

 

자. 그럼 이제는 약간 실전의 느낌을 내며 AML과 C#코드가 어떻게 연동되는지 살펴보겠습니다.
위의 프로젝트를 수정하겠습니다.

<Window x:Class="WPFApplication1.Window1"
    xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
    Title
="Window1" Height="300" Width="300">
    
<StackPanel>
        
<Button x:Name="a">Button a </Button>
        
<Button Name="b">Button b</Button>
        
<Button>Button C</Button>
    
</StackPanel>
</Window>

여기서 주의해야 할 점은 x:Name과 Name을 사용하고 있는데, 기본적으로는 같은 놈이 아닙니다.
x:Name은 단순히 Element에 이름을 붙여주는 Element이구요, Name은 몇몇 class에서 자기 자신을 표현하는 일종의 Property라고 생각하시면 되겠습니다.
(예를 들어 FrameworkElement, FrameworkContentElement 는 Name Property를 가지고 있어서, Button이나 기타 컨트롤 등에 Name을 사용할 수 있는 것이지요)

두개를 혼용해서 쓰는 경우 하나의 Element에는 당연히 둘 중 하나를 써야하구요. 이름 값은 xaml 한 페이지에서 중복이 있으면 안됩니다.
위의 코드 처럼 x:ClassModifier 를 public으로 하지 않는한, 저렇게 x:Name, Name을 주면, 해당 Class WPFApplication1.Window1에서는 internal로 각각의 object를 정의하고 있습니다.

public partial class Window1 : System.Windows.Window, System.Windows.Markup.IComponentConnector 
{
    
internal System.Windows.Controls.Button a;
    internal 
System.Windows.Controls.Button b
;
        
    private bool 
_contentLoaded;
        
    
/// <summary>
    /// InitializeComponent
    /// </summary>
    
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
    
public void InitializeComponent() {
        
if (_contentLoaded) {
            
return;
        
}
        _contentLoaded 
= true;
        
System.Uri resourceLocater = new System.Uri("/WPFApplication1;component/window1.xaml", System.UriKind.Relative);
        
System.Windows.Application.LoadComponent(this, resourceLocater);
    
}
        
    [System.Diagnostics.DebuggerNonUserCodeAttribute()]
    [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(
"Microsoft.Design""CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
    
void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) {
        
switch (connectionId)
        {
        
case 1:
        
this.a ((System.Windows.Controls.Button)(target));
        return;
        case 
2:
        
this.b ((System.Windows.Controls.Button)(target));
        return;
        
}
        
this._contentLoaded = true;
    
}
}

위의 코드는 WPFApplication1.Window1 Class가 왜 Windows1.xaml.cs에 partial로 정의 되어야 하는지를 말해주는 단적인 예가 되겠습니다.
(참고로 위의 코드는 Windows1.xaml.cs에 있는 IntializeComponent 함수를 Go Definition 하면 볼 수 있습니다.)
버튼을 3개 만들었는데, x:Name이나 Name이 붙은 경우만 Object를 정의하고 있는 것을 아실 겁니다.
만약 C# 코드에서 버튼의 위치나 Content를 바꾸거나, 혹은 스토리보드 애니메이션을 연결할 수 도 있겠지요? 그냥 a.Content = "blah blah" 하면 될테니까요.

이것으로 잠깐이나마 XAML의 특징을 알아보았습니다.
이제 차차 WPF 강좌를 올리도록 하겠습니다.

chaoskcuf
프로그래밍/TIP& Study 2007/06/26 08:34

트랙백 주소 : http://chaoskcuf.com/trackback/91

댓글을 달아 주세요

  1. 김준환 2007/06/27 09:57  수정/삭제  댓글쓰기

    오옷 훌륭합니다! 앞으로의 연재가 기대됩니다!!

Powerd by Textcube, designed by criuce
rss