WPF Architecture #1
최근 프로젝트로 인하여 별로 사용할 일이 없었던 WPF에 손을 대고 있습니다.
지금까지 사용했던 적이 없던 것이 아니지만.. Canvas를 이용해서 컨텐츠를 개발하는 정도가 고작이었던터라.. 나름대로 새롭게 공부할 필요를 느껴 서적을 들여다보니.. 아키텍처와 관련하여 아주 잘 정리된 내용이 있더군요. (검색을 통해서는 잘 알 수 없었던...)
언제나 느끼지만.. 플랫폼을 접할 때 그 플랫폼에서 개발을 어떻게 하는지만을 배울 것이 아니라.. 좀 더 근본적으로 그 플랫폼이 어떻게 생겨먹었는지 알아가는 것이 먼저라는 생각이 듭니다.
이런 이유로 WPF아키텍처에 대해서 제가 이해한 내용을 간단하게 포스팅합니다.
이번 포스팅에서는 기본적인 WPF Architecture를 비롯해 Dispatcher Object, Dependency Object에 대해서 다루어보고.. 다음 포스팅에서 Visual과 UIElement, Framework Element, Control등에 대해서 다루어 보겠습니다.
WPF Architecture
WPF는 닷넷 3.0과 함께 발표되었고, 2.0을 기반으로 3.0에 포함되었다.
WPF는 관리코드로 작성되고 노출되어 있으며, 아래 아키텍처 상에서 푸른색으로 표시된 부분이 핵심이라고 할 수 있다.
Presentation Framework와 PresentationCore는 순수한 관리 코드로 구성되있다.
개발자가 만나게 되는 WPF는 바로 이 부분이다. CLR 바로 위에 노출되어 있는 층이 Presentation Core부분인데 WPF를 위한 API부분을 주로 정의하는 부분이다.
우리가 Windows 클라이언트에서 UI를 프로그래밍 할 때 API와 Library 형태로 많은 것을 제공 받아왔다. WPF의 Core부분은 API형태Library, 컴포넌트 형태를 띠고 있다.
최상위 계층에 Presentation Framework를 배치하고 있는데 여러 WPF 핵심 기능을 조합해서 새로운 Framework를 만들어 제공하는 부분이다. WPF클래스를 읽을 때 Core부분과 Framework부분이 분리되어 있다는 것도 염두에 두어야 한다.
CLR계층 아래 milcore 계층은 DirectX와 효율적인 통신을 위해 비관리 코드로 제작된 부분이다. CLR 하부에 배치됨으로써 개발자에게 쉽게 접근을 허용하지 않는다. 개발자는 DirectX와 통신을 milcore 어셈블리에 미루면 된다. WPF에서의 모든 디스플레이는 DirectX엔진을 통해 수행되므로 효율적인 하드웨어 및 소프트웨어 렌더링을 허용한다. 관리 코드를 사용함으로써 효율적인 메모리 및 자원 관리도 가능하다. milcore는 성능을 위해 관리 코드의 이점을 포기한 부분이다.
WPF관련 어셈블리는 크세 세 부분으로 나눌 수 있다.
1. WindowBase.dll 2. PresentationCore.dll 3. PresentationFramework.dll
|
WindowsBase.dll은 WPF를 위한 기본 서비스를 제공하는 어셈블리로 Dispatcher 클래스와 DependencyObject 클래스를 포함한다. WPF 어플리케이션이 구동되기 위해서는 반드시 필요한 어셈블리라고 할 수 있다.
실제로 WPF 어플리케이션의 기본 프로젝트를 생성하면 언급한 어셈블리들이 참조되어 있는 것을 확인할 수 있다.
Dispatcher Object
닷넷에서 상속의 최상위 클래스라면 System.Object를 말한다. 하지만 WPF를 이야기할 때 대부분의 WPF객체들은 DispatcherObject에서 파생된다. System.Windows.Threading 네임스페이스에 DispatcherObject 클래스가 위치하게 된다. 네임스페이스가 System.Windows에서 시작되고 Threading 네임스페이스로 이어지는데 기존 닷넷 프레임워크 2.0에 있는 System.Threading과는 다른 네임스페이스이다. 언급한 네임스페이스는 그대로 유지되고있다. WPF가 새로운 Threading모델을 사용하고 관련 클래스를 정의한 네임스페이스가 System.Windows.Threading이다.
WPF는 STA(Single Thread Apartment) 모델과 호환되는 시스템이다. STA모델이란 실행 컨텍스트에 하나의 스레드만 존재하는 시스템을 말한다. STA모델에서 어플리케이션이 실행된다면 스레드에는 한 순간 하나의 객체만 존재하게 되므로 객체간에 서로 통신할 방법이 필요하게 된다.
WPF에서 Dispatcher는 복수의 작업을 대기시킬 수 있는 큐이다. Dispatcher의 또 하나의 중요한 기능은 접근 가능 여부를 확인시켜 주는 것이다. WPF는 어플리케이션이 시작될 때 UI 스레드와 렌더링 스레드를 포함해서 최소 두 개 이상의 스레드를 생성한다. 렌더링 스레드는 백그라운드에서 실행된다.
DispatcherObject는 WPF의 이러한 스레드 모델을 지원하는 객체이고 Dispatcher라는 속성을 갖는데 자신이 속한 스레드의 Dispatcher에 접근하기 위한 방법인 것이다. 그러므로 쉽게 스레드의 Dispatcher와 DispatcherObject의 Dispatcher가 동일한지 여부를 쉽게 비교할 수 있다. CheckAccess()와 VerifyAccess() 메서드가 그런 역할을 담당한다.
Dispatcher의 또 다른 중요한 기능 중의 하나는 Dispatcher에 Invoke()나 BeginInvoke()를 호출해 Frame을 Queueing할 수 있다. 같은 Dispatcher를 공유하지 않을 때 Invoke()나 BeginInvoke()를 이용해야 한다.
Dependency Object
Dependency Object는 WPF의 Property System 서비스를 가능하게 하는 객체이다. 바꾸어 말하면 Dependency Object는 WPF의 Property System에 의존하는 객체인 것이다.
또 다르게 말하면 개발자가 Dependency Object에서 파생만 한다면 WPF에서 관리를 한다는 뜻이기도 하다.
WPF는 메서드 이벤트 보다는 속성 중심으로 설계되어 있는데 객체의 멤버를 살펴볼 때 속성의 늘어난 숫자에 놀라게 된다. 데이터 중심, 모델 중심으로의 관전의 전환이라고 할 수 있다. 데이터 중심 프로그래밍을 설명할 때 코드를 데이터로 부터 분리하는 것을 의한다. 데이터 중심 프로그래밍에서 데이터는 단순히 상태 정보만을 의미하는 것은 아니다. 객체지향에서 Data는 캡슐화된 숨겨진 정보를 의미한다. 데이터 중심 프로그래밍에서 데이터는 프로그램에서 흐름 제어까지 포함한다. 데이터 중심 프로그래밍이란 코드에 의한 데이터 변경을 최소화하고 데이터 구조 중심의 프로그래밍을 하겠다는 것이다. 잘만 설계가 이루어지고 행해진다면 데이터 중심의 프로그래밍은 코드의 양이 적고 선언적인 형태를 보이게 된다. 마이크로소프트의 XAML 관련 문서를 검토하다 보면 흐름 제어, 선언적이라는 단어를 만나게 된다. WPF가 디자인과 통합 유지 관리의 편의성 등을 위한 선택일 것이다. XAML에서는 이벤트와 액션까지 모델화하고 선언적으로 처리하는 것을 볼 수 있다. 제어의 많은 부분을 바인딩을 통해 가능하게 되었다.
WPF 속성 시스템은 속성 식 간의 종속성을 추적하고 종속성이 변경될 때 속성 값의 유효성을 자동으로 다시 검사하는 면에서 진정한 '종속성' 속성 시스템이라고 할 수 있다. WPF가 지원하는 형태의 속성을 Dependency Property라고 하는데 양방향 변경 알림과 추적이 가능하다. 부모에서 상속된 속성이 있을 경우 부모에서 변경이 된다면 자식에게 영향을 미치고 반대의 경우에도 마찬가지이다. 객체 지향의 관점에서 본다면 별개의 인스턴스가 되므로 서로 영향을 미칠 수 없지만 WPF의 속성에서는 가능한데 이는 클래스 수준의 속성 관리와 별개의 관리 시스템에 인스턴스가 등록되고 관리되기 때문에 가능하다. WPF에서 많은 속성은 정적이고 선언과 동시에 등록이 이루어진다.
앞에서 WPF는 데이터 중심, 속성 중심의 양방향 알림 공지가 가능한 시스템이라고 설명했는데 한 걸음 더 나가 속성 시스템은 프레임워크 수준의 바인딩 스타일 등을 지원한다. 프레임워크 수준에서 지원하므로 객체 수준의 하드 코딩된 바인딩이나 스타일 등은 권장하지 않는다는 것이다. 프레임워크의 정점에 있는 객체가 Dependency Object이다. 바인딩 스타일 등은 바인식 식(Expression)에 의해 지원되는데 사용자가 마음대로 변경할 수 없는 패쇄된 시스템이다. 정해진 방법과 키워드를 통해 바인딩 스타일 등을 지정해야 하는 엄격함을 지니고 있다. 이는 선언적 머크업과 호환 가능한 시스템을 구현해야 하므로 XML의 엄격함을 수용했을 것이다.
프레임워크 수준의 바인딩 종속성, 손쉬운 데이터 공유 등의 기능은 속성의 값을 모든 객체의 인스턴스에 저장하지 않아도 가능하게 했다. 예로 시스템이 지원하는 폰트가 있다고 할 때 모든 인스턴스가 폰트의 클론 데이터를 유지할 필요는 없다. 폰트 변경에 대해 적절한 타이밍에 서로 알려 줄 수만 있다면 한곳에 저장해도 아무런 문제를 발생시키지 않는다. 가벼운 속성 시스템을 유지하는 것이 가능한 것이다. 기본 값이나 많은 인스턴스가 같은 값을 동시 참조할 때 성능 면에서 도움이 크다.
또 하나는 XAML에서 사용하는 연결된 속성이다. 역시 Dependency Object이어야만 지원 받을 수 있는 기능이다. 예를 들어 보면 흔히 말하는 DockingPanel이 있고, Button이 내부에 배치될 때 Button.Left 혹은 Button.Docking 정도의 속성으로 설계가 이루어 질 것이다. Button의 속성을 통해 이루어지는데 Docking 속성은 어떤 데이터 형을 가져야 하는 것일까? 난감하다..
Docking의 내용은 Button을 포함하는 객체의 Type에 따라 달라진다. 포함 객체가 어떤 기능을 제공하느냐에 따라 달라져야 한다. 이럴 경우 포함 객체는 Button에 의해 제약을 받거나 혹은 반대로 포함 객체가 Button을 제약하게 된다. 불필요한 Coupling이 발생하게 되는 것이다. 해결책은 포함 객체는 Docking에 관한 방법을 제공하고 포함되는 객체는 제공된 방법 중 선택을 해서 보고해야 한다. 이러한 부분을 WPF 프레임워크가 담당하게 된다. 연결 속성의 지원을 받으려면 Dependency Object이어여 한다.
다음 예는 Canvas라고 하는 포함 객체가 있고 Button이 여기에 포함된다. Canvas는 좌표 값을 통해 배치하는 것이 가능한 컨테이너이다. 여기에 좌표는 당연히 컨테이너가 정해준 방법에 따라 달라져야 할 것이다.
<Canvas> <Button Canvas.Left="100" Canvas.Top="100" Width="100" Height="50"> This is Button </Button> </Canvas>
|
이와 같은 예에서 WPF를 처음 접하면 난감한 부분이 Button의 Attribute중 Canvas.Left라고 하는 연결 속성이다. 배치는 Canvas가 책임지고 Button은 Canvas에게 보고를 하면 나머지는 이루어지는 것이다. 연결 속성은 다른 객체의 속성으로 사용이 가능한 것이다.
Dependency Object가 WPF 프레임워크 내에서 하는 역할을 알아보았는데 WPF의 속성 시스템의 지원을 받기 위해서는 Dependency Object에서 파생해야 한다는 것이다.