Since the window is working nicely, I want to start drawing onto the canvas.
The final line of the constructor is CreateCircles(); which initializes some parameters for generating the circles then goes into a loop to instantiate and set-up each of the circles.
Every circle will be centered at (actually, near) the center of the canvas. It is very simple to find the center of the canvas:
double centerX = this.MainCanvas.ActualWidth / 2.0;
double centerY = this.MainCanvas.ActualHeight / 2.0;
We don't know the size of each person's screen so use the "ActualWidth/ActualHeight" property to get the rendered dimensions of the canvas width and height respectively. It is possible to set the height and width using the "Width/Height" property but it may not be rendered with that value for a variety of reasons (here about the height of the object):
Height is one of three writable properties on FrameworkElement that specify height information. The other two are MinHeight and MaxHeight. If there is a conflict between these values, the order of application for actual height determination is that first MinHeight must be honored, then MaxHeight, and finally, if it is within bounds, Height.
The next thing in the "CreateCircles" method is the list of valid colors for each circle:
Color[] colors = new Color[] { Colors.White, Colors.Green, Colors.Green, Colors.Lime };
Just a simple array of colors, this is good programming practice when you have a specific style in mind or otherwise already know which values should be valid as you then have a specific object with which you can check against to determine if a value is valid or which can be iterated through to retrieve valid values. In this case, we want to choose a color during the for-loop and with this approach we can just choose an arbitrary element from the colors list. To change the aesthetic, it's simply a matter of reorganizing the list rather than finding and changing all the hard-coded values.
The colors and center of the canvas are the only values needed to be determined before the loop. Actually, that's not quite true...many of the values are randomized. The random number object is instantiated in the constructor and the variable for it is declared as an instance variable beforehand:
public partial class Window1 : Window
{
...
private Random rand;
public Window1()
{
...
rand = new Random(this.GetHashCode());
...
}
...
}
As noted in the comments of the GetHashCode documentation, it's not very useful on its own and, I might add, really not useful as a seed for a random number generator. In this case, the object is never different so the seed will always be the same so the random numbers will always have the same sequence. You can see this by running the program more than once--it's always the same. I would rather seed it with the time the program is run, that way the user will always get something exciting so I used "Environment.TickCount" instead of "this.GetHashCode()" for my rewrite.
When I was looking at the code, I noticed that while the animation looks like each circle is being created at a different time, the loop is instantiating them all at once. I saw each animation was set with a delay and concluded that that's where the magic happens. To find out if I was right, I commented out all the animation stuff in the for-loop to leave only:
Ellipse e = new Ellipse();
byte alpha = (byte)rand.Next(96,192);
int colorIndex = rand.Next(4);
e.Stroke = new SolidColorBrush(Color.FromArgb(alpha, colors[colorIndex].R, colors[colorIndex].G, colors[colorIndex].B));
e.StrokeThickness = rand.Next(1, 4);
e.Width = 0.0;
e.Height = 0.0;
double offsetX = 16 - rand.Next(32);
double offsetY = 16 - rand.Next(32);
this.MainCanvas.Children.Add(e);
e.SetValue(Canvas.LeftProperty, centerX + offsetX);
e.SetValue(Canvas.TopProperty, centerY + offsetY);
Note: Elipses are shapes, they need using System.Windows.Shapes; at the top.
Of course, without the animation, the circles will not grow so I set "e.Width" and "e.Height" to "10.0" for testing purposes. Curiously, those 24 little circles were in the top-left corner of the screen rather than the center. I traced the problem to not inserting this.Show(); before calling the "CreateCircles" method. What that extra line does is force the window to render along with all its child elements (namely the maximized canvas) that way the layout manager will know where the center is.
All that's left to do is animate and since I learned about that (in more detail) from both the Alarm Clock Sample and Calculator Demo, I'm not going to cover it here. I will just note that these animation do not rely on a storyboard, a random delay (offsetXAnimation.BeginTime = TimeSpan.FromSeconds(delay);) is used to make it look like the circles are created at different times, and remember to put using System.Windows.Media.Animation; at the top.
Also, this works perfectly well without the "DispatcherTimer" and "lastTick" variables. I fail to understand their use here. Perhaps I will learn later.