Visual Basic & Smart Devices (Matt Gertz)
Last week I talked a bit about operator overloading, and used the concept to support a library of matrix functions. This week, we’ll use those functions in a Smart Device application to create a matrix calculator.
Smart Device applications
It’s extremely easy to create an application for your Smart Device. Let’s start from the solution we created last week. Assuming that solution is loaded, choose “File,” “Add,” “New Project…” and then in the resulting dialog, expand the “Smart Device” node under the Visual Basic node. You’ll find 3 child nodes – one for Pocket PC 2003, one for Smartphone 2003, and one for Windows CE 5.0. For the work I’ll be doing today, the choice actually matters little, and mostly just affects the default size of the window and the colors in the form – I’ll be sticking to basic controls and so everything I’ll be doing will be common to all of the SD platforms. (Note that this is not always the case – there are device-specific differences.) I arbitrarily chose “Windows CE 5.0” and then selected “Device Application,” giving the application the name “MatrixMath.”
After you press “OK,” you’ll get a form designer, just like with a normal Windows application. By default, the form is sized for a WebPad, so the first thing I do is change the size to 240 x 320, which will work nicely for most devices on the market (except, alas, for my new Treo, which has a square 240 x 240 screen – but it should at least scroll automatically on that). The only reason I’m changing the size is to give me an idea as to how the layout will look on that size screen, which is the most common size — as Daniel Moth reminded me, generally speaking “full screen” will always be an appropriate setting. I change the title bar text to be “MatrixMath” and set Menu to “none” – we won’t be doing menus today, but if you decide to design menus, it’s essentially the same as designing menus in Windows applications, which I touched on briefly in my earlier series on Euchre.
Now, I’ll create a layout for my form. I’ve decided to go with a 4×4 matrix, since that’s the most useful one when doing robotics (and for smaller square matrices, you can still make the various functions work as they normally would – just set the elements in the extra columns and rows to 0 except for the diagonals, which should be set to 1). I’ll add the appropriate number of text boxes to the form to account for the elements, and give them the names “M00,” “M01,” etc. I’ll also add a label & text box (set to read-only in the properties) to display the determinant – in my calculator, this value will be updated automatically when the matrix changes. Finally, I’ll add buttons for the following: Inverse, Transpose, Scale, +, -, *, =, CE (clear entry), and AC accumulator clear). The resulting form is shown in “MatrixMath-form.jpg” attached in the ZIP file.
Now, I have to start hooking up the various events. First, though, I’ll need to reference the class library we created last week. However, SD applications can’t use “normal” class libraries (which target the .NET 2.0 framework), so once again I choose “Add New Project,” navigate to the Windows CE 5.0 node, and select “Class Library,” giving it an appropriate name. I copy and paste my class into that new class library (replacing the default class that got auto-generated). Note that I could have avoided this problem if I had created the class library as a smart device class library last week — .NET projects can reference SD projects, but not vice-versa! (Thanks again to Daniel for reminding me that I hadn’t made this clear when I’d first posted this.) I do need to build the class library (right-click the class library project in the Solution Explorer and choose “Build”), and then add the reference to MatrixMath in the usual way (right-click MatrixMath in the Solution Explorer, choose “Add Reference…”, navigate to the Projects tab and select the new class library).
I also choose the “Set as Startup Project” command on Matrix Math in order to make sure we can debug it correctly later on.
Now, we start leveraging the class library code. Right-click the form in the Solution Explorer and choose “View Code.” In the resulting editor, I’ll add “Imports Matrices” at the top, so that I don’t have to fully qualify my usage of the Matrix class. I’ll also add the following class members:
opNull = 0
opAdd = 1
opSub = 2
opMul = 3
Dim lastOp As operations = operations.opNull
Dim Accumulator As New Matrix(4, 4)
Dim ScratchPad As New Matrix(4, 4)
Dim map(3, 3) As TextBox
The TextBox array held in “map” will just make it easier for me to map textboxes to coordinates, and I will initialize it in the Load event handler.
Private Sub MatrixForm_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
map(0, 0) = M00
map(0, 1) = M01
The Accumulator will hold the current value of the matrix, the ScratchPad will hold the matrix that the user is currently defining, and the “operations” enum will help me define & remember what the previous operation was, so that when “=” is pressed, I can apply it. (This calculator is a simple accumulator calculator, with no implemented stack to nest operations – i.e., there is no operator precedence. I’ve left that as an exercise for the reader… J)
Now, we’ll need methods to clear a matrix and also to push a matrix into the edit boxes. These are just business as usual:
Private Sub PushMatrix(ByVal m As Matrix)
For i As Integer = 0 To 3
For j As Integer = 0 To 3
map(i, j).Text = m.data(i, j)
Private Sub ClearMatrix(ByVal m As Matrix)
For i As Integer = 0 To 3
For j As Integer = 0 To 3
m.data(i, j) = 0.0
Everything else is just event handling. First, we need to handle the event where the data in the scratchpad is changed by the user, so that (a) we can update our internal representation of that matrix and (b) update the determinant. When I first coded this, I decided that I would handle the TextChanged event, but that was a poor decision because it would fire on every keystroke – very non-performant, and also ambiguous if you’d just entered a “-“ or “.” or other partial number. So, instead I went with “LostFocus,” which would fire when the user clicked away from the cell (and before any subsequent event would fire. The code is similar for all of the edit boxes, and I’ve consolidated them into one handler to save space, although again we have the problem with mapping a control to an array (this time in the opposite direction):
Private Sub Matrix_LostFocus(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles M00.LostFocus, M01.LostFocus, M02.LostFocus, M02.LostFocus, _
M10.LostFocus, M11.LostFocus, M12.LostFocus, M13.LostFocus, _
M20.LostFocus, M21.LostFocus, M22.LostFocus, M23.LostFocus, _
M30.LostFocus, M31.LostFocus, M32.LostFocus, M33.LostFocus
Dim value As Double = 0
value = CDbl(CType(sender, TextBox).Text)
Catch ex As Exception
‘ If junk got put in the text box, compensate
value = 0
CType(sender, TextBox).Text = value
If sender Is M00 Then
ScratchPad.data(0, 0) = value
ElseIf sender Is M01 Then
ScratchPad.data(0, 1) = value
Dim det As Double = ScratchPad.Determinant
Me.DeterminantBox.Text = CStr(det)
Now, I’ve decided that there are three special functions that operate on the scratchpad matrix in place, but don’t modify the accumulator – Inverse, Transpose, and Scale. (These are similar in nature to the Sine, Inverse, etc. functions that you’d find on any calculator, which just operate on the most recent thing you typed in.) With our previously defined functions, the first two are very simple. For example, here’s the Inverse handler:
Private Sub InvertBtn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles InvertBtn.Click
ScratchPad = ScratchPad.Inverse()
The Scale function is slightly more complicated because we need a value to scale the scratchpad, and the screen is too cramped to add much more functionality. Instead, I’m going to use a modal dialog. These work identically to their Windows application counterparts. To create on, right-click on the MatrixMath project in the Solution Explorer and choose “Add” then “Windows Form,” giving it an appropriate name like “Scalar” before pressing OK. In the resulting window, I’ll resize the form to be 200 x 100, and add a label, text box, and “OK” button as shown in Scalar-form.jpg in the attached ZIP file. (Don’t forget to set the return Dialog Result property of the OK button to be “OK,” as shown in that same JPG.) I will also get rid of the default menu, as with the other form. There’s only one event handler in the dialog, which is for the OK button click – it just calls the following:
Public Class ScalarDlg
Private Sub OKBtn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles OKBtn.Click
And using the dialog is just as simple:
Private Sub ScaleBtn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ScaleBtn.Click
Static scalar As Double = 1.0
Dim dlg As New ScalarDlg
dlg.scalarEdt.Text = scalar.ToString
If dlg.ShowDialog() = Windows.Forms.DialogResult.OK Then
scalar = CDbl(dlg.scalarEdt.Text)
Catch ex As Exception
scalar = 1.0 ‘ User put in junk; ignore and scale by 1
If scalar <> 1.0 Then ‘ No point if wasting time if nothing will change
ScratchPad = scalar * ScratchPad
Note that I’ve chosen to make “scalar” a static, so that the last choice will be the default the next time the matrix is scaled.
Next are the operations that do affect the accumulator. The general “infix” plan for those is:
1) The user types in a scratchpad matrix and then presses +, -, or *.
2) The scratchpad gets pushed to the accumulator and the last operation type is cached.
a. Note that, like a normal calculator, the scratchpad doesn’t get cleared after being pushed, so that it is essentially the default value for the next operation.
3) The user modifies the scratchpad as normal and then presses +, -, *, or =. The accumulator and the scratch pad are acted upon by the previously cached operation request, the result is placed in the accumulator and presented on the scratchpad, and the new operation request is cached. (In the case of “=”, the last operation is Null, the operation of which will be essentially a “no-op.”
The code then is just:
Private Sub OpBtn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles AddBtn.Click, SubtractBtn.Click, MultBtn.Click, EqualBtn.Click
Select Case lastOp
Accumulator = Accumulator + ScratchPad
Accumulator = Accumulator * ScratchPad
Accumulator = Accumulator – ScratchPad
‘ We haven’t done any ops yet, so just push it into the accumulator.
If sender Is AddBtn Then
lastOp = operations.opAdd
ElseIf sender Is SubtractBtn Then
lastOp = operations.opSub
ElseIf sender Is MultBtn Then
lastOp = operations.opMul
ElseIf sender Is EqualBtn Then
lastOp = operations.opNull
In the class library for the matrices, you’ll note that I added a “Copy” method which simply copies the contents of one matrix to another if they are the same size, in order to make the above operations easier. This wasn’t strictly necessary – I could have simply said ScratchPad = 1.0 * Accumulator, but that’s lame and poor use of memory (since a new matrix would be created). What I couldn’t do would be to say “ScratchPad = Accumulator,” since that would not copy the data but instead would copy the reference to that data, so that both Accumulator and ScratchPad would be pointing to the same object instance – that would be very bad unless I subsequently assigned Accumulator to point to a different matrix. Nor is there any way to do operating overloading on “=” as an assignment, because that would block the very referencing assignment I just mentioned. Thus, a “Copy” routine is required.
Finally, I need to support the CE and AC buttons, but these just leverage the “ClearMatrix” and “PushMatrix” methods. AC resets everything:
Private Sub ACBtn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ACBtn.Click
Me.DeterminantBox.Text = CStr(0)
lastOp = operations.opNull
While CE just clears the scratch pad matrix, but not the accumulator or last operation:
Private Sub CEBtn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CEBtn.Click
Me.DeterminantBox.Text = CStr(0)
Debugging and execution
At some point you’ll want to start debugging your code, and copying it over to a Windows app to do this would be wasteful. Fortunately, you don’t have to do that; Visual Studio comes with useful smart device emulators. Assuming you’ve set the MatrixMath project to be the startup project as described above, then simply hit F5. You’ll be presented with a list of choices for debugging. You can deploy the app to a connected device and run it “for real” on there, debugging as usual, or you can choose one of the emulators presented. I have an attached device, but I prefer not to deploy any application to it until I know that it’s bug free, so I always choose “Pocket PC 2003 SE Emulator.” The emulator has identical functionality to an actual device of that type. When you deploy to it, it will take a minute to get it onto the emulated device because it really is “installing” it there. You can then set breakpoints in your code and debug as usual, even checking values. (Note that I created the project as a Windows CE 5.0 project, but am debugging it using the Pocket PC emulator. Both can use .NET CF 2.0; I’ve stuck to basic functionality, and so there’s no problem here.)
There’s something else cool that you can do, though. Note that .NET CF is an almost perfect subset of .NET, and so you can actually run your application right on your desktop machine! Just dig down to the EXE that got built below your project directory, and double-click it – the application should run like it would on the handheld, provided that you didn’t write any code specific to the various buttons on the handheld.
That about wraps it up for this week. The final code is attached in the ZIP file, as usual. Next week I’m hoping to write a mercifully shorter post which discusses snippets and error correction. ‘Til then…