Fun with Buttons
by James Webster, Test Manager, .NET Micro Framework
and Jerry Kindall, Programmer/Writer
Many programs running on the Microsoft® .NET Micro Framework get user input through General Purpose Input/Output (GPIO)-type buttons on their respective hardware devices. The .NET Micro Framework hardware emulator provides five built-in GPIO buttons – four directional buttons and a Select button. Of course, some devices have additional buttons.
When you press or release a button, the GPIO interface generates an interrupt. The .NET Micro Framework translates these interrupts into events called ButtonUp and ButtonDown. You can register your own handler to receive either or both of these events.
Microsoft Visual Studio® can provide the code you need to get started handling button events when you create a new .NET Micro Framework project. To start a new project in Visual Studio, perform the following steps.
1 On the File menu, point to New and then click Project.
2 In the New Project window, expand the Visual C# node in the Project Types pane if it is not already expanded.
3 Under Visual C#, click Micro Framework. You’ll see the standard .NET Micro Framework project templates displayed in the Templates pane.
4 In the Templates pane, double-click Window Application.
This article shows you how to write code that handles button events using the .NET Micro Framework. Keep in mind that you won’t always have to do all this work yourself. Many .NET Micro Framework user interface elements can handle buttons for you. For example, the ListBox class supports using the Up and Down buttons to move the selection bar through a list; you do not need to handle this task yourself. Even in this case, though, you will need to handle the Select button, so it is still important to be familiar with techniques for handling button input.
Button Mapping
A new MFWindowApplication project like the one you’ve just created contains an automatically generated class called GPIOButtonInputProvider. This class establishes a mapping scheme between the device’s GPIO pins and the buttons they represent.
The GPIOButtonInputProvider class works with the .NET Micro Framework emulator, which uses GPIO pins 0 through 4 for buttons. If you are running your applications on the emulator (or on a hardware device with the same button mappings), no changes are needed. If you are using a hardware device with other button mappings, you can modify the class to map the correct pins to the buttons. A recent article on this site, titled “Getting Started with Freescale i.MXS,” provides an example of adapting the GPIOButtonInputProvider class for the Freescale i.MXS platform. The same concept applies to other hardware, although the details might differ. The code in this article uses the default mappings.
Basic Button Fun
The Program.cs file in a fresh MFWindowApplication project includes a stub event handler method, OnButtonUp, which is called when any button is released. The stub prints the name of the pressed button in the Output window in Visual Studio. This method is where you will add your own code, probably using a switch statement to provide an action for each button, as in the following sample method:
private void OnButtonUp(object sender, ButtonEventArgs e)
{
switch (e.Button) // e is the event record
{
case Button.Left:
((Text)mainWindow.Child).TextContent = "Left";
break;
case Button.Right:
((Text)mainWindow.Child).TextContent = "Right";
break;
case Button.Up:
((Text)mainWindow.Child).TextContent = "Up";
break;
case Button.Down:
((Text)mainWindow.Child).TextContent = "Down";
break;
case Button.Select:
((Text)mainWindow.Child).TextContent = "Select";
break;
default:
((Text)mainWindow.Child).TextContent = "Button " + e.Button;
break;
}
}
Our example handler places the name of any pressed button in the middle of the screen (where the text “Hello World!” initially appears). Try this now by replacing the stub OnButtonUp method with the one shown here, then pressing F5 to run the project in the .NET Micro Framework emulator.
Advanced Button Fun
The stub handler in the Window Application template responds to ButtonUp events, which means that your code does not receive an event until the user has already released the button. This is the conventional way to handle simple button events, but in some cases, it is too late to be of any use. For example, you might want a button to repeatedly perform some action as long as it is held down. This requires code to run when the button is first pressed. The .NET Micro Framework provides a ButtonDown event that you can use in conjunction with a timer to implement this functionality.
The Timer object in the System.Threading namespace periodically calls a method that you specify when you instantiate the timer. This callback method actually does most of the work; the ButtonDown and ButtonUp handlers primarily serve to instantiate and dispose of the timer. In this section, you will learn how to program the Up and Down buttons to continuously increment or decrement a counter. Additionally, the longer the buttons are held down, the faster the counter changes. You can apply similar techniques to graphical elements or controls, such as sliders.
If you have not used timers before, you may not know that a method called by a timer runs in its own thread. For this reason, you should use the lock statement to synchronize the timer thread and the main thread whenever you are entering a critical section of code—that is, any code that touches the counter or the timer object while more than one thread might be running. You can only lock on object references, so in this example, the timer will be used for locking.
Preliminary Steps
To get started, create a new .NET Micro Framework Window Application as before, then open the Program.cs file in the new project. Near the top of the file, with the other using directives, add the following code to facilitate easier use of the Timer class.
using System.Threading;
Next, find the following lines in the CreateWindow class.
// Connect the button handler to all of the buttons.
mainWindow.AddHandler(Buttons.ButtonUpEvent,
new ButtonEventHandler(OnButtonUp), false);
After these lines, insert the following line to add a ButtonDown event handler. By convention, this method is named OnButtonDown. You will add the code for this method later.
mainWindow.AddHandler(Buttons.ButtonDownEvent,
new ButtonEventHandler(OnButtonDown), false);
Now, somewhere in the Program class, add the following declarations. (They can be anywhere in the class as long as they are not in a method, but you might want to put them right before the stub OnButtonUp method so they will be close to the methods that use them.) The variable counterValue is the number we will be adjusting with the buttons. The rest of these variables have to do with the timer that will provide the repeat function.
// the value to be adjusted using Up/Down buttons
private int counterValue = 50;
// initial, minimum, and current settings for timer in milliseconds
private const int initialInterval = 500;
private const int minimumInterval = 50;
private int currentInterval;
// the active timer
private Timer repeatTimer = null;
OnButtonDown
With the preceding code in place, insert the following lines to add the OnButtonDown method.
// handle a button press, starting a timer for repeats
private void OnButtonDown(object sender, ButtonEventArgs e)
{
HandleButtons(e);
lock (repeatTimer)
{
// dispose of existing timer (in case more than one button is down)
if (repeatTimer != null) repeatTimer.Dispose();
// start the timer firing every initialInterval milliseconds
currentInterval = initialInterval;
if (e.Button == Button.Up || e.Button == Button.Down)
repeatTimer = new Timer(OnTimer, e, currentInterval,
currentInterval);
}
}
The OnButtonDown method receives control whenever any button is pressed. First, it calls the HandleButtons method (passing the event record, e, so that HandleButtons knows which button was pressed and can adjust counterValue appropriately). If the Up button or Down button was pressed, this method instantiates a Timer object, specifying the OnTimer method (which we will discuss later in this article) as the callback function. Timers can pass a state object to the callback; in this example we specify the event record, e, because OnTimer needs the event record to pass to HandleButtons.
It is possible for a user to press two or more buttons simultaneously on an actual hardware device. Although this is not possible with the emulator, you should address this possibility in your code so that the application does not behave in a way the user does not expect. If the timer already exists (as indicated by a non-null value in repeatTimer), meaning that another button is already pressed, the existing timer is disposed of before a new one is instantiated. This results in “last button wins” behavior that users will find natural.
OnButtonUp
The ButtonUp event handler, OnButtonUp, merely disposes of the timer to stop the repeating behavior. You should replace the stub OnButtonUp handler with the following:
// stop the repeat timer when the user releases a button
private void OnButtonUp(object sender, ButtonEventArgs e)
{
lock (repeatTimer)
{
// if a timer exists, dispose of it
if (repeatTimer != null) repeatTimer.Dispose();
repeatTimer = null;
}
}
The OnButtonUp method is called when any button is released, not just the Up button or the Down button, although the latter two buttons are the only ones that start the timer. Therefore, we dispose of the timer only if it actually exists (that is, if the repeatTimer reference is non-null). Otherwise, releasing the Select button, for example, would throw an exception trying to dispose of a timer that does not exist. (If a user has pressed more than one button on the device at the same time, this means that the first button that is released will stop the timer. Releasing any other buttons afterward will have no effect.) After disposing of the timer, we set repeatTimer to null to record that the timer no longer exists.
OnTimer
The OnTimer method, which follows, is called periodically by the timer, initially every 500 milliseconds (every half second).
// called by the timer to repeat a button action
private void OnTimer(Object e)
{
HandleButtons(e);
lock (repeatTimer)
{
// decrease timer interval for the next repeat
if (currentInterval > minimumInterval)
{
currentInterval -= 50;
if (currentInterval < minimumInterval)
currentInterval = minimumInterval;
repeatTimer.Change(currentInterval, currentInterval);
}
}
}
The first thing OnTimer does is call HandleButtons to make sure that the counter gets updated appropriately for the button being held. Then the timer interval is reduced so that the repeated increment or decrement action goes faster (up to a prescribed limit) the longer the button is held down.
HandleButtons
The HandleButtons method is the last method needed for our press-and-hold functionality. It contains a switch statement much like the one you saw in the “Basic Button Fun” section earlier in this article. Because OnButtonDown and OnTimer both need this functionality to process button actions, the necessary code has been “broken out” into HandleButtons, as follows:
// button handler called from OnButtonDown and by timer
private void HandleButtons(Object e)
{
lock (repeatTimer)
{
switch (((ButtonEventArgs)e).Button)
{
case Button.Up:
if (counterValue < 99) counterValue++;
break;
case Button.Down:
if (counterValue > 0) counterValue--;
break;
case Button.Select:
counterValue = 50;
break;
}
}
// display the new value of the counter on the screen
((Text)mainWindow.Child).TextContent = counterValue.ToString();
mainWindow.Child.Invalidate();
}
The only slightly tricky aspect of the HandleButtons method is the cast of the event record, e, to type ButtonEventArgs, which is necessary because the method is used as a callback function for a timer, which can pass any object reference. Note also that we need to invalidate the text object to force it to be redrawn immediately. The rest of the code should be familiar to you by now.
Trying It Out
After making the preceding changes to Program.cs, save it and run it in the .NET Micro Framework emulator by pressing F5. Hold the emulator’s Up button and observe how the displayed value changes slowly at first, but then changes faster and faster as you continue to hold the button down. Also notice how you can easily make fine adjustments by pressing and releasing the buttons quickly. This behavior comes for “free”—as long as you release the button before the initial timer interval, it acts much like it would have if you had programmed only a ButtonUp handler. Behind the scenes, of course, a timer is being instantiated by OnButtonDown, then disposed of by OnButtonUp before it has time to activate.
The emulator screen initially displays “Hello World!” until you press the Up button, the Down button, or the Select button. This is a leftover behavior from the Window Application template. You can eliminate it by opening the Resources.resx file and changing the value of String1 to 50, the initial value of the counter, as in the following illustration.
As an exercise, you might add functionality for the Left and Right buttons. Perhaps the Left button could set the counter to 0 and the Right button could set it to 99, providing easier access to the top and bottom of the counter’s range. This requires adding just a few lines of code to one method. A more ambitious addition would be to give the Left and Right buttons repeat functionality as well, perhaps having them add or subtract 5 from the counter.
Copyright © 2007 Microsoft Corporation. All rights reserved. This article was originally published on MSDN Blogs.