by
kirupa | 19 December 2009
In the
previous page, you added your behavior to your
LayoutRoot and were able to see the falling snow
effect for yourself. We started diving into the code
to better understand how everything works, and let's
continue where we left off on this page.
Next up is the
ApplicationLoaded method (event handler to be
precise) that is responsible for creating the animation:
- void
ApplicationLoaded(object
sender,
RoutedEventArgs
e)
- {
- foreach
(FrameworkElement
element
in
this.AssociatedObject.Children)
- {
-
FrameworkElement
localCopy
=
element;
-
- double
yPosition
=
Canvas.GetTop(localCopy);
- double
xPosition
=
Canvas.GetLeft(localCopy);
-
- double
speed
=
2*randomNumber.NextDouble();
- double
counter
=
0;
- double
radius
=
30
*
speed
*
randomNumber.NextDouble();
-
- localCopy.Opacity
= .2
+
randomNumber.NextDouble();
-
-
CompositionTarget.Rendering
+=
delegate(object
o,
EventArgs
arg)
- {
- counter
+=
Math.PI
/
(180*speed);
-
- if
(yPosition
<
Application.Current.RootVisual.DesiredSize.Height)
- {
-
yPosition
+= .2
+
speed;
- }
- else
- {
-
yPosition
=
-localCopy.Height;
- }
-
- Canvas.SetTop(localCopy,
yPosition);
- Canvas.SetLeft(localCopy,
xPosition
+
radius
*
Math.Cos(counter));
- };
- }
- }
As methods in this tutorial go, this is a bit on
the large side so let's look at bits and pieces of
it in greater detail starting with the loop that
goes through each of the children:
- foreach
(FrameworkElement
element
in
this.AssociatedObject.Children)
What this loop is doing is going through each of
the children our AssociatedObject has. If you
recall, our AssociatedObject is a Canvas, and all of
your snowflakes are contained inside it:

[ the behavior is attached to LayoutRoot, or
AssociatedObject ]
Each child, a snowflake, is referenced by the
element variable
that I declare as a FrameworkElement inside the loop
itself. All of the code you will see will run once
for each element, so if you have a lot of
snowflakes, just be glad that you aren't the one
having to do all of this heavy lifting.
- FrameworkElement
localCopy
=
element;
Inside the foreach loop, the first thing I do is
create a new copy of the reference to child element.
You'll see why this is important in a little bit.
- double
yPosition
=
Canvas.GetTop(localCopy);
- double
xPosition
=
Canvas.GetLeft(localCopy);
-
- double
speed
=
2*randomNumber.NextDouble();
- double
counter
=
0;
- double
radius
=
30
*
speed
*
randomNumber.NextDouble();
-
- localCopy.Opacity
= .2
+
randomNumber.NextDouble();
In the next few lines of code, I am declaring and
initializing some variables that will help set some
properties on the snowflake as its falls.
In the first two lines,
yPosition and
xPosition get the current location of my
snowflake using Canvas's
GetTop and GetLeft
methods.
The speed and
radius variables use
the randomNumber variable you initialized earlier
to define the speed with which the snowflakes fall
and how wide the radius of their oscillation will
be.
In the last line, I set the
Opacity of my
snowflake to be something random. This is what gives
your snowflakes a semi-transparent look when you are
running it.
All of this sets us up for the next section of
code that actually creates the animation loop:
- CompositionTarget.Rendering
+=
delegate(object
o,
EventArgs
arg)
- {
-
counter
+=
Math.PI
/
(180*speed);
-
-
if
(yPosition
<
Application.Current.RootVisual.DesiredSize.Height)
-
{
-
yPosition
+= .2
+
speed;
-
}
-
else
-
{
-
yPosition
=
-localCopy.Height;
-
}
-
-
Canvas.SetTop(localCopy,
yPosition);
-
Canvas.SetLeft(localCopy,
xPosition
+
radius
*
Math.Cos(counter));
- };
I spoke about CompositionTarget.Rendering in my
earlier
Creating Killer Animations in Code tutorial, but
I will summarize the interesting details that you
will need to understand how this fits in with the
overall falling snow effect.
The Rendering
event is the equivalent of a loop that just keeps
going each time your screen refreshes. Becase this
loop doesn't block the UI, you can safely specify
any animation-related changes here and not have to worry
about whether the resulting animation will look
smooth and fluid.
There is one critical detail that I employ in
this use of the Rendering event. Notice that my
event handler is explicitly a delegate, and since it
this is all inside my foreach loop, each snowflake
will be getting a copy of this delegate so that you
have individual control over each snowflake despite
dealing with an event that exists application wide.
If you are familiar with Flash, this is as close
as you can get to emulating the
enterFrame/onEnterFrame event.
All of the code inside the
Rendering event,
like I mentioned earlier, will fire each time the
screen refreshes. On most machines, that is 60 times
a second, but your computer may have a higher or
lower refresh that alters this a little bit. What I
am trying to say is that any code will get called
many times a second. Thererfore, any type of
incrementing or decrementing I do needs to be
sufficiently small so that the changes, in aggregate
over a second, are reasonable.
Speaking of incremting, the
first thing I do inside the delegate for the
Rendering event is increment my counter value:
- counter
+=
Math.PI
/
(180*speed);
The counter value is incremented ever so slightly
each tick. and you'll see shortly why it is being
incremented as slowly as it is.
- if
(yPosition
<
Application.Current.RootVisual.DesiredSize.Height)
- {
- yPosition
+= .2
+
speed;
- }
- else
- {
- yPosition
=
-localCopy.Height;
- }
This chunk of code here is what is responsible
for defining the current position of the snowflake
as it falls. First, I check to see if the snowflake
is still visible by comparing my current position
with the total height of the application's viewing
area.
If the snowflake is still visible, I incremeent
the yPosition variable slightly:
- yPosition
+= .2
+
speed;
If the snowflake is about to hit the edge, I kick
it back to the top where it can continue its
looping:
- yPosition
=
-localCopy.Height;
Changing just the variable that represents
position doesn't actually do much, but we fix that
right up....
- Canvas.SetTop(localCopy,
yPosition);
- Canvas.SetLeft(localCopy,
xPosition
+
radius
*
Math.Cos(counter));
All of the previous lines of code were basically
the setup to the two lines you see here. In these
two lines, the updated x and y positions for your
snowflakes are used to actually change the position
of your snowflake.
This should be pretty straightforward. I simply
set the Top and Left properties on my Canvas to the
variables I've been fiddling with. Just to call one
thing out, the oscillation each snowflake
experiences is caused by the
Math.cos(counter)
code that I pass in to the Canvas.SetLeft method.
Before we call it a day, notice what I am using
as the target element the for SetTop and SetLeft
method. It isn't the
element variable that you see as part
of your foreach
loop. Instead, it is is
localCopy - which is basically the exact same
thing as your element....except it isn't. When it
comes to delegates and anonymous methods, variable
scoping doesn't work as you would expect.
If I just passed in the element variable, all of
the code would affect just the last snowflake. By
creating another copy of the variable in the form of
localCopy, each time the loop runs and another
delegate is created, that delegate gets its own copy
of the element variable. This ensures that each
delegate affects the appropriate element instead of
being overwritten everytime until the last element
is left standing.
And
with that, you are done with this tutorial on how to
create falling snow. While I kinda call this the
faling snow technique, there is nothing about this
that limits its use only for simulating snow
particles. You can make all the tweaks to the speed
and radius that you want, and you can even change
the eillipses to something else and the code will
adapt well.
Anyway, below you will find the source files for
the version of the falling snow example I described
over the past four pages:
|