User Input and Events


At this point, we know how to construct displays and draw items on the display. The next thing we need to consider is how we can get elements on our windows to respond to user input. We saw in Chapter 4 that we can bind methods to hardware events, so that a change of the input level on a pin can cause code to run in our program. Now we need to bind physical hardware events to WPF elements in our program. This is something that the operating system does for us on a larger computer, but on the .NET Micro Framework, it is up to us to decide which input pins correspond to the buttons on our display and then bind each one to the appropriate event for our window elements. This is a two-stage process.

We need to perform the first stage-connecting a particular input pin so that it can cause a Button event in the WPF-in a manner specific to a particular hardware configuration, depending on which input pins connect to which buttons on the device itself.

The second stage-connecting a Button event to a component in a .NET Micro Framework program-will produce a program that will work on any platform. We can give display elements such as the ListBox the input focus and then make them respond to Button events.

Binding to Hardware Events

In this section, we will create a class, GpioToButtons, that will connect hardware events in our device to a Windows Presentation Foundation application. This class will act as a conduit between the underlying hardware and the application.

We will start by considering how we will feed button press events from GpioToButtons into the WPF system. We do this using the InputManager class.

The InputManager class coordinates all events connected with a given application. The class itself contains a static property called CurrentInputManager, which provides a reference to the current input manager instance.

 InputManager inputMgr = InputManager.CurrentInputManager; 

Now that we have a reference to the InputManager in use for this application, we can ask the InputManager to make us an InputProviderSite instance.

 InputProviderSite inputProvSite = inputMgr.RegisterInputProvider(this); 

The method RegisterInputProvider receives a reference to the class that will be providing the input. In this case, it is the active instance of the class, so we can just use the keyword this to pass a reference to the current instance, which RegisterInputProvider will use to create an instance of InputProviderSite.

So, at this point, we have an instance of an InputProviderSite that is able to receive events. It will pass these events into the WPF for us. We deliver the events by calling the ReportInput method on this instance. This method has a parameter of type InputReport that will contain the event we wish to send. In this case, we want to send a RawButtonInputReport that describes the button event caused by the user. This report contains the identity of the button, the time of the event, and the new state of the button.

It is not possible to call ReportInput directly to deliver this information because of threading issues. When an interrupt event is generated, the method dealing with the event is executed on a thread specially created for this purpose. However, the thread attached to the application must process input events. To get around this, we need to create a delegate that we can request the application thread to invoke.

A delegate is a type-safe reference to a method in an instance of a class. When we need to report a button event, we ask the application thread to execute this method with our RawButtonInputReport instance. We create the delegate as follows:

 reportInputMethod = new ReportInputCallback(inputProvSite.ReportInput); 

The variable reportInputMethod is an instance of a delegate type that we create as follows:

 private delegate Boolean ReportInputCallback(InputReport inputReport); private ReportInputCallback reportInputMethod; 

We now have the entire infrastructure that we need to allow us to pass input events into our application. Next, we need to create some InterruptPort instances to respond to events produced by the buttons.

 InterruptPort selectPin = new InterruptPort(    Cpu.Pin.GPIO_Pin3,                          // pin 3    true,                                       // want glitchfilter    Port.ResistorMode.PullDown,                 // pull-down resistor    Port.InterruptMode.InterruptEdgeBoth);      // interupts on both edges 

This creates an instance of an InterruptPort that is connected to Gpio pin 3. The port is configured as is appropriate for the device hardware we are using, in this case a pull-down resistor is requested, which means that the button will be regarded as pressed when the input level goes from low to high. The interrupt mode is set to InterruptEdgeBoth so that interrupts are generated on transitions from low to high and high to low. It is important to use this value; otherwise, we will miss button-up or button-down messages.

Finally, for this button we need to bind the interrupt port to the event-handler method in our class. The method is called PinInterruptHander.

 GPIOInterruptEventHandler interruptHandler =    new GPIOInterruptEventHandler(PinInterruptHander); selectPin.OnInterrupt += interruptHandler; 

This completes the code to manage Gpio pin 3. We must repeat the process for all other pins that are to be used as inputs to our program. Note that our interrupt event-handler method will be the same for all pins; it will be able to identify the pin responsible for the event and deliver the appropriate message. The complete constructor for the GpioToButtons class is given in the following example:

 public GpioToButtons(Dispatcher inDispatcher) {    dispatcher = inDispatcher;    InputManager inputMgr = InputManager.CurrentInputManager;    InputProviderSite inputProvSite = inputMgr.RegisterInputProvider(this);    reportInputMethod = new ReportInputCallback(inputProvSite.ReportInput);    GPIOInterruptEventHandler interruptHandler =       new GPIOInterruptEventHandler(PinInterruptHander);    InterruptPort selectPin = new InterruptPort(       Cpu.Pin.GPIO_Pin3,                              // pin 3       true,                                           // want glitchfilter       Port.ResistorMode.PullDown,                     // pull-down resistor       Port.InterruptMode.InterruptEdgeBoth);          // interupts on both edges    selectPin.OnInterrupt += interruptHandler;    InterruptPort upPin = new InterruptPort(       Cpu.Pin.GPIO_Pin2,                              // pin 2       true,                                           // want glitchfilter       Port.ResistorMode.PullDown,                     // pull-down resistor       Port.InterruptMode.InterruptEdgeBoth);          // interupts on both edges upPin.OnInterrupt += interruptHandler; InterruptPort downPin = new InterruptPort(        Cpu.Pin.GPIO_Pin4,                             // pin 4        true,                                          // want glitchfilter        Port.ResistorMode.PullDown,                    // pull-down resistor        Port.InterruptMode.InterruptEdgeBoth);         // interupts on both edges downPin.OnInterrupt += interruptHandler; } 

The Dispatcher passed as a parameter to the constructor is the dispatcher for the application to which we want the events delivered. We call the constructor itself as follows:

 // declared as part of the class static myGpioToButton; ..... // Inside the code for my application Application app = new Application(); myGpioToButton = new GpioToButtons(app.Dispatcher); 

This creates a new instance of the GpioToButtons class. The variable myGpioToButton refers to it. We made this variable a static member of a class so that it will not be removed by the garbage collector.

 We now have the entire infrastructure that we need to allow us to pass input events into our application. Next, we need to create some InterruptPort instances to respond to events produced by the buttons. 

When hardware events occur at the pins nominated as our input buttons, these cause interrupt events that call the method PinInterruptHander to deliver the button presses to our application.

 void PinInterruptHander(Cpu.Pin pin, Boolean state, TimeSpan timespan) {    Button buttonPressed;    switch (pin)    {       case Cpu.Pin.GPIO_Pin3:          buttonPressed = Button.Select;          break;       case Cpu.Pin.GPIO_Pin2:          buttonPressed = Button.Up;          break;       case Cpu.Pin.GPIO_Pin4:          buttonPressed = Button.Down;          break;       default:          return;    }    InputReport ir =       new RawButtonInputReport(          null,          timespan, buttonPressed,          state ?             RawButtonActions.ButtonUp :             RawButtonActions.ButtonDown);    dispatcher.BeginInvoke(reportInputMethod, ir); } 

This method receives the identity of the pin that caused the event, the new state of the pin, and a time stamp. It uses a switch to determine the button that it should generate. Then, it creates an instance of a RawButtonInputReport from this information and uses BeginInvoke to invoke the method referred to by reportInputMethod on the application thread, with this report as the parameter.

The method uses the value of the state parameter as supplied to the event to control whether it creates a ButtonUp or a ButtonDown message. If we reverse the sense of the buttons, that is, the input level on the button is set to low when the user presses the button, we would also have to reverse the sense of this code.

This is a fairly tortuous path, but by studying the code example, you should be able to see how you can generate your own button events using your assignment of pins to buttons. If the button-pressed messages come from a keyboard connected via alternative means, for example to a serial port, you will still be able to use the mechanism to inject button codes into an application.

Port and Button Mapping

If you are using standard hardware, there is a particular mapping of ports to user interface buttons. This is the one the emulators supplied with the .NET Micro Framework use for pin assignments:

  • Left button: Gpio pin 0

  • Right button: Gpio pin 1

  • Up button: Gpio pin 2

  • Select button: Gpio pin 3

  • Down button: Gpio pin 4

If you create a .NET Micro Framework Windows application, it will include a GpioButtonInputProvider class that you can use to manage hardware button events in a similar manner to that used in the preceding example. You can modify this to match the hardware in use, or replace it with an input provider of your own design.

Using Button Events

At this point, we have connected our hardware to the application so that Windows Presentation Foundation button events can be generated on display elements. Now, we have to bind the button events to the elements that want to use them. If you refer to Figure 7-16, you will see that the UIElement class provides two methods, OnButtonUp and OnButtonDown. The application can call these methods when those events occur. In the UIElement class, these methods do not do anything, but elements that must respond to user input override them.

For example, the ListBox class overrides these methods so that it can receive button events and allow the user to navigate to a particular ListBoxItem in the display.

If you make your own display elements by extending the UIElement class, you can make them respond to the events in the appropriate manner.




Embedded Programming with the Microsoft .Net Micro Framework
Embedded Programming with the Microsoft .NET Micro Framework
ISBN: 0735623651
EAN: 2147483647
Year: 2007
Pages: 118

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net