Object-Oriented C: Creating Application Classes Part 2
Creating a hierarchical window-based application is a breeze using the OOC fundamentals presented last month.
Continuing with object-oriented C (OOC), we will now take the foundation classes that we developed last month and put them to use in creating our application. This is where the fun begins. Creating the application classes brings your code alive; now you see the power of OOC. In Part 1 of this article (February 2001, p. 78) we developed our foundation classes from the CObject base class, which provided an interface to the low level graphic APIs. Once we established our CObject class, we incorporated it into an aggregation relationship with three GUI classes: CButton, CPanel, and CText. These classes provide us with a toolbox of GUI control objects, which we will put to use in our sample application.
However, before we dive into writing our application code, we must figure out what we want to accomplish and how to go about it. In scanning over the horizon for an application that would provide enough challenge to showcase the various aspects of OOC application model, I decided that we would create a home security system menu application. This will be somewhat trivial; however, the OOC application model principles needed to develop this application are basically the same for developing a complex application. Due to the amount of code necessary for even a small application like this one and the limited space I have here, I will showcase areas of the code that are essential to the OOC application project model. It is up to you to fill in the remaining gaps.
Whenever I begin the design concept of an application, I find it useful to prototype my ideas. This task can be accomplished in many ways, from drawing a picture on a napkin to a full-blown demo application written in a higher level language such as Visual Basic. In this case, I decided to create a series of slides. See Figure 1.
Here I will outline the basic behavior. The home security system will consist of two screens: the Home screen and the Settings screen. The Home screen is where the user activates or deactivates the home security system through the use of a keypad, which is also echoed on the on-screen display (OSD). The Settings screen allows the user to turn the four security zones in the house off or on.
Establishing the application object
Now that we have the application specified, we need to make some important software architecture decisions, namely the hierarchy of the windows. The first thing that comes to mind is the number of windows required to build the two OSD screens. It's important to understand what a window means in OOC. A window is any area of OSD that can receive and send messages. In applying the window concept to our application, it appears that we need four window classes: one parent class container which holds the aggregate of three child window classes, as shown in Figure 2. The parent class, CWndApp, only contains an instance of the child window class CWndMenu. This CWndApp will be the base class, which will be represented by the application object objWndApp, as shown in Listing 1.
The CWndMenu class will be an aggregate of CWndHome and CWndSettings. The reason behind this window hierarchy is to provide an encapsulation of the GUI control objects necessary to provide the appearance and behavior. This hierarchy creates the order in which messages are disseminated from the parent window to the child windows.
Application window class template
The application window class (AWC) template is the cornerstone on which we will build our window classes. In C++, creating pure virtual functions in the parent class to which all child classes must adhere can enforce interfaces for a class. With OOC, this enforcement must be an agreement among the software engineers to follow the AWC template. Deviation from the AWC template can cause the breakdown of a consistent approach, which may lead to confusion among the software engineers.
All window classes, except for CWndApp, should contain the AWC attributes and member functions shown in Table 1.
Table 1: Application window class template
|Member attribute and functions||Comments|
|m_bDirty||A flag that determines whether the window needs to be painted.|
|Activate||A member function that handles the window paint and event handling.|
|Deactivate||A member function that handles the restoration of the window back to its original appearance and state.|
|Constructor||This member function initializes all objects that are associated with this instance of the class with the objects' corresponding resources.|
In the CWndMenu class structure, you will see that besides the m_bDirty attribute, it contains a collection of foundation class objects, including the button and panel object types. See Listing 2. It also contains the two child window objects-objWndHome and objWnd-Settings-and the public member functions-Activate, Deactivate, and Construct. The associated function declarations are listed along with the NewWndMenu function that performs the functional binding for this class. It is necessary that any child window class be part of its parent class.
In developing an application model, I wanted to use terminology that is widely used by a vast number of software engineers. Hence, I borrowed from the Visual C++ approach to application building. WinMain and WinTerm provide behavior similar to the Visual C++ application model, but with much less overhead.
In devising a messaging approval for OOC application model, I've found that the best approach is a top-down message flow. This implies that the user's key commands are first interpreted by WinMain and then dispatched to the parent CWndMenu class. If a match is not found, the messages are dispatched to the child window. If a match is not found in the child window classes, the messages are ignored without any behavioral changes to the application. The OOC Application Model is depicted in Figure 3.
For the sake of focusing on the application model, let's assume that a message is generated when the user presses a key on the keypad. These messages are sent to our application task, whose entry point is the WinMain function. Once received by WinMain, the message is translated into an OOC window types message format. Once the application task has been initialized, the application will create an INITIALIZE message received by WinMain, which in turns calls the Run function with the WM_CREATE message. This message invokes the InitInstance function, which performs all necessary functional binding and then calls the Construct function where all objects, including window and foundation class objects associated with our application, are initialized. The ACTIVATE message is received by WinMain when the OSD needs to be displayed. This ACTIVATE message, which is remapped to WM_ACTIVATE, is sent to the Run function. Run invokes the objWndMenu to be painted, as shown in Listing 3. The WinTerm and Run functions are listed before the WinMain function due to the fact that they are static/private functions to this file.
To summarize, when the user presses a key (for example, user pressed the right arrow key), a message is received by WinMain, which translates it to a WM_KEYDOWN message with wparam being assigned to WM_CMD_KEY_RIGHT_ARROW message type.
To shutdown the application, a DEACTIVATE message is received by WinMain, remapped to WM_DESTROY, and sent to the Run function. The Run function then calls the WinTerm function, which performs any shutdown routines to clean up the application and restores the application back to its standby state, as shown in Listing 4.
All windows, including the Run function, contain a maximum of four arguments as shown in Table 2. The Run function does not have a this pointer since it is not part of any class.
Table 2: The window arguments
|Member attributes and functions||Comments|
|this||The address of the object that is calling the member function.|
|message||A copy of the message that was sent by WinMain.|
|lparam||Reserved for sending an address of a variable or structure that could provide additional information for the window class.|
|wparam||The user key command type.|
Window command messages sequence
In examining the window command message sequence, the Activate and Deactivate member functions perform a key role in handling and dispatching messages to other windows. However, before the messages are sent to these window member functions, Run first performs an evaluation of possible user movements. For example, if the user presses a key on the keypad, a WM_KEYDOWN message is received from WinMain. If the WM_KEYDOWN is caused by a navigational key, the following sequence occurs:
Window restoration: a check is first performed to see if the user is allowed to navigate onto a particular location in the navigational grid. If the user is prohibited from movement due to entries in the navigational grid, nothing occurs. However, if the user is permitted to move to a new location, the area of the window that had previously been altered is restored to its original appearance. This is done by calling the Deactivate member function for that particular window.
Navigation grid evaluation: once the window is restored, we then check whether the user is allowed to move in that direction based on the entries in the navigation grid. I will discuss this later in more detail.
Painting new appearance: once the movement is evaluated, we then call the window to be activated, which paints the new location with the appropriate appearance.
The navigational grid is a three-dimensional array that contains only dynamic GUI control objects that can receive focus. Each of these "dynamic" objects must have a unique identifier (for example, IDC_HOME), which will allow the window to determine which object needs to receive the incoming message.
The navigational grid can be thought of as a group of tables, where each table represents a window, as shown in Figure 4. These tables are arranged in the same order as the window IDs, allowing window navigation through use of the window ID index. Locations in the three-dimensional array that don't receives focus are assigned as shown in Listing 5.
A control object unique identifier can occupy more than a single entry in the navigational grid. This is helpful when you want to place focus on an object whenever the user presses the left arrow key, no matter what objects in the window already have focus. This occurs with the Home window. When the user presses the left arrow key, the focus returns to the IDC_HOME control object regardless of whether the focus is on IDC_KEY_7, IDC_KEY_4, IDC_KEY_1, or IDC_KEY_0. The tFocus structure holds the focus position of the navigational grid.
Besides the row and column positions being updated when the user moves, the window position also needs to be updated. This typically occurs in the child window procedure. Inside the child window Activate member function, the parent window'S unique identifier is modified. For instance, in the mWndHomeActivate member function, the IDC_HOME is placed in the switch statement, as shown in the Listing 6 code fragment.
It is here that the Home window is first deactivated restoring the window appearance. Window index is then assigned to the window focus position (tFocus.pos). A paint call is then made to the parent window, placing focus on the Home button.
Window routing procedure
The Activate and Deactivate member functions follow a specific code procedure, which I call window routing steps. Both of these member functions have two or three routing steps, as shown in Table 3.
Table 3: The window routing steps
|Window routing steps||Activate member function||Deactivate member function|
Window painting only applies to Activate member functions. If we want to either paint the window for the first time or repaint the window due to some dynamic changes to the window appearance, all we need to do is to set the m_bDirty flag to TRUE and call Activate for that window. After the window is painted, focus needs to be placed on the active control, which will be dealt with in the next step.
If a message is sent to a window, the first course of action to perform, besides possibly painting the window, is to check if one of the window objects contains the focus object. This is based on the focus position within the navigational grid. If one of the control objects has focus, its switch statements would handle this message. The objective in the Activate member function is to orchestrate the different behaviors of the application, which could range from updating the window appearance to calling other tasks, which, in turn, could cause other behavioral changes.
Within the Home control object (IDC_HOME), another switch layer is used to determine which window event messages this object will handle. In this case, only the ACTION key will cause the object to perform some function; otherwise the Home button gets painted with a focus appearance. When any other key is pressed, the IDC_HOME button control will flicker from paint appearance to focus appearance. What is happening here is that the deactivation restores the appearance of the button and then the activation repaints it to a focus appearance.
The Deactivate member function behaves in the same manner, except that its objective is to restore the state of the window's appearance.
In the last step, a check against window IDs is performed to see whether the messages received by the window need to be passed into one of the child windows. Just like control objects, all windows need to have a unique identifier (for example, IDW_HOME). These window identifiers are arranged in the same sequential order as the tables in the navigational grid. If a match is found, the message is passed to the child window and the same set of window routing steps occurs for the child window, as shown in Listing 7.
As mentioned in Part 1 of this article, resources for all foundation class objects are declared in the application's resource file. The resource file contains the initial conditions of an object. Due to the fact that even relatively small applications can contain a large number of control objects, devising an organizational method is important. What works for me is to group the control objects that are involved in a particular window together. Within each of these window groupings, the objects are arranged in alphabetical order based on the object type, as shown in Listing 8. This allows the resource file to serve as a kind of table of contents for the much larger resource.c source file, which contains each object's initial properties. Listing 9 contains a partial list of the project resources.
A little more work still needs to be done; however, you should have an idea of how to complete the rest of the project.
In Part 1 of this article, we explored the features of C++ that are supported by object-oriented C. We also created a class construct using a structure that contains functional pointers. In developing the OOC foundation classes, we found out that additional overhead is necessary to support concepts such as the "this" pointer and functional binding, along with creating class hierarchy with aggregation relationships. This follow-up article put these ideas to work by developing the AWC infrastructure, which follows a common template. Also, we used some familiar terminology when developing our windowing message structure.
In writing these two articles, I was hoping to provide an alternative insight to developing abstract constructs and a window application model within the C language. As languages become more abstract and software engineers are moved further away from the inner workings of a software language, object-oriented C allows you to stay close to what's behind the scenes and also have the abstraction and the ease of use that more modern languages possess. esp
Matthew Curreri is a senior software engineer at Panasonic AVC American Laboratories. Matthew has developed software for both HDTV and TV satellite set-top boxes. He holds an MS degree in electrical engineering from Drexel and a BS degree in electrical engineering from Rutgers. He may be reached at email@example.com.
- Figure 1: The home security system story board
- Figure 2: The application class diagram
- Figure 3: OOC application model
- Figure 4: A navigational grid
- Listing 1: The CWndApp Class header and source (app.h and app.c)
- Listing 2: The CWndMenu class (wndmenu.h)
- Listing 3: The WinTerm, Run, and WinMain functions (wndmain.c)
- Listing 4: The InitInstance and Construct functions
- Listing 5: The navigational grid (global.c)
- Listing 6: The Home Window Activate member function (wndhome.c)
- Listing 7: The Activate, Deactivate, Constructor, and New functions (wndmenu.c)
- Listing 8: Project resource declarations (resource.h)
- Listing 9: Project resources (resource.c)
Return to Table of Contents