2016년 5월 12일 목요일

CompositionTarget.Rendering 프레임이 바뀔떄마다 발생하는 이벤트

브라우저의 각각 프레임이 끝날 때마다 떨어지는 이벤트를 구현할 수 있는 방법이 소개되어서 올려봅니다.

(이게 WPF에는 있는 내용이라고 하더군요. Storyboard가 TimeLine 방식이었다면, 이건 Rendering 방식인가보네요..)

이 렌더링 이벤트 함수는 애니메이션이라던지, 레이아웃이 구성 트리(visual Tree)에 적용된 후에 호출이 되게 됩니다.

그리고 구성트리(visual Tree)가 강제로 업데이트될 때도 이 이벤트함수는 호출이 됩니다.

그래서, 임의로 렌더링 likely하게 구성하던 Dispatch Timer라던지, Storyboard Timer 대신에, 이 것을 사용하면. 모듈 메인루프

함수처럼 구현이 가능하다는 이야기입니다. (끝나지 않고 계속 실행되는..)

이 문법은 간단합니다.

CompositionTarget.Rendering += new EventHandler(MainGameLoop);

(주 : CompositionTarget 그대로 써주시면 됩니다.)

앞에서 작성해보았던 SnowFlake demo를 예로 들어보면, 메인루프를 Storyboard Timer를 사용했었습니다.


public partial class Page : UserControl
{
Storyboard _snowflakeTimer = new Storyboard();

public Page()
{
InitializeComponent();

_snowflakeTimer.Duration = TimeSpan.FromMilliseconds(0);
_snowflakeTimer.Completed += new EventHandler(SnowFlakeTimer);
_snowflakeTimer.Begin();
}

private void SnowFlakeTimer(object sender, EventArgs e)
{
MoveSnowFlakes();
CreateSnowFlakes();
}
}

이것을 그냥 이렇게 쓸 수 있다는 것이죠..

public partial class Page : UserControl
{
Storyboard _snowflakeTimer = new Storyboard();

public Page()
{
InitializeComponent();

CompositionTarget.Rendering += new EventHandler(SnowFlakeTimer);
}

private void SnowFlakeTimer(object sender, EventArgs e)
{
MoveSnowFlakes();
CreateSnowFlakes();
}
}

감사합니다.

-----------------------------------------------------------------------------------------------

코딩이 간편해지는 효과가 있군요.. 퍼포 상으로는 변화가 있는지는 잘 모르겠습니다.

WPF 로딩시간 대처방법

WPF를 사용하여 구현하고 있는 현재 프로젝트에서 한가지 문제가 생겼다.
개발 환경인 Windows Vista에서 테스트 할 때와 Windows XP에서 테스트 할때
로딩 시간이 엄청나게 차이가 난다는 것이다.

물론 간단한 UI를 가지고 있는 Application이라면 로딩시간이 차이가 별로 나지 않아 중요 하냐 싶지만,
현재 작업중인 프로젝트와 같이 규모가 큰 Application이라면 꼭 체크해봐야 할 문제다.

Vista에서 실행했을때는 1초 미만의 로딩 시간이 소요됬지만, XP에서는 짧게는 8초 길게는 20초까지 걸렸다.
아마 저사양 PC에서도 마찬가지의 성능을 보일것 같다.

프로그램을 실행 시켰을때 뭔가 실행되고 있다는 느낌이 나야 기다리든지 말든지 할텐데,
화면상에 아무런 반응이 없어 프로그램이 실행되고 있는건지 알 방법이 없다.
(나는 처음엔 하드 디스크 돌아가는 소리로 실행됫는가를 판단햇엇다 -_-ㅋ )

한국사람들의 특성상 3초만되도 반응이 없다면 왜 실행이 안되 하고 다시 실행을 하려는 사람이 대부분 일것이다.
나또한 그렇지만, 어쨋든 WindowsXP사용자를 고려 해야 하기 때문에 해결할 수 밖에 없는 상황.

먼저 원인을 찾아야 했다. 어.디.서. 이렇게 오래걸리는것인가???
몇번의 디버깅으로 원인은 쉽게 찾을수 있었는데,

XAML에서 작성한 Code를 C# Code에서 사용가능하도록 해주며
프로그램이 실행될때 XAML에서 생성한 객체들을 초기화 해주는 .g.cs 파일 의 InitializeComponent()가 원인이였다.

보통 UserControl이나 Window를 VS상에서생성하게되면 InitializeComponent()부분을
해당 Class의 생성자에서 호출하도록 자동으로 구현해 놓는다.

객체를 생성하면 화면에 아무것도 보이지 않는 상태에서 열심히 XAML에서 생성한 객체들을 열심히 초기화 하는것이다. 전체적인 안정성면에서 이방법이 좋을지 모르겟지만, 기다릴줄 모르는 사용자들을 위해서라도 로딩시간에는 뭐라도 보여줘야만 한다.

그렇다면 어떻게 해결하면 좋을까?

나는 이러한 문제를 해결하기 위하여 Loaded 이벤트를 추가하고, Loaded이벤트에
InitializeComponent()의 호출부분을 추가했다.

Loaded 이벤트는 객체의 핸들이 모두 생성되었을때 호출되는 건데 이때부터 컨트롤의 크기를 조절하거나,
이름을 바꾼다거나 하는 작업이 가능하다.

Loaded가 되면 아래와 같이 Visibility속성을 Visible로 변경해 화면에 보여지도록 변경하고,
타이틀을 로딩중이다라는 메세지로 변경했다. 그다음 InitializeComponent()가 호출을 했다.

void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
this.Visibility = Visibility.Visible;
this.Title = "로딩중입니다요!!";
InitializeComponent();
}

이렇게하면, 내부적으로 Window를 생성하는데 최소한의 부분한 실행이 되기 로딩시간을 최소화 할 수 있으며,
사용자에게 로딩중이라는 메세지를 전달 할 수 있다.

나는 아래와 같이 MainWindow를 띄우기전 로딩창을 만들어 사용자에게 로드되고 있음을 더 명확하게 알려줄 수 있도록 구현했다.

void LoadingWindow_Loaded(object sender, RoutedEventArgs e)
{
InitializeComponent();
this.Visibility = Visibility.Visible;
MainWindow Main = new MainWindow();
Main.Show();
this.Close();
}

내용을 살펴보면 Load가 완료되었을때 이번엔 InitializeComponent();가 먼저 나와있다.
로딩중이라는 메세지를 XAML로 작성했는데. 이부분은 어쩔수 없이 로딩을 해야 하기때문이다.
(최소한의 Code를 사용하여 부하를 줄였다.)

마찬가지로 Visibility 속성을 Visible로 바꾸고, 본격적으로 보여줘야하는 MainWindow를 생성을 했고,
생성이 끝나면 MainWindow를 Show해줌으로서 로딩창을 닫았다.

MainWindow에서의 InitializeComponent()는 어느 위치에 놓여져도 상관은 없다.
로딩화면에서 미리 로딩중이라는 메세지를 미리 전달을 했기때문에!!

하고보면 되게 간단하지만, 개발자나 사용자 입장에서 볼때 로딩창은 꼭 필요한 요소라고 다시한번 느꼇다.
오피스나 포토샵을 실행할때 괜히 로딩창나와서 시간만 잡아먹네 하고 생각했었는데,
이러한 문제를 겪고 나보니 왜 필요한가를 절실히 깨닳을수 있는 기회엿던거 같다.

이렇게 간단하게 해결할 수 있는 부분을 귀찮다고 빼먹지 말자!