1 Results for '타이머'

  1. 2007.06.14 [Silverlight] 타이머 클래스 데모 (6)
싱글스레드 프로그래밍에서 비동기 실행에 필수적인 것, 바로 타이머죠.
Silverlight는 자체적으로 타이머 클래스를 지원하고 있지 않지만 약간의 트릭을 사용하여 타이머를 만들 수 있습니다.

다음에 설명할 예제는 아래에서 다운받을 수 있고요
TimerTest.zip

타이머 클래스 데모



동작 데모는 아래 사이트에서...
http://gongdo.oranc.co.kr/Silverlight/Samples/TimerTest/TestPage.html

Storyboard 트릭

타이머 지원의 핵심은 바로 Storyboard!
Silverlight의 Storyboard는 대상 Animation이 없더라도 지정된 시간 동안 스토리를 진행하고 이벤트를 띄워주는 구멍 아닌 구멍을 가지고 있죠. 바로 이런 점을 이용하여 빈 스토리 보드를 리소스로 추가하고 Begin시킨 다음, Completed 이벤트가 발생하면 또다시 Begin시키고 또 Completed되었을 때 Begin하는 방식이에요. XAML 코드의 예를 들자면...

XAML
<Canvas.Resources>
    <Storyboard x:Name="timer" BeginTime="00:00:00.000" Duration="00:00:01.000" Completed="OnTimer" />
</Canvas.Resources>

그리고 코드-비하인드에는 이 스토리 보드를 Begin시킬 코드와 Completed 되었을 때 처리할 이벤트 핸들러가 필요하겠죠. 이 이벤트 핸들러에서 스토리보드를 다시 시작하면 일정한 간격으로 계속 해서 이벤트가 발생하는 타이머 효과를 얻을 수 있죠.
C#
void OnTimer(object sender, EventArgs e)
{
    // 스토리보드의 Duration이 지나 종료됨, 다시 스토리보드 시작
    ((Storyboard)sender).Begin();
}

매니지드 코드만으로 구현하는 타이머

그런데, 타이머가 필요할 때마다 XAML을 사용하여 리소스에 등록하는 일은 아주 귀찮은 일이죠. 그냥 Timer tmr = new Timer(); 이런 깔끔한 문장으로 타이머를 만들 수 있다면 얼마나 좋겠어요?
C# 매니지드 코드만으로 Storyboard 생성하는 방법
Storyboard story = new Storyboard();
story.BeginTime = TimeSpan.Zero;
story.Duration = new Duration(new TimeSpan(0, 0, 0, 0, 500));
story.Completed += new EventHandler(story_Completed);

위의 코드는 새 스토리보드의 인스턴스를 생성하고 시작시간(BeginTime)을 Zero로 설정하고 진행 기간을 500ms로 설정한 후 Completed 이벤트 핸들러를 추가해줬습니다. 한 가지 주의 깊게 볼 것은 Duration은 Duration 클래스로 표현되고 Duration의 오버로드된 생성자에 TimeSpan 개체를 넣어주면 되지요.

TimeSpan은 또 뭐냐구요? 시간과 시간의 간격을 나타내는 클래스인데 다른건 몰라도 그냥 생성자에 날짜, 시간, 분, 초, 밀리초를 집어 넣으면 해당 시간 만큼의 간격 값이 저장된다고 생각하시면 돼요.

참 쉽죠?

그런데, 이게 다가 아니에요. 스토리보드는 반드시 어떤 개체의 트리거로 연결되거나 어떤 XAML 페이지의 탑레벨 엘리먼트 즉, 루트 엘리먼트의 Resource로 등록이 되어야만 정상적으로 동작해요.

예를 들어 MainPage.xaml의 코드-비하인드에서 위의 story를 리소스로 추가한다면 this.Resources.Add(story); 라는 단순한 구문으로도 가능하죠.

하지만 조금만 더 생각해봐요. 범용적인 Timer 클래스를 만드는데 위와 같이 하면 항상 메인 XAML에 대한 코드-비하인드에 Timer 코드를 복사해서 넣는 건 정말로 불합리하잖아요?

따라서 Timer를 클래스화 한다면 다음과 같이 클래스의 생성자에는 스토리보드가 추가될 메인 XAML의 루트 엘리먼트에 대한 참조를 넘겨주는 코드가 필요할 거에요.
C#
public Timer(FrameworkElement rootElement)
{
    rootElement.Resources.Add(story);
}

그런데 이게 끝이 아니에요. 또 뭔가 싶죠?
위의 클래스는 약점이 있는데 바로 클래스 초기화 인자로 받는 rootElement가 진짜로 ROOT인지 확인을 하지 않는 다는 점이에요.
클래스 설계시 주의할 점 중에 하나는 '사용자는 반드시 잘못된 인자를 넘겨준다.'라고 생각해요.
rootElement의 타입이 FrameworkElement라서 FrameworkElement이기만 하면 TextBlock을 넘기든 Rectangle을 넘기든 이 코드는 똥인지 된장인지 구분 못하고 넙죽 받아먹는다는 거죠.

좀 더 안전한 코드를 작성하기 위해 다음과 같은 함수로 어떤 FrameworkElement에 대한 최상위 Element 즉, 진짜 Root 엘리먼트를 받을 필요가 있습니다.
C#
// 혹시 모를 사용자 실수를 대비하여 해당 엘리먼트의 절대적인 루트 엘리먼트를 반환
private FrameworkElement AbsoluteRootElement(FrameworkElement element)
{
    FrameworkElement root = element;
    // 최상위 엘리먼트라고 판단 될때까지 부모 엘리먼트를 얻기
    while(root.Parent != null)
    {
        root = (FrameworkElement)root.Parent;
    }
    return root;
}

약간 복잡한 것 같지만 단계별로 잘 생각해보면 어렵지 않아요.
작성한 Timer 클래스의 완성본은 첨부한 프로젝트를 참고하시고 다음은 동작 데모 동영상.


http://gongdo.tistory.com/attachment/cfile25.uf@2101C1335878F5791FC106.wmv


참고
http://silverlightrocks.com/community/blogs/silverlight_games_101/archive/2007/05/15/creating-a-game-loop.aspx
실버라이트로 게임을 구현하려는 블로그... 하지만 업데이트가 안 되는 것 같네요 -_-
신고
Posted by gongdo


티스토리 툴바