Bicycle Computer #8 – Changing Display Size – the UI Thread, EWR, and Touch Calibration

This is the 8th in a series of articles demonstrating how someone with modest .NET programming capabilities can now write applications for embedded devices using the .NET Micro Framework and Visual Studio.  To jump to the initial article, click here. The full project source code can be found on netmfbikecomputer.codeplex.com.  Remember, you can help determine what we cover in the series with your questions and suggestions.

First, the confession.  This blog is a week later than I had intended because I ran into a bug related to the calibration of the screen.  In the touch calibration of the screen, you are creating a mapping of the touch coordinates reported by the screen to the logical locations on the UI.  During this process, you need to get raw coordinates which may be outside the logical display space.  You use StartCalibration() to get those raw coordinates. However, there were some objects that were still being clipped to the SystemMetrics.  This made the calibration impossible in those objects.  The fix is included in the Version 4.1 Release Candidate that we are working on now.  I won’t post this code to Codeplex until that version is available.

Second, I want to thank Josh Young of GHI Electronics who contributed a great new look for the project.  I told you at the start that I was no UI designer.  Well Josh clearly is.  Here is the splash screen and you can see the Ride View when we get to the end of the article and get everything working on the new display.

SplashScreen_480X272

Back when I wrote the UI code for the RideView, I mentioned that using WPF would save us time if we wanted to change the display that we were eventually using.  Well, in this article, we get to see if I was correct.  The RideView seems a little tight with all the information that I am trying to display so I am going to shift to a 4.3 inch screen.  This will allow us to see how much UI code has to change for the display to work.  This will also give us a chance to explore touch calibration (since this is not the default screen for this platform) and Extended Weak References (EWR) for storing that calibration information in Flash between sessions. 

As you may recall, when we set up our main window, we gave it the size (in pixels) of the display that we are using:

mainWindow.Height = SystemMetrics.ScreenHeight;
mainWindow.Width = SystemMetrics.ScreenWidth;

Where did we get these values to assign into the mainWindow?  They were provided by the driver that came with the hardware that we used.  So, if you want to change displays, you may need to change drivers which requires different firmware.  If you are using the Porting Kit to build your own firmware, this is not a problem but that is outside the scope of this series of articles.  Fortunately for me, GHI has taken a different approach and made a driver that is configurable to different displays.  The GHIElectronics.NETMF.Hardware.Configuration.LCD class contains the objects that expose the display configuration and allow you to set it (see here for details).

So, the first thing is to do when we shift to the larger display is to add that configuration setup.  The metrics of the new display are 480 wide by 272 high.  So, right at the top of Main(), I put the following:

clip_image002

If the SystemMetrics are not correct, I call the setup method and reboot the device with the new metrics.  The metrics are read in by the runtime at startup so changing them in the middle does not take effect until you reboot.  The setup method looks like this with this code lifted directly from the GHI web site:

image

How to work with a Single UI thread

Before we get to the Calibration logic. there is one more piece of background information required.  It was a little tricky to find where to do the calibration since it requires that the application be running so that I can bring up the calibration UI but the UI needs to be calibrated before touch events will make sense.  I decided to put it in the Gesture handler so that the first time I get a gesture, if there is no calibration data, I invoke the calibration routine.  There is something important to understand about and this is that I will be executing a procedure inside the context of an event handler (on the UI thread).  This is a very common thing to want to do but one that trips up NETMF developers frequently.  NETMF only has one UI thread.  This means that when you are in your procedure call, you are blocking the UI thread from functioning.  So, it would be very natural to do the following: change the display to the calibration display, do the calibration processing, and return.

image

If you run this what will happen is that your calibration display never shows up.  Before it comes up, the thread is already executing the CalibrateDisplay() method which relies on the display being up so it is waiting and blocking the display from coming up.  Here is what you need to do instead: change the display and then invoke the CalibrationDisplay()method on the UI thread by using the Dispatcher property of the UI Element that you need to target.  That is done as follows:

image

Here I am using the BeginInvoke to invoke the procedure asynchronously in the UI.  I am also using an Anonymous Delegate to invoke the routine without having to set up a covering method call.  This makes sense in cases like this where we will only call this procedure once and only from here.  So, the DisplatcherOperationCallback takes 2 arguments with the first one being the method.  In this case, the first argument is replaced by the Anonymous Delegate definition “delegate(object arg) { _calibrateDisplay.CalibrateDisplay();return null}.  If you need to invoke a procedure asynchronously on the UI thread but you do not have a specific  UIElement you want to target, then you can use a DispatcherTimer.

I have another example of this in the code.  I added a button to the settings view to allow you to zero the inclinometer.  This means that you don’t have to mount the bicycle computer exactly flat.  The zeroing function will test and store the angle that it is at and then use that as the 0 point.  But again, I want to do some processing in a UI event handler.  I want to show the ‘Processing….’ message to tell the user that the zeroing is taking place (because they have to hold the bike still), do the zeroing, and then go back to the setting screen.  The code for this looks like:

image

Saving values to Flash

In the sample calibration code shipped with NETMF, most of it is related to displaying a set of crosshairs on the display and reading the position of the touch for a set of points.  This data is then used to calibrate the display by calling:

image

This setting needs to happen every time we start up this new display since the Fez Cobra allows multiple displays and does not know which one we have connected if it is not the default one.  That means that we will either have to start every session with the bike by calibrating the screen or save these settings and re-apply them on startup.  We could write them out to a file on an SD card (and we will do that for the ride data) but this is just a small amount of data so it is an opportunity to try using Extended Weak References.  This is a unique NETMF extension to .NET System.WeakReferences that enables you to store and retrieve serializable objects from Flash memory. 

I created a new view – which is my CalibrateDisplayView.  In the constructor for this object, I first create a static ExtendedWeak Reference field:

imageThen I add the call to ‘Recover or Create’ the reference.  This will read in the contents of the reference or create the reference if it is not there.  It is set up as you can see to survive both reboot and powerdown.  This determines how quickly the data is written to flash to make sure that it is not lost.  I also set the priority of the data to ‘NiceToHave’.  this is a hint for the Garbage Collector on when it can take this data.  Since I only need this data briefly at startup, I don't need it to hang around. 

image

The DisplayCalibrationSelectorType is the serializable data that is written and read to Flash.  Here I have used the calibration data that we saw above:

image

Bringing this all together in the calibration logic

With all these parts, we can now complete the calibration.  The first step happens at startup when I see if there is any existing calibration data in the Extended Weak Reference.  This will be the most common case.  If there is, I read is into the calibration settings and set the Calibrated state to true. 

image

If, on my first user interaction, Calibrated is false, I run the calibration as you saw above by using BeginInvoke to run it in a separate thread.  The calibration logic is a combination of drawing a crosshairs on rendering the view and waiting for a touch to capture the point coordinates.  When the points are all paired up, I call SetCalibration to set the system up for the coordinates of the display. 

image

image

image

image

image

 

Reformatting the Display for the Larger Screen

Now we are ready to use the new display.  This is where we get some of the payback for using WPF.  To reformat the screen for this larger display requires exactly nothing.  The screen reflows to the new size.  It looks less crowded as well and Josh’s new look is much better. There was a lot to do to make the code support multiple displays but there was nothing to do to make the view adapt to the larger display space.

clip_image002[1]