WPF Dartboard scoring application
| |
In this article I’ll review the steps to creating a WPF-based touch-screen scoreboard application that we use in our Clarity Cricket recreational dart league. In doing so, I'll show some examples of how to create custom animations in Visual Studio .NET and wire them up to StoryBoards in XAML |
|
Jerry Brunning
Difficulty: Advanced
Time Required: Greater than 10 hours (to build custom animations)
Cost: Free
Software: Microsoft Visual Studio 2005 .NET Framework, 3.0 November 2006 CTP of the Visual Studio 2005 extensions for .NET Framework 3.0, Microsoft Expression Blend 2 May Preview
|
In this article I’ll review the steps to creating a WPF-based touch-screen scoreboard application that we use in our Clarity Cricket recreational dart league. In doing so, I'll show some examples of how to create custom animations in Visual Studio .Net and wire them up to StoryBoards in XAML
Back in October one of my co-workers at Clarity, Kevin Marshall, wrote an article about how he modified the foosball table in our employee lounge to allow us to track game results and individual player statistics (see article here). Statistics are the basis for bragging rights, and at a competitive software development company, we’re big on bragging rights. Following in the tradition of the foosball, we next set out to make similar capabilities to track scoring and statistics for our recreational dart board. We ended up building a WPF-enabled windows form-based application to use as our virtual scoreboard, attached it to a touch screen monitor, and hooked it up to a database to track statistics. This article will show how we went about building a Cricket scoreboard application.
Since the application makes heavy use of animations, a video works better to convey some of the features. You can view a video of the dart board application in action here.
Windows Presentation Foundation
Windows Presentation Foundation (WPF) is Microsoft’s next generation framework for building rich user experiences for both forms-based and web-based applications. At the core of WPF is the Extensible Application Markup Language (XAML – pronounced “Zamel”) which allows developers to lay out a user interface using XML in a hierarchical tree structure. If you’re not familiar with WPF, you may want to stop reading this article and first read an overview of WPF on MSDN. This article assumes you have a basic understanding of the concepts of WPF. I built the application in this article using the November 2006 CTP of the Visual Studio 2005 extensions for .NET Framework 3.0. Make sure you install the .NET 3.0 runtime prior to installing the Visual Studio extensions.
Laying out the main form
In support of WPF, Microsoft has released a slew of tools that make writing WPF applications easier. Expression Studio is a suite of tools that Microsoft market’s towards “creative designers”, but these tools are equally important for developers, especially with the WPF framework’s designer glitches in the current release. In many cases you cannot use the designers built into Visual Studio. For example, if your forms use controls that you create, you will not be able to open your XAML file in the visual designer in Visual Studio. To get around this, you can use Expression Blend to open your Visual Studio project and view your form (Expression Blend can natively open Visual Studio project and solution files).
Figure 1: Microsoft Expression Blend XAML editor
Expression Blend is great for editing XAML and creating Storyboard (animations) but it is important to note that the tool is not a code editor. You cannot edit the .NET code behind files or even HTML or Javascript files from within Expression Blend. However, there is nice integration with Visual Studio .NET - opening a code file from within Expression Blend will automatically kick off an instance of Visual Studio, open your solution, and present you with the source file to edit.
Our application contains just a few screens - the main dartboard form, plus a couple forms used to manage the players in the game and view statistics. Our scoreboard application will be run in a kiosk and use a touch screen monitor as the main interface. The idea is that a player throws the dart, and then touches the appropriate section of the screen to indicate where the dart landed. The scoreboard then tracks the score and statistics based on the accumulated data.
To create the main form I started with a vector image of a dartboard and used Expression Design to export the image to XAML. The vector image consists of paths for each individual component so the resulting XAML will contain the details for each path. Below is a sample snippet from a one of the Path elements in the scoreboard’s XAML. Each individual Path element makes up a second of the image.
<Path x:Name="Path" Width="93.8075" Height="113.784" Canvas.Left="120.154" Canvas.Top="641.654" Stretch="Fill" Fill="#FFFF1A00" Data="F1 M... />
Once I have the XAML representation of the board, I can rename the individual Path elements and make them “clickable” by simply adding the appropriate attributes to each Path element, and then writing the event handler in the code behind file. In this case, I added the OnLeftMouseUp attribute to each Path element which would be raised for each section whenever the player clicks on the board.
<Path x:Name="Double16" MouseLeftButtonUp="Double16Click" Width="93.8075" Height="113.784" Canvas.Left="120.154" Canvas.Top="641.654" Stretch="Fill" Fill="#FFFF1A00" Data="F1 M... />
In the example Path above – the Double16 – the resulting code behind that would handle the event looks like this:
C#
void Double16Click(object sender, EventArgs e)
{
OnDartThrown(Darts.Dart16, 2);
}
VB
Private Sub Double16Click(ByVal sender As Object, ByVal e As MouseButtonEventArgs)
OnDartThrown(Data.Darts.Dart16, 2)
End Sub
The OnDartThrown method simply performs a swtich on the Dart enum and performs the appropriate animation followed by notifying the game class to record the score.
Scoring and Rules
Since we envision using this framework to support multiple dart games, we created a BaseGame class and derived a CricketGame class from BaseGame. Our scoreboard has a member variable of type BaseGame that is set to an instance of the current game type (note: we didn’t implement multiple game types yet –that could be done by adding an additional screen to choose the type of game to play – so for this version all games are of type Cricket). Our BaseGame has a virtual method called UpdateGame that is called after every dart is thrown. This gives the game class a change to update any game data, player scores, statitistics, or other state as required. Since our game is Cricket, CricketGame class manages the scoring rules specific to Cricket, such as maintaining open/close state of each target number:
C#
if (lastDartThrown != Darts.DartOutOfBounds)
{
score = UpdateScores(lastDartThrown, numberThrown);
UpdateClosedStatus(lastDartThrown);
}
List<int> dartMarks = _players[_currentPlayerIndex].MarkDart(lastDartThrown, numberThrown);
_dartHistory.Push(new DartHistoryItem(score, lastDartThrown, numberThrown,wasClosed, _currentPlayerIndex, _round, dartMarks));
VB
If lastDartThrown <> Darts.DartOutOfBounds Then
score = UpdateScores(lastDartThrown, numberThrown)
UpdateClosedStatus(lastDartThrown)
End If
Dim dartMarks As List(Of Integer) = _players(_currentPlayerIndex).MarkDart(lastDartThrown, numberThrown)
_dartHistory.Push(New DartHistoryItem(score, lastDartThrown, numberThrown, _
wasClosed, _currentPlayerIndex, _round, dartMarks))
Setting up the animations
One of the key new capabilities WPF gives to developers is a way to add animations to their applications. For the dartboard framework, we wanted to add animations after each scoring throw. To give us a good variety, we played around with several different types of animations as part of the storyboards in our application. For the dartboard scoreboard we built custom animations for various events that occur during the game – scoring throws, transitions between the scoreboard and the settings screens, and transitions between rounds during the game. We implemented our animations in .Net and referenced the .Net classes from within the XAML by adding a namespace declaration in the root node of our scoreboard XAML file. Referencing .Net code from within XAML (and also referencing XAML-defined elements from within .Net) is an important concept to understand when writing complex WPF applications.
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:DartScoreboard.GUI.Controls"
xmlns:CustomAnimations="clr-namespace:DartScoreboard.Animations" .../>
With the namespace for our custom animations declared, we can reference them via storyboards in the XAML. For example, the XAML below is a storyboard that refers to our BounceDoubleAnimation.
<Storyboard x:Key="roundChangedAnimation" SpeedRatio="2.0">
<CustomAnimations:BounceDoubleAnimation
From="-500" To="0" Duration="0:0:5" EdgeBehavior="EaseOut"
Storyboard.TargetName="roundChangedBounce"
Storyboard.TargetProperty="(TranslateTransform.Y)"/>
</StoryBoard>
This animation is used during the transition from one round to the next, and results in the text bouncing as it moves from the top of the screen to the bottom.
DependencyProperties
One of the steps in implementing the animation in a .Net code-behind class is to define DependencyProperties. DependencyProperties are special types of properties that can be used by WPF to perform things like data binding, animations, inheritance, and styling. It is DependencyProperties, for example, that allow the WPF runtime to render the styling of a child element based on where it is nested in the XAML tree. For our custom animations, any properties we want to add to our classes that affect or control the animation behavior must be defined as DependencyProperties. For example, our BounceDoubleAnimation has a property called EdgeBehavior that controls whether the animation starts on the screen and moves off the screen (EdgeOut) or starts off the screen and moves onto the screen (EdgeIn). We define this as a DependencyProperty below.
Creating a DependencyProperty in your code required two steps: First, you need to register the property with WPF. Second, you need to define property Get and Set methods that call the special GetValue and SetValue functions used to read and write DependencyProperties. The example below shows how to register the “EdgeBehavior” DependencyProperty used by the BounceDoubleAnimation:
C#
public static readonly DependencyProperty EdgeBehaviorProperty =
DependencyProperty.Register("EdgeBehavior",
typeof(EdgeBehaviorEnum),
typeof(BounceDoubleAnimation),
new PropertyMetadata(EdgeBehaviorEnum.EaseInOut));
VB
Public Shared EdgeBehaviorProperty As DependencyProperty = _
DependencyProperty.Register("EdgeBehavior", _
GetType(EdgeBehaviorEnum), _
GetType(BounceDoubleAnimation), _
New PropertyMetadata(EdgeBehaviorEnum.EaseInOut))
This snippet below shows how to create the property Get and Set methods for a DependencyProperty. Notice the calls to the GetValue and SetValue methods.
C#
/// <summary>
/// Specifies which side of the transition gets the "bounce" effect.
/// </summary>
public EdgeBehaviorEnum EdgeBehavior
{
get
{
return (EdgeBehaviorEnum)GetValue(EdgeBehaviorProperty);
}
set
{
SetValue(EdgeBehaviorProperty, value);
}
}
VB
Public Property EdgeBehavior() As EdgeBehaviorEnum
Get
Return CType(GetValue(EdgeBehaviorProperty), EdgeBehaviorEnum)
End Get
Set(ByVal Value As EdgeBehaviorEnum)
SetValue(EdgeBehaviorProperty, Value)
End Set
End Property
Invoking Animations from managed code
Like most things in WPF, animations can be defined declaratively in the XAML, or via managed code. Similarly, they can be triggered from within the XAML, or dynmaically from .NET. In this case, we've defined the animations in Storyboard elements in the XAML, but we'll call them from .NET (for example, as a result of a dart being thrown). It is easy to get a reference to a Storyboard and invoke methods against the managed Storyboard class:
C#
private void AnimateDoubleBull()
{
Storyboard doubleBullAnimation = (Storyboard)FindResource("doubleBullAnimation");
doubleBullAnimation.Begin(this);
}
VB
Private Sub AnimateDoubleBull()
Dim doubleBullAnimation As Storyboard = CType(FindResource("doubleBullAnimation"), Storyboard)
doubleBullAnimation.Begin(Me)
End Sub
Putting it all together
While all the WPF related features were fun to build, the real purpose of the framework is to track statistics. For our purposes we will track the typical win/loss record, plus player efficiency, which is the ratio of darts thrown to scoring throws. Since a “scoring throw” has a different meaning depending on what game is being played, we write the code to handle scoring in the derived BaseCricket class. For example, for Cricket we define a scoring throw as any throw that hits a 15 or higher, or a bullseye. We store this per player in a simple database and then compile statistics on the most efficient players. This isn’t perfect of course but it works well for what we wanted. With a little work (and some interruption to the game’s flow), you could compile statistics on a per target level to answer questions like who is the best at hitting the triple 20.
With the game framework coded and looking good we next looked to find a good place to position the touch screen monitor. We wanted a place that was convenient to the thrower, but yet not in the way. At first we mounted the monitor on the wall adjacent to the thrower. This worked well but we didn’t have a good place to put the PC. We eventually ended up building a kiosk to house the PC with an angled top containing the touch screen monitor.
This made the whole presentation seem a little nicer and with the addition of some color and a couple Clarity logos it could make a great conversation piece when it comes to recruiting.