OS/2 Frequently Asked Questions (PM Related)
Written by Dean Roddey
- 1 Introduction
- 2 The Subjects of This Article
- 3 Changing Window Styles
- 4 Dialogs as Main Windows
- 5 Window Enumeration
- 6 Font Ids
- 7 PM Resources and Resource Ids
- 8 PresParams and System Colors
- 9 Subclassing and Superclassing
- 10 Transparent Windows
- 11 Window Extra Data
- 12 Window Messages
- 13 Window Relationships
- 14 Summary
As someone who spends a considerable amount of time in the CIS OS/2 developer's forum and in the OS/2 developer news groups, I've seen that there are a core set of PM questions that are asked time and time again. The obvious reason for this pattern is surely the lack of practical documentation on these subjects I'm sure. Since EDM/2 is geared towards filling this hole in IBM's documentation, I thought I would add my two cents and answer these questions in as practical and (hopefully) unconvoluted a manner as possible.
The Subjects of This Article
I will cover a number of topics in this section that rotate around one or other popular misconception or obscurity related to PM programming. They are not in any particular ranking or order necessarily. (well ok they are alphabetically arranged.) Here is a list of the topics covered:
- Changing Window Styles
- Dialogs as Main Windows
- Dialog User Interaction (and how to do it yourself)
- PM Resources and Resource Ids
- Presentation Parameters and System Colours
- Transparent Windows
- Window Extra Data
- Window Messages
- Window Relationships
Changing Window Styles
A very commonly asked question is "Can I change a window's styles after it's created?" The answer is maybe, maybe not. It depends upon the particular type of window. There is, unfortunately, no standard mechanism for doing this kind of thing. So it becomes just a matter of experience as to what styles you can manipulate after the fact.
In a Perfect World
The obvious solution to this issue would be a formalized mechanism through which window styles can be modified, and through which a window could indicate to a client application what styles it will allow to be changed. For instance a WinSetWindowStyles() method would take a window handle, a set of new styles, and a relevant bits mask. It would get the current styles, weave in the new ones, and then send a message to the window telling it the new styles (WM_NEWSTYLES perhaps.) The window would look at the current styles and new styles, update itself with the ones it would accept, and return to PM the changes it could do. PM would return these to the client code. The update would be as efficient as possible since the window would know what parts of itself were affected.
However, there is no such thing in PM. To a PM window, its styles are just a set of bit flags put into a predefined place in the window's extra data words (see Window Extra Data below.) During window creation, PM will place the styles (passed to WinCreateWindow) into this area. After that, there is no formalized usage of the styles flags. Each class of window is free to do one of 3 things.
- Look at the styles on every paint and paint itself accordingly
- Look at the styles upon creation and ignore them for evermore
- Look at some style bits in manner #1 and some in manner #2
Many simple window classes can work purely on the first 'stateless' principle.On each paint it just looks at its current styles and paints accordingly. For these types of windows, the window styles can be easily modified by extracting the current styles, modifying them as desired, putting them back, and invalidating the whole window (unless you know for a fact that only a particular area of the window is affected and can successfully calculate it.) This is obviously less desirable than the perfect world, since often this scheme will cause gratuitous repainting which is unsightly and wasteful of CPU time. But at least it can be done.
Some window classes only look at certain styles once at creation and then never bother to check them again. The most obvious example is when a particular window class can optionally create child windows (such as scroll bars.) Once those scroll bars are created, there is no real reason to look at those styles again, at least from the window's perspective. One reason for taking this approach is the very lack of a formalized mechanism for updating styles. For most controls, the only thing they can really do is look at the styles during a paint. But, for doing things like creating/destroying/moving child controls and whatnot, the paint event is not a very good place to do such things.
Unlike normal window styles, frames do have a formal mechanism (separate from the window styles system) for addition and subtraction of standard frame controls. So, for the most part, you can cause them to come and go as you please. You just need to destroy unwanted ones or create new desired ones and then send a WM_UPDATEFRAME message with bits on for the controls you deleted or want added. Be sure to give them the correct FID_XXX window ids and make the frame their parent and owner, though some frame controls are not separate windows and have no id.
Confusingly enough, the frame window class has styles for most of the frame controls; but, you should only use them from a dialog resource (or so the documentation states.) When creating a frame via WinCreateStdWindow() or WinCreateWindow(), you should use the FCF_xxxx values. These are passed in via a FRAMECDATA structure as the window control data.
Dialogs as Main Windows
Lots of people ask about how to use dialog windows as main windows. The reasons are obvious, such as being able to draw the windows instead of doing time consuming positioning calculations and creating them on the fly. Also, you get the benefit of having the window sized to fit the default system font on the target hardware. A main window dialog is definitely doable, and it's pretty easy. But there are a number of different approaches to do it, each of which has its own pros and cons.
Everyone has used WinDlgBox() to run dialogs but, to understand the best way to use dialogs as main windows, you need to understand what goes on under the hood when you call it. Basically two APIs, WinLoadDlg() and WinProcessDlg() make up the guts of WinDlgBox(), with some extra housekeeping in between. So first a quick overview of what happens when you create a dialog.
How Dialogs Work
The WinLoadDlg() API uses DosGetResource() to load up the dialog description of your dialog (see PM Resources and Resource Ids below) from the resources left in the DLL or EXE by the resource compiler. It uses this description to create a frame window and all of the individual controls defined by the dialog. The controls are created in the same order as you put them in the dialog, which becomes important below. Once everything is created it will subclass the frame using your dialog procedure (see Subclassing and Superclassing below) and send you a WM_INITDLG message.
Once done with all of that, WinLoadDlg() destroys the dialog definition buffer and returns. Note that it only creates the windows and gets everything set up but nothing else. The frame was left invisible so you don't see it yet (unless you showed it during WM_INITDLG processing.) At this point, the frame window is no different than if you had manually created it, and its controls, yourself. So, as far as windows go, a dialog is very literally a frame window. It is only special in the fact that it is subclassed by a cooperative party (you) and that it is processed differently. You are the cooperative party in that you voluntarily pass any unhandled messages to WinDefDlgProc(). Much of the magic of dialogs is really in WinDefDlgProc(). It provides the standard dialog style responses to incoming user and system input. If you did not pass messages to WinDefDlgProc() then you would not get any of the standard dialog reactions.
Modal Message Loops
When a dialog is being processed, you've probably noticed that it will not let any input go to the other windows of the program. It is in a 'modal message loop'. When you run a dialog, your message loop is no longer being used. Instead, a message loop inside PM is getting input. This gives it control over where user input messages go to or if they are allowed to go anywhere at all, since input messages are always posted and therefore come in via a message loop).
The WinProcessDlg() call is what provides this modal loop. It does the usual WinGetMsg() and WinDispatchMsg() calls, but also watches for dialog specific circumstances such as the dismissal of the dialog. WinDismissDlg() does two things, it stores the caller's return code parameter in the frame's extra window word (at QWS_RESULT) and sets the FF_DLGDISMISSED flag in the frame's QWS_FLAGS window words. WinProcessDlg() will check the dismiss flag and see that its time to return. So it will pull the result code from the window's extra data and return it to the caller as the result code of the dialog.
Theoretically, you could clear the dismissed flag and call WinProcessDlg() again to start up the dialog again. But it would look ugly because the dismiss would hide the dialog and then it would reappear again. I have not tried it myself, but you can give it a try if you think it would be useful.
Also, you could theoretically create a frame, and any desired control windows within it, subclass it to a dialog procedure and then call WinProcessDlg() to process it modally as a dialog. WinProcessDlg() probably wouldn't know or care that the windows were created manually instead of as a dialog resource. Just be sure that you subclass it to a window procedure that is written like a dialog procedure, i.e. it uses WinDismissDlg() and passes unhandled messages to WinDefDlgProc().
WinProcessDlg() does not destroy the window when it returns, but it does make it invisible. If you use the separate load and process steps yourself, then you must destroy the dialog frame. If you called WinDlgBox() it will destroy the frame before it returns, and it will pass on back to you the return that came back from WinProcessDlg().
The Easy Way Out
The easiest way to have a dialog as a main window is to just do the usual WinInitialize() and WinCreateMsgQueue(), then use WinDlgBox() to run a dialog. When it returns, terminate and exit. Having the dialog do all the work is nice, but it is also the reason why this is sometimes not the best way to do a main window as a dialog. For starters you don't have a lot of control and some of the standard dialog processing is not really appropriate for main windows. For instance:
- Definitely don't just destroy the frame window when you use this scheme. That will not cause the modal message loop to exit, but it will destroy your ability to interact with your program. So basically the program will just be hung.
- There are certain dialog'isms like the fact that pressing escape will, if the WM_CHAR message gets to WinDefDlgProc(), cause a dismissal of the dialog, thus exiting your program. This is not always desired behavior for a main window.
- You cannot do the usual step of allowing the user to back out of an exit because you don't have control over the message loop.
The Next Step
The next step up the ladder of complexity vs flexibility is to use the WinLoadDlg() API to load up the dialog and then enter your own message loop, so that you are not processing it modally. As discussed above there is nothing magic about a dialog window itself, i.e. using WinLoadDlg() is not much different from you creating the frame and controls yourself, or at least the results are not. But once you move to this non-modal dialog style, you are not really a dialog any more, so you don't want to keep calling WinDefDlgProc() or otherwise acting like you are still a dialog when you are not. You can still pretty much get away with such things but you might run into some interesting problems. For instance, if you just dismiss the dialog on a WM_COMMAND (as a normal dialog would), you will end up with a hung program (from the user's perspective.) The dialog will be hidden but the window will not be destroyed and no WM_QUIT will be issued to the message queue to cause the program to exit. There are some other small problems of that sort, that are liveable but can bite you or confuse your users.
What I suggest is this. Use WinQueryClassInfo() to query the class info for the WC_FRAME class. Remember that the class name for a standard PM controls is not a real text string but a magic pointer, so the name you pass to WinQueryClassInfo() is in the form "#0000x", where x is the number from the defined WC_FRAME value (look in PMWin.h). This information structure has the address of the original frame class' window procedure. If you now pass unhandled messages to that procedure instead of WinDefDlgProc(), you will effectively bypass the dialog specific processing and turn your dialog into a regular subclassed frame window. Keep in mind that you should still handle WM_INITDLG to do any setup if you want to, but you will not see WM_CREATE because the window is already created before it is subclassed to your dialog procedure. If you now just get rid of WinDismissDlg() and you have a pretty normal frame window. If you don't need the subclass any more, for instance because your dialog has a client window, you can call WinSubclassWindow() with the original frame window class. This will get rid of your subclass and save processing effort in the process, since every frame message does not have to go through two layers before it gets handled. I haven't tried it but you could possibly pass the original frame procedure as the dialog procedure and not have to have a dialog proc at all? You try it though and let me know, and I will use it if it doesn't waste your partition table or anything. (I'm so brave. [grin])
As an aside, WinDefDlgProc() is really doing the same thing itself internally, handling some messages and passing others on to the original frame procedure. You are just skipping the middle man by doing this yourself.
Though this is the most technically correct approach, it does have one downside. You lose the automatic tabbing support that WinDefDlgProc() provides. This though is very easily dealt with. See #Dialog User Interaction below for a discussion of this issue.
The Ultimate Control
The ultimate control of dialogs is accomplished by manually parsing the dialog resource and creating the controls yourself. That is a pretty large subject so I will not cover it here. However you might want to read my article in the July/Aug 1994 issue of 'OS/2 Developer', which covers this subject pretty well. Given the information in this article and that one, you should be able to handle it with a little experimentation. This knowledge is also useful for creating your own dialog editor if you care to do such things.
I use this method in my CIDLib class libraries, which allows the TDialogWnd class to be very expandable. Its constructor provides a callout (which is called for each new control parsed from the resource) so that the calling code can optionally override the type or attributes of each control. It's a pretty powerful and flexible system, but without all the work of providing your own screen builder (though I will probably do that eventually.)
Dialog User Interaction
For various reasons, people often want to understand how tabbing in dialogs works. Sometimes, as in the previous section, they've loaded a complex dialog as a main window and want to retain the tabbing capability. Other times, they have a similar complex set of controls that they have created manually and want to provide tabbing for them. This section provides some background on how tabbing works and tells you how to easily provide it for yourself.
The basis of tabbing is the ability to enumerate all of the children of a particular window. The WinBeginEnumWindows() API will begin an enumeration. You provide it with the window whose children it is to enumerate. It will return you a handle, which it uses to maintain an internal state table for the enumeration session. Once you have an enumeration handle you can use WinGetNextWindow() to get each successive child window handle. When done, you use WinEndEnumWindows() to close the enumeration handle. WinGetNextWindow() will return a nul handle when it hits the end of the list.
The Z-order of the child windows drives the iteration process. In other words, the windows are enumerated in their defined Z-order. This is why the order of creation of controls in the dialogs is important for the desired tabbing order, as you will see in the next section.
Window enumeration is a fundamental type of magic that is behind many cool utilities that show you the hierarchical arrangements of any process' windows. You can use it to good effect sometimes, so check it out.
Tabbing is accomplished via the WinEnumDlgItem() API. This API is not magical in any way, and just builds upon the basic window enumeration system discussed above. It effectively just enumerates the child controls of a particular window, queries their styles, and looks for WS_TABSTOP and WS_GROUP style bits to decide what to do. It can answer questions like, "From the current focus window, what is the first/last control in the next/previous group" or "From this particular window, what is the next window that has the tabstop style" or "What is the next/previous window in the same group". The tabstop and group styles really have no other use, so they are often not used when you create controls manually. But, if you want to provide tabbing for manually created controls, you must apply them.
As is often times the case in PM, the Dlg part of the name in WinEnumDlgItem() is a misnomer. It is called that because that's all OS/2 itself uses it for. However, you can use it on your own frame windows, non-modal dialogs, or on the children of your client window, to easily provide tabbing without having to use dialog processing. You always call it with a parent window whose children it is to enumerate, so there is no ambiguity about what window it is operating on.
When a dialog is loaded, the loading code will create the controls in the order they are found in the dialog definition, giving each one HWND_BOTTOM as the z-order. This effectively stacks the controls up in the same order as they were drawn by the dialog editor (or manually written by you into a dialog definition.) When you want to provide tabbing for your own manually created controls, you just need to make sure to create them in the correct order and give them the desired tab stop and group styles, just as though you would have done them in a dialog editor.
Doing It Yourself
If you want to provide your own tabbing, I would suggest that you write a single function, say bDoTab() or something similar, that is just a wrapper around WinEnumDlgItem(). That way, in every application, you can just put a call to it in your client window's WM_CHAR message handling. It will look at the character and see if it is a tab or arrow key or Alt-mnemonic character. If so, it will do the enumeration and move the focus to the new focus window, returning TRUE to say it handled the message. The WM_CHAR code should then just return TRUE to say it was handled. Otherwise, it returns FALSE and the WM_CHAR code should pass it on to the default handler or eat it or process it as needed. The type of character (tab, back tab, up, down, left, right) controls the third parameter to WinEnumDlgItem(). This parameter is one of the EDI_xxxxx values, which tell WinEnumDlgItem() what kind of enumeration to do.
To really work like dialogs do, you also need to check for mnemonics. So, if the Alt key is pressed and the WM_CHAR message is a character message, you should enumerate the child windows and send each one a WM_MATCHMNEMONIC message. If a control responds back saying that it matches the mnemonic character, put the focus on that control.
If the character is an Enter key, then enumerate the children and send each one the WM_QUERYDLGCODE message. This will cause the child window to return a set of DLGC_xxxx bit values. You just need to look for one that has the DLGC_DEFAULT bit on and send it a BM_CLICK message. This will cause that default button to send a WM_COMMAND message as though the user clicked on it.
There are some more obscure features you might have to deal with to get exactly the same results as a dialog but those are the most common ones and should serve most apps. And some of the dialog processing, as mentioned above, is not desirable for a normal window anyway. Of course, if you are doing a class library, then the tabbing support should probably be implemented in the basic window class so that all windows can optionally provide tabbing for their children. In my class library, the TWindow class has an 'autotab' attribute. If set, it calls its internal tabbing support wrapper function. This makes it trivial to provide tabbing any time its desired. If the character input causes a tab operation, then the window is not even bothered by telling any input occurred so the support is transparent and automatic.
Font ids can cause no end of problems if they are not managed correctly. Unfortunately here as well there is just not sufficient documentation of the big picture. Each process can have up to 255 logical font ids in existence at once. Since font ids are associated with a presentation space, and most presentation spaces are cached and therefore have short lifetimes (usually just for a WM_PAINT), the maximum number of font ids is usually not an issue. Other applications, such as those which create persistent presentation spaces or more complex ones that have separately developed black box subsystems or that have multiple drawing threads, correct management of font ids is important.
The simplest application is one that never uses any other font than the default system proportional font. Such programs have no font id management issues because the 0th font id always magically exists. For more complex software the two biggest problems with fonts are that multiple, separately developed, subsystems fight over the same ids and/or the leakage of font ids. The first will create weird problems because creation of logical fonts will fail (due to the id being in use by someone else) or logical fonts will be destroyed behind the back of someone who is using it. The second will eventually bring the application to an eventual, often non-obvious demise.
Font Id Management
The first step in font management beyond just using the system proportional font is to define a set of global defines for the fonts to be used by the program. If only one thread does all painting via cached presentation spaces, using font ids set up and then torn down all within the WM_PAINT code, then this scheme will work fine and all windows can share the same logical font ids without any conflict. It's simple and easy to understand, as long as the program is simple.
Once a program starts creating persistent presentation spaces, which can cause font ids to be in use outside of WM_PAINT code, or using multiple GUI threads, which can possibly have font ids in use simultaneously, the issue of font id management needs to be more formalized. Though a program can certainly provide its own font id management system, there is one built into PM that works well enough. You could though improve upon it if you felt it worth doing.
The GpiQueryNumberSetIds() API will return the current number of font ids in use by the process (which are also used for custom patterns by the way, so this number may not just reflect fonts). The GpiQuerySetIds() API will return information into 3 separate arrays (confusingly allocated as a single block of memory though) that tell you about the font ids in use by the process. Using these two APIs, you can determine what ids are currently in use and, by extension, figure out one that is safe to use.
The easiest way to use the PM font id management APIs is to write a function/method named, for instance, ulGetNextId(). It will find and return the next available id using the APIs mentioned above. The only catch here is that, if you have multiple GUI threads or are writing general purpose code that might have to handle that situation, you must make the finding of a new id, and its being put into use, an atomic operation that is protected by a semaphore. The reason for this precaution is that GpiQuerySetIds() API doesn't care whether you've checked and found an id you liked. It only knows which ones are in use. If you use it to find an unused id, then another thread gets scheduled before you get the id into use and calls ulGetNextId(), it would get the same id. One of them would later lose when trying to create a logical font with that id, causing errors from weird displays to outright crashes.
So you really need another layer of code above the ulGetNextId() call. You need to provide APIs that lock a common semaphore, get an id, create logical fonts or custom patterns, then release the semaphore. This synchronization will insure that your management of font ids are thread safe. In most programs, just the natural synchronization of PM's message passing architecture will insure that the semaphore is not actually ever needed. But even in single threaded programs its just safer to do that small amount of work and never worry about it again.
Of course, none of this helps if you must link to 3rd party code which chooses not to play with you. It cannot know ahead of time what your locking scheme will be so you should assume (unless told otherwise) that it will probably use font ids (if at all) in the most optimistic manner, as discussed above. If you are working from some common base library code it should definitely provide such an API for all to use.
PM Resources and Resource Ids
Some of the most obvious missing pieces of information in the OS/2 documentation is related to the use of resources and resource ids, and the possible schemes for managing them. Each OS/2 resource has a type (defined by the RT_xxxx values in BSEDOS.H) and an id. When you watch the resource compiler putting out numbers like 3:100, that means a menu (RT_MENU is 3) with the id 100.
The type and id values are used to extract the resource from a particular DLL or EXE module, using the DosGetResource() API. You might never use DosGetResource() yourself, but that is the underlying API that extracts a resource into a system allocated memory buffer. A program does not have to be a WINDOWAPI program to access resources in this way because the actual resource is not created, just the description of it is loaded.
The Resource Id 'Name Space'
Since you must access a resource by providing the module handle of the DLL or EXE module that it is attached to, there is effectively a 'resource name space' with three levels. So a particular resource has a 'path' like this:
This means, though it never seems to be pointed out, that the type:id pairs are automatically unique within each DLL or EXE module. So, for instance, you can have a bitmap with an id of 1 in every single Dll in a program and there will be no problem with id conflict. There can also be a bitmap and a string and a dialog with the id of 1 within a particular DLL or EXE. This is particularly convenient ("Nay, absolutely required," he said in a regal manner), when building black box DLLs for distribution or doing large, multiple group development. You just don't have to worry about resource id distribution except within a particular DLL or EXE module.
Resource Access APIs
You will generally access resources via a set of higher level (read PM oriented) APIs which themselves use DosGetResource() to load up the raw resource description. These APIs then use that description to build the actual PM resource, after which they can discard the raw buffer. WinLoadString(), WinLoadDlg(), WinLoadPointer(), etc. are examples of these convenience resource APIs. You could easily (relatively speaking of course) replace these APIs with your own and enhance or expand on them, because they are not magical in any way and have no privileged knowledge or system access. The format of the raw buffer is, as far as I know, the exact format from the .RES file that was attached to the DLL or EXE. Finding the details of that format for some of the resource types is though sometimes like drawing blood from a stone.
For instance, in my class libraries, I wanted to have a strict separation between GUI and non-GUI code but wanted to have string resources accessible in the lowest level (kernel encapsulation) DLL. So I provided my own methods to extract string resources. Technically I could probably call WinLoadString() with a 0 HAB, but I did not want to cheat. It's quite simple and you can do things like cache the most recently accessed strings for fast access (if that makes sense for your access patterns.) String resources are actually grouped in sets of 16, called string tables, so you extract them in these tables and pull out the particular one of interest. So caching this table is the most convenient way to keep them in memory. This also makes it most efficient to group your strings (if they are not all just consecutively numbered) so that they start on ids which are multiples of 16.
While I'm spending your development dollars here, another good use of this technique is in a scalable interface. Lets say you wanted to provide a 4 level interface (from expert down to severely challenged, for me), you could arrange your string ids such that each 'message' started on a multiple of 16. But a 'mes sage' would actually be 4 strings at successive ids starting from the first one. The program itself would just ask to load the 'message id' which would be the id of the first string in each group. The loading function would pull in the string table for that 'message' and then use the program's current interface level to return the actual string for that level. Having the base id for each message an even multiple of 16 insures that all of its related messages are in the same string table and loaded together.
I also use DosGetResource() to load up dialog definition resource buffers and use that template to create my own dialog windows. This gives me more control. See Dialogs As Main Windows above.
PresParams and System Colors
Though they are relatively well covered in the documentation, presentation parameters and system colors (or at least their proper usage) are not well known. Part of the problem is that their relationship to the standard controls, their relationship to each other, and how custom controls should use them in order to be good citizens is definitely not explained clearly enough.
The Standard Controls
The best way to see the intended use of presentation parameters (or presparams as they are called) and system colors is to look at how the standard PM controls use them. A basic requirement of the standard controls (because of how widely they are used) is that they be configurable to the greatest degree practical. An obviously important area of configurability for a control is that of the colors and fonts used in drawing itself. So they all have a logic kind of like this:
- For drawing my foobar, I will use presentation color X
- If presentation color X is not present I will use system color Y
So, when you use the system color palette to set a system wide color (by holding down Alt when you drag and drop a palette color), you are setting a particular system color. You can also do this via your own programs, so you can create your own system palette manager if you desire. There is nothing magical about the system palette widget. So every window which has not had presentation color X explicitly set on it will use the new system color Y. In order to make everyone update immediately, every window is sent a WM_SYSCOLORCHANGE message and the screen is redrawn by invalidating all windows.
In order to allow particular windows to buck the trend and have their own color/font scheme (while still remaining user configurable), PM supports presentation parameters. PM supports a predefined set of presentation parameters which are used by the standard controls (and user programs if they care about presentation parameters at all.) Dropping a color on a window (without holding Alt) will cause the new presparam to be stored on the window and a WM_PRESPARAMCHANGED message to be sent to it. Its reaction will be similar to the WM_SYSCOLORCHANGE discussed above. Presentation colors/fonts are set via the WinSetPresParam() API and queried via WinQueryPresParam(). They can be removed via WinRemovePresParam(). The WinQueryPresParam() API will automatically search up the ownership chain of the target window, if you ask it to, searching for some ancestor window with that particular presentation parameter. I assume that this is intended to allow a single owner window to control the appearance of its owned windows. When a color or font is dropped on a window, PM just calls WinSetPresParam() on that window.
The important things to remember here are that system colors only have to be stored once for the whole system and that all windows call APIs, WinQuerySysColor() in this case, to get their values. Presentation parameters are stored separately for each window on which they are set. PM maintains this storage for you and keeps each window's presparams associated with the correct window handle.
Don't try to read any rhyme or reason into the names of the system/presentation colors used by a window for a particular attribute of its appearance. There are a set of predefined, and pretty generically named, colors. In order to avoid massive proliferation of them, the standard controls will just pick from that list ones with names that are hopefully somewhat related to the purpose for which they are used (but sometimes seemingly unrelated at all.) The documentation on what standard controls use what colors, is extremely pathetic. So its only through the patient experimentation of you and/or others that most of them get figured out. If you have IBM on-line documentation, open the PM Reference and search for "Default Colors" to get about as much as you are likely to find. And even this documentation only says what they system/pres colors are, not what visual aspect of the control they are used for.
Blending Into the Crowd
So, if you want to write a control window that acts like a standard control in its use of colors and fonts, you must take a couple of steps. The most obvious is to write a wrapper function that takes a system color and a presentation color. It will first look for the presentation parameter and return it if found, else it will query the system color and return that. This function then provides the basis for the paint logic's use of colors, while moving lots of possibly replicated grunt code to one place.
You can take two approaches to the presentation colors and system colors your window uses. You can either just be very stateless and query them on every paint, or you can cache them. In the later scheme, you set them initially via a manual query and thereafter update them on any WM_SYSCOLORCHANGE or WM_PRESPARAMCHANGED message. Your window class would just call, for instance, a SaveColors() function initially upon creation and then upon receipt of either message thereafter. This would keep all of the code in the same place.
Custom Presentation Parameters
You can create your own presentation parameters by using values greater than PP_USER. The system will not send them to you of course since it doesn't know them from Adam, but you can send them to your own windows. If you set them on 'foreign' windows they will just be ignored (though PM will store them on the window anyway) because those windows will not understand them. Worse, they might have defined their own custom params with the same id (and different data layout) and freak out.
Saving/Reload Presentation Parameters
The WinStoreWindowPos() and WindowRestoreWindowPos() APIs will save and restore the size, position, and presentation parameters of a window. The information is stored in the system INI file, so you have to provide the application and key names to use. These may or may not be very useful to you, and the same functionality is pretty easy replicated on your own if these do not do exactly what you want.
Subclassing and Superclassing
PM provides two simple (though loose and not very formalized) mechanisms for inheritance with window classes, called subclassing and superclassing. If I want to have a button that acts just slightly different than the standard push button, it makes more sense for me to try to just affect the behavior that I want to change and let the other functionality be handled as it was before. Actually, in this case, I could possibly make it a user-drawn button but that is a special case as it only affects visual behavior, and muddies the waters of this discussion for the moment so pretend it does not exist. Let's say that I need a way to affect just the click of button 2 so that it pops up a help screen for that button, so really all I want to do is change what a button2 click does.
Since almost all stimuli that affect a window come in the form of messages, if you can get the messages to come to you first, you can handle the ones you want and pass the others on to the original recipient, letting him/her handle them as before. The simplest mechanism for doing this is to subclass the window, via the WinSubclassWindow() API. You pass the window handle of the window you want to subclass, and a new window procedure. PM makes your new window procedure the new recipient of all messages coming to that window. It also returns to you the address of the previous window procedure, so you can pass any unneeded messages on to the original procedure. Actually you have a number of options:
- Handle the message and return without letting the original see it
- Provide some preprocessing, then pass it onto the original
- Let the original handle it first, then provide some post processing
- Let it go straight to the original window procedure.
As far as I know, the window procedure to which a window's messages go is always stored in the QWL_PFNWP window words, so most likely all WinSubclassWindow() does is get the current value out, put the new one in, and return the old one. So you could easily do this operation yourself if you chose to do so. When a window is first created, this value is set by looking up the window class passed to WinCreateWindow() and getting out the window procedure given when the window class was registered via WinRegisterClass().
Now we can provide the special processing for the button by trapping WM_BUTTON2DOWN and WM_BUTTON2UP and doing our special processing on the up event. In this case we would not pass them on to the original since we don't want it to take its default action of posting a WM_COMMAND to its owner.
Dialog windows are subclassed frames. When you call WinLoadDlg() or WinDlgBox(), you provide your dialog procedure address. After WinLoadDlg() (which is in turn called by WinDlgBox() if you call that) creates the frame and the controls, it then calls WinSubclassWindow() on the frame using your window procedure and saves the original frame procedure. So messages first come to your window procedure letting you deal with it. If you pass it to WinDefDlgProc() it will possibly handle it, in order to provide standard dialog functionality such as tabbing, or pass it on to the original WC_FRAME window procedure. So the effect is of a layered set of 'virtual message handlers' that cooperate in providing an extensible feature set, with each layer taking one of the above 4 approaches to each message received.
Superclassing is less often done than subclassing because its advantages, though sometimes significant, are often not needed enough to justify it. The best way to understand the differences between subclassing and superclassing is to realize that subclassing is for applying per-window overriding of messages. In order words, subclassing is done on a per-window basis, after a window has already been created. The creator of the window usually decides whether he/she wants to apply the subclass, not the window itself. Another disadvantage of subclassing is that, since the window must already exist in order to subclass it, the WM_CREATE processing happens before the subclass can be applied.
It would be nice to be able to extend, limit, or otherwise modify the functionality of an existing window class such that it applied to all instances of that window (without explicit action having to be taken by the client code.) Superclassing provides these benefits. To superclass a window you must follow these steps.
- Query the class information of the class to super class
- Store away the count of extra data words it has and the window procedure of the class
- Register a new window class, providing your own window procedure when you register the class.
- If your window class needs any extra window data beyond that of the original class, add your needs to the original class' when you register your new class. One reason you might want to create extra window words is to have a place to store per-window information without using the QWL_USER area that the users of the new window class might need.
The important things to note here is that you have created a new window class. So, when client code creates the window, it will provide your window class to WinCreateWindow(). This has important ramifications to your control of the situation. They are:
- Since the client is creating your window class, you will get the WM_CREATE message. Make sure that you pass it on to the underlying window class first, to let it handle its initialization. Then take care of your own initialization needs.
- You are in control of the situation. You don't need to count on client code to create a window of one class and then apply some subclass you provide to it. This means that the superclass will naturally apply to all instances of the window, not just to selected instances of it.
- You get to add more per-window instance data words, which is often necessary. You cannot use any existing window words because they could all be used by the window class you superclassed (or by per-instance subclasses applied by client code.) By adding your storage needs to the underlying class' your data is at indices beyond those accessed by other users of the window (because they don't know they even exist probably.)
- You can tell at runtime whether a window is of your particular class because you can query its class name or class info. There is not an easy way to tell if a subclassed window has had a particular subclass applied to it. You cannot just compare its stored window procedure address with your class' window procedure because it could have been subclassed as well by client code.
Some standard controls support an 'owner mode'. This is not a subclass or superclass because, in this case, the control is just letting its owner window handle a particular aspect of its visual appearance. Subclassing and superclassing are applied to the control window itself and prevent the parent/owner windows from having to be particularly aware of them once they are set up.
Lots of people ask how a window like a combo box can cover a large part of the screen but only make use of the bottom part when it drops down the window. It does not affect the underlying windows any other time, letting you get straight to the windows underneath via mouse clicks. And you can write a small program to check that the combo box is not changing its size when the drop down list is lowered. It is that size all of the time, also obvious when you use the dialog editor to draw them.
Effectively what is happening is that the combo box is selectively making parts of itself transparent visually and to input. Now everyone probably knows that its easy to make a window visual transparent by just not drawing anything during its painting events. Usually they first find this out by accident and wonder why their window seems to be dragging around a bit of the desktop background. But how do you make the window selectively transparent to mouse clicks?
When it is time to dispatch a mouse event to a window, PM will send it a WM_HITTEST message. The window can respond it one of four ways by returning one of the HT_xxxx values.
- "Yes I'll take that mouse event", HT_NORMAL
- "I'm transparent at that point so let it pass on to lower z-order windows", HT_TRANSPARENT
- "I can't take it but I'm not transparent either so don't pass it on", HT_DISCARD
- "Its an error for me to get a mouse event now", HT_ERROR
In the first instance, the event is passed on to the window. In the second case, WM_HITTEST is passed to the next control in the z-order chain that contains the coordinate of the mouse event. In the third case, the event is just eaten. In the last case, the window is brought to the foreground and PM beeps at you, but the mouse event is otherwise eaten with no effect. This is how disabled windows can easily ignore mouse events but still get the required button down effect of coming to the foreground.
From that explanation, it's probably pretty obvious now how a combo box does its thing. When it gets a WM_HITTEST, it checks whether the event is over a part of itself that is not transparent. If the list is dropped down, then either the list box or entry field will get the message and just return HT_NORMAL. If the button hits the backing window, because the list box is not covering it, it just returns HT_TRANSPARENT and the button goes on to the window that is visible 'through' the combo box.
Handling Your Own Hit Testing
In your own window classes you likely never need to handle WM_HITTEST. You can just let it pass on to WinDefWindowProc(), which just returns HT_NORMAL to let the mouse event come to the window or HT_ERROR if the window is disabled. This is the correct behavior 99.99% of the time. If you do need to handle it, just use the passed mouse position to determine if your window is transparent to mouse input there and return the appropriate hit test result. This result will be passed to the actual mouse event later, as one of the message parameters (which is probably pretty universally ignored but its there.)
Window Extra Data
Under PM, since every window of a given window class shares the same window procedure, there must be a mechanism for each instance of a given window class be distinguishable. This is accomplished by every window having a window handle which is passed to the window's procedure for each message received. Associated with each window handle is a small amount of extra data storage that is recreated for each instance of a window class. Since this window data is associated with the window handle, the window procedure can be totally independent of which window is calling it. The window procedure becomes a state machine and the window's extra is the state storage area, in which the window remembers its current state until the next message is received.
Of course this whole argument applies to the less common use of window classes, which is to create a standard window type that can be used by many different subsystems or processes simultaneously. Some PM programmers have only had need to create a window class for their own client windows or dialogs. Since there is usually only one instance of these windows ever in existence, global variables might be used to track state information. That is a valid approach in these cases, but PM makes it trivial to make even these windows inherently reentrant so that they can easily be instantiated multiple times in the future if needed.
Just FYI, you can also use the same window procedure for multiple classes of window. Often times, if I have multiple small dialogs in a single module, I will make one dialog procedure and use it for all of the dialogs. The id of the dialog can be used, within each case of the switch statement used to process the individual messages, to determine the correct action to take. So the dialog procedure is really a nested set of switch statements. The outer one gets you to the correct message handling code. Within the case for each handled message is a switch on the id of the dialog, which will usually call out to a function appropriate for each dialog. Sometimes, if the code is trivial, it can just be done right there in the second switch. This scheme can cut down on redundant code and keeping track with what dialogs use what dialog procedures, but it has its own set of possible gotchas.
The extra window data of a window is accessed via a set of APIs named WinXXXWindowULong(), WinXXXWindowUShort(), and WinXXXWindowPtr(), where XXX is either Query or Set. There is an extra WinSetWindowFlags() that sets bits in a 32 bit field of extra data. There is not a WinQueryFlags() because WindowQueryULong() is fine for reading them. There is a separate flag setting API because it provides a 'relevant bits' mask parameter which lets you indicate which actual bits you want to affect. Otherwise, you would have to query the existing flag field, weave your changes in, then write them back. Instead you can do all that in one operation.
When you call these APIs, you tell it the index of the memory field you want to access or set. These indices come in two forms, as is discussed next. But the bottom line is that the data is not just a flat memory buffer for you to access at will but a special area that must be accessed via an API.
The Blessed Data, The Common Data
There are two 'classes' of window data. One set is defined by PM and its somewhat magical and the other is window data created by the outside world. In this case the outside world is any window class, including the built in window classes. These magic window data fields are created automatically for every window so they are always available. The data created by each window class for its windows can vary from window class to window class. The amount of memory that a window class needs is indicated when the class is registered by WinRegisterClass().
The indices for the magic window words are easily recognized in PMWin.H. They are the QWL_xxxx and QWS_xxxx values that have negative numbers. All non-blessed window words have indices of 0 or greater. There are other window word indices defined in PMWIN.H, for the frame window class for instance, but they are for window class data and have positive indices.
If you look at the indices for the magic words and the ones for the frame window class, you will notice something interesting. The window class specific data seems to be laid out as a single buffer, though I said it was not above. For instance, the QWL_xxx values each start at an index 4 bytes beyond the last one. The fact is that the class specific data probably is a contiguous buffer, but you are not supposed to assume that because it might change. The indices of the magic words obviously have no relationship to the size of the values they point to. They are just sequentially numbered negative numbers. This probably implies that these values are just stored in various internal structures and the special indices trigger the access APIs to go get them.
Since the class specific data really is like a contiguous range of bytes that can be referenced at any offset, you can use WinQueryUShort() to query the low or high word of a field that is actually 32 bits just by adjusting the offset. This line would query the top two words of the QWL_USER field. This would not work with the system defined values because they are not indices in the same sense.
USHORT usVal = WinQueryWindowUShort(hwndMine, QWL_USER+2);
Ad Hoc Window Data
It is important to note that most window data is 'class wide' meaning that is used in the same way by the window procedure for all instances of that window class. But, for very general purpose windows such as the standard PM controls, there is very often a need for some storage that can be used in an ad hoc way by each individual instance of the window. So, all of the standard controls provides a user definable area, QWL_USER, that can be used differently by each instance of the window, because it accessed by client code not by the window procedure. If you write very general purpose window widgets, this is a good practice. QWL_USER is defined as 0, so it is the first index in the class specific window words. But you should probably not assume it will remain so defined. If you create symbolic names for the indices of your window words, you should technically do them as offsets from QWL_USER, such as QWL_USER+4. This way they will adjust automatically.
The section on Subclassing and Superclassing above covered one way to extend the window storage of a predefined window class. This is sometimes needed to allow extension of the class wide storage while still leaving the user definable storage area alone.
Frame Window Words
The frame window class has the most class defined storage words of any of the predefined controls, about 0x54 bytes or so it seems from perusing the headers. Partly this is because some of them are used when a frame is in 'dialog drag', and partly because the frame control has the most need to play magic tricks in the process of providing standard system functionality. For instance, when a frame is activated, it will put the focus on the last of its descendant windows that had the focus. This is accomplished by its storing the current focus window into QWL_HWNDFOCUSSAVE, when it gets a WM_ACTIVATE message indicating that it is being deactivated. When the frame is minimized or maximize then restored, it goes back to its previous position. It does this by saving away the current size and position in QWS_XRESTORE, QWS_YRESTORE, QWS_CXRESTORE, and QWS_CYRESTORE, when it receives a WM_ADJUSTWINDOWPOS message indicating that it is being minimized or maximized. QWL_FLAGS holds a set of bit flags that give lots of information about the current state of a frame window.
Stupid Window Data Tricks
You can often play nice tricks by knowing the content of the system defined or class defined window words and adjusting them as desired. As discussed above in Changing Window Styles, you can sometimes extract the QWL_STYLES words, modify them, and put them back to change the styles of a window on the fly. As far as I know, you can write a new value to QWS_ID to change the id a window (including to accidently setting it to the same id as another child, which can cause weirdness later.) If you want to control the position to which a frame window will restore, then adjust the restore window words discussed in the previous section. On restoration, the frame window will pull these values out and uses them to restore itself. For a dialog, you can, as far as I know, reenter the dialog after WinDismissDlg() is issued (assuming you used WinLoadDlg()/WinProcessDlg() instead of WinDlgBox()) by clearing the FF_DLGDISMISSED flag and calling WinProcessDlg() again. If you leave FF_DLGDISMISSED set and call it, it will probably just hide itself immediately because it will think it is already dismissed. You can change the window to which focus will be put when the frame is activated by changing QWL_HWNDFOCUSSAVE. Though I have not done it myself, I think you can adjust where the frame will minimize to the next time by adjusting the QWS_MINIMIZE and QWS_MAXIMIZE words. I assume that, when the minimized frame is moved it updates these with the last place it was dropped (or maybe it just waits until its restored and does it then.)
Window messages are pretty well explained in the documentation, however there are a few less understood issues surrounding them that I wanted to cover. Of course the reason PM uses messages is that the system must dynamically link to code in the client's program, because it does not know ahead of time what windows the client code will use, how many of them, where their window procedures are, who will subclass them, etc. So, instead of having a window provide virtual functions to call (like an object oriented system would), it allows each window class to provide one function pointer which it will call back when events occur. Since it only allows one function, the message id is passed in order to allow multiple actions to be overloaded on this single function. It's definitely not very type safe but allows a kind of flexibility that would otherwise not be very feasible for a procedural system.
System Defined vs User Messages
The system defines a number of messages, by defining a set of ids in the toolkit headers. There is nothing to keep you from using these message ids for your own programs, but they will probably die a slow (or fast) painful death because the system will also continue to send them to you as well, passing the parameters that it says are correct for them. This will certainly cause crashes. You can, and will, sometimes fake system messages to your own windows but that is a special case and you must carefully emulate the parameter conventions of the system message you are faking.
One common case is for WM_SIZE messages. Many window procedures will have very special code that is run when it gets a WM_SIZE, which will calculate the sizes of its various parts and store them away. However a window does not get sent a WM_SIZE message when it's first created; I assume because it is already told how big it was in the WM_CREATE. Since the size code is in WM_SIZE, to avoid pulling the code out to a separate function, you can just have WM_CREATE send itself a WM_SIZE message to cause that code to correctly calculate everything for the initial size. The initial window size is passed in the PCREATESTRUCT parameter of WM_CREATE.
PM defines a value, WM_USER, above which all user messages must be placed. So you always define your own user defined messages as WM_USER+x, where x is some relative displacement value.
One common area of misconception is that all windows in a program must have different user defined message or problems will occur because of misinterpreting a window message. But this is only partly true, and is driven by the direction of the message. All windows can define ids, for the messages that they want others to send to them, any way they want without worries of id clashes (because they are the only class of window which will receive them.) Sure there is the pathological problem of a window sending a message to the wrong window, but that is inherent in the looseness of such a message passing architecture. The situation is not significantly improved by going through all the trouble of insuring unique incoming message ids.
When messages are sent out from a window or subsystem to other windows, then the situation is different. Since the sending window cannot know what incoming message ids are used by other windows, steps must be taken to insure uniqueness. If the message is being sent out of some subsystem, a registration mechanism is often possible in which a client program registers what messages it wants sent to it. I've used this a number of times. In situations where messages must be transmitted to foreign process, third party, or otherwise uncontrolled windows for which the registration mechanism might not be feasible, the only solution is often to create a system atom. The system atom table is a mechanism for creating guaranteed unique values within the system. This atom can them be used to create a unique outgoing message id.
The catch of using atoms is that they are no longer compile time constants and therefore cannot be used as cases in the usual window procedure switch statement. So you either have to check for them in default case of the switch or via a set of if-then-elseif blocks prior to entering the main switch statement. And, of course, the parties involved must agree on the atom by either passing it along or having a common atom name which they can all look up on startup.
I guess that the writers of most OS/2 documentation assume that the relationships of windows to one another are so obvious that they don't need to say the obvious things about them. But, there are a couple of often misunderstood issues surrounding window relationships.
Basically there are two defined relationships between windows, parentage and ownership. However, these two relationships are not on the same level at all. Of the two parentage is the one that is most important and 'built into' PM. Ownership is more of an artificial relationship that was created to deal with a common GUI problem.
Every window in PM except for the magical desktop (HWND_DESKTOP) and object (WM_OBJECT) windows have parent windows. This is a required relationship that always exists, though the parent of a window may be changed as desired. Parenthood implies these facts:
- All windows are placed relative to their parent window. When you query or set a window's position, that position is relative to the lower left corner of its parent window.
- Since windows cannot exist without a parent, they are destroyed when their parent is destroyed. This is both a convenience and a necessity.
- When a window gets a WM_DESTROY, its child windows still exist so that they can be queried, their size/position saved, etc....
- When a window gets its WM_CREATE message it cannot have any child windows because no one could have created any yet (other than PM itself who won't).
The window handle is not returned from WinCreateWindow() until WM_CREATE processing is completed, so no one has had access to the window in order to create a child window for it or assign one to it.
- Child windows will never show outside of their parent windows because they clipped to it. This is a natural effect of PM and does not require any special effort on your part.
- WinQueryWindow(hwndChild, QW_PARENT) cannot fail if hwndChild is a valid window because it must have a parent to exist. There might be some pathological multi-threaded situation where the query is made while the switch is occurring.
Parenthood of a window can be changed using the WinSetParent() API. This API is used often by PM to control, for instance, menus which often must appear and disappear on demand. Instead of rebuilding them every time, their parent is set to HWND_OBJECT, which hides them without affecting their size/position information. You can use this scheme yourself to say display one of a set of windows in a particular area of your client screen according to some circumstance. Be aware that, when you do this, those windows will not be destroyed automatically when your client is destroyed now because automatic destruction only occurs for child windows. So you must generally track them by window handle so you can destroy them when you get a WM_DESTROY message. You cannot use ids to find them because any process can have windows under HWND_OBJECT, some of which could have your same id. Another exception to this rule is that frame windows will destroy all of their child windows (naturally like everyone else does) plus all of their owned windows. This makes sure it gets all of those windows cleaned up successfully.
Whereas window parentage is a somewhat 'physical' relationship, ownership is more of a 'legal' relationship. In PM, when a user interacts with a control window, that window will send a WM_CONTROL message to its owner window. In an object oriented system, this would have probably worked the other way where control windows would handle their own events in an extendable way via polymorphism; but, in a non-OOPs system, the most practical way to provide extensibility of the standard controls is to let the client code respond on their behalf as suits the application's particular circumstances. Given that, there arose the need to know 'who' was interested in these events. Therefore an artificial relationship of ownership was invented, so that a 'legal contract of notification' could be arranged between two windows. Ownership is not affected by the parenthood hierarchy at all, so a window's owner could possibly be almost any other window.
Since this relationship is more artificial, it can be ignored totally (i.e. a window is not required to have an owner) and it can be used by custom window classes to represent some other type of legal contract. The (almost) only reason it is magical at all is because WinQueryWindow() allows a window's owner to be queried. But you could easily arrange to provide other relationships between your own windows if you wish to do so. It would be more work than ownership because you would have to provide your own internal mapping tables or store a reference to the other window inside each window. But it would be just as 'legitimate' a relationship as ownership.
Ownership can be changed or set via the WinSetOwner(). It can be set to 0, to make the window no longer have an owner. This might be a convenient way to make child controls 'shut up' for extended periods of time when the owner is not prepared to deal with them.
Note that the ownership relationship is used (just for confusion's sake) for a couple things other than the event notification. Presentation parameters can be inherited via the ownership chain and frame windows have an option to be moved relative to their owner. This allows them to float outside their parent but maintain the same position relative to it. Also, frame windows minimize their owned windows when they are minimized and destroy their owned windows when they are destroyed, which is unlike any other window class.
I hope this quick tour of OS/2's rougher surfaces has helped some. I realize that this article jumps around somewhat erratically and that most of these subjects could each be extended to fill a full book chapter in order to cover all of their details. But this overview should hopefully give you a leg up on some of the problem areas and some potential areas of improvement in your code. And besides, I would not have any time to actually write any code and would have nothing to write about next time. Hopefully I will be able to expand upon some of these topics in subsequent issues.
If you have any questions or need amplication of any of these issues feel free to drop me a line to droddey@jaguNet.com. Have fun with PM, but don't put your eye out with that thing!