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


티스토리 툴바