Wolf schmidt씨가 Loaded event timing in Silverlight에서 실버라이트와 WPF의 Loaded 이벤트 발생 시점의 차이와 이에 따른 workaround에 대해 설명했는데요, 실버라이트는 FrameworkElement.Loaded 이벤트가 템플릿이 적용되기 전에 발생하지만 WPF에서는 템플릿이 적용된 후에 발생한다고 해요.

MSDN 문서에서는 실버라이트의 Loaded 이벤트에 대해 “FrameworkElement가 레이아웃을 잡는 과정을 완료하고, 렌더링 되고, 인터랙션을 할 준비가 완료되었을 때”라고 설명하고 있는데요, 이것보다는 “FrameworkElement가 생성되고 오브젝트 트리(비주얼 트리)에 추가되었을 때 발생”이 더 적합한 설명이므로 차후 SDK에서는 설명을 변경하겠다는군요.

이 차이는 개발자들에게 혼란을 주는데요 왜 이런 차이를 주었는지에 대한 것은 모르겠어요.

여튼, 실버라이트는 Loaded 이벤트에서는 아직 템플릿이 적용되지 않은 상태이므로 템플릿이 적용된 명확한 시점을 알 수 있는 방법이 없는데요, Wolf씨는 다음과 같은 workaround를 소개하고 있어요(날림 번역 발동). 각각의 workaround는 가능성과 한계가 다르므로 상황에 맞춰서 써야겠죠.

  1. 컨트롤을 서브클래싱한다면 Loaded 이벤트를 핸들링하는 것보다 OnApplyTemplate을 오버라이드 하는 것이 낫습니다.
    OnApplyTemplate은 템플릿으로 부터 비주얼 트리를 얻었고 이제 그 비주얼들을 사용하거나 조정하려고 하는 상황을 위해 명확하게 의도된 콜백입니다.
    이 방법의 한계는 이 서브클래싱된 컨트롤을 사용하는 코드에서는 OnApplyTemplate의 정의를 바꿀 수 없고 또한 OnApplyTemplate이 호출되는 시점에 다른 추가적인 처리를 할 수 없다는 점입니다.
  2. Loaded를 사용할 수도 있습니다. 그렇지만 사용자는 첫번째 호출되는 Loaded 이벤트 핸들러에서 Control.ApplyTemplate을 명시적으로 호출해야 합니다. ApplyTemplate은 동기적인 메서드이기 때문에 이 호출이 완료된 후에는 템플릿에서 만들어진 비주얼 트리를 사용 가능한 상태가 됩니다.
    다만 ApplyTemplate을 호출하는 것은 이미 Loaded 이벤트에서 일어났던 여러가지 실버라이트의 서브시스템(렌더링, 레이아웃 등)에 중복하여 적용되지 않습니다. 이런 시도를 사용하려면 해당 엘리먼트는 FrameworkElement와는 달리 Control일 필요가 있고 실제 템플릿과 엮여 있어야 합니다. 그래서 이 상황은 보통 ContentControl이나 ItemsControl에 적합합니다. (왜냐면 ContentControl이나 ItemsControl은 항상 비주얼 트리의 결정이 템플릿이 적용된 이후에 이루어지기 때문이죠.)
  3. 또한 Loaded 이벤트 대신에 LayoutUpdated 이벤트를 이용할 수 있습니다. LayoutUpdated는 실버라이트 UI에서 컨트롤을 만드는 과정 중에서 가장 마지막에 일어나는 “오브젝트 라이프타임”이벤트입니다. 중요한 제한은 LayoutUpdated가 일련의 초기화 과정 중에서 단 한번만 발생하지는 않는다는 점입니다. LayoutUpdated는 레이아웃의 변화(예를 들어 레이아웃 안에 있는 요소가 사이즈가 변경되는 등)와 연관되어 발생할 수 있기 때문입니다. 따라서 아주 작은 프로퍼티의 변화가 있더라도, 심지어 비주얼 트리의 변화가 없더라도 이 이벤트가 발생할 수 있으므로 LayoutUpdated 이벤트가 실제로 의미 있는 변화가 있었는지를 결정하는 로직이 따로 필요할 것입니다.

그럼 제 경우는 어떻게 하느냐…

기본 컨트롤의 경우는 어쩔 수 없지만 새로 작성하는 모든 커스텀 컨트롤은 ControlBase라는 기반 클래스에서 파생되도록 하고 있어요. 그리고 이 ControlBase 클래스는 Control에서 파생되었으며 OnApplyTemplate을 좀더 기대되는 타이밍에 수행될 수 있도록 오버라이드 했어요.

 public abstract class ControlBase : Control
    {
        /// <summary>
        /// 템플릿 바인딩이 완료되었는지 여부를 설정하거나 반환합니다.
        /// </summary>
        public bool IsTemplateApplied { get; private set; }
 

        /// <summary>
        /// 템플릿 적용이 완료되었을때 발생합니다.
        /// </summary>
        public event EventHandler TemplateApplied;

        /// <summary>
        /// 기본 메서드는 적절한 이벤트 처리를 위해 숨깁니다. 오버라이딩된 메서드를 이용해야 합니다.
        /// </summary>
        public sealed override void OnApplyTemplate()
        {
           this.OnApplyTemplate(this); 

            IsTemplateApplied = true;           

            if (TemplateApplied != null)
                TemplateApplied(this, EventArgs.Empty);
        }

        /// <summary>
        /// 템플릿 적용이 완료된 시점에서 수행할 동작을 정의합니다.
        /// 기본 메서드는 구현 내용이 없습니다.
        /// </summary>
        protected virtual void OnApplyTemplate(object sender)
        {
        }
    }

사실 또 한가지 의문이라면 OnApplyTemplate이란 메서드가 도대체 왜 public인가인데요, 이 부분은 좀 더 공부를 해 봐야 겠네요.

정리하자면, Custom Control을 사용할 때엔 템플릿이 적용되는 시점 제어를 세심하게 해야 한다는거죠.

저작자 표시 동일 조건 변경 허락
신고
Posted by gongdo

Dave Relyea’s Silverlight Blog에 10월 11일에 올라온 글인데 왜 놓쳤는지 몰라요. 엄청 중요한 내용이라 그대로 옮겨 볼께요. 이 글은 실버라이트의 컨트롤이 생성되고 속성이 설정될 때 어떤 이벤트들과 오버라이드들이 어떤 타이밍으로 호출되는지를 설명합니다.

동작 XAML에서 만든 컨트롤 코드에서 만든 컨트롤
컨트롤의 생성자Ctor 시작 태그가 파싱되자 마자. new로 인스턴스를 생성할 때.
명시적인 스타일 적용됨 Style 속성이 XAML에서 설정되었다면 종료 태그가 파싱되자 마자 적용. Style 속성이 설정되자 마자.
빌트인 스타일(generic.xaml) 적용됨 명시적인 스타일이 적용된 후 종료 태그가 파싱되자마자. 이것은 명시적인 스타일을 덮어쓰지 않음. 컨트롤이 비주얼 트리에 들어갈 때. 이것은 명시적인 스타일을 덮어쓰지 않음.
프로퍼티 설정됨 어트리뷰트가 파싱되었을 때. 해당 속성을 설정했을 때.
Loaded 이벤트 엘리먼트가 비주얼 트리에 추가되었을 때 전달됨. 이벤트는 다음 프레임이 렌더링 되기 전에 발생되고 레이아웃 변경 전에 일어남. (XAML과 동일)
템플릿 적용됨(컨트롤의 비주얼이 템플릿에서 만들어진 경우) 레이아웃 측정 과정.
컨트롤이 비주얼 트리를 가지고 있지 않다면 템플릿 프로퍼티가 적용됨. 컨트롤은 비주얼 트리 없이 라이프 사이클을 시작하고 템플릿 프로퍼티가 설정될 때 비주얼 트리가 클리어됨. ApplyTemplate을 호출하여 명시적으로 지시할 수 있음.
(XAML과 동일)
OnApplyTemplate 호출됨 템플릿이 적용된 시점. 기반 클래스의 OnApplyTemplate을 호출할 필요는 없지만, 사용자가 구현한 커스텀 컨트롤을 상속 할 때에는 필요할 수 있음. (XAML과 동일)
비주얼이 최초로 사용 가능하게 됨. OnApplyTemplate에서 GetTemplateChild로 획득한 비주얼에 대해서. (XAML과 동일)
MeasureOverride 호출됨 레이아웃 측정 과정.
만약 측정 과정중에 템플릿이 확장된 경우 MeasureOverride는 템플릿 확장이 끝난 후 호출됨.
(XAML과 동일)
ArrangeOverride 호출됨 컨트롤의 정렬 과정. 측정 과정이 완료된 후 발생. (XAML과 동일)
SizeChanged 이벤트 측정과 정렬 과정이 완료된 후. (XAML과 동일)
LayoutUpdated 이벤트 SizeChanged 이벤트가 발생된 후. (XAML과 동일)
저작자 표시 동일 조건 변경 허락
신고
Posted by gongdo

나를 위한 메모.

오늘도 실버라이트로 굿뺑이 나이스뺑이를 치고 있어요.
여튼 평소와 같이 간단한 컨트롤을 하나 만들고 이것저것 하고 있는데, 이상하게도 겨우 50개의 컨트롤을 올리는데 로딩시간이 5초 이상 걸리는거에요. 물론 약간 복잡한 형태를 담고 있는 컨트롤이긴 하지만 아무리 생각해도 이건 아니었죠.

해결하기 위해 별 짓을 다 해봤어요. 컨트롤 안에 있는 무거운 구성요소(이미지, 스토리보드)들을 싸그리 주석처리 하고 다시 해보기도 하고 배경에 있는 다른 컨트롤들을 주석처리 하기도 하면서요. 그래도 거의 차이가 없더군요.

문제는 바로 RootVisual인 Page 클래스의 생성자 또는 Page의 Loaded 이벤트에서 50개의 컨트롤을 죄다 생성했기 때문이었더군요.

일반적으로 생각했을 때 Loaded 이벤트에서 다수의 컨트롤을 초기화 하는 것은 나쁜 생각이 아니지만 실버라이트 전체 페이지가 로드 될 때라면 얘기가 약간 달라지는 것 같아요.
전체 페이지의 초기화 사이클에서는 플러그인 초기화, 메모리 할당, 템플릿 및 스태틱 리소스 초기화 등 여러가지 일이 동시에 일어나기 때문에 일반적인 상황에 비해서 컨트롤이나 오브젝트의 생성/할당 작업이 훨씬 더 느려지는 걸로 보여요.

결국 이 문제를 해결하기 위해서 50개의 컨트롤을 생성하는 작업을 DispatcherTimer를 사용하여 0.5초 정도 지연 시켜서 했더니 5초 이상 걸렸던 초기화 작업이 거의 1초 이내에 완료가 되더군요.

여튼 실버라이트에서 성능적으로 가장 큰 저하가 일어날 때는 다수의 컨트롤을 한꺼번에 오브젝트 트리에 올릴 때인데요, 항상 대용량 데이터를 올릴 때에는 그 상황에 다른 로딩 작업이 있는지를 잘 생각해야 할거에요.

나중에 시간 여유가 생기면 이러한 차이를 체감할 수 있는 샘플을 만들어보도록 하죠.

신고
Posted by gongdo
벌써 silverlight.net에는 이런저런 샘플들도 올라왔네요.
어떤 컨트롤을 사용할 수 있는지 한눈에 보시려면 다음의 링크로...
http://silverlight.net/Samples/2b1/SilverlightControls/run/default.html
신고
Posted by gongdo
Silverlight Controls 에서 가져온 내용.
요즘 바쁘답시고 아무것도 안하고 있는데 이러다가 펌블로거로 전락할까 두렵네요 -_-

뭐 번역하고자시고도 없어요. 그냥 링크된 주소의 그림을 보시면 큼지막하게 잘 나와있으니까요.

요약하자면, 현재 1.1 Alpha버전의 컨트롤 지원 계획(Planned)이에요.
블로그에서는 1.0에 대한 얘기가 없는데 어쩌면 1.0은 이젠 지원 계획이 없을지도 몰라요.
한가지 의아한건 컨트롤이 프레임웍 레벨에서 제공되려면 XAML도 수정이 필요할텐데 그 때 1.0과 1.1의 차이는 어떻게 메꿀 건지 좀 의문이네요. (그래도 1.0은 아웃오브안중이라... -ㅅ-)

Control 지원 계획
Button, TextBox, Scrollbar, Slider, ListBox, RadioButton, ComboBox

Layout 지원 계획
Canvas, Grid, StackPanel, ViewBox


지원 계획에서 빠진 것
TreeView, RichTextBox, DataGrid

과연 이 컨트롤들이 완전히 OS 중립적으로 구현될지 아니면 슬라이더를 제외하고 HTML 브라우저의 컨트롤을 활용하여 OS/브라우저 별로 차이를 갖게 될지는 알 수 없어요.

제 생각엔 이런 기본 컨트롤들은 OS/브라우저 환경에 따라서 표현되는 게 좋은데요, 예를 들어 일반 버튼이나 라디오 버튼이 OS의 테마에 영향을 받는다거나 하는 것 말이에요.
어찌되었건 -특히 입력 부분에서- 지원만 잘 되면 상관 없겠죠.

이런 생각에 약간 힘을 실어주는 건 HTML 브라우저에서 기본으로 지원하지 않는 TreeView, RichTextBox, DataGrid는 빠져있다는 거죠. DataGrid는 WPF에서도 3rd파티 지원이니까 논외로 친다고 해도 말이죠.

Layout은 반드시 나와줘야만 하는 거죠. Layout을 사용자 컨트롤 레벨에서 구현하면 정말로 괴로워질거에요. 프레임웍 레벨에서 구현해야만 하는게 맞겠죠.
DataGrid 컨트롤이 지원되지 않지만 Grid를 잘 활용하면 그리 어렵지 않게 구현할 수 있을거에요. 그리고 화면 레이아웃은 Grid와 StackPanel을 잘 조합하면 지긋지긋한 HTML의 Table/DIV 지옥을 벗어날 수 있을거라 기대해요.

마지막으로, 피똥싸가면서 실버라이트용 사용자 컨트롤을 개발하고 계신다면 위의 계획을 참고해서 겹치는 부분은 그냥 대충 만들고 나중에 대체하는게 정신건강에 도움이 될거라고 생각되네요.^^
신고
Posted by gongdo

Silverlight 커스텀 컨트롤을 하나 만들어봤습니다.
Silverlight이 현재는 기본으로 제공되는 컨트롤이 몇 개 없어서 단순한 버튼 조차 만들어 써야 하는데요, 내친김에 VISTA의 글래스 스타일로 만들었어요.
디자인 감각은 꽝이라 비슷하게 만들었는지 어쨌는지 모르겠네요. =ㅅ=

(여기에서 테스트 가능 : http://gongdo.oranc.co.kr/Silverlight/Samples/GlassButton/)

사용자 삽입 이미지

연습 삼아 만들어본거라 문서화는 제대로 하지 않았지만 코드 안에다가 주석은 충분히 달았으니 관심 있으신 분은 한번 소스를 받아보세요.
GlassButton.zip

GlassButton 샘플


간단하게 컨트롤에 대해 요약할께요.
Namespace : Gongdosoft.Silverlight.UX
Class Name : GlassButton
Properties :
  • Width : 버튼 전체의 너비
  • Height : 버튼 전체의 높이
  • Radius : 버튼의 둥글기 정도(픽셀 단위, 네 모서리에 적용)
  • Background : 배경색상 브러쉬
  • Foreground : 전경색상 브러쉬
  • LightColor : 마우스를 올렸을 때 표시될 색상값
  • ShadowMask : 버튼을 클릭했을 때 버튼을 마스킹하는 브러쉬
  • Opacity : 버튼 전체의 불투명도(0~1)
  • OpacityMask : 버튼 전체의 불투명도 마스크 브러쉬
  • BackgroundOpacity : 버튼 배경색상의 불투명도
  • Caption : 버튼의 캡션(문자열)
  • CaptionHorizontalAlign : 캡션의 수평 정렬
  • CaptionVerticalAlign : 캡션의 수직 정렬
  • FontUri : 다운로드할 폰트 또는 폰트가 들어있는 Zip의 경로를 설정
  • FontFamily : 사용할 폰트 종류(현재 한글은 Arial Unicode MS만 사용가능)
  • FontSize : 폰트의 크기(픽셀 단위)
  • FontStyle : 폰트의 스타일(이탤릭)
  • FontWeight : 폰트의 두께
  • TextDecoration : 폰트의 꾸밈(밑줄)
  • TextWrapping : 줄바꿈 속성(현재는 제대로 동작하지 않음)
Events :
  • DownloadCompleted : FontUri에서 설정한 폰트를 다운로드 완료했을 때
  • DownloadFailed : FontUri에서 설정한 폰트를 다운로드 실패했을 때
  • DownloadProgress : FontUri에서 설정한 폰트를 다운로드 하는 진행 과정
  • Clicked : 버튼을 클릭했을 때

나름대로 생각은 많이 했는데 제일 걸리는 부분은 한글 문제에요.
내부적으로 Glyphs가 아닌 TextBlock을 사용했는데요 때문에 현재 사용할 수 있는 폰트는 Arial Unicode MS 폰트밖에 없어요.
그 외의 폰트라면 영문은 제대로 표시되니까 FontUri에 폰트 파일의 경로를 설정하고 FontFamily에 폰트 이름을 설정하면 작동할거에요.

그 외에 몇가지 속성은 XAML에서 설정할 수 없는 문제가 있는데 이건 아무래도 Silverlight의 미완료된 부분인 것 같아서 좀 더 알아봐야 할 것 같네요.

사용기나 버그 리포트 대환영입니다. :)

신고
Posted by gongdo
Silverlight 1.1 바로 시작하기

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

커스텀 컨트롤 만들기

컨트롤의 기본 구성
Silverlight에서 컨트롤은 UI를 구성하는 XAML 마크업과 개체 모델을 구성하는 코드-비하인드로 구분할 수 있습니다. 특히 Silverlight와 같이 풍부한 사용자 인터페이스가 강조되는 프로그래밍에서 UI의 미려함은 매우 중요하므로 디자이너는 미적 감각을 충분히 살려 디자인된 컨트롤을 마크업으로 전달하고 개발자는 코드-비하인드에서 컨트롤을 모델링하고 제어하여 다른 XAML 페이지에서 재사용할 수 있습니다.

이렇게 작성된 XAML과 코드는 하나의 라이브러리 파일(.dll)로 컴파일 되며 다른 프로젝트에서는 이 라이브러리 파일의 어셈블리를 참조하여 사용이 가능합니다.

여기에서는 커스텀 컨트롤의 개요를 살펴보기 위해 MyButton이라는 간단한 버튼을 만들어보겠습니다.
이 버튼은 아주 단순하게 캡션과 너비 및 높이 속성을 가지며 클릭 이벤트를 노출하여 버튼이 클릭되었을 때 핸들링 할 수 있도록 할 것입니다. 또한 속성을 변경하면 캡션이 현재 너비 및 높이의 한 가운데에 표시될 수 있도록 하겠습니다.

Silverlight 컨트롤 프로젝트 만들기
기본적으로 커스텀 컨트롤은 XAML이나 코드를 포함하지 않는 라이브러리의 형태로 배포될 것입니다. 때문에 Silverlight 컨트롤은 일반적으로 Silverlight 클래스 라이브러리 프로젝트를 기반으로 작성합니다.

Howto:3-1 Silverlight 컨트롤 작성을 위한 클래스 라이브러리 프로젝트 만들기
1. Visual Studio를 실행하고 메뉴에서 File->New->Project를 선택하거나 Ctrl+Shift+N를 눌러 새 프로젝트를 선택합니다.
2. 새 프로젝트 다이얼로그 박스에서 Project types를 Visual C#->Silverlight로 선택하고 Templates에서 Silverlight Class Library를 선택한 후 프로젝트 이름을 MyButton이라고 입력하고 [OK]를 누릅니다.
사용자 삽입 이미지

3. 기본으로 생성되어 있는 Class1.cs는 필요 없으므로 솔루션 익스플로러에서 Class1.cs를 선택하고 마우스 오른쪽 버튼을 누른 후 Delete를 선택하여 삭제합니다.
4. 메뉴에서 Project->Add New Item을 선택하거나 Ctrl+Shift+A를 눌러 새 아이템을 선택합니다.
5. 새 아이템 다이얼로그 박스에서 Silverlight User Control을 선택하고 클래스 이름을 MyButton이라고 입력하고 [OK]를 누릅니다.
사용자 삽입 이미지

노트
다시 설명하겠지만, Silverlight User Control은 하나의 라이브러리 파일로 배포하기 위해 XAML파일의 build action 속성을 embedded resource 형태로 설정하며 따라서 컴파일시 XAML 파일은 어셈블리에 포함되게 됩니다.


UI 디자인하기
실제 작성해야 할 컨트롤은 미려한 디자인과 기능성 그리고 인터페이스 접근성을 고려해야 할 것입니다. 그러기 위해 UI의 디자인은 Expression Blend나 Design 등 XAML 코드를 작성할 수 있는 디자인 툴을 사용하는 것이 좋습니다.

여기에서는 단순히 TextBlock을 하나 만드는 걸로 만족하겠습니다.
Howto:3-2 XAML로 기본적인 UI 디자인 하기
1. MyButton.xaml을 열고 다음 코드를 루트 Canvas 엘리먼트 안에 넣습니다.
XAML
<TextBlock x:Name="text"></TextBlock>

2. 이 TextBlock은 버튼의 캡션을 표시하는 역할을 수행할 것입니다.
코드에서 제어할 필요가 있는 UI 요소는 반드시 x:Name으로 이름을 지정하여 코드에서 참조할 수 있도록 해야 합니다.

개체 참조 얻기
디자인이 완료되었으면 코드-비하인드에서는 UI 모델을 구현하기 위해 제어할 UI 개체의 참조를 획득해야 합니다.
MyButton.xaml.cs를 보면 다음과 같이 Control 클래스를 상속하며 기본 생성자가 미리 만들어져 있음을 확인할 수 있습니다.
C# 기본 생성자
namespace MyButton
{
    public class MyButton : Control
    {
        public MyButton()
        {

            System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream("MyButton.MyButton.xaml");
            this.InitializeFromXaml(new System.IO.StreamReader(s).ReadToEnd());
        }
    }
}

이 코드는 MyButton 클래스의 어셈블리로부터 XAML 파일을 리소스 스트림으로 읽어와서 그 스트림으로부터 초기화를 수행합니다. 이를 위해 MyButton.xaml 파일의 속성을 보면 build action이 embedded resource로 설정되어 있을 것입니다.

이 특수한 형태의 초기화는 Control 클래스의 InitializeFromXaml 메소드를 통해 구현됩니다.

미리 생성된 코드에서 InitializeFromXaml 메소드를 호출만 하지만 이 메소드는 XAML 스트림을 읽어서 초기화한 후 이 XAML의 루트 개체에 대한 참조를 반환합니다. 이 참조를 클래스의 지역 변수로 저장하여 UI 개체를 코드로 제어할 수 있습니다.

또한 루트 개체에 대한 참조를 얻으면 XAML에서 x:Name을 지정한 개체에 대한 참조를 FindName 메소드를 사용하여 얻을 수 있고 이런 식으로 XAML내의 모든 개체를 코드에서 제어할 수 있습니다.
Howto:3-3 XAML에서 디자인된 개체의 참조를 얻기
1. MyButton.xaml.cs를 엽니다.
2. 클래스의 private 지역 변수로 FrameworkElement 타입의 implementationRoot를 선언하고 InitializeFromXaml의 반환값을 implementationRoot에 대입합니다.
3. TextBlock에 대한 참조를 보유하기 위해 클래스의 private 지역 변수로 TextBlock 타입의 text를 선언하고 implementationRoot.FindName("text") 메소드를 사용하여 참조를 얻습니다. 이렇게 완성된 코드는 다음과 같습니다.

C# 개체 참조를 보유하기 위한 코드 수정
namespace MyButton
{
    public class MyButton : Control
    {
        private FrameworkElement implementationRoot;
        private TextBlock text;

        public MyButton()
        {

            System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream("MyButton.MyButton.xaml");
            implementationRoot =
this.InitializeFromXaml(new System.IO.StreamReader(s).ReadToEnd());

            text = implementationRoot.FindName("text") as TextBlock;
        }
    }
}

속성 및 이벤트 추가하기
컨트롤에서 가장 중요한 것은 적절한 속성(Property)과 이벤트(Event)를 정의하는 것입니다.
속성과 이벤트는 이 컨트롤을 사용하는 마크업에서 어트리뷰트의 형식으로 선언하고 설정할 수 있으며 이 컨트롤 개체에 대한 참조를 획득한 코드에서도 접근할 수 있습니다. 앞서 얘기한 것처럼 여기에서는 캡션(Caption), 너비(Width), 높이(Height) 속성과 Click 이벤트를 추가하도록 하겠습니다.

Control 클래스는 FrameworkElement를 상속하고 있으며 기본적인 너비(Width), 높이(Height), 클리핑 영역(Clip), 커서(Cursor), 불투명도(Opacity) 등 많은 기본적인 속성을 그대로 받아 옵니다. 따라서 우리가 추가하려는 속성인 너비와 높이는 FrameworkElement에서 이미 선언이 되어 있으며 이 속성은 virtual이 아니므로 오버라이딩이 불가능하고 만약 똑같이 Width라는 이름의 속성을 만든다면 컴파일러는 이 속성이 FrameworkElement.Width 속성에 의해 숨겨질 것이라는 경고를 내릴 것입니다.

이렇게 부모 클래스에서 미리 선언된 일반적인 이름의 속성을 재정의 하고 싶을 때에는 속성의 선언에 new 키워드를 추가해야 합니다.
Howto:3-4 속성 추가하기
1. Width 속성을 추가합니다. Width는 implementationRoot 즉, 컨테이너 역할을 하는 루트 캔버스 개체의 너비와 싱크되며 이 값을 수정하면 텍스트의 위치를 가운데 정렬하기 위해 레이아웃 계산을 다시 수행합니다. Width라는 이름은 이 컨트롤이 상속하는 FrameworkElement의 Width와 중복되어 이 속성이 숨겨지므로 new 키워드를 사용하여 재정의 하고 이 속성을 다른 클래스로 파생할 수 있도록 virtual 키워드도 추가하였습니다.
C# Width 속성 정의(클래스에 추가할 것)
public virtual new double Width
{
    get { return implementationRoot.Width; }
    set
    {
        implementationRoot.Width = value;
        UpdateLayout();
    }
}

UpdateLayout 메소드는 텍스트의 위치를 중앙 정렬하는 기능을 수행하며 일반적으로 컨트롤의 속성이 변경되어 레이아웃이나 화면 출력을 변경할 필요가 있을 때 내부적으로 사용됩니다. UpdateLayout 메소드의 정의는 아래에 있습니다.

2. Width와 마찬가지로 Height 속성을 추가합니다.
C# Height 속성 정의(클래스에 추가할 것)
public virtual new double Height
{
    get { return implementationRoot.Height; }
    set
    {
        implementationRoot.Height= value;
        UpdateLayout();
    }
}

3. 텍스트 내용을 나타내는 Caption 속성을 추가합니다. Caption은 새로 정의하는 속성이므로 일반적인 속성 선언과 같이 하면 됩니다.
C# Caption 속성 정의(클래스에 추가할 것)
public string Caption
{
    get { return text.Text; }
    set
    {
        text.Text = value;
        UpdateLayout();
    }
}

4. 속성이 변경될 때 텍스트 위치 계산을 위한 UpdateLayout 메소드를 구현합니다. UpdateLayout은 내부적으로만 사용되므로 private으로 선언합니다. TextBlock은 현재 자신의 텍스트 내용에 해당하는 너비와 높이를 반환하는 ActualWidth와 ActualHeight이라는 편리한 속성을 제공합니다.
C# UpdateLayout 메소드 정의(클래스에 추가할 것)
private void UpdateLayout()
{
    text.SetValue(Canvas.LeftProperty, (implementationRoot.Width - text.ActualWidth) / 2);
    text.SetValue(Canvas.TopProperty, (implementationRoot.Height - text.ActualHeight) / 2);
}

이제 이벤트를 추가합니다.
우리가 추가할 이벤트는 버튼 클릭이 완료된 시점입니다. 클릭 동작은 마우스 버튼이 눌렸다가(down) 뗀(up) 그 순간을 의미하며 이런 이벤트는 별도로 정의되어 있지 않습니다.

주의해야 할 점은 버튼 위에서 마우스 버튼을 눌렀다가 떼지 않고 버튼 바깥으로 이동할 수도 있다는 겁니다. 따라서 클릭 이벤트를 구현하기 위해서는 마우스 버튼이 눌린 상태를 기억하는 변수를 하나 만들고 버튼 바깥으로 마우스가 나간 경우 그 상태를 클리어 해줄 필요가 있습니다.
Howto:3-5 이벤트 핸들링 및 외부로 노출할 이벤트 정의
1. 마우스의 눌린 상태를 기억하기 위한 멤버 변수를 클래스의 private으로 선언합니다.
C# 마우스 버튼 상태 멤버 변수 추가(클래스에 추가할 것)
private bool mouseDowned;

2. 외부로 노출할 이벤트를 선언합니다. 이벤트는 delegate를 정의하여 전달할 파라미터의 타입을 명시해야 하는데 우리가 추가할 Click 이벤트는 특별히 전달해야 할 파라미터가 없으므로 미리 선언된 delegate인 EventHandler를 사용하면 됩니다. 클래스에 다음의 이벤트 선언을 추가합니다. 만약 사용자가 특정한 파라미터를 전달하고 싶다면 EventArgs 클래스를 상속하는 파라미터 전달용 클래스를 정의하고 delegate를 따로 선언해야 합니다.
 
C# 이벤트 선언 추가(클래스에 추가할 것)
public event EventHandler Clicked;

3. 루트 개체의 마우스 이벤트를 처리할 이벤트 핸들러를 추가합니다. 여기에서 필요한 이벤트는 MouseLeftButtonDown, MouseLeftButtonUp 그리고 MouseLeave 입니다. MyButton 생성자 코드에 다음 코드를 추가합니다.
C# 이벤트 핸들러 추가(생성자에 추가할 것)
implementationRoot.MouseLeftButtonDown += new MouseEventHandler(implementationRoot_MouseLeftButtonDown);
implementationRoot.MouseLeftButtonUp += new MouseEventHandler(implementationRoot_MouseLeftButtonUp);
implementationRoot.MouseLeave += new EventHandler(implementationRoot_MouseLeave);

기본적으로 이벤트 핸들러는 코드 자동완성 기능을 사용하는게 편리합니다. 예를 들어 MouseLeftButtonDown 이벤트는 MouseLeftButtonDown += 까지 입력하고 탭을 두번 누르면 골격 코드가 자동으로 생성됩니다.

4. 자동으로 생성된 코드를 수정하여 마우스 버튼이 눌렸을 때 mouseDowned를 true로 설정하고 마우스 버튼이 들렸을 때 mouseDowned가 true일 경우 Clicked 이벤트를 발생시키고 마우스 커서가 빠져나갔을 때 mouseDowned를 false로 설정합니다.
C# 이벤트 핸들러 구현
void implementationRoot_MouseLeftButtonDown(object sender, MouseEventArgs e)
{
    mouseDowned = true;
}

void implementationRoot_MouseLeftButtonUp(object sender, MouseEventArgs e)
{
    if (mouseDowned == true && Clicked != null)
    {
        Clicked(this, null);
        mouseDowned = false;
    }
}

void implementationRoot_MouseLeave(object sender, EventArgs e)
{
    mouseDowned = false;
}

주의할 점은 Clicked 이벤트가 null인지 여부를 반드시 확인해야 합니다. 또 여기서 Clicked 이벤트의 EventArgs를 null로 전달하였기 때문에 사용하는 쪽에서 이 파라미터를 null 체크 없이 접근할 경우 억세스 위반이 일어날 것입니다.

여기까지 작성된 소스코드 및 프로젝트를 첨부하였습니다. 직접 작성한 것과 비교해보세요 :)
MyButton1.zip

첫번째 예제



컨트롤 테스트 하기

테스트를 위한 Silverlight 페이지 프로젝트 추가
작성된 컨트롤은 라이브러리일 뿐 실제 UI가 아닙니다. 이 컨트롤을 테스트하려면 실제로 이 컨트롤을 Silverlight 페이지에 로드하여 표시해야 합니다.

Howto:3-6 테스트 프로젝트 추가하기
1. MyButton 프로젝트를 그대로 열어놓은 상태에서 메뉴의 File->Add->New Project를 선택하고 Silverlight Project 템플릿을 선택한 후 이름을 MyButtonTester라고 설정하고 [OK]를 누릅니다. 솔루션 익스플로러에서 다음과 같은 모습을 확인할 수 있습니다.


2. 메뉴의 Project->Add Reference를 선택하거나 솔루션 익스플로러에서 MyButtonTester 프로젝트의 Reference 항목에서 마우스 오른쪽 버튼을 클릭한 후 Add Reference를 선택합니다. 참조 추가 창에서 [Project] 탭을 보면 작성된 MyButton 프로젝트 항목이 있을 것입니다. MyButton을 선택하고 [OK]를 누릅니다.


3. 솔루션 익스플로러의 MyButtonTester 프로젝트에서 마우스 오른쪽 버튼을 클릭한 후 Set as Startup Project를 선택하고 TestPage.html에서 마우스 오른쪽 버튼을 클릭한 후 Set as Startup Page를 선택하여 테스트 프로젝트로 디버깅 할 수 있게 합니다.

4. 테스트할 페이지 즉, Page.xaml의 루트 엘리먼트에 사용할 컨트롤의 네임스페이스를 선언하고 어셈블리를 지정합니다.
XAML 네임스페이스 선언 및 어셈블리 지정
<Canvas x:Name="parentCanvas"
    xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Loaded="Page_Loaded"
    x:Class="MyButtonTester.Page;assembly=ClientBin/MyButtonTester.dll"
    xmlns:my="clr-namespace:MyButton;assembly=ClientBin/MyButton.dll"
    Width="640"
    Height="480"
    Background="White">
</Canvas>

외부 라이브러리를 사용할 때에는 위와 같이 루트 엘리먼트에서 "xmlns:이름" 구문으로 네임스페이스를 지정하고 clr-namespace를 "컨트롤의 네임스페이스명" 으로 지정하며 assembly를 라이브러리 파일(.dll)의 경로로 지정해주면 됩니다.

5. 루트 캔버스에 컨트롤을 추가합니다.
XAML 컨트롤 추가
<Canvas ... 생략 ...>
    <my:MyButton x:Name="testButton" Width="100" Height="50" Caption="button test" />

</Canvas>

6. 파일을 저장하고 Clicked 이벤트를 처리하기 위해 코드-비하인드에 Clicked 이벤트 핸들러를 추가합니다. 앞서서 x:Name으로 testButton을 지정했으므로 코드-비하인드에서는 자동 생성된 코드에 의해 같은 이름으로 개체를 참조할 수 있습니다.
C# MyButton의 Clicked 이벤트 핸들러 추가
public void Page_Loaded(object o, EventArgs e)
{
    // Required to initialize variables
    InitializeComponent();
    testButton.Clicked += new EventHandler(testButton_Clicked);
}

void testButton_Clicked(object sender, EventArgs e)
{
    testButton.Caption = "Clicked!";
}

7. F6을 눌러 빌드하고 F5를 눌러 디버그 모드로 실행하여 클릭했을 때 글자가 바뀌는지 확인합니다.


여기까지 진행해보면 분명히 클릭 이벤트는 잘 작동하지만 우리가 의도한대로 텍스트가 버튼의 한가운데로 정렬되지 않았음을 알 수 있습니다. 왜냐하면 마크업에서 MyButton의 어트리뷰트에 Width=100 Height=50을 지정하였는데 이대로라면 텍스트가 해당 위치를 벗어나지 않는 다는 것을 의미합니다. 그런데 출력된 텍스트는 한참 멀리 떨어져 있고 자세히 보면 640, 480의 한가운데로 정렬되어 있음을 알 수 있습니다.

이 문제는 Silverlight의 버그인지 사용 방법상의 차이인지 확실치 않습니다만 다음 절에서 얘기할 약간의 추가 작업을 통해 수정할 수 있습니다.
우선 여기까지의 프로젝트를 첨부하였습니다.
MyButton2.zip

두번째 예제



특수한 문제점과 해결

Silverlight 페이지 즉, MyButtonTester에서 MyButton을 사용할 때의 모습을 간략하게 도식화하면 다음과 같습니다.


MyButtonTester의 XAML코드에서 MyButton을 하나 선언하였고 여기에서 Width, Height, Caption속성을 설정(set)하였습니다. 따라서 이 프로젝트를 디버깅 모드로 실행했을 때 MyButton의 Width, Height, Caption 속성의 'set' 부분의 코드가 실행될 것을 기대할 수 있습니다.
MyButton.xaml.cs에서 Width, Height, Caption 속성의 'set' 코드에 F9를 눌러 브레이크 포인트를 각각 설정하고 F5를 눌러 디버깅 모드로 실행해보시기 바랍니다.

이상하게도 Caption 속성에는 브레이크가 걸리지만 Width와 Height에는 걸리지 않는 걸 확인할 수 있습니다.
혹시 Width와 Height이 정상적으로 작동하지 않은지 확인하기 위해 MyButtonTester의 코드-비하인드에서 코드로 직접 Width와 Height을 설정해보면 그 코드에 대해서는 브레이크가 작동하며 코드 자체는 정상적이라는 걸 알 수 있습니다.

이 문제는 Control에서 상속받은 모든 기본 속성에서 공통적으로 나타납니다. 따라서 Control 및 그 상위 클래스들에서 상속받는 속성과 같은 이름의 속성을 재정의했을 때에는 이들 속성에 대한 초기화를 별도로 해줄 필요가 있습니다.

Howto:3-7 상위 클래스에서 상속받은 속성과 같은 이름을 가지는 속성을 재정의 할 때의 추가 작업
1. MyButton 클래스의 생성자에 컨트롤이 로드 완료되었을 때 처리할 이벤트 핸들러를 추가합니다. 마찬가지로 코드 자동 생성기능을 활용합니다.
C# 컨트롤 로드 완료시 처리 이벤트 핸들러 추가
public MyButton()
{
    // ... 생략
    implementationRoot.Loaded += new EventHandler(implementationRoot_Loaded);
}

2. 추가된 이벤트 핸들러에서 상위 클래스(base)와 중복되는 이름의 속성을 하나씩 가져옵니다.
C# 이벤트 핸들러 작성
// 클라이언트 측에서 이 컨트롤의 로드를 완료하였을 때 처리
void implementationRoot_Loaded(object sender, EventArgs e)
{
    // 로드가 완료되었을 때 상위 클래스의 속성으로부터 미완료된 속성을 수작업으로 설정해야 함
    implementationRoot.Width = base.Width;
    implementationRoot.Height = base.Height;
    UpdateLayout();
}

3. F6을 눌러 빌드하고 F5를 눌러 다시 실행하여 정상적으로 Width와 Height 속성이 적용되었는지 확인합니다.

여기까지 완성된 프로젝트입니다.
MyButton3.zip

완성된 컨트롤 프로젝트


이 외에도 커스텀으로 작성한 컨트롤을 올린 페이지는 Expression Blend를 사용할 수 없게 되며 VS의 IDE에서도 XAML 코드의 인텔리센스를 지원하지 않는 등 외부적인 문제도 아직 남아있습니다. 하지만 이 문제는 아마도 정식판이 나오면서 해결되지 않을까 기대를 해봅니다.

참고

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

Silverlight 샘플 컨트롤 참고: Silverlight 1.1 SDK에서 SilverlightControls 폴더를 참고합니다.

개발자들이 공개하는 커스텀 컨트롤:
http://blogs.msdn.com/devdave/archive/2007/05/17/silverlight-1-1-alpha-layout-system-and-controls-framework.aspx (Grid, StackPanel 등의 컨테이너 샘플)
신고
Posted by gongdo


티스토리 툴바