WPF Deep Dive -6 Application type Startup Event

WPF 기반의 응용프로그램은 Application 개체의 생성과 동시에 시작된다고 볼 수 있으며, Application 개체의 Run() method가 반환되는 시점에 종료된다. 이렇듯 Application 개체는 응용프로그램 전반의 Life Time을 결정 짓는 핵심 역할을 담당하는 데, 상태변경이 있을 때 개발자가 자신의 코드를 포함시킬 수 있도록 몇몇 Event 를 정의하고 있다.

Activated, Deactivated, DispatcherUnhandledException, Exit, SessionEnding, Startup

image

image 

Startup Event

일반적으로 응용프로그램의 각종 초기화를 수행하는 방법에는 Application 개체의 생성자나 Starup Event가 발생했을 때 초기화 코드를 삽입하는 방법이 있을 수 있으나, Startup Event Handler에 초기화 코드를 넣는 것이 조금 더 좋아 보인다(OnStartup() method를 overriding 하는 것도 동일하다). Startup Event 의 경우 Application type의 instance에 대한 초기화가 완전히 끝났음을 보장할 수 있기 때문이다. 실 예로 Application.Current Property 의 경우 Application type의 생성자에서는 접근할 수가 없다. 그러므로 OnStartup() method를 overriding하는 코드는 다음과 같이 구성할 수 있다.

class InheritTheApp : Application
{
[STAThread]
public static void Main()
{
InheritTheApp app = new InheritTheApp();
app.Run();
}
protected override void OnStartup(StartupEventArgs args)
{
base.OnStartup(args);

Window win = new Window();
win.Title = "Inherit the App";
win.Show();
}

여기서 조금 특별한 부분을 발견할 수 있다. OnStartup() method내의 win 객체는 OnStartup() method 내에서 생성되었으므로, OnStartup()을 빠져나오게 되면, Dangling object가 된다. .NET Framework의 경우 이러한 Dangling object는 Garbage Collection의 대상이 되므로, 다음번에 Garbage Collector Thread가 깨어나면 정리대상이 된다.(C/C++ 개발자라면 win 객체를 InheritTheApp의 member variable로 옮기고 OnStartup() 에서 동적생성, OnShutDown() 에서 삭제하는 식의 코드를 작성하고 싶을지도 모르겠다.) 이 코드의 비밀은 Window type의 생성자에서 그 해답을 찾을 수 있다. Window Type의 생성자는 가장 마지막 단계로 Initialize() 라는 method를 호출하게 되는데 이 method의 내부 구성이 다음과 같다.

private void Initialize()
{
base.BypassLayoutPolicies = true;
if (this.IsInsideApp)
{
if (Application.Current.Dispatcher.Thread == Dispatcher.CurrentDispatcher.Thread)
{
this.App.WindowsInternal.Add(this);
if (this.App.MainWindow == null)
{
this.App.MainWindow = this;
}
}
else
{
this.App.NonAppWindowsInternal.Add(this);
}
}
}

이 코드를 살펴보면, Application Type의 객체가 앞서 만들어 졌다면 this.App(=Application.Current) 의 WindowsInternal collection에 자신을 추가하고, MainWindow가 없다면 자신을 MainWindow로 정한다. 이러한 이유로 OnStartup() 에서 생성된 win 객체는 현재 Application 객체의 WindowsInternal collection에 의해서 referencing되며 Dangling Object가 되지 않게 되므로, Garbage Collector의 칼날을 피할 수 있게 된다. 게다가 운이 좋으면 MainWindow가 되기도 한다.

  1. Application Type의 객체가 만들어졌는가? 그렇다면 2 이하를 수행한다.
  2. 자신을 현재 Application 객체의 WindowsInternal Collection에 추가한다.
  3. MainWindow가 null이면 자신을 MainWindow로 할당한다.

물론 WPF를 충분히 이해하고 있는 개발자라면 이러한 WPF 개발팀의 이러한 배려가 없어도 잘 살아갈 수 있었을런지 모른다. 하지만 WPF 개발팀은 다른 선택을 하였으며, WindowsInternal Collection을 견고히 하기 위해서 이 Collection을 외부에서 변경하는 것을 허용하지 않고 있다. Application Type을 자세히 살펴본 사람이라면 이 Type이 Windows 라는 Property를 가지고 있고, 이 Property를 통해 현재 Application 객체가 관리하는(혹은 소유하는) Window 객체의 Collection을 획득할 수 있으며, 그 내용은 WindowsInternal과 같다는 것을 알고 있을 것이다. 그렇다면 Windows Property를 통해 얻은 Collection의 내용을 변경하면 무엇인가 색다른 동작을 얻을 수 있지 않을까? 게다가 다행스럽게도 Windows Property는 WindowsCollection type 이며, 이 type은 친절하게도 Add/Remove() 류의 method를 제공해 주고 있다. 하지만 이러한 operation은 동작하지 않는데, 그 이유는  Application의 Windows Property가 다음과 같이 WindowsInternal 자체가 아니라, 그의 복제본을 반환하기 때문이다. Windows Property로 획득되는 Collection은 WindowsInternal과 내용은 같을지 모르지만 전혀 다른 복사본이다.

public WindowCollection Windows
{
get
{
base.VerifyAccess();
return this.WindowsInternal.Clone();
}
}

앞서 Posting한 글에서 Charles Petzold의 Application = Code + Markup 의 첫번째 예제에 대한 비판적인 견해를 밝힌적이 있는데, 그 이유를 알아보자.

class SayHello
{
[STAThread]
public static void Main()
{
Window win = new Window();
win.Title = "Say Hello";
win.Show();

Application app = new Application();
app.Run();
}
}

사실 이 코드만으로는 완벽하게 잘 동작한다. 사실 비판적인 견해는 동작 유무를 떠난 내부적인 구조의 문제를 말하는 것이다. 다음과 같이 app.Run(); 를 호출하기 이전에 다음과 같은 코드를 넣으면 어떻게 될까?

class SayHello
{
[STAThread]
public static void Main()
{
Window win = new Window();
win.Title = "Say Hello";
win.Show();

Application app = new Application();
app.ShutdownMode = ShutdownMode.OnMainWindowClose;
app.Run();
}
}

위 코드는 정상동작할 수 있을까? 다음으로 Application app = new Application(); 행을 가장 앞으로 옮긴다.

class SayHello
{
[STAThread]
public static void Main()
{
Application app = new Application();
Window win = new Window();
win.Title = "Say Hello";
win.Show();

app.ShutdownMode = ShutdownMode.OnMainWindowClose;
app.Run();
}
}

이 코드는 어떻게 동작할까? 다음으로 아래 코드의 출력결과는 무엇일까? 다음으로 Window Win = new Window(); 행을 가장 앞으로 옮긴 다음 실행해보자.
[STAThread]
public static void Main()
{
Application app = new Application();
Window win = new Window();
win.Title = "Say Hello";
win.SizeChanged += delegate
{
if (Application.Current != null)
{
System.Diagnostics.Trace.WriteLine("Window Count = " + app.Windows.Count.ToString());
}
};
win.Show();

app.ShutdownMode = ShutdownMode.OnMainWindowClose;
app.Run();
}

Window Type의 생성자의 동작방식을 이해하였다면, 왜 Window win = new Window(); 의 위치가 변경됨에 따라 그 결과치가 틀려지는지 충분히 이해할 수 있을 것이다. 이런 이유로 Charles Petzold 의 SayHello 예제는 다음과 같이 변경하는 것이 가장 좋다고 생각한다.

class SayHello
{
[STAThread]
public static void Main()
{
Application app = new Application();
Window win = new Window();
win.Title = "Say Hello";
app.Run(win);
}
}


별차이가 없어 보일 수도 있다. 그래도 눈엣가시다.

by 김명신 | 2007/11/29 20:58 | 복잡한컴퓨터이야기 | 트랙백 | 덧글(3)
트랙백 주소 : http://himskim.egloos.com/tb/1674662
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Commented by 미친감자 at 2008/01/08 09:19
코드에 색 어떻게 넣어요???? 이쁘네요
Commented by Freecoder at 2009/08/21 11:48
와우~ 멋지네요 .
우연히 들르게 되었는데 좋은글이라
퍼가고 싶은데요 .괜찮을까요? ^^
Commented by 김명신 at 2009/08/31 16:36
Freecoder : 당근됩니다.

:         :

:

비공개 덧글



< 이전페이지 다음페이지 >