Silverlight 1.1 바로 시작하기

매니지드 코드의 기초
준비 사항
Silverlight 개발의 기초에서 개발에 필요한 도구와 기술에 대해 설명하고 있습니다.
이벤트 핸들링에서 개체의 이벤트를 처리하는 방법에 대해 설명하고 있습니다.

Silverlight 프로그래밍 모델

프로그래밍 모델 소개
Silverlight 프로그래밍은 크게 Silverlight 1.0 Beta로 릴리즈되어 JavaScript를 사용하는 언매니지드 코드 API와 Silverlight 1.1 Alpha로 릴리즈되어 C#또는 VB.NET을 사용하는 매니지드 코드 API를 사용하는 모델로 구분할 수 있습니다. 1.1 Alpha 릴리즈에서는 DLR(Dynamic Language Runtime)을 통해 파이선이나 JScript를 사용할 수도 있습니다.

어느쪽이든 Silverlight 컨트롤(또는 페이지)는 HTML 파일 안에 호스팅되는 웹 애플리케이션으로써 JavaScript를 통해 초기화되고 HTML 페이지에 로드되며 이에 따라 매니지드 코드와 언매니지드 코드를 섞어서 사용할 수도 있습니다.

현재 1.1 Alpha에 대한 정확한 문서화는 이루어져있지 않지만 대다수의 API 모델과 사용 방법이 1.0 Beta의 그것과 동일합니다. Silverlight 1.1 Alpha에서의 매니지드 코드 작성은 VS의 IDE에서 제공하는 인텔리센스, 코드 자동완성 기능과 개체 브라우저를 활용하여 직관적인 코드 작성이 가능하며, 개체의 구체적인 기능과 사용 방법은 1.0 Beta의 레퍼런스를 참고할 수 있겠습니다.

이 글에서는 직접적인 사용 방법 보다는 Silverlight 프로그래밍에 빈번히 사용되는 기초적인 개체의 기초적인 사항과 기반 지식을 간략하게 소개합니다. Silverlight 1.1 Alpha API는 성능이나 기능 개선 등의 이유로 언제든지 변경될 수 있으니 세부적인 사항은 항상 MSDN을 참고하시기 바랍니다.

매니지드 클래스 계층 구조
Silverlight의 컨트롤과 UI 기능을 제공하는 많은 클래스는 기본적으로 다음과 같은 계층 구조로 이루어집니다.


WPF 프로그래밍을 해보셨다면 WPF의 엘리먼트 계층 구조와 유사하다는 것을 알 수 있을 것입니다. 실제로 몇 가지 기능적인 제약을 제외하고는 기본적인 계층 구조의 각 클래스의 역할이 WPF와 같습니다. Silverlight에서는 FrameworkElement를 상속받는 클래스를 일반적인 엘리먼트 클래스라고 생각할 수 있고 이것은 UI를 구성하는데 기본적으로 필요한 유용한 속성과 메서드 및 이벤트를 제공하며 XAML 마크업에서 UI의 한 부분으로 선언할 수 있습니다.

하지만 XAML에서 사용하는 모든 요소가 FrameworkElement는 아닙니다. 특수한 UI 컴포넌트나 이벤트를 처리하기 위한 컨트롤들은 그들만의 고유한 속성을 가지고 있습니다. 예를 들어 Animation이나 Storyboard에서 파생되는 클래스들이 있는데, 이들은 UI 엘리먼트의 타겟 속성으로 사용될 것입니다.

개체 트리와 개체 찾기
이 절에 대한 내용은 앞서 포스팅한 이벤트 핸들링에서 자세히 설명하고 있습니다. 여기에서는 간략하게 요약하겠습니다.

Silverlight에서 UI 구성은 XAML 마크업을 통한 계층 구조로 이루어지며 마크업의 루트 엘리먼트는 x:Class를 정의하고 여기에서 정의된 클래스가 코드-비하인드에서 매니지드 코드를 사용하여 제어할 수 있게 됩니다.

이 때문에 코드-비하인드내에 정의된 같은 이름의 클래스에서 this 키워드는 마크업의 루트 엘리먼트를 의미합니다.
마크업에서 정의된 하위 엘리먼트들을 접근하는 방법은 루트 엘리먼트가 Panel에서 파생되는 Canvas 등의 개체일 경우 Panel.Children 컬렉션을 통해 인덱스 또는 반복자를 사용하거나 코드-비하인드에서 접근할 엘리먼트에 x:Name 어트리뷰트를 지정하고 코드에서 DependencyObject.FindName 메소드를 사용하여 해당 이름으로 개체의 참조를 얻을 수 있습니다.

각 개체들은 FrameworkElement.Parent 속성을 통해 자신의 부모 개체를 참조할 수 있으며 루트 엘리먼트에서 이 속성은 null을 가리킵니다.

주요 개체와 속성들

너비(Width)와 높이(Height)
너비와 높이는 FrameworkElement가 제공하는 대표적인 UI 엘리먼트를 위한 속성입니다. FrameworkElement에서 파생된 Canvas, TextBlock 또 Shape와 같은 개체들의 너비와 높이를 이 속성으로 설정하거나 얻을 수 있습니다.

TextBlock과 같은 어떤 엘리먼트들은 ActualWidth와 ActualHeight이라는 동적으로 계산되는 읽기 전용 속성을 제공하기도 합니다. TextBlock의 ActualWidth와 ActualHeight은 사용자가 지정한 Width와 Height에 관계없이 실제 표시될 Text에 해당하는 실제 너비와 높이를 의미합니다.

반대로 얘기하자면 어떤 엘리먼트들은 Width와 Height의 설정이 실제로 표시되는 내용의 너비와 높이와 정확히 일치하지 않을 수도 있습니다.

첨부된 속성(Attached Properties) 설정 및 얻기
첨부된 속성은 심지어 코드상의 멤버 목록에 없는 속성일지라도 마크업에서 다른 속성과 같이 어트리뷰트로 정의할 수 있는 속성을 말합니다. 예를 들어, Canvas는 자신의 자식 엘리먼트들에게 그들의 레이아웃 좌표를 결정할 수 있게 하는 속성을 제공합니다. 이 속성은 Canvas.Left와 Canvas.Top 그리고 Canvas.ZIndex입니다. XAML에서 이러한 첨부된 속성을 사용하려면 반드시 그 속성의 Owner.Property와 같이 전체 경로를 지정해야 합니다.

XAML에서는 간단하게 Owner.Property 구분으로 설정이 가능하지만 코드에서는 다른 방법을 사용해야 합니다. 왜냐하면 코드에서 Owner.Property = 값; 이런식으로는 단 하나의 개체에 대한 속성만 지정할 수 있기 때문입니다.

매니지드 코드에서 첨부된 속성은 DependencyObject.GetValue와 SetValue로 읽고 쓸 수 있습니다. 첨부된 속성은 DependencyProperty라는 Object 형태로 표현되며 따라서 읽고 쓸 때 해당 DependencyProperty 타입과 실제 타입으로 캐스팅이 필요합니다.
XAML 첨부된 속성 설정의 예
<Canvas Width="500" Height=200">
    <TextBlock x:Name="myText" Canvas.Left="200", Canvas.Top="10" Text="test" />
</Canvas>

C# DependencyProperty를 사용하는 방법
// 가져오기
double currentLeft = (double) myText.GetValue(Canvas.LeftProperty);

// 설정하기
myText.SetValue(Canvas.LeftProperty, 100);

DependencyProperty로 캐스팅하는 첨부된 속성의 설정과 읽기는 캐스팅 과정 때문에 성능상의 불이익이 있으므로 첨부된 속성이 아닌 일반 속성들은 GetValue나 SetValue를 사용하지 않고 해당 속성을 직접 설정하는 것이 유리합니다.

TextBlock과 Text API
TextBlock은 연속적인 텍스트를 표현하는데 효과적인 수단을 제공합니다. 현재는 중/동아시아 지역 언어를 제대로 표현하지 못하고 있지만 정식 버전이 나올 때에는 해결될 것입니다. 이 예제에서는 정상적으로 표현되는 영문 폰트만을 사용합니다.

TextBlock은 단순한 한줄 짜리 텍스트에서 여러 줄에 걸친 다양한 표현까지 출력하기 위한 수단을 제공합니다.
XAML 멀티 라인을 지원하는 TextBlock 예제
<TextBlock FontSize="30">
    <Run FontFamily="Arial" FontSize="20" Text="Arial, first line" Foreground="red" />
    <LineBreak />
    <Run FontFamily="Comic Sans MS" Text="Comic Sans MS, second line" />
</TextBlock>


위의 예제에서 TextBlock은 일종의 텍스트 컨테이너 루트라고 볼 수 있고 그 안에 Run이란 엘리먼트로 독립적인 폰트 속성의 텍스트를 추가할 수 있고 LineBreak란 엘리먼트로 폰트 사이즈에 맞춰 다음 줄로 넘어갈 수 있습니다. 매니지드 코드로 위의 예제를 표현하자면 다음과 같습니다.
C# 매니지드 코드로 작성한 TextBlock
TextBlock tb = new TextBlock();
tb.FontSize = 30;

Run
run = new Run();
run.FontFamily = "Arial";
run.FontSize = 20;
run.Foreground = new SolidColorBrush(Color.FromRgb(0xFF, 0x00, 0x00));
run.Text = "Arial, first line";
tb.Inlines = new Inlines();
tb.Inlines.Add(run);

tb.Inlines.Add(
new LineBreak());

run =
new Run();
run.FontFamily = "Comic Sans MS";
run.Text = "Comic Sans MS, second line";
tb.Inlines.Add(run);

XAML의 엘리먼트와 매니지드 코드의 클래스가 1:1로 매치되고 있어서 쉽게 접근할 수 있습니다. TextBlock의 상세한 속성과 기능은 MSDN을 참고하십시오.

컬러와 브러쉬
XAML에서 색상은 약 256가지의 일반적인 색상의 이름(Red, Blue)을 사용하거나 #를 붙인 RGB 표기 또는 ARGB 표기를 사용할 수 있습니다.
RGB는 Red, Green, Blue 색상을 순서대로 16진수로 표현하고 ARGB는 불투명도를 의미하는 Alpha를 앞에 붙입니다.

매니지드 코드에서 RGB 색상과 ARGB 색상은 Color.FromRGB와 Color.FromScRgb 및 Color.FromArgb 메소드를 통해 얻을 수 있습니다. 이 색상 값은 보통 어떤 컨트롤을 단일 색상 브러쉬로 칠할 때 사용됩니다. 사용 예제는 앞서 소개한 TextBlock 예제를 참고하십시오.

브러쉬는 어떤 영역을 칠하는 방법을 나타내는 클래스로 단일 색상을 의미하는 SolidColorBrush, 직선형 그래디언트를 의미하는 LinerGradientBrush, 원형 그래디언트를 의미하는 RadialGradientBrush를 비롯해 색상이 아닌 이미지나 심지어 비디오를 소스로 사용할 수도 있습니다. 자세한 것은 역시 MSDN을 참조하십시오.

자주 사용되는 복잡한 형태의 그래디언트나 이미지 브러쉬들을 그때그때 XAML이나 코드로 작성하여 사용한다면 유지보수가 굉장히 힘들어지고 메모리도 많이 차지하게 될 것입니다. 브러쉬 등의 개체는 build action을 embedded resource로 설정한 XAML을 사용하여 리소스의 형태로 사용할 수 있으며 이렇게 리소스화 된 개체는 다른 매니지드 코드에서 XamlReader 클래스를 사용하여 로드할 수 있습니다.

복잡한 형태의 드로잉을 가지는 개체는 Expression Blend와 같은 디자인 툴을 사용하여 XAML로 작성하고 이것을 코드에서 로드하여 사용하는 형태가 이상적일 것입니다.

마우스와 마우스 포인터의 위치
Silverlight UI는 HTML 상에 하나의 컨트롤로써 로드되기 때문에 마우스 포인터의 위치를 항상 얻을 수 있는 건 아닙니다. Silverlight에서 마우스 포인터를 획득하는 것은 마우스 포인터가 루트 컨트롤의 영역 내에 있을 때만 가능하며, 마우스 포인터의 위치는 오직 마우스 관련 이벤트를 통해서만 얻을 수 있습니다.

일반적인 마우스 관련 이벤트는 UIElement 클래스에서 제공하고 있습니다. 대표적인 마우스 이벤트인 MouseMove는 마우스 포인터가 해당 컨트롤 위에서 움직일 때 발생하며 MouseMove 이벤트 핸들러를 작성하여 마우스 포인터의 위치를 얻을 수 있습니다.
C# 마우스 포인터의 위치 얻기
void Page_MouseMove(object sender, MouseEventArgs e)
{
    Point pos = e.GetPosition((UIElement)sender);
    double x = pos.X; // X 좌표
    double y = pos.Y; // Y 좌표
}

마우스 관련 이벤트는 보통 MouseEventArgs 클래스를 통해 전달됩니다. Silverlight는 마우스 포인터의 위치를 직접 X, Y로 전달하는 대신 GetPosition 메소드를 통해 X, Y 좌표를 나타내는 Point 개체를 획득하도록 되어 있습니다.

약간 번거러워 보이는 이 메소드는 대신 인수로 좌표의 기준이 되는 UIElement의 참조를 전달할 수 있으며 null을 전달할 경우 Silverlight 컨트롤 전체에서의 좌표를 반환합니다. 심지어 이벤트와 관계 없는 다른 UIElement의 참조를 전달하여 해당 엘리먼트로부터의 좌표를 구할 수도 있지만 보통은 이벤트가 일어난 개체를 기준으로 할 것이며 이 때 이벤트가 발생한 개체를 의미하는 sender 개체를 UIElement로 캐스팅하여 전달하면 됩니다.

현재 Silverlight는 윈도우즈뿐만 아니라 MAC도 고려하는 크로스 플랫폼으로 개발되고 있기 때문에 윈도우즈 UI 프로그래밍에서 당연하다고 생각되는 몇 가지 속성이나 이벤트가 빠져 있습니다. 예를 들어 마우스 오른쪽 버튼과 휠 버튼이라던가 Alt 키가 눌렸는지 여부 등 윈도우즈에서는 여러모로 유용한 정보들이 있습니다.

이것은 기술적인 문제보다는 정책적인 문제가 고려되는 사항으로 앞으로의 지원 계획이 정확히 어떻게 될지는 확실하지 않습니다.

XamlReader 클래스와 스트림
JavaScript에서는 생성자가 없어서 엘리먼트의 새 인스턴스를 생성하여 개체 트리에 추가할 수 없습니다. 그래서 동적으로 표현할 UI 요소들을 DHTML 기법에서 그랬던 것처럼 숨겨진 엘리먼트를 미리 만들어두고 시나리오에 따라 보여주거나 움직이는 방법을 사용했습니다. 또는 Control.content.createFromXAML 메소드를 이용하여 문자열로부터 엘리먼트를 동적으로 생성하여 개체 트리에 추가하는 방법을 사용하기도 했습니다.

매니지드 코드에서는 개체를 선언하는 것으로 생성자에 접근할 수 있습니다. 때문에 대부분의 경우 XamlReader.Load(createFromXAML과 같은 역할을 하는 메소드) 메소드로 XAML 텍스트를 직접 코드로 로드하는 것은 불필요합니다. 왜냐면 매니지드 코드는 개체 모델에서 새로운 개체를 생성하는 것이 전혀 어렵지 않기 때문입니다.

더 구체적으로 디자인이 동적으로 복잡하게 변경될 수 있는 경우를 생각해 볼 수 있습니다. 분명히 코드에서 개체를 생성하는 것 자체는 어렵지 않지만 복잡한 디자인을 그때그때 코드로 작성한다는 것은 굉장한 낭비입니다. 이런 경우 앞서 얘기한 리소스의 경우처럼 디자이너가 필요한 UI 디자인이나 컨셉을 XAML로 작성하고 개발자는 코드에서 그 XAML 파일을 동적으로 로드하여 시나리오에 맞춰 보여주는 게 더 유리합니다. 그러면 디자이너와 개발자가 서로 격리되어 작업이 가능하며 디자이너와 개발자는 서로 정해진 네임스페이스와 개체 이름만을 공유하여 결과물인 XAML 파일만 다시 로드하면 될 것입니다.

이러한 동작은 System.IO.StreamReader를 사용하여 효율적으로 구현할 수 있습니다. StreamReader는 여러가지 상황에 맞춰 다양한 생성자를 오버로딩하고 있으며 크게 다른 Stream 개체에서 초기화하는 방법과 파일로부터 초기화하는 방법이 있습니다.

어떤 리소스를 XAML파일로부터 동적으로 로드하고 싶다면 두 가지 상황을 고려할 수 있습니다.
하나는 완성된 XAML이 정적일 경우 프로젝트의 embedded resource로 추가하여 어셈블리 안에 리소스로 함께 컴파일 되도록 하고 Assembly.GetManifestResourceStream 메소드를 통해 리소스의 스트림을 획득하는 방법이 있고 다른 하나는 XAML 파일이 런타임에 동적으로 변경되는 경우 StreamReader 클래스를 생성할 때 해당 XAML의 위치를 직접 지정해주는 방법입니다.

전자의 경우 XAML 파일의 경로는 반드시 네임스페이스를 포함한 전체 경로를 지정해야 하며, 후자의 경우 해당 XAML파일의 존재 여부를 런타임에 보장할 수 없으므로 반드시 예외 처리가 필요할 것입니다.
C# 동적으로 XAML을 로드하는 방법
// embedded resource로 추가된 XAML에서 스트림을 얻고 로드하는 방법
Stream s = this.GetType().Assembly.GetManifestResourceStream("TestProejct.test.xaml");
StreamReader sr = new StreamReader(s);
Brush dynamicBrush = XamlReader.Load(sr.ReadToEnd());
sr.Close();
s.Close();

// XAML 파일에서 직접 스트림을 얻고 로드하는 방법
StreamReader sr = new StreamReader("/Resources/test.xaml");
Brush dynamicBrush = XamlReader.Load(sr.ReadToEnd());
sr.Close();

Downloader 클래스
Silverlight는 웹 애플리케이션입니다. 보통은 웹 서버에서 호스팅되며 따라서 모든 컨텐츠는 일단 웹 서버에 존재하게 됩니다.
코드에서 동적으로 이런 컨텐츠를 접근하려고 하면 HTTP특성상 접근하는데 시간도 걸리고 서버도 부하가 늘어나게 될 것입니다. 빠른 반응과 효율적인 서버 운영을 위해서는 어떤 페이지에서 필요한 리소스들은 가급적 한번에 다운로드하고 같은 컨텐츠를 불필요하게 다시 요청하지 않는 게 좋을 것입니다.

Downloader 클래스는 서버에 있는 컨텐츠를 현재 실행중인 클라이언트로 다운로드할 수 있게 해줍니다. 이렇게 다운로드 받은 컨텐츠들은 Downloader 클래스의 지원 메소드들을 통해 스트림으로 얻거나 폰트 소스 설정 등으로 활용됩니다.

Downloader는 일반적으로 새 인스턴스를 생성한 후 DownloadProgressChanged, DownloadFailed, Completed 이벤트 핸들러를 추가한 후 Open 메소드로 읽어올 파일의 URI를 지정하고 Send 메소드를 호출하여 다운로드를 시작합니다. 만약 URI 지정이 잘못되었거나 문제가 있을 경우 DownloadFailed 이벤트가 발생하며 다운로드가 시작되면 진행 상황을 DownloadProgressChanged 이벤트를 통해 알 수 있고 다운로드가 완료되면 Completed 이벤트가 발생합니다.

아직 Downloader 클래스의 구체적인 활용 방법은 문서화되어 있지 않습니다. 현재 시점에서 가장 많이 활용되는 것은 TextBlock에 표시할 폰트를 다운로드 받고 변경하는 코드이며 자세한 활용 방법은 MSDN을 참고하십시오.
C# Downloader 사용 예제
public void Page_Loaded(object o, EventArgs e)
{
    // ... 생략 ...
    Downloader dn = new Downloader();
    dn.Completed += new EventHandler(dn_Completed);
    dn.DownloadFailed += new ErrorEventHandler(dn_DownloadFailed);
    dn.DownloadProgressChanged += new EventHandler(dn_DownloadProgressChanged);
}

void dn_DownloadProgressChanged(object sender, EventArgs e)
{
    // 다운로드 진행률 얻기
    double progressPercent = ((Downloader)sender).DownloadProgress * 100;
}

void dn_DownloadFailed(object sender, ErrorEventArgs e)
{
    // 다운로드 실패
}

void dn_Completed(object sender, EventArgs e)
{
    // 다운로드 완료
}

호스팅 브라우저에서 이벤트와 정보 얻기
Silverlight 컨트롤은 자신을 호스팅하는 HTML DOM에 접근할 수 있고 이를 통해 HTML 엘리먼트의 이벤트나 속성을 Silverlight 컨트롤의 코드에서 컨트롤 할 수 있습니다. 하지만 이런 방법으로 호스트 브라우저의 크기나 이벤트를 얻기는 상당히 어렵고 번거롭습니다.

BrowserHost 클래스는 호스트 브라우저에 대한 실제 크기 및 풀 스크린 모드 여부를 제공하는 편리한 이벤트와 속성을 제공하며 화면을 꽉 채우거나 풀 스크린을 사용하는 Silverlight 컨트롤에서 특히 유용하게 쓸 수 있습니다.  BrowserHost 클래스를 사용하려면 System.Windows.Interop 네임스페이스를 추가해야 합니다.
C# BrowserHost 클래스 사용 예제
public void Page_Loaded(object o, EventArgs e)
{
    // ...생략...
    BrowserHost.Resize += new EventHandler(BrowserHost_Resize);
    BrowserHost.FullScreenChange += new EventHandler(BrowserHost_FullScreenChange);
}

void BrowserHost_Resize(object sender, EventArgs e)
{
    // 호스트 브라우저의 크기가 변경되었을 때 이벤트와 실제 크기 얻기
    double hostWidth = BrowserHost.ActualWidth;
    double hostHeight = BrowserHost.ActualHeight;
}

void BrowserHost_FullScreenChange(object sender, EventArgs e)
{
    // 호스트 브라우저의 풀스크린 모드 변경과 상태 얻기
    bool hostFullScreen = BrowserHost.IsFullScreen;
}

호스트 브라우저의 HTML DOM에 직접 접근하는 것은 System.Windows.Browser 네임스페이스와 그 하위 클래스들로부터 지원됩니다. 자세한 사항은 다른 글에서 자세히 다룰 것입니다.

참고

Silverlight 공식 QuickStarts 참고:
http://silverlight.net/QuickStarts/BuildUi/CallJavascript.aspx

신고
Posted by gongdo


티스토리 툴바