Programming for the OS/2 PM in C:Window Resources & Controls

by Rick Papo

The window message processor - Index - Window State & Some Graphics

Part IV. - Window Resources & Controls
Last month we added a simple class message processor to our test program, and now we will expand on that example, adding several standard controls and demonstrating how they are dealt with.

DOS executable files (EXEs) stored program code and data for loading into memory, in blocks referred to as 'segments'. In general, code segments contained executable instructions and no data, and data segments contained exactly that: data - numbers and texts that would be used in the computations. Under OS/2 this separation was enforced more strongly, in that code segments could no longer be treated as data, and that data segments could no longer be treated as instructions either. In addition, a new type of segment was added: the resource segment. Resource segments are like data segments, except that they cannot be altered. They are for reference (read) only.

Why do this? One problem that IBM (and to a lesser extent Microsoft) had was support for internationalization of programs. The basic functionality of programs (the instructions themselves) does not really need to change between countries, but the language and certain other aspects of the program's presentation do need to change. A script language (RC) for describing resource segments and a compiler (RC.EXE) was built for converting these scripts into resource (RES) files, which could then be attached to EXE files for use by those programs.

I didn't explain this last month, though I used it in the sample program, albeit for a very simple purpose: moving the window title text into the resource segment by means of one of the types of information that can be stored there, a String Table. A resource file can contain a single string table, and that string table can contain up to 65536 different text strings, each associated with a unique numeric identifier. String Tables are defined in the RC file in a way similar to this: Normally, one would define the numeric identifiers in a header file shared by the RC file and your program source files. The RC compiler understands the #include and #define directives used by C, but be warned: it does not understand complicated versions of #define using mathematics to define the constant. All #define statements used for resource identifiers must be numeric constants, nothing more complicated.

To use the string table texts from within your program, the function 'WinLoadString' is used, like this: char Title[80];              // Text buffer. WinLoadString(Anchor,        // From WinInitialize.   ResourceLibrary,            // Normally zero.   IDS_TITLE,                  // String identifier.   sizeof(Title),              // Text buffer size.   Title);                     // Text buffer address. If the text string was longer than the buffer you provided, as much of it as will fit is returned along with an error code.

There was another use made of the resource segment last month also, to define the program's icon and associate that icon with the window. There were several things that had to be done to accomplish this: first, the frame window control flags needed to include FCF_ICON, which indicates that upon constructing the frame window the icon will be retrieved from the resource segment. Second, the resource segment handle must be specified in the FrameControlData structure. If the resources are directly attached to EXE file (they don't have to be), this handle is zero. Third, the common identifier for all resource items to be used by the frame window must be specified in the FrameControlData structure. The constant ID_RESOURCES has been defined for this purpose in the sample program. Fourth and finally, the icon must be defined in the resource script, like so: POINTER ID_RESOURCES Article4.ICO OK. We have now explained the uses made of the resource segment in last month's sample program. Now we will make further use of resources to improve the program. First, we will add a menu. The standard window frame class knows how to manage two different menus: the system menu and the action bar. Last month's sample program had a system menu already, activated by the FCF_SYSMENU frame control flag. To add an action bar menu we must add another frame control flag, FCF_MENU. In addition, we must describe the menu in the resource script. This is done as follows: MENU ID_RESOURCES {                        SUBMENU "~File", IDM_FILE_MENU {                                MENUITEM "E~xit\tF3", IDM_EXIT }                        ...                 } The menu resource must have the same identifier as the other resources that will be used by the frame window, and within the menu definition can be a number of entries, all them either MENUITEM or SUBMENU. Both of these entries are defined by their keyword, followed by their text in double-quotes, followed by their own identifiers. These identifiers should all be unique, and should be identified in the shared header file. We will see how these identifiers will be used shortly. In addition, the menu item texts have one special attribute: if the character '~' is placed in them, then when the menu item is displayed, the character following it will be underlined and will be the 'mnemonic' for the menu item. That is, if you have the menu displayed, and you type that character, then that menu entry will be selected.

Two other things about menu entry texts: first, if you embed a tab character (\t) in the text, the menu manager ensures that the tab moves beyond the length of the longest text defined in the same submenu. By custom, the tab is used for only one thing: to tell the user about a keyboard shortcut (accelerator) associated with the menu item. The menu, however, does not implement the accelerator itself. This requires more resource definition.

To define a keyboard accelerator for the frame window, the ACCELERATOR resource needs to be defined. This definition looks like this: ACCELTABLE ID_RESOURCES {                  VK_F3,     IDM_EXIT,  VIRTUALKEY } The accelerator table should have the same identifier as the other resources used by the frame window, and the frame control flag FCF_ACCELTABLE should be set in the frame control data. The accelerator table entries have the general format of 'keycode, command identifier, options'. In this case, we want to detect the F3 key and convert it to the File/Exit command IDM_EXIT. Because it is not a normal character, but rather what is referred to as a virtual key, the key is specified by VK_F3 (defined in os2.h with INCL_WIN), and the accelerator table entry option is VIRTUALKEY. If you wanted to use the X key instead, then you would have entered "X" instead of VK_F3, and the VIRTUALKEY option would have been omitted. One warning about this: accelerator table entries are case sensitive. If you want to capture a particular alphabetic key all the time, you will need to create two table entries, one for the capitalized letter, another for the lowercase letter. If you want to use the same key several different ways, in combination with the shift, alt or control keys, you should define the table entries starting with the most restrictive combination first, like SHIFT, ALT before SHIFT. The available options include VIRTUALKEY, SHIFT, ALT, CONTROL, LONEKEY and others.

Now that we've defined a menu and a set of keyboard accelerators to match it, what do we do with them? By virtue of having added the definitions and having added the new FCF_ specifiers to the frame control data, the window now has a menu and keyboard accelerators. The only problem is they don't do an awful lot. The Presentation Manager tells the program when the menu items are selected, or the accelerator keys are detected, but the program is not reacting to those messages. When either of these two events occurs (or a button is pressed, for that matter) a WM_COMMAND message is sent to the program, with the menuitem/accelerator/button identifier number as a parameter, as well as some other information. To the window message processor the following case must be added to the switch on the message identifier: case WM_COMMAND: { switch (SHORT1FROMMP(mp1)) { case IDM_EXIT: { WinSendMsg (hwnd,WM_CLOSE,0,0); break; } /* endcase */ } /* endswitch */ return (0); } /* endcase */ The WM_COMMAND message contains the originating control's identifier stored in the first word of the first message parameter, so the macro SHORT1FROMMP can be used to extract it. We switch on this identifier because we will presumably be reacting to a variety of control's messages here, and each control will probably require distinct processing. In this case we are only reacting to the IDM_EXIT identifier, and the appropriate response here is to command the window to close itself. In the sample code you will find the appropriate responses to a number of other messages.

The window message processor - Index - Window State & Some Graphics