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:
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:
Post a Comment