1 Results for 'RoutedEvent'

  1. 2008.07.01 실버라이트 2의 이벤트 라우팅 (4)

※역시 똑같은 내용도 MSDN을 그대로 번역하면 당췌 댓글이 안달리고 별 도움도 안되는 것 같아요. 앞으로는 가급적이면 MSDN에 99% 있는 내용이더라도 나름의 분석과 생략압축으로 좀 더 읽기 쉽게 써야겠네요. 그래도 중요한 건 대부분 MSDN에 다 있으니 제대로 공부하시는 분이라면 꼭 참조에 있는 링크들을 찾아보시길.


실버라이트 2는 보다 정교하고 합리적인 이벤트 모델을 채택하고 있죠. 이 이벤트 모델은 WPF의 그것과 매우 유사하지만 축소된 부분이 많이 있어요. 이 글에서는 실버라이트 2에서의 이벤트 모델, 특히 라우팅과 관련된 이벤트 모델에 대해 알아보도록 할게요.

실버라이트 이벤트 모델

실버라이트는 개념적으로 크게 입력 이벤트input event와 비입력 이벤트non-input event라는 두 종류의 이벤트로 구분하고 있어요. 간단하게 차이점을 살펴보면 입력 이벤트는 마우스 클릭, 키보드 입력과 같이 사용자와의 상호 작용 의해 발생하는 이벤트를 말하고 비입력 이벤트는 다운로드 완료, 타이머의 Tick과 같이 애플리케이션 코드의 흐름에 따라 발생하는 이벤트를 말하죠.

입력 이벤트

실버라이트는 플러그인 아키텍처로 브라우저 위에 호스팅되죠. 이 점은 제가 누누히 강조하는 부분인데요, 실버라이트는 웹 브라우저 위에 호스팅되는 클라이언트 기술이라는 거죠. 여튼, 입력 이벤트는 브라우저에 의해 처리된 것이 실버라이트 플러그인으로 전달되고 실버라이트 플러그인은 거기에 대한 이벤트를 만들게 된다는 점을 기억하세요.

비입력 이벤트

비입력 이벤트는 일반적으로 개별 오브젝트들의 상태가 변경되었음을 알리는데 사용되는데요, 특히 WebClient등의 네트워크나 BackgroundWorker와 같은 스레드관련 처럼 비동기적으로 수행되어야 하는 처리에 매우 유용하고 적합하죠.

또 MediaElement와 같이 내부적인 상태 변화가 사용자나 코드의 간섭 없이 발생하는 경우에는 이벤트 없이 처리를 한다는 건 어려울거에요.

이 외에 FrameworkElement.Loaded 이벤트 처럼 오브젝트의 라이프타임 관리에 필수적인 것도 있죠.

마지막으로 실버라이트 플러그인에서만 처리할 수 있는 비입력 이벤트가 하나 있는데요, 바로 OnError 이벤트죠. OnError 이벤트는 HTML-DOM에서의 스크립트 실행을 하는 중 발생되는 에러를 핸들링하기 위한 이벤트로 플러그인 레벨에서 동작하므로 매니지드 프로그래밍 모델까지 전달되지 않고 오직 자바스크립트 코드로만 처리가 가능해요.

이벤트 라우팅

실버라이트의 엘리먼트-여기에서는 UI를 표시하는 오브젝트-들은 서로의 포함 관계에 따라 '위', '아래'의 개념이 생기고 다른 모든 엘리먼트를 포함하는 엘리먼트를 가장 위에 놓고 그 최상위 엘리먼트에 포함되는 하위 엘리먼트들의 순서대로 트리 형태로 표현할 수 있죠. 이것을 실버라이트 오브젝트 모델(SOM)이라고 부르기도 해요.

※이처럼 오브젝트 간의 포함 관계는 전통적으로 '트리'라고 불리우고 최상위 오브젝트를 나무의 뿌리라고 하여 Root라고 부르고 각 하위 클래스들을 노드Node 그리고 각 노드의 가장 끝에 있는 클래스들을 잎사귀Leaf라고 부르죠. 그런데 이 개념은 실제 사물로 그대로 그려놓고 봤을 때는 상하 관계의 혼동을 일으키기 쉬운 것 같아요. 오브젝트 트리의 '상위'에 있는 루트 오브젝트는 실제 나무에서는 가장 아래에 있으니까요. http://gongdosoft.com/295 여기에 관련된 내용을 포스팅 했으니 참고하세요.

어쨌든 나무보다 뭔가 적절하게 비교할 수 있는 게 있으면 좋겠는데요... 혹시 좋은 생각 있으면 제보해주세요^^

트리 형태로 구성된 엘리먼트에서 어떤 이벤트가 발생했을 때 어떤 엘리먼트가 이 이벤트를 처리해야 하는가에 대한 문제가 생기죠. 버튼을 예로 들어보죠, 사용자가 버튼을 눌렀을 때 버튼을 구성하는 어떤 엘리먼트가 이벤트를 받아야 할까요? 보통은 버튼을 구성하는 다른 모든 엘리먼트를 포함하는 최상위 엘리먼트가 이것을 수행해야겠죠. 그러나 실버라이트의 버튼은 단순히 텍스트나 이미지만으로 구성되어 있지 않고 버튼 안에도 마우스의 이벤트를 처리할 수 있는 다른 엘리먼트가 포함될 수도 있어요. 이 때 마우스의 이벤트를 받는 것은 아마도 버튼 안에서 -사용자 쪽으로- 가장 앞에 있고 활성화되어 있으며 눈에 보이는 엘리먼트가 되겠죠. 만약 여기에서 이벤트의 처리가 완료된다면 이 버튼은 정상적인 버튼이라고 말할 수 없을거에요. 버튼내부에 있는 어떤 오브젝트가 마우스를 누르는 이벤트에 관심이 있다면 그 오브젝트와 버튼 자체 양쪽 모두 이벤트를 처리해야 정상적이라고 할 수 있어요.

바로 이런 경우 즉, 어떤 엘리먼트들이 트리 관계를 이룰 때 발생된 이벤트는 트리를 구성하는 부모 또는 자식에게 전달될 필요가 있고 트리를 따라서 이벤트가 전달되는 과정을 이벤트 라우팅이라고하고 이렇게 트리를 따라 마치 사실chain과 같이 전달된 이벤트를 Routed Event라고 해요.

특히 입력 이벤트는 거의 대부분 이벤트를 라우팅 할 필요가 있고 FrameworkElement.Loaded나 MediaElement의 몇몇 이벤트들 처럼 일부 비입력 이벤트도 엘리먼트 트리로 라우팅할 필요가 있죠.

엘리먼트 트리에서 이벤트를 전달하는 방법에는 발생된 이벤트를 이벤트가 발생된 엘리먼트부터 최상위 엘리먼트까지 위쪽 방향으로 올려주는 버블링Bubbling과 발생된 이벤트를 이벤트가 발생된 엘리먼트를 포함하는 최상위 엘리먼트에서 이벤트가 발생된 엘리먼트까지 아래 방향으로 내려주는 터널링Tunneling이 있어요.

위의 그림은 엘리먼트 트리와 각 엘리먼트간의 이벤트 라우팅을 잘 나타내고 있어요. WPF의 경우는 이 두 가지 경우를 다 지원하지만 불행히도 실버라이트는 이 중 버블링만을 지원하고 있으니 컨트롤을 설계할 때에 충분히 주의를 해야 해요.

이벤트 라우팅의 조건

여러 개의 엘리먼트가 트리를 이룰 때 이벤트 버블링 모델에서는 트리의 가장 아래쪽 즉, 화면상의 가장 앞쪽에 있는 엘리먼트에서 이벤트가 시작되어 그 엘리먼트를 포함하는 최상위 엘리먼트까지 전달되는데, 이 때 어떤 엘리먼트가 이벤트 라우팅 체인에 속해있더라도 다음의 조건을 만족하지 못하면 그 엘리먼트로는 이벤트가 전달되지 않아요.

  • 보일 것(Visibility = Visibility.Visible)
  • Brush로 표시되는 엘리먼트 또는 영역일 경우 Brush가 null이 아닐 것(Brush != null)
  • 활성화 속성이 있다면 활성화 되어 있을 것(IsEnabled = true)
  • 마우스 이벤트일 경우 IsHitTestVisibile이 활성화 되어 있을 것(IsHitTestVisible = true)

위의 조건에 따라 심지어 완전히 투명(Opacity=0)하여 보이지 않더라도 해당 엘리먼트는 이벤트 라우팅 체인에 속하게 되어 해당 이벤트를 받을 수 있죠. 이 점은 디자인 적으로는 아무런 역할을 하지 않지만 특정 영역에 대한 이벤트를 받고 싶을 때 -예를 들어, 미디어 플레이어의 특정 영역에 마우스 커서가 들어왔을 때 배너 광고를 보여주거나 하는 경우- 자주 활용되기도 해요.

이벤트 라우팅 경로와 원본에 대한 참조

실버라이트는 앞에서 설명한 버블타입의 이벤트 라우팅 컨셉을 지원하고 주로 다음과 같은 프레임워크 레벨의 입력 이벤트들에 사용되고 있죠.

KeyDown, KeyUp, MouseEnter, MosueLeftButtonDown, MouseLeftButtonUp, MouseMove, BindingValidationError...

일반적인 이벤트 핸들러는 이벤트를 전달해준 sender라고 하는 object 타입의 파라미터와 해당 이벤트에서 전달하고자 하는 데이터를 의미하는 e라고 하는 EventArgs(혹은 사용자 정의) 타입의 파라미터를 갖는 딜리게이트로 이루어져 있어요. 보통의 이벤트는 이벤트를 제공하는 오브젝트 즉, 발행자Publisher와 이벤트에 관심이 있는 오브젝트 즉, 구독자Subscriber로 구성되어 발행자는 sender를 자기 자신으로 설정하여 누가 이벤트를 발행했는지를 구독자에게 알려주게 되죠.

그런데 라우팅되는 이벤트의 경우 이벤트 버블의 규칙에 따라 어떤 엘리먼트에서 위와 같은 이벤트가 발생했을 때 이벤트는 엘리먼트 트리의 가장 바닥 즉, 화면상에 가장 앞에 있는 엘리먼트부터 이벤트가 전달되는데요, 이때 구독자의 입장에서 sender가 자신의 바로 앞에서 이벤트를 전달Route해 준 오브젝트인지 최초로 이벤트를 발생시킨 발행자인지를 구분할 필요가 있을 거에요.

실버라이트에서는 이렇게 라우팅된 이벤트를 전달할 때에는 이벤트 핸들러의 두 번째 파라미터로써 RoutedEventArgs또는 이것을 상속받는 특별한 데이터 클래스를 사용하여 이벤트를 발생시킨 발행자 정보를 전달하게 되죠. 예를 들어 우리가 가장 흔히 사용하는 MouseLeftButtonDown 이벤트의 정의를 보면...

public event MouseButtonEventHandler MouseLeftButtonDown

이고, MouseButtonEventHandler는 다음과 같은 딜리게이트로 이루어져 있어요.

public delegate void MouseButtonEventHandler(object sender, MouseButtonEventArgs e);

또 이 딜리게이트의 두 번째 파라미터의 타입인 MouseButtonEventArgs는 MouseEventArgs 클래스에서 파생되었고 다시 MouseEventArgs 클래스는 바로 RoutedEventArgs 클래스로부터 파생되었죠.

RoutedEventArgs는 다음과 같이 엄청나게 단순하게 구성되어 있어요.

public class RoutedEventArgs : EventArgs
{
    public RoutedEventArgs();
    public object Source { get; set; }
}

여기에서 Source가 바로 최초로 이벤트를 발생시킨 발행자에 대한 참조를 의미하죠.

정리하자면, 라우팅된 이벤트의 이벤트 핸들러에서 sender는 이 이벤트를 전달해준 엘리먼트 즉, 현재 이벤트 핸들링 체인의 직전에 있는 엘리먼트에 대한 참조를 말하고 최초의 이벤트 발행자에 대한 참조는 이벤트 핸들러의 두 번째 파라미터인 RoutedEventArgs.Source를 통해 참조하게 되요.

우리가 앞으로 클래스들과 이벤트 핸들러 및 이벤트 라우팅 체인을 만든다면 위와 같은 방식으로 만들어야 협업할 때 문제가 생기지 않을 거에요. 예를 들어 라우팅 이벤트 핸들러의 sender가 난데 없이 원본을 참조하고 있다거나 RoutedEventArgs.Source가 변경된 값에 대한 참조를 하고 있다거나 하면 안되겠죠.

근본적으로 이런 문제는 sender와 RoutedEventArgs가 object타입라서 어떤 종류의 참조라도 받을 수 있게 되어 있기 때문에 발생하는데요, 이에 대한 내용은 다음 글로 넘길께요.

어떤 종류의 피드백도 환영합니다. ^^


참조

Events Overview(Silverlight 2)
찰스 페졸드의 WPF(APPLICATION = CODE+ MARKUP) ; Chapter 9. 입력 이벤트의 라우팅
Routed Event Overview(WPF)

신고
Posted by gongdo


티스토리 툴바