The most annoying part of losing the XAML was making the animations work. Looking back, it seems pretty simple but getting there led me to a lot of frustration.
Every framework element has a resources property but that page doesn't get into detail about using the resource property without XAML. I had to figure it out from other code. After <Window.Resources> in traditionalclock.xaml, you come across the Storyboard Class, the website for which offers a good example of using it in C# but does not mention the ParallelTimeline Class which is used next in the file. Again, the documentation from Microsoft offers no help about using it without XAML.
The tutorial Particle Effects in WPF was very helpful in showing me that you must add animation types to the ParallelTimeline by registering them as children of the ParallelTimeline. However, I still wasn't able to get the animation working (I was either getting no build errors with the no animation or build errors that told me I didn't yet know what I was doing. It wasn't until I came across a much more advanced animation example when I finally understood everything I was doing wrong and what I needed to do right. (Now, that I look at that example again, I see it comes from a set of very nice examples which give pure C# representations alongside XAML + C# code-behind).
Anyway, here's the play-by-play:
- Setting up the Storyboard
- XAML:
<Window.Resources> <Storyboard x:Key="clockHandStoryboard"> <ParallelTimeline> <DoubleAnimation From="-9" To="351" Duration="00:01:00" RepeatBehavior="Forever" Storyboard.TargetProperty="Angle" Storyboard.TargetName="secondHandAngle"/> ...[other DoubleAnimation code is similar]... </ParallelTimeline> </Storyboard> </Window.Resources> - C#:
NameScope.SetNameScope(this, new NameScope()); Storyboard clockHandStoryboard = new Storyboard(); ParallelTimeline pt = new ParallelTimeline(TimeSpan.FromSeconds(0)); DoubleAnimation secondHandAnimation = new DoubleAnimation(-9, 351, new Duration(new TimeSpan(0, 1, 0))); Storyboard.SetTargetName(secondHandAnimation, "secondHandAngle"); Storyboard.SetTargetProperty(secondHandAnimation, new PropertyPath(RotateTransform.AngleProperty)); secondHandAnimation.RepeatBehavior = RepeatBehavior.Forever; ... pt.Children.Add(secondHandAnimation); ... clockHandStoryboard.Children.Add(pt); this.Resources.Add("clockHandStoryboard", clockHandStoryboard); this.RegisterName("clockHandStoryboard", clockHandStoryboard); - Notes:
A name scope is created for the window so that we can later register the storyboard and animation target names with the scope. A storyboard and ParallelTimeline are then instantiated to hold the animation information. Notice that to create the DoubleAnimation in C#, several steps are used whereas in XAML it is one line. "SetTargetName" specifies a string which is the name of the object, in the name scope, to be animated. Once the animations were set-up, I added them to the ParallelTimeline, then added the ParallelTimeline to the Storyboard. Finally, I added the Storyboard to the window's Resources collection since that's how it was outlined in XAML and then added its name to the name scope since that's what Microsoft suggested.
- XAML:
- Triggering the Animation (this is the same for each hand)
- XAML:
<Canvas.Triggers> <EventTrigger RoutedEvent="Canvas.Loaded"> <EventTrigger.Actions> <BeginStoryboard Name ="clockHandStoryboard" Storyboard="{StaticResource clockHandStoryboard}" /> </EventTrigger.Actions> </EventTrigger> </Canvas.Triggers> - C#:
EventTrigger canvasTrigger = new EventTrigger(); canvasTrigger.RoutedEvent = Canvas.LoadedEvent; BeginStoryboard beginStory = new BeginStoryboard(); beginStory.Name = "clockHandStoryboard"; beginStory.Storyboard = clockHandStoryboard; canvasTrigger.Actions.Add(beginStory); clockCanvas.Triggers.Add(canvasTrigger); - Notes:
With each step towards understanding, this is getting easier and easier. Again, I noticed that the XAML and C# don't correspond exactly but it's easy for me to see now that "Canvas.Triggers" and "EventTrigger.Actions" must be some kind of collection so I immediately check for an "Add" method or "Children.Add" method.
- XAML:
- Registering the animation
- XAML:
<Polygon.RenderTransform> <RotateTransform x:Name="hourHandAngle" CenterX="4" CenterY="45" /> </Polygon.RenderTransform> - C#:
RotateTransform hourHandAngle = new RotateTransform(); this.RegisterName("hourHandAngle", hourHandAngle); hourHandAngle.CenterX = 4; hourHandAngle.CenterY = 45; hourHand.RenderTransform = hourHandAngle; - Notes:
It took me far too long to discover that you must register the transform's name. I had assumed that "x:Name" meant the transform had a "Name" property but it doesn't so I spent a long time trying different things before I looked it up in the various sources mentioned above. I don't want to rely on external sources, I want to try to figure it out myself through debugging but when you really don't understand, it's best to look it up instead of wasting time.
- XAML:
The pure C# clock finally works. There is no need to change assemblyinfo.cs if you want to add that information into the properties dialogue. The executable of the port is significantly smaller than the original. I guess that's because it doesn't need to hold any of the XAML information (though I would expect it to all be parsed and compiled similarly to my rewrite). Also, the original build and my new one are clearly using methods supported by Microsoft, I would have expected them to create an animation library that doesn't consume 50% cpu when looping forever. If there's another way besides "RepeatBehavior="Forever" I'd really like to know.
No comments:
Post a Comment