n my last article, I showed you how to get started with the QUALCOMM BREW UI toolkit and introduced BREW’s notion of forms and widgets. In this article, you’ll build on that knowledge and look at an entire multi-screen application to see how forms, widgets, models, and listeners fit together.
The IOU application (see Figure 1 and Figure 2) consists of three screens: one to list the money you owe people and money they owe you, and two to let you enter in IOUs and debts owed to you (referred to as “UOMe’s” in the source code). It’s not a commercial application, but serves two very real purposes: it lets us track our “coffee debts” around the office where I work, and it provided me with a playground in which to work with the BREW UI Toolkit before using what I learn in commercial applications.
Constructing a List of Items
The previous article demonstrated how to set up a simple form and populate it with widgets. Listing 1 shows you how to do this same thing for a form with a menu. However, using the UI Toolkit’s support for lists and menus (vertical and horizontal lists are provided, as is a grid list) is a bit trickier.
- Create a form
- Create a container
- Bind the container to the form
- Create some widgets
- Add the widgets to the container
- Configure the widgets so they appear the way you want
There are a few key differences between initializing a form with static or text widgets and initializing a form with menu widgets.
When configuring the list widget itself, I provide an explicit model for the list widget. This model (created at startup) is responsible for containing the list of items in the list. Listing 1 uses a simplistic data model, keeping each IOU as a name and an amount in a wide character string. More importantly, however, is that the list widget is actually a decorator?a widget that wraps the behavior of another widget, in essence decorating it. In the UI Toolkit, many widgets, such as those providing menus and scrollbars, are actually decorators, that modify the behavior of the widgets they contain through their handling of events and the data stored in their models. The list widget takes another widget (in Listing 1, it’s a static text widget), and uses that widget to draw each element of the list given information from the list widget’s own data model, the IMenuModel set using IWIDGET_SetModel.
In addition to setting the list widget’s data model, you must also configure the height of a list widget’s individual item. Do this by obtaining the decorator interface to the list widget, and then setting its item height to the preferred height of the contained widget. Before releasing the decorator interface, set the decorator’s widget to be the contained static text widget.
Finally, Listing 1 shows how to intercept events going to a specific widget or to a form. This is essential for large applications, because it lets you keep form-specific event handling in separate methods, which prevents you from cluttering up your event handler. If you’re writing in C++, you can think of this as the means by way you provide an event handler method for a specific form class. Listing 1 does this in two ways: it creates a handler for the form containing the list (called the MenuForm), and it also creates a focus listener for the list which will receive notifications whenever the list changes state as a result of user action.
Handling Events on Behalf of Forms
Large applications have complex event-handling requirements. You may need to manage moving focus in specific ways, handle specific key-presses, and often handle application events as well. In traditional BREW applications, this can lead to long and unwieldy event handlers, or a large set of functions to manage events by their type or application state, a situation which can easily lead to complex and confusing code.
By partitioning your application into forms, you can assign an event handler to each form, using the UI Toolkit’s HandlerDesc structure and setting a specific event handler for a form. The HandlerDesc structure is a lot like an AEECallback structure. It simply records a function (your form’s event handler) and the user data you’d like the event handler to accept. The UI Toolkit provides macros for filling and managing a HandlerDesc structure; as with similar kinds of structures in BREW, be sure not to allocate it on the stack, because it needs to persist throughout its use. Listing 1 sets up a HandlerDesc. This encapsulates the event handler for the MenuForm, the function MenuForm_HandleEvent (Listing 2), which must manage the events when you press the left or right soft keys.
(Listing 2) shows one of the best parts about working with forms: managing state changes. If you create your forms at application launch (or at least have their creation encapsulated in a construction method), switching screens is simply a matter of manipulating the stack of forms managed by your application’s root form. At any time, the active view?the one drawn to the screen and receiving events?is the top form on the stack. The IRootForm interface allows you to push new views on the stack, pop views from the state stack, and even insert or remove items at any point in stack. Consequently, the MenuForm_HandleEvent method is trivial?for each of the soft key presses, it only pops the current form on the stack (which is the MenuForm) and then pushes the appropriate form on to the stack.
The MenuForm_HandleEvent must do one more critical thing: pass unhandled events on to the underlying form’s event handler. This is crucial, because much of how the BREW UI Toolkit works is through event handling, and failing to chain to the underlying event handler will cause all sorts of nasty bugs and crashes. Fortunately, it’s easy to do. When you initialize your HandlerDesc using HANDLERDESC_Init, it assigns the new event handler to the form, and returns the old event handler in your HandlerDesc structure. In your event handler, you simply pass unhandled events to the previous event handler using HANDLERDESC_Call. Using these macros, you can construct arbitrarily long chains of event handlers. When you’re done with a HandlerDesc, be sure to invoke HANDLERDESC_Free, to ensure it’s correctly freed. While not strictly necessary if you’re only chaining two handlers together, for longer chains it’s crucial to avoid a memory leak.
Chaining has an important side effect that typically escapes people until after they’ve been bitten by it: you have to be careful about which methods you invoke in a chained event handler. Because so many parts of the form and widget frameworks communicate through event passing (even innocuous things like getting and setting properties can trigger event passing) it’s very easy to get into situations where your event handler is calling itself recursively, trying to get the value of a property and calling itself over and over again. In general, it’s best if you cache the data you need (at least the form or widget in question) in your application structure or the data structure you register in the HandlerDesc, and examine the incoming event before trying to perform operations on your application’s forms or widgets. Fortunately, the frustration of this problem is easily offset by how easy it is to debug: on the simulator you’ll hit a stack overflow instantly, so once you do, you know exactly what you’re up against, and refactoring your code to avoid the offending call is often trivial. For a better understanding of the issue, peruse the various widget and form headers; they provide a list of the events used by the BREW UI Toolkit, which can help you avoid this problem in your design.
Listening to Model Changes
Widgets and other bits of code can listen and receive function invocations whenever a model changes. This is the means by which a widget knows when and what to draw, and by the same token, it’s how models can communicate changes to your application.
The IOU application uses a single listener, to listen on the model for the list to determine when a list item is selected. It does this using a focus listener, which receives events whenever the list’s model receives changes with regard to which item has focus. It’s important to realize that the focus listener isn’t listening to the model of the list, but the model of the view for the list. There’s a subtle but important difference: the list model contains the items of the list; the list’s view model is the model for the view, and keeps track of things such as who has focus. The focus listener has been set using the code at the end of Listing 1, indicating that when the list model changes, the system will invoke the listener function Menu_ListenerFocus (Listing 3).
Unlike event handlers, you’re not responsible for chaining listeners?the BREW UI Toolkit will do that for you. Thus, Menu_ListenerFocus need only handle the menu selection action (an EVT_MDL_FOCUS_SELECT event) and populate the text models for the input form that needs to be shown before popping the menu form and pushing the appropriate edit form to the top of the form stack.
Managing the Models
When initializing a screen, you must be sure to put the appropriate contents in each widget’s models. For some widgets, like labels, this is easy. A quick IWIDGET_SetText call may be all that’s necessary. For other widgets, you may need to harvest data from a data store, such as a BREW database, file, or another model. Listing 3 does this, using the ITextModel instances associated with each of the input lines for a specific form. The ITextModel is a subclass of IModel, and provides information about the contents, current selection, and cursor position in a text input line. This information is provided by the TextInfo structure, which you can obtain from any text model by calling its ITEXTMODEL_GetTextInfo method. You can use this method to obtain a model’s text info, select all of the text currently in the model by setting a selection range from zero to the count of characters, and then replacing the current text with the new text. This replacement metaphor is the standard one when working with text models; using replacement you can insert, delete, or append text to the contents of a text model.
SaveIOUForm gets the text in each of the two models by obtaining the model’s text info and copying it from the TextInfo’s pwText parameter. It then builds the string that will appear in the menu with the entered person and debt. Once the menu string is constructed, it’s either added to the end of the menu (if the form was brought up for a save command) or used to replace the old value in the menu.
Of course, using the menu model as the data store is only a stopgap while the application is running. On application launch, initialize the model with the contents of a file, and on application exit, commit the contents to the same file (Listing 6). This code represents the IOUs as an array of fixed-length fields in wide string format, making them almost a carbon copy of the actual menu model’s contents. It’s crude, but it works. A more robust application probably deserves a database, and storage of the debt amounts as numbers so that cumulative debts can be managed as actual accounts, rather than as simple textual notes.
You’ve learned how widgets and forms replace the existing BREW IControl metaphor for user interface development. You’ve learned the basics of constructing and configuring widgets, attaching them to containers, and managing application flow through forms and event handlers. Now, it’s your turn: contact Qualcomm to get a pre-release of the BREW UI Toolkit, and start building your next generation application!