FREE hit counter and Internet traffic statistics from freestats.com

Wednesday, May 26, 2004

Baseball and the Compact Framework

Two of my favorite pastimes came together recently when as an exercise (and a possible marketing opportunity for Quilogy) I created a Pocket PC application using the .NET Compact Framework. This application was to be used by fans attending a game in a luxury suite in order to show off the technology and give them some extra fun during the game.

To get started I built a VB .NET app using the Pocket PC platform within Visual Studio. I also created a web site on my local machine. The idea for the app was to allow the Pocket PCs in the suite to connect to a wireless hub connected to a notebook with a web server. The application would provide interactive capabilities through a web site built with ASP.NET that could be viewed through Pocket IE as well as the Compact Framework application using web services. To facilitate the speed of development the web methods in the web services used DataSets for passing data back and forth. That also allowed the app to use data binding (although not the most performant technique).

The two main functionalities for the application included a set of quizzes where the user could test their knowledge against other fans in the suite as well as a prediction game. Both would result in prizes for the winners. The main interface looked as follows:

The app includes a custom configuration system that reads a config.xml file since the .NET CF doesn't include the System.Configuration namespace. This is used to configure the IP address of the web server. At load time the app calls a web service on a background thread to retrieve the teams playing in the game as well as the list of possible players (input ahead of time by an administrator). The user then has an Options form to choose which player they are.

After selecting Quizzes they are presented with a form that shows the available quizzes as well as summary stats for each quiz. These are pulled on a background thread from the web service.

If they choose Take Quiz the quiz question metadata downloaded from the web service is used to build the form to display the question and answers. Although the quizzes are multiple choice the application can handle audio quizzes that associate .WAV files on the device with questions. It then can play the associated audio file and allow the user to then make their selection. I used this capability to provide an Announcer quiz where the user guessed which homerun call belonged to which announcer.

If the user chooses an incorrect answer they are notified and the app keeps track of how many correct answers they had. It also has a tab that shows a simple DataGrid with the scores for each player.

In the prediction game the user is asked to choose which team will win the game and several facts about the game. These are scored on the server, which notifies the client app when the game is over as to who the winner was.

In all, the development effort on this application was approximately 25 hours and included creating a database with about 10 tables in SQL Server, a set of web services in ASP.NET (9 web methods), and the client application in the Compact Framework (4 forms). Several aspects of the application required features that are not a part of the Compact Framework 1.0 including:

  • A LinkLabel control for the options on the main form shown above

  • Configuration Settings class to read the config.xml file and expose shared properties to the application

  • An Invoker class so that a background thread can update a form passing it a set of arguments

  • A Utils class to expose P/Invoke features such as playing .WAV files


  • In and of themselves these classes were pretty simple to implement. For example, the Invoker class looks as follows:
    
    
    Namespace Atomic.CF.Utils

    Public Delegate Sub UIUpdate(ByVal args() As Object)
    Public Class Invoker

    Private _control As Control
    Private _uiUpdate As UIUpdate
    Private _args() As Object

    Public Sub New(ByVal c As Control)
    ' store the control that is to run the method on its thread
    _control = c
    End Sub

    Public Sub Invoke(ByVal UIDelegate As UIUpdate, _
    ByVal ParamArray args() As Object)
    ' called by the client and passed the delgate that
    ' points to the method to run
    ' as well as the arguments
    _args = args
    _uiUpdate = UIDelegate
    _control.Invoke(New EventHandler(AddressOf _invoke))
    End Sub

    Private Sub _invoke(ByVal sender As Object, ByVal e As EventArgs)
    ' this is now running on the same thread as the control
    ' so freely call the delegate
    _uiUpdate.Invoke(_args)
    End Sub

    End Class
    End Namespace


    The client code can then use the class like so:

    
    
    ' Declarations
    Private inv As Invoker = New Invoker(Me)
    Private ui As UIUpdate = New UIUpdate(AddressOf BindPlayers)


    Private Sub DisplayPlayers(ByVal ar As IAsyncResult)
    dsPlayers = s.EndGetPlayerList(ar)
    inv.Invoke(ui, Nothing)
    End Sub

    Private Sub BindPlayers(ByVal args() As Object)
    cbPlayers.DataSource = dsPlayers.Tables(0)
    cbPlayers.DisplayMember = "Name"
    cbPlayers.ValueMember = "ID"
    btnSelect.Enabled = True
    End Sub


    Note that in this case we didn't pass any arguments to the BindPlayers method that runs on the main thread. The ClickLabel class was also interesting and simply inherits from Control. There is a version of this type of control included in the Smart Device Framework on www.opencf.org but I decided to use some existing code I found on the web (I don't remember where) and modify it. It ended up looking as follows:

    
    
    Namespace Atomic.CF.Utils

    Public Class ClickLabel
    Inherits Control

    Private bStyle As BorderStyle = BorderStyle.None
    Private txtAlignment As ContentAlignment = ContentAlignment.TopLeft
    Private fntName As String = "Tahoma"
    Private fntSize As Integer = 9
    Private fntStyle As FontStyle = FontStyle.Underline Or FontStyle.Bold

    Event labelClick(ByVal sender As Object, ByVal e As System.EventArgs)
    Event labelPaint(ByVal sender As Object, ByVal e As System.EventArgs)

    Public Property BorderStyle() As BorderStyle
    Get
    Return bStyle
    End Get
    Set(ByVal Value As BorderStyle)
    bStyle = Value
    End Set
    End Property

    Public Property FontName() As String
    Get
    Return fntName
    End Get
    Set(ByVal Value As String)
    fntName = Value
    End Set
    End Property

    Public Property FontSize() As Integer
    Get
    Return fntSize
    End Get
    Set(ByVal Value As Integer)
    fntSize = Value
    End Set
    End Property

    Public Property FontStyle() As FontStyle
    Get
    Return fntStyle
    End Get
    Set(ByVal Value As FontStyle)
    fntStyle = Value
    End Set
    End Property

    Public Property TextAlignment() As ContentAlignment
    Get
    Return txtAlignment
    End Get
    Set(ByVal Value As ContentAlignment)
    txtAlignment = Value
    End Set
    End Property

    Private Sub clsLabelPaint(ByVal sender As Object, _
    ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
    Dim grfx As Graphics = e.Graphics
    Dim p As New Pen(Color.Black)
    Dim b As New SolidBrush(ForeColor)
    Dim f As New Font(fntName, fntSize, fntStyle)
    Dim s As SizeF
    If Me.BorderStyle = BorderStyle.FixedSingle Then
    grfx.DrawRectangle(p, 0, 0, Me.Width - 1, Me.Height - 1)
    ElseIf Me.BorderStyle = BorderStyle.Fixed3D Then
    grfx.DrawRectangle(p, Me.ClientRectangle)
    End If
    s = grfx.MeasureString(Me.Text, f)
    Select Case txtAlignment
    Case ContentAlignment.TopCenter
    grfx.DrawString(Me.Text, f, b, (Me.Width - s.Width) / 2, _
    (Me.Height - s.Height) / 2)
    Case ContentAlignment.TopLeft
    grfx.DrawString(Me.Text, f, b, 2, (Me.Height - s.Height) / 2)
    Case ContentAlignment.TopRight
    grfx.DrawString(Me.Text, f, b, Me.Width - s.Width - 2, _
    (Me.Height - s.Height) / 2)
    End Select
    p.Dispose()
    b.Dispose()
    f.Dispose()
    RaiseEvent labelPaint(sender, e)
    End Sub

    Private Sub clsLabelClick(ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles MyBase.Click
    RaiseEvent labelClick(sender, e)
    End Sub
    End Class
    End Namespace


    In any case it was an interesting exercise and shows some of the power of the Compact Framework and its ability to do multi-threading and access web services.

    No comments: