사실 세미나실 한칸도 못채울까봐 걱정했는데 생각보다 많은 분이 와주셨네요^^
거두절미하고, 발표 슬라이드 및 데모는 다음 링크에서...

다운로드 롸잇 나우!

여기에서 다룬 내용을 간단하게 요약하자면...
  • 실버라이트 애니메이션
    • 애니메이션의 기초
    • 프레임 기반 애니메이션
    • 실시간 애니메이션(스토리보드)
    • 파티클 시스템 기초
  • 실버라이트 그래픽 시스템과 성능 팁
    • 실버라이트 런타임의 렌더링 원리와 과정
    • 레이아웃
    • 성능 카운터(프레임 레이트 카운터)
    • 즉시 렌더링 영역(Intermediate Surface)
    • GPU 하드웨어 가속
    • 미디어 성능
  • 실행 성능 모니터링 및 분석
    • UI Spy 툴 사용
    • Visual Studio 2010 Profiler (VSP)
    • Windows Performance Toolkit (WPT)
꽤 많죠? 가능한 관련 정보에 접근할 수 있는 레퍼런스 링크를 많이 달았으니 부족한 부분은 해당 링크를 통해 검색해보길 바래요.

저작자 표시 동일 조건 변경 허락
신고
Posted by gongdo
실버라이트 애니메이션 모델에서 가장 아쉬운 점 세 가지를 들어보자면,
1. Path를 따라가는 애니메이션 작성 불가.
2. 임의의 스토리보드를 거꾸로 재생 불가.
3. 스토리보드의 특정 키프레임 혹은 특정 시점에서 발생되는 이벤트 부재.
정도에요.

이 중에서 스토리보드를 거꾸로 재생하는 것은 어느 정도 제한이 있긴 하지만 상당히 간단한 코드로 가능해요.
피터씨가 포스팅한
10. Expression Blend_ListBox 간지나게 보이기
11. Expression Blend_Menu에서 스토리보드를 거꾸로 재생하는 프로토타입 코드가 있었죠.

우선 동작 완구부터...

별거 없어요. 그냥 Forward하면 정방향으로, Reverse하면 역방향으로 애니메이션이 진행되는 거죠.
위 정도의 애니메이션이라면 그냥 노가다로 거꾸로 진행되는 걸 하나 만들어도 그만이죠.
그러나 애니메이션에 들어가는 키프레임이 수백개가 넘는다면? 악몽이죠. 그걸 거꾸로 돌리고 싶다면...

역방향 재생을 어떻게 할 수 있을지 간단하게 정리해 보자면...
1. 실버라이트는 스토리보드를 거꾸로 재생하는 메서드는 제공하지 않지만 AutoReverse 속성을 true로 설정하면 한번은 정방향으로 재생하고 그 직후 다시 역방향으로 재생할 수 있어요.
2. 또한 실버라이트의 스토리보드는 Seek 메서드를 사용하여 특정 시간으로 점프할 수 있어요.
3. 그러므로 역방향으로 재생할 때에는 스토리보드의 AutoReverse 속성을 true로 설정한 후 스토리보드를 처음부터 시작하고 곧바로 Seek메서드를 호출하여 스토리보드의 총 길이 만큼 이동하면 그 직후 AutoReverse에 의해 스토리보드가 거꾸로 가게 되죠.

이 외에도 범용적으로 사용하기 위해 스토리보드의 실제 총 길이를 계산하는 메서드도 필요하고 정방향으로 재생할 때에는 AutoReverse에 의해 역방향으로 재생되지 않도록 적절한 시점에 Pause를 걸어주는 처리도 필요하죠.

이 모든 처리를 하나의 클래스(ReverseStoryboard)에서 사용할 수 있도록 만들어봤어요.
소스 코드를 다운로드해 보시면 쉽게 이해할 수 있을거에요.

사용법은 간단해요.
// 타겟 스토리보드를 가지고 새 인스턴스 생성
private ReverseStoryboard rs = new ReverseStoryboard(TargetStoryboard);
rs.BeginForward();  // 정방향으로 재생
// 또는...
rs.BeginReverse();  // 역방향으로 재생
게다가 빌트인 Storyboard처럼 Completed 이벤트도 지원하고 Completed 이벤트에서 정방향 재생이었는지 역방향 재생이었는지를 알 수 있어요.
참 쉽죠? ;)

저작자 표시 동일 조건 변경 허락
신고
Posted by gongdo

너무나도 오랫만의 포스팅이에요. 그간 바빴단 핑계는 안통하겠죠?
오늘어제 은광여고 모임에서 급하게 준비했던 데모를 가지고 실버라이트의 애니메이션 모델에 대해 간단하게 소개합니다. 이 부분은 저도 계속 공부중이고 아직은 확신이 잘 안가는 부분도 많으니 오류나 더 좋은 방안이 있으면 바로 얘기해주세요.


실버라이트 애니메이션 소개

개요

애니메이션을 구현하는 방법은 여러가지가 있을 수 있겠지만 크게 프레임frame기반의 애니메이션 모델과 타임라인timeline기반의 애니메이션모델로 구분할 수 있을거에요. 이 글에서는 이 두가지 애니메이션 모델에 대해 간단하게 소개하고 차이점을 비교해보도록 할께요.

먼저 애니메이션을 한 마디로 정의 내리기는 쉽지 않지만 나름대로 정의를 내려볼까요?

정지된 다수의 장면을 연속적이고 순차적으로 화면에 표시하여 마치 움직이고 있다고 착각하게 만드는 효과

다른 말을 덧붙이고 싶으신 분들도 많으시겠지만 위의 정의에 대체로 동의한다는 가정하에 중요하다고 생각하는 몇 가지 용어도 알아보죠.

  • 프레임frame
    정지된 화면 한 장면 한 장면을 프레임이라고 합니다.
  • 렌더링rendering
    프로그래밍 코드에서 한 프레임을 이루는 요소들을 화면에 그리는 동작을 말합니다. 엄밀하게 얘기하자면 렌더링은 실제 화면이 되는 비디오 메모리에 픽셀 정보를 기록하는 것을 말하지만 실버라이트는 픽셀 단위의 비디오 메모리 조작은 불가능하고 모든 화면 구성 요소가 개체로 관리되므로 '화면에 표시될 개체의 속성을 변경하는 동작'이라고 표현해도 좋을 것입니다. 앞으로 렌더링이라고 하면 후자의 의미로 사용할 것입니다.
  • FPS(Frames Per Second)
    1초 동안 몇 개의 프레임이 표시되는지를 나타내며 일반적으로 FPS가 높을 수록 움직임이 자연스럽습니다.
  • 간격interval
    한 프레임과 한 프레임이 표시되는 시간적인 간격을 의미하며 1초를 FPS로 나눈 값입니다. 되며 예를 들어 30FPS인 애니메이션은 대략 33ms의 간격을 갖습니다. 이 간격이 짧을 수록 보다 높은 FPS로 애니메이션을 재생할 수 있습니다. 일반적으로 윈도2000 이상의 OS에서 제공하는 기본 타이머로 얻을 수 있는 최소 간격은 약 15~16ms로 알려져 있으며 따라서 이 기본 타이머만으로 구현할 수 있는 애니메이션의 최대 FPS는 64~66FPS라고 생각할 수 있습니다.

어떤 모델이든 위의 기본적인 개념은 잘 이해하고 있어야 해요. 그럼 본격적으로 각 애니메이션에 대해 생각해보죠.

전통적인 프레임 애니메이션

전통적인 애니메이션 프로그래밍은 앞서 말한 애니메이션의 기초 요소들을 그대로 모델링하고 있어요. 프로그램은 목표 FPS를 위한 간격의 타이머를 구동시키고 이 타이머내에서 렌더링을 수행해요. 물론 렌더링할 개체들의 표시 상태라던가 위치라던가 색상이라던가 투명도라던가하는 속성 값들을 개발자가 직접 작성한 코드로 계산하여 입력해줘야 하겠고요. 또한 복잡한 프레임의 경우 렌더링을 위한 계산 시간과 렌더링 자체에 걸리는 물리적인 시간도 어느 정도 소모되므로 간격을 이 시간도 고려해야 해요.

예를 들어 30FPS를 목표로 하는 애니메이션은 약 33ms의 간격을 유지해야 하므로 한 프레임을 렌더링 하는데 대략 3ms가 소요된다면 타이머가 30ms주기로 작동해야 할 거에요. 물론 플래쉬와 같은 애니메이션 툴에서는 이런 단순한 계산은 툴에서 지원을 해주고 FPS값만 지정해주면 몇 개의 프레임을 진행하는데 얼마의 시간이 걸릴지를 보여주니까 그다지 깊게 생각할 필요가 없을 거에요.

여튼, 프레임 애니메이션에서는 모든 장면을 프레임 단위로 생각하고 표시할 개체의 속성 변화 역시 프레임 단위로 계산된다는 게 핵심이라고 할 수 있어요.

실버라이트의 타임라인timeline 애니메이션

실버라이트는 프레임 기반의 애니메이션 대신 사람이 이해하기 쉬운 개념으로 추상화한 타임라인 기반의 애니메이션 기법을 제공해요. 하지만 한글을 사용하는 우리에겐 타임라인이나 프레임이나 어렵긴 매한가지지만 그러려니 해야죠. :)

먼저 타임라인의 개념에 대해 이해할 필요가 있어요. 아래 그림을 보시죠.


타임라인timeline은 말 그대로 시간의 흐름이 한줄로 늘어서있는 것을 말해요. 이 타임라인에 그림에서 보이는 화살표와 같은 어떤 흐름을 놓는다고 생각해보세요. 이 흐름이 유한하다면 -시작과 끝이 있다면- 타임라인상에서 시작시간begin time과 지속시간duration을 갖을거에요. 즉, 타임라인이란 특정 시공간내에서 진행되는 어떤 흐름의 시작시간과 지속시간을 의미한다고 볼 수 있어요. 이 타임라인에 애니메이션이란 흐름을 올려놓으면 바로 타임라인 애니메이션이 구성되는 것이죠.

실버라이트는 이 타임라인에 기반한 몇 가지 애니메이션 클래스들을 제공하고 있어요. 여기에서 타임라인은 애니메이션의 시작시간과 지속시간만을 관리하고 실제 개체의 속성은 바로 실제 애니메이션 클래스들이 제공하는 속성들-Double, Color, Point등-을 설정함으로써 시간의 흐름에 따라 자동으로 계산되게 되죠.

애니메이션은 크게 시퀀스sequence 애니메이션과 키프레임keyframe 애니메이션으로 구분할 수 있지만 여기에서는 개념을 이해하기 위해 보다 단순한 시퀀스 애니메이션을 중심으로 설명드릴께요. 다음의 그림을 보세요.


애니메이션은 타임라인위에 배치되고 이 타임라인이 애니메이션의 시작시간과 지속시간을 관리해요. 그리고 애니메이션은 변경될 대상의 속성값을 시작값from에서 목표값to까지 시간의 흐름에 따라 변경하는 역할을 수행하죠.

프레임 애니메이션과 달리 타임라인 애니메이션은 애니메이션이 진행되는 동안 벌어지는 일에 대해 손을 댈 필요가 없어요. 시작시간, 지속시간, 시작값, 목표값만 정해주면 나머지 계산과 렌더링은 런타임에서 알아서 수행하게 되죠. 아마 프레임 애니메이션에 익숙하신 분들은 이런 의문이 들거에요.

그럼 도대체 타임라인기반 애니메이션은 몇 FPS로 렌더링되는 거죠?

답은 무책임해보이지만 '알 필요 없다 '에요. 왜냐면 FPS는 현재 시스템 상황에 맞춰 실버라이트 런타임이 자동으로 계산한 결과물일 수밖에 없기 때문이죠. 그래서 FPS가 딱 정해졌다고 말할 수는 없어요. 다만 실버라이트 런타임은 자신의 현재 렌더링 FPS를 브라우저의 상태 표시줄에 표시할 수 있는데요, 방법은 MSDN의 EnableFramerateCounter실버라이트 성능향상 팁의 마지막 예제 코드를 보시고, 이 글 마지막의 샘플 프로젝트도 한번 보세요. 참고로 이 방법은 인터넷 익스플로러에서만 동작하고 보안설정에서 '스크립팅->스크립트를 통해 상태 표시줄 업데이트 허용'을 '사용'으로 체크하셔야 해요.

애니메이션의 집합, 스토리보드storyboard

실버라이트는 또한 시간의 흐름의 기준이 되는 타임라인 아래에 동시에 진행되는 여러개의 애니메이션을 올려 놓을 수 있는 병렬 타임라인PararellTimeline이란 개념을 제공해요. 바로 이 병렬 타임라인을 구현하는 클래스가 블렌드에서 익숙하게 사용하던 스토리보드storyboard에요.

스토리보드는 스스로가 하나의 타임라인인데요, 즉 시작 시점과 지속 시간을 가지고 있고 이 시간의 흐름이 스토리보드에 올라갈 여러개의 애니메이션의 공통된 기준이 되는 거죠. 스토리보드에 올라간 애니메이션은 각자 자신이 해야할 명확한 일 -어떤 개체(Name)의 어떤 속성(Property)을 어디에서(From) 얼마까지(To) 언제 시작하여(BeginTime) 얼마동안(Duration) 변경할 것인가- 에 대한 정의를 가지고 있고 스토리보드는 이 애니메이션들의 시작과 중지를 제어하는 메서드를 제공해요.

스토리보드가 시작되면 각 애니메이션은 자신이 정의하고 있는 시작 시간과 지속 시간을 스토리보드의 타임라인에 맞춰서 렌더링 동작을 수행하게 되고 애니메이션이 끝날 때까지 개발자는 이 중간 과정에 어떤 일이 벌어지는지 알 수 없어요. 그래서 기존의 프레임 기반의 애니메이션에 익숙한 분이시라면 굉장히 답답함을 느낄 수도 있겠지만 저는 오히려 이러한 고수준의 추상화가 작업을 상당히 단순하게 만들어준다고 생각해요. 그것이 바로 개체 지향이 추구하는 방향이기도 하겠죠.

프레임 애니메이션 VS 타임라인 애니메이션

지금까지 간단하게 실버라이트에서 구현할 수 있는 프레임 애니메이션과 타임라인 애니메이션에 대해 알아봤는데요, 그래서 과연 어떤 모델을 사용하는게 더 좋은 건지 따져봐야겠죠? 이 두가지 모델을 비교하기 위한 몇 가지 샘플을 준비했어요. 먼저 말씀드릴건 저는 프레임 애니메이션을 그다지 많이 해보지 않았고 그래서 프레임 애니메이션에서 아주 초보적으로 흔하게 사용되는 로직도 잘 몰라요. 때문에 일부 프레임 애니메이션의 코드는 매우 비효율적으로 작성되었을 수도 있으니 더 좋은 방법을 알고 계신 분께서는 조언을 해주시기 바래요. 또 이 샘플들의 자세한 설명이나 분석은 다음 기회로 미룰께요.

전체 다운로드


샘플) 등가속도로 운동하는 개체

샘플) 자유 낙하하는 개체

샘플) 자연스러운 가속을 받는 개체

샘플) 동적인 애니메이션과 사용자 정의 컨트롤

이런 단순하고 단편적인 애니메이션은 사실 어느쪽 모델로 해도 별로 관계 없을거에요. 하지만 코드를 살펴보고 원리를 생각해보면 이 두 모델이 가진 장단점을 비교해볼 수는 있죠.

구분 프레임 애니메이션 타임라인 애니메이션
XAML로의 표현 불가능 가능
코딩의 복잡도 복잡 비교적 단순
속성 제어의 정밀도 정밀 단순
동작 효율 높음 (단, 코딩 상태에 따라 크게 차이가 남) 보통 (최소한의 노력으로 좋은 효율을 얻을 수 있고 단순반복적인 움직임의 효율은 매우 높음)
강점 상태의 복잡한 변화를 정밀하게 제어할 수 있고 특히 다수 개체의 상호 연관된 상태 변화를 제어하는데 유용 비교적 단순한 변화를 최소한의 노력으로 표현할 수 있고 특히 다수 개체의 독립적인 상태 변화를 제어하는데 유용하고 XAML을 통한 협업이 유리
약점 여러 종류의 애니메이션을 하나의 시간축에 통합하기 어렵고 시간 단위로 제어하기 어려움 동적으로 복잡하게 변하는 상태를 제어하기 어렵고 타임라인만으로는 개체끼리의 충돌과 같은 상태 간섭을 검출하기 힘듬

타임라인 애니메이션의 최대 장점은 바로 XAML로 표현이 가능하다는 것이라고 할 수 있어요. 애니메이션의 동작조차 XAML로 단순하게 선언이 될 정도로 잘 추상화 되어 있고 XAML을 통해 디자이너와 개발자와의 협업이 좀 더 자연스럽게 끊김없이 이루어질 수 있다는 거죠. 또한 사용되는 개념이 사람이 이해하기 쉽고 직관적으로 구성되어 있어서 조금만 훈련을 하면 매우 빠르게 작업이 가능해요. 반면 최대의 약점은 게임에서 흔하게 사용되는 개체끼리의 정확한 충돌collision검출이 어렵다는 점인 것 같아요.

결론

실버라이트는 프레임 단위의 전통적인 애니메이션 모델을 구현할수도 있지만 보다 직관적이고 디자이너와 개발자와의 협업이 가능한 타임라인 애니메이션을 사용하는 것이 일반적인 움직임을 표현할 때에 매우 효과적이에요. 지금은 이런 모델이 많이 낯설고 자료도 부족하지만 차근차근 연구해보면 더 효율적인 애니메이션을 구사할 수 있다고 생각해요.

참고


신고
Posted by gongdo
Storyboard XAML 문법의 난해함

UI 프로그래밍 기법에 있어서 WPF와 Silverlight의 가장 큰 특징은 Storyboard와 Animation 개체를 사용한 애니메이션의 구현이 아닐까 생각해요. 물론 비슷한 개념은 이미 플래쉬나 다른 곳에서도 볼 수 있었지만요.

그런데 이 스토리보드와 애니메이션을 수작업으로 XAML 코딩 한다는 건 만만치가 않습니다. 바로 애니메이션의 대상 개체와 대상 속성을 지정하는 문법의 귀찮음난해함 때문이죠.

간단하게 테스트 해볼까요?
페이지가 로드 되었을 때 다음과 같은 Rectangle 엘리먼트가 오른쪽으로 100픽셀 이동하면서 회전하는 애니메이션을 Expression Blend를 사용하지 않고 코딩해보세요. 단, Orcas를 사용해도 좋습니다.

<Rectangle Width="100" Height="100" Canvas.Left="50" Canvas.Top="100" Fill="Black" />

이걸 고민 없이 해내는 분이 있다면 쓰부로 모실테니 연락주세요!

당연한 얘기지만 전 그렇게 못해요. 그래서 이런 애니메이션을 만들 땐 곧바로 Expression Blend로 프로젝트를 열어보지요.
블렌드로 대충 끄적여 볼까요? 간단한 과정인데 캡쳐하기가 애매해서 동영상으로 캡쳐했어요.


원본은 800 x 600이니까 다운 받아서 보세요.
http://gongdo.tistory.com/attachment/cfile5.uf@25123E3A5878F5810E67EB.wmv


대략 아래와 같은 XAML 코드가 만들어질거에요. (루트 엘리먼트는 생략.)
XAML
<Canvas.Triggers>
    <EventTrigger RoutedEvent="Canvas.Loaded">
        <BeginStoryboard>
            <Storyboard x:Name="Timeline1">
                <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="rectangle" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
                    <SplineDoubleKeyFrame KeyTime="00:00:01" Value="100"/>
                </DoubleAnimationUsingKeyFrames>
                <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="rectangle" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)">
                    <SplineDoubleKeyFrame KeyTime="00:00:01" Value="360"/>
                </DoubleAnimationUsingKeyFrames>
            </Storyboard>
        </BeginStoryboard>
    </EventTrigger>
</Canvas.Triggers>

<Rectangle Width="100" Height="100" Canvas.Left="50" Canvas.Top="100" Fill="Black" RenderTransformOrigin="0.5,0.5" x:Name="rectangle" >
    <Rectangle.RenderTransform>
        <TransformGroup>
            <ScaleTransform ScaleX="1" ScaleY="1"/>
            <SkewTransform AngleX="0" AngleY="0"/>
            <RotateTransform Angle="0"/>
            <TranslateTransform X="0" Y="0"/>
        </TransformGroup>
    </Rectangle.RenderTransform>
</Rectangle>

와... 이거 코딩은 고사하고 해독하는데도 만만치가 않네요. 단지 몇 픽셀 옮기면서 돌렸을 뿐인데...
특히 이 코드의 백미는 Storyboard.TargetProperty의 복잡함이죠. 죄다 문자열이라서 인텔리센스의 도움을 받을 수 없는데 베이스 클래스의 이름부터 순서대로 쫘악 나열해야 하는데 중간에 배열 번호까지 들어가요. 해당 속성이 컬렉션일 경우엔 추가된 순서까지 맞춰서 코딩해야 한다는 거죠.

이쯤되면 수작업으로 작성하는 건 GG죠.

이런 이유로 실버라잇의 애니메이션은 그냥 블렌드를 사용하여 작성하는게 정신 건강에 훨씬 좋아요. 블렌드에서 대충 원하는 형태의 애니메이션을 만들고 세부적인 값을 수정하는 거죠.

현재 스토리보드의 문제점

그나마도 디자인타임에 애니메이션이 고정되는 건 블렌드와 약간의 수정을 통해 뭐라도 만들 수 있어요. 하지만 동적으로 순간순간 변화하는 변수를 적용해야 하는 애니메이션이 있다면? 그럼 더 머리 아파지죠.

여기에서 XAML과 코드-비하인드를 연계하여 코딩하는데 익숙하신 분이라면 XAML의 Canvas나 TransformGroup 엘리먼트처럼 하위 엘리먼트를 포함할 수 있는 경우 코드-비하인드에서는 object.Children 이라는 컬렉션 속성을 통해 하위 엘리먼트에 접근하거나 추가/삭제 할 수 있다는 걸 아실거에요. 예를 들어 위에서 작성한 XAML을 개체 트리로 나타내 보자면 다음과 같습니다.


이것을 다시 의사 코드로 작성해보면 아마도 아래와 같겠죠
C# (의사 코드)
Canvas canvas = new Canvas();    // 새 캔버스 생성

BeginStoryboard bs = new BeginStoryboard();    // 새 스토리보드 시작기 생성

Storyboard sb = new Storyboard();    // 새 스토리보드 생성

DoubleAnimationUsingKeyFrames dak = new DoubleAnimationUsingKeyFrames();    // 더블 애니메이션 생성
dak.KeyFrames.Add( new SplineDoubleKeyFrame() );    // 애니메이션에 키프레임 추가

sb.Children.Add( dak );    // 스토리보드에 생성한 애니메이션 추가

dak = new DoubleAnimationUsingKeyFrames();    // 다른 애니메이션 새로 추가
dak.KeyFrames.Add( new SplineDoubleKeyFrames() );

sb.Children.Add( dak );

bs.Storyboard = sb;    // 스토리보드를 설정

Rectangle rect = new Rectangle();    // 새 사각형 생성

TransformGroup tg = new TransformGroup();    // 새 변형 그룹 생성

tg.Children.Add( new ScaleTransform() );        // 변형 그룹에 변형 규칙 추가
tg.Children.Add( new SkewTransform() );
tg.Children.Add( new RotateTransform() );
tg.Children.Add( new TranslateTransform() );

rect.RenderTransform = tg;    // 사각형의 변형 속성을 설정

canvas.Children.Add( tg );    // 사각형을 캔버스에 추가

canvas.Triggers.Add( bs );    // 스토리보드 시작기를 캔버스의 트리거에 추가

꽤나 복잡해 보이지만 위의 개체 트리 구조를 보면 별로 어려울 건 없을 거에요. XAML에서 하위 요소는 코드-비하인드에서 일반적으로 같은 이름의 컬렉션으로 구현된다는 거죠.
이렇게라도 동작해주면 얼마나 좋겠습니까...만,
안됩니다.

TransformGroup.Children이나 Canvas.Children 또는 Canvas.Triggers와 같은 Collection 개체는 일반적으로 하위 개체를 생성하여 추가, 삭제, 접근할 수 있게 되어 있어요.

하지만 이상하게도 Storyboard의 Children 속성은 분명히 TimelineGroup이라는 이름의 클래스임에도 불구하고 실제로 들어가보면 단지 Timeline을 가리킬 뿐이에요. 다시 말해서 컬렉션이 아니기 때문에 하위 요소를 하나밖에 추가할 수 없다는 얘기죠.

예제에서 우리는 [오른쪽으로 이동] 이라는 애니메이션과 [360도 회전]이란 애니메이션을 동시에 처리하였죠? 그런데 스토리보드의 Children이 단일 요소만 가리키게 되어 있으니 이건 뭐 방법이 없는 거에요.

이 부분은 아마도 1.1 Alpha라는 테스트 중인 버전이기 때문에 그렇지 않을까... 정식판이 나오면 스토리보드의 Children이 Animation의 컬렉션으로 변경되지 않을까... 하는 기대를 할 수밖에 없네요.

XamlReader 클래스를 활용한 동적 스토리보드 작성

그렇다고 코드에서 스토리보드를 동적으로 생성하고 애니메이션을 추가하는게 불가능한 건 아니에요.
다만 효율이 조금 떨어질 뿐이죠.

실버라잇은 XamlReader라는 매우 리버럴한 클래스를 제공하는데요, XamlReader.Load 메서드를 사용하면 파라미터로 전달한 XAML 코드를 동적으로 파싱하여 개체로 반환해주죠.

예를 들어 새 사각형 개체를 XamlReader를 이용하여 동적으로 추가한다면...

C#
string strRect = @"<Rectangle Width='100' Height='100' Canvas.Left='100' Canvas.Top='100' Fill='Red' />";
Rectangle rect = (Rectangle)XamlReader.Load(strRect);
this.Children.Add(rect);

심플하죠? 그냥 동적으로 추가할 XAML 코드를 문자열로 저장하고 XamlRead.Load 메서드의 파라미터로 전달하고 원하는 타입으로 캐스팅만 하면 돼요.

마찬가지로 스토리보드와 일련의 요소들을 XAML 코드로 작성하여 XamlReader로 동적으로 생성할 수 있다는 거죠.
처음 Blend로 작성했던 예제를 조금 변형해서 이번엔 텍스트1을 클릭하면 오른쪽으로 100픽셀, 텍스트2를 클릭하면 왼쪽으로 100픽셀을 회전하며 이동하는 코드를 작성해보죠.

먼저 텍스트1, 2과 결과 확인을 위한 텍스트3을 추가하고 기존에 작성되었던 애니메이션은 제거하며 사각형은 그대로 놔둡니다. 역시 루트 엘리먼트인 Canvas는 생략했으니 주의...
XAML
<TextBlock x:Name="text1" Text="You spin me RIGHT round round round..." Canvas.Top="0" />
<TextBlock x:Name="text2" Text="You spin me LEFT round round round..." Canvas.Top="20" />
<TextBlock x:Name="text3" Text="O_O" Canvas.Top="40" />


<Rectangle Width="100" Height="100" Canvas.Left="50" Canvas.Top="100" Fill="Black" RenderTransformOrigin="0.5,0.5" x:Name="rectangle" >
    <Rectangle.RenderTransform>
        <TransformGroup>
            <ScaleTransform ScaleX="1" ScaleY="1"/>
            <SkewTransform AngleX="0" AngleY="0"/>
            <RotateTransform Angle="0"/>
            <TranslateTransform X="0" Y="0"/>
        </TransformGroup>
    </Rectangle.RenderTransform>
</Rectangle>

오른쪽/왼쪽으로 움직이는 스토리보드가 하나 있어야겠죠? 클래스의 멤버 변수로 스토리보드를 하나 선언합니다.
C#
Storyboard _story = new Storyboard();

그리고 페이지의 로드 이벤트 핸들러에 텍스트1과 2가 눌렸을 때 처리할 이벤트 핸들러를 추가합니다. 이벤트 핸들러는 아래에서 채워 넣을 거에요.
C#
// 텍스트 클릭 이벤트 핸들러 추가
text1.MouseLeftButtonDown += new MouseEventHandler(text1_MouseLeftButtonDown);
text2.MouseLeftButtonDown += new MouseEventHandler(text2_MouseLeftButtonDown);

이제 이벤트 핸들러에 각각 스토리보드를 로드하는 코드를 만들어야겠죠? 이 작업은 앞서 얘기했듯이 수작업으로 머리 싸매가면서 할 필요 없어요. 그냥 Expression Blend로 빈 프로젝트를 하나 만들고 애니메이션 타임라인을 생성하되 새 타임라인을 추가할 때 리소스로 만들기(Create as a Resource)를 선택하세요.


그리곤 원하는 애니메이션을 대충 만들면 <Canvas.Resources> 태그 내에 맨 처음에 Blend로 만들었던 Storyboard 코드가 만들어질 거에요. 여기에서 <Storyboard>의 x:Name이 Timeline1로 지정되어 있는데 이건 코드에서 동적으로 로드하기 때문에 필요 없으니 삭제하세요. 마지막으로 각 태그의 어트리뷰트 설정을 위해 겹따옴표가 있는데 이건 홑따옴표로 교체하시고 코드-비하인드에 문자열로 붙여넣으시면 다음과 같은 코드가 만들어지죠.
C#
void text1_MouseLeftButtonDown(object sender, MouseEventArgs e)
{
string strStory =
    @"<Storyboard>
        <DoubleAnimationUsingKeyFrames BeginTime='00:00:00' Storyboard.TargetName='rectangle' Storyboard.TargetProperty='(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)'>
            <SplineDoubleKeyFrame KeyTime='00:00:01' Value='100'/>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames BeginTime='00:00:00' Storyboard.TargetName='rectangle' Storyboard.TargetProperty='(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)'>
            <SplineDoubleKeyFrame KeyTime='00:00:01' Value='360'/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>";

    _story = (Storyboard)XamlReader.Load(strStory);
    _story.Completed += new EventHandler(_story_Completed);
    this.Resources.Add(_story);

    _story.Begin();
    text3.Text = "Spin started.";
}

void
_story_Completed(object sender, EventArgs e)

{
    text3.Text = "Spin completed.";
}

text2를 눌렀을 때의 이벤트 핸들러 코드는 생략했어요. (갈수록 날로 먹으려는 이 수작!)
뭐 text1의 핸들러와 똑같고 두 애니메이션의 Value값을 '0'으로만 바꾸면 되니까 직접 해보세요. -ㅇ-

여기서 주의할 점은 코드로 제어하는 스토리보드는 반드시 루트 엘리먼트의 리소스로 등록이 되어야 한다는 것과 XamlReader로 동적으로 로드하는 XAML 엘리먼트는 x:Name이 설정되면 안된다는 사실. 잊지 마세요.

작성된 프로젝트는 다음에 첨부했으니 코딩이 귀찮으신 분들은 다운 받아보세요. :)
StoryboardTest.zip

스토리보드 테스트 프로젝트



동작 데모는 다음과 같습니다.

http://gongdo.tistory.com/attachment/cfile27.uf@253353395878F57F1002F7.wmv

여전히 남아있는 불합리성

여기까지 더운데 땀 삐질삐질 흘려가며 작성하셨다면 지금쯤 꽤나 열이 받으셨을 거에요.

"블렌드를 써서 만든 코드를 복사해서 도로 붙여 넣고 따옴표 바꿔주고 문자열로 만들고 코드 몇 줄 더 추가하고... 이럴 바에야 스토리보드를 각각에 상황에 맞게 더 만들고 말겠다!"

네, 정답입니다. 위의 예제 같은 경우는 구태여 코드에서 동적으로 만들 필요성 따윈 전혀 없어요.
단순한 애니메이션이라면 그냥 Blend를 써서 만드는게 훨씬 빠르고 합리적이죠.

하지만 이런 경우를 생각해보죠. 만약 애니메이션의 이동 범위가 런타임에 수시로 바뀐다면? 심지어 이동 방향 조차 변경될 수 있다면?
또는 마우스 커서가 이동하면 마우스 커서에서 시작되는 애니메이션을 만들고 싶다면?

이럴 때 위와 같이 문자열로 XAML 코드를 옮기되 동적으로 수정하고 싶은 부분을 {0}, {1}, {2} ... 이렇게 치환해놓고 String.Format 메서드로 포매팅하면 되겠죠. 예를 들어 위의 예제에서 진행시간, 이동 범위를 동적으로 변경한다면...
C#
string strStory =
    @"<Storyboard>
        <DoubleAnimationUsingKeyFrames BeginTime='00:00:00' Storyboard.TargetName='rectangle' Storyboard.TargetProperty='(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)'>
            <SplineDoubleKeyFrame KeyTime='{0}' Value='{1}'/>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames BeginTime='00:00:00' Storyboard.TargetName='rectangle' Storyboard.TargetProperty='(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)'>
            <SplineDoubleKeyFrame KeyTime='{0}' Value='360'/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>";

    _story = (Storyboard)XamlReader.Load( string.Format( strStory, new object[] { "00:00:05", "200" } ) );

이렇게 문자열의 포매팅을 활용할 수 있겠죠.

휴...
스토리보드, 애니메이션, 타임라인은 다른 개체들이 상당히 직관적으로 구성되어 있는데 비해 굉장히 난해한 편인 것 같아요. 그냥 개체나 멤버의 이름만 보고 직관적인 코딩이 어렵다는 거죠. 코드만 가지고 적절하게 스토리보드를 만들지 못하고 상당히 지저분한 방법을 동원하게 되었는데요, 다시 얘기하지만 이런 점은 정식판이 나오면서 개선되었으면 하는 바램이에요.

신고
Posted by gongdo


티스토리 툴바