Life in WPF
Written by Mike James   
Monday, 28 June 2010
Article Index
Life in WPF
Data binding
User interaction
Computing Life

Banner

At this point you might be thinking that we have to spend a lot of time working out from the mouse position which Ellipse had been clicked on - well you don't have to because of the way WPF routed events work. The MouseDown event is actually raised on the Ellipse that the user clicked on and, because the Ellipse doesn't handle it, it bubbles up to the Grid which does handle it. The point is that the OriginalSource property of the event Args  contains a reference to the object that was clicked on, i.e the Ellipse in question, so we don't have to work out which Ellipse is involved as we are told this as part of the event.

We have to check that the source of the event is indeed an Ellipse but apart from this we can simply cast to Ellipse and update. The only problem is that we don't want to update the Ellipse - we want to update the cell in the array that corresponds to the Ellipse and let databinding update the Ellipse automatically. All we have to do to find out which cell in the array is bound to the Ellipse is examine its DataContext and cast to a cell:

private void grid1_MouseDown(
object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource is Ellipse)
{
Ellipse temp=((Ellipse)e.OriginalSource);
Cell C = (Cell)temp.DataContext;
C.state = !C.state;
}
}

An alternative would have been to implement a two-way data binding and simply update the Ellipses Opacity property but sometimes a direct approach is easier.

At this point you can now run the program and attempt to draw on the Grid - but nothing happens. The problem is that while the initial setting of the cell's state was transferred to the Ellipses when everything was being set up, there is nothing to trigger the Ellipse's property update when the cell's state changes. So although you are changing the cell's state when you click on an Ellipse, this isn't being transferred to the Ellipse's bound property.

The solution is very simple - you have to implement the INotifyPropertyChanged interface.  You first need to add:

using System.ComponentModel;

to the start of the program and to the Cell class:

class Cell: INotifyPropertyChanged

To implement the interface you have to define an event:

public event PropertyChangedEventHandler 
PropertyChanged;

which has to be raised when the property is changed. To do this we need to modify the set modifier functions:

set
{
_state = value;
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs("state"));
};
}

The if statement checks to see whether another object has subscribed to the event. If it has it raises the event with parameters set to the object that the property belongs to and the name of the property that has changed. Notice that once added to a class the same event can be used to signal a change on any property.

Now if you run the program you can click on an Ellipse (or where an Ellipse is hidden) and it will change its state.

 

Life1

  A starting pattern

Generations

This is all we need for a design mode so time to move on to run the display.

The idea is that when the user clicks the button a timer is activated to call routines which calculate and update the array - which in turn automatically updates the Grid.

In this case the timer can be a System Timer which uses a thread from the thread pool to run the code. In most cases you can't use such a timer to update the UI because it is not run using the UI thread and the result is an exception. In this case the thread doesn't access the UI at all.

First we need a timer:

private System.Timers.Timer timer = 
new System.Timers.Timer(200);

and a Timer setup routine:

private void setupTimer()
{
timer.Elapsed += (sender2, e2) =>
{
counter();
nextgen();
};
}

The two routines counter and nextgen have yet to be written and they apply the rules of Life to the array. Calling both computes the next generation and displays it - more of these later. Also notice that the Elapsed delegate, called each time the Timer period is up, is set using a Lambda expression.

<ASIN:1430219106>

<ASIN:1430226501>

<ASIN:0735627045>

<ASIN:0470499834>

<ASIN:0672330318>

Banner



Last Updated ( Monday, 28 June 2010 )