Programming Life in the WPF – Part 5

Drawing the Grid

When I first started mucking around with the implementation of Conway’s Game of Life in WPF my thought was that I would create a drawing surface and use System.Drawing objects to paint in my grid. I went as far as looking into which WPF object would be right to do the drawing on, settling on Canvas, because it offered absolute x,y positioning. I started to examine the Canvas object more closely, and compare it to the drawing tools that you find in System.Drawing. It quickly became clear that I was on the wrong track. None of the tools in System.Drawing line up with the way things are defined in the WPF. Take the most basic facility of 2D drawing in Windows.Forms: the Graphics class. This class implements IDeviceContext, and is what you paint on. There are two ways to get one: in the arguments of a Paint event (WM_PAINT message); and by calling the CreateGraphics() method on a Form. Well, there isn’t a CreateGraphics() defined on a System.Windows.Controls.Canvas object. Nor is there one on a System.Windows.Window object. There also isn’t a Paint event on a WPF window or control (although you can embed a WPF control into a System.Windows.Forms.ElementHost, and that has a Paint event). Something wasn’t right, and that something was the fact that I wasn’t yet thinking in WPF. I was thinking in terms of the old way of creating 2D images in a window. I started Googling, and that’s when I ran into Scott Allen’s article on OdeToCode. When I saw what Scott did to paint the grid in his version of the Game of Life, I was a little startled, and more than a little intrigued. As you’ll see in a minute it is just about the last way you would think of painting an interface if you were writing a game, and I’m sure it’s the last way Scott would think of doing it if he were writing one.

What Scott did was to take advantage of what WPF was built to do, graphics-wise, and that is render vector shapes. The System.Drawing graphics architecture is raster-based. That means it knows about rows of pixels in a region. You change the values of pixels, singly or in bulk, to create a certain shape or image on the window’s device context. If you did any graphics programming in Win32 then you recognize the fairly thin layer that Windows Forms provides over the raster-based graphics API of GDI+. The WPF graphics architecture is vector-based. That is, it defines shapes in terms of the lines that bound them, and the colors that fill them. It’s more like PowerPoint than Photoshop. The System.Drawing graphics tools just wouldn’t have worked in my version of the application, because in general you can’t share “airspace” between the GDI+ and WPF rendering engines. They operate in completely different ways, and Microsoft only guarantees interoperability in certain specific scenarios. GDI+ renders, as I mentioned before, using the Device Context architecture. WPF renders on Direct3D, and this is why, despite the Aero interface and its demands, Vista’s GUI feels quite smooth and responsive. It is also one reason why Scott could get away with painting the grid by taking advantage of WPF’s core facilities for rendering and manipulating shapes, rather than dealing with pixels and regions. A less efficient rendering architecture would add a lot of overhead.

So how, and where, do we paint the grid? You may recall that I defined a Grid called LifeGrid in the XAML skeleton code example from section 4. It is the last child of the outer DockPanel that fills the remaining space in the middle. In the XAML source this grid is empty, having only the default single row and column, and containing no child controls. It does define two styles, and we’ll come back in a bit to see how they are used, but otherwise it’s a vast echoing plane of nothing. Here’s the declaration of the Grid:

<Grid Name="LifeGrid" AllowDrop="True" >
	<Grid.Resources>
		<Style TargetType="{x:Type Rectangle}">
			<Setter Property="Opacity" Value="{Binding Path=IsAlive}" />
		</Style>
		<Style BasedOn="{StaticResource {x:Type Rectangle}}"
			TargetType="{x:Type Rectangle}" x:Key="RectStyle" >
			<Setter Property="Fill" Value="Red" />
		</Style>
	</Grid.Resources>
</Grid>

So what’s going on here? Not much as far as the declaration of the Grid is concerned. The only attribute I’m setting on LifeGrid, after the name, is AllowDrop, which is set to true. This tells the framework that the LifeGrid instance will accept objects dropped onto its window realestate with the mouse. This allows me to define handlers that will load the models you can drag over from the Life Lexicon website and drop on AvalonLife’s client area. These handlers either use HTTPWebRequest to grab the data from the web, or chain off to the normal file handling methods to read from disk. I’m not going to talk about them any further here. They were fun to write, and the code is pretty clear on how they work.

More interesting are the two styles LifeGrid has in its Resource collection. The first has a TargetType of {x:Type Rectangle} and no key, which means that it will apply to any Rectangle that is a child of the LifeGrid. The single Setter binds the value of a Rectangle’s Opacity property to a data source property called IsAlive. If you go back to the model discussion from Section 3, you’ll see that this is a property of LifeCell that fires NotifyPropertyChanged events when its value is updated. The Opacity property takes a value from 0 to 1.0 that specifies how opaque the object is. A value of 1 indicates fully opaque, and corresponds to a value of true for the IsAlive property of a LifeCell. In this way whenever a LifeCell dies the Rectangle it is bound to has its opacity set to 0, and whenever a LifeCell is born the opacity of the Rectangle is set to 1. Think about this one for a minute. On the one hand, it’s a pretty cool design: the LifeCell changes state, and the Rectangle follows. On the other hand, doesn’t that mean a round-trip event dispatch for every cell that changes state? Yes, it does. Couldn’t we just paint the damn things all in one go? Yes, we could. But that wouldn’t be the WPF way.

The second of the two Styles is a little curious. The BasedOn property of a Style is an inheritance mechanism. Essentially what this means is that when a Style is applied to a control, and has a BasedOn Style, it will fire off the Setters of its parent Style, and then fire its own. In this case I have created a second Style to apply to Rectangles, and based it on the first. The reason why I did this will become clearer when we look at the C# code below. Basically I want to be able to programmatically change part of the Rectangle’s style without messing up the other part. The last point to make about these two Style declarations is where they are declared. Why put them in the LifeGrid’s Resource collection rather than in Window.Resources with the rest of the Styles? Because of the scoping mechanism on Resource lookups, my generic Rectangle style will only apply to Rectangles that occur in the Grid. If I placed it in the Window’s resources it would apply to any other Rectangle that happened to occur anywhere in the UI, and didn’t explicitly set a style.

It should be pretty obvious at this point that I’m going to populate the LifeGrid with Rectangles, so why are there no Rectangles defined in the XAML, and no cells defined for the LifeGrid? The reason for this is that AvalonLife doesn’t know until runtime how large its grid is going to be. Once it is running you can also make the grid larger or smaller. So what I do is build the grid up in code, which you can find in ALMainWin.xaml.cs (available as listing 4). Here are the functions that construct the “playing surface”:

private void InitGrid()
{
	LifeGrid.Children.Clear();
	LifeGrid.RowDefinitions.Clear();
	LifeGrid.ColumnDefinitions.Clear();
	for (int i = 0; i < _ls.Model.Rows; i++)
	{
		LifeGrid.RowDefinitions.Add(new RowDefinition());
	}
	for (int i = 0; i < _ls.Model.Columns; i++)
	{
		LifeGrid.ColumnDefinitions.Add(new ColumnDefinition());
	}
}

private void PopulateGrid()
{
	for (int row = 0; row < _ls.Model.Rows; row++)
	{
		for (int col = 0; col < _ls.Model.Columns; col++)
		{
			Rectangle rect = new Rectangle();
			Grid.SetRow(rect, row);
			Grid.SetColumn(rect, col);
			LifeGrid.Children.Add(rect);
			rect.DataContext = _ls.Model.CellGrid[row, col];
			rect.MouseDown += new MouseButtonEventHandler(Rect_OnMouseDown);
			rect.MouseMove += new MouseEventHandler(Rect_OnMouseEnter);
		}
	}
}

private void ApplyRectStyle()
{
	Brush cellBrush = GetCellBrush();
	Style style = new Style(typeof(Rectangle), (Style)LifeGrid.FindResource(typeof(Rectangle)));
	Setter setter = new Setter();
	setter.Property = Rectangle.FillProperty;
	setter.Value = cellBrush;
	style.Setters.Add(setter);
	LifeGrid.Resources.Remove("RectStyle");
	LifeGrid.Resources.Add("RectStyle", style);
	UIElementCollection rects = LifeGrid.Children;
	foreach (UIElement uie in rects)
		((Rectangle)uie).Style = (Style)(LifeGrid.Resources["RectStyle"]);
}

These three functions are called in sequence in the order shown above, and serve to initialize the grid and populate it with Rectangles that have the proper brush in their Fill properties, and are bound to the proper LifeCell objects. I’ll briefly describe what each does. The InitGrid() function is the first step, and the first thing it does is clear out any existing setup in the LifeGrid by clearing its Children, RowDefinitions, and ColumnDefinitions collections. If the Grid was previously populated this essentially puts it back into the empty state it was in as declared in the XAML. If the game is just starting then the only effect of these calls is to strip the default single row and column. The second thing the function does is to loop through adding RowDefinitions, and ColumnDefinitions to the Grid. The number to be added comes from the LifeModel.Rows and LifeModel.Columns property, and there are two loops because the grid may not be square.

The second function, PopulateGrid(), has the job of filling the LifeGrid’s cells with something useful. A Grid cell is defined by the intersection of a RowDefinition and a ColumnDefinition. It’s a concept, and as far as I know doesn’t have an implementation. You define where something lives in a Grid by setting two attached properties on the object that you’re putting into it. Those properties are Row and Column, and after first creating a new Rectangle I set them inside a nested loop that iterates through all the RowDefinitions and ColumnDefinitions on the LifeGrid. If you recall I mentioned attached properties back when we looked at DockPanel.Dock. Each child control placed into the DockPanel “inherits” this property, and the setting of the property takes the form of an attribute assignment on the declaration of that child control. Why, then, are we setting the Row and Column by making calls to methods of the Grid class, i.e. Grid.SetRow() and Grid.SetColumn()? This is the form that attached properties take when you access them from code. Instead of setting an attribute on the child control you call the Set<Propertyname>() method on the object that implements the attached property, passing the value for it, and the child control it is being set for.

Once the row and column are assigned to the Rectangle I do a couple of additional things to it. The Rectangle.DataContext property is set to an instance of the LifeCell class. This instance is obtained by using the LifeModel.CellGrid property, which returns the model’s internal LifeCell array, and then accessing it using the loop counters. Once I set the DataContext to an instance of LifeCell that Rectangle’s Opacity property is bound to the IsAlive property of the LifeCell, by virtue of the Style that we looked at above. The last step is to add two event handlers for the Rectangle, one for MouseDown, and one for MouseEnter. These are used for editing the cell grid, and handle clicking on a Rectangle and dragging across several Rectangles. I’m not going to go into these handlers in detail, because all they really do is flip the state of a Rectangle’s underlying LifeCell. Once PopulateGrid() completes its work, every cell of the LifeGrid contains a Rectangle shape, with a LifeCell as its data source, and event handlers for mouse input. If you stopped here and allowed the Window’s client to be displayed, what you would see are solid red rectangles wherever there is a live cell in the model. I thought solid color cells were ugly, and since Scott had used a gradient brush in his version, I decided to do the same in mine, but also allow the brush to be defined and saved by the user. The next thing I had to figure out was how to apply the brush at runtime.

I won’t go into the dialog I built for defining the brush, or how it is stored across sessions. You can get the gist of that from the code. The thing that turned out to be interesting was the application of the selected brush to the Rectangles at runtime. That shouldn’t have been much of a puzzle. After all, every Rectangle has a Fill property, and all I should have needed to do is set that property to my Brush and be done with it. In fact that works fine, except for one little issue specific to the context of this app. Recall that the opacity of a Rectangle in the Grid is bound to a LifeCell’s IsAlive property by specifying a binding in a Style that is applied to all the Rectangles in the Grid. I alluded once before to differences in the timing of when Styles are applied, and when properties that are set directly get their value. This is an instance where it caused an irritating graphics issue. The gist of it is this: if I set the Fill property of a Rectangle directly, in the loop in PopulateGrid, the fill would be applied prior to the Opacity property binding being set up and updated for the first time. This caused all the cells in the Grid to flash their “alive” color briefly, before the binding kicked in and they went back to their appropriate state. This can’t be solved by setting Opacity to 0 when the Rectangle is created, because setting Opacity blows away the data binding.

I messed around with a number of different orderings of operations, but the best I can tell is that the default Rectangle style doesn’t get applied to the new Rectangles until some time after they are rendered for the first time. The only way I was able to get around this was to avoid setting the Fill property directly, and instead replace the Style that affected the Fill property. That way both the new Fill and the Opacity binding would be applied from Styles, and presumably take effect at the same time. The ApplyRectStyle() function accomplishes this by first building a new Style to apply the selected Brush to the Rectangle.Fill property, using it to replace the existing Style in the Grid.Resources collection, and then iterating through the children of the LifeGrid applying the style. Note that once a Style has been applied to an element you can only change its effects by creating a new one and reapplying it. This is consistent with the idea that the Style just fires off Setters that assign values to properties. The technique worked like a charm, and the irritating flash was gone.

There is one other WPF feature that I am taking advantage of in the graphical interface of AvalonLife, and I want to give it a quick mention here before I move on. That feature is Adorners, and while they are too complicated to cover in detail, they are too cool to skip over. When you run AvalonLife you’ll see that by default it has a reticle, or crosshairs, drawn over the playing grid. You can turn it on or off in the Settings menu. The point of it is to give the user some reference when placing cells to build a model. When I started looking into how to do this I was trying to figure out how to create a semi-transparent control and layer it over my Grid. That’s when I stumbled on the Adorner class. An Adorner is an object that does exactly what I was looking for: you attache it to a FrameworkElement to create a layer over it that you can draw on. The Adorner class is abstract, and must be derived from, so that you can override the OnRender() method and draw something. In my Adorner, which is called ReticleAdorner and is defined in ALMainWin.xaml.cs, I simply draw two lines with the current reticle pen color, but you can draw anything in an Adorner. Each object can have multiple Adorners attached to it, and you can control their visibility and opacity just like any other object, as well as collect user mouse input from them. This feature is intended to enable things like the frame that appears around a graphical object in most drawing programs when it is selected. Adorners solve a whole range of interface design needs, and I hope to spend some more time playing with them soon.

And with that all the pieces of the graphics interface for AvalonLife are in place. While the methods and techniques are new and present a learning curve, it’s surely a lot less steep than that of GDI+. Most of the work is being done under the hood by WPF. All the program has to do now is loop through the model applying the rules and setting LifeCells to the appropriate state, and the graphical part will just follow along. Slick, yes, but it must perform just abysmally, right? That’s what I was thinking as I finally reached the point in the development cycle where I could punch a button and see some stuff on the screen. Every cell that changes state requires a round-trip property notification event. Every time you resize the Window it requires thousands of Rectangles to be pumped through the layout and rendering engine. It has to be slow as an old dog on a hot day. As it turned out I was surprised, and I think you will be too, at how smoothly it works. Even running a monster model like SpaceFiller on a 70 x 70 grid, as shown in the screenshot below, the rendering engine can come close to producing the ten frames per second that the fastest simulation speed calls for.

avalon_life_spacefiller.png
AvalonLife running monster model SpaceFiller on a 70 x 70 grid

If you look at the bottom of the image you’ll see that so far this run has produced a peak of 1001 live cells, and currently has 1001 live cells. That’s because it is still in its initial growth phase. It will eventually peak at more than 1300 live cells before collapsing into chaos at time = 150 or so. Do a little math on what this involves. There are 4900 cells in the grid, meaning 4900 Rectangle instances, 4900 LifeCell instances, 4900 Binding objects to bind the Rectangle’s Opacity property to the LifeCells. Nearly 15,000 individual WPF objects wired up to present the state of the model. Despite this the animation runs smoothly, and the window resizes smoothly. I have tested models up to 200 x 200, or 40,000 cells. They run, although a heck of a lot less smoothly. I once asked the game to create a 1000 x 1000 grid, just for laughs. By the time I ended up killing the AvalonLife process it had a working set of nearly 1.5 gigabytes, but nothing had choked (nor was there any sign it would be done building the model soon.) Just FYI, I did all the development and testing of AvalonLife on a machine with an AMD X2 4400+ dual core CPU, 2 gigabytes of ram, and a GeForce 7600GT graphics card. No slouch of a box, but not top-end anymore either.

I was pretty impressed by the performance of the framework, and if you download and run the application I think you will be too. If you do that, send along an email and let me know how it ran for you. More importantly, I think this level of performance bodes very well for Microsoft’s chances of getting developers to adopt the 3.0 version of .NET, in order to take advantage of these new features. In the next and last section of the article, I’ll offer a few quick thoughts on why and then wrap this thing up.

Introduction
Part 1 – The Game of Life
Part 2 – AvalonLife’s Architecture
Part 3 – XAML and the User Interface
Part 4 – Resources and Styles
Part 5 – Drawing the Grid
Part 6 – Conclusion

AvalonLife Program
AvalonLife Program with Source

Leave a Reply

Your email address will not be published. Required fields are marked *