Notebook Key ProcessingWritten by Roman Stangl |
The problemThe OS/2 PM notebook control has a limitation that causes inconsistent usability compared to other PM controls, namely it does not pass on all unprocessed messages to its owner. As a result the following consequences, which we want to avoid, exist:
The window layout we assumeA typical window containing a notebook is a dialog window (most likely created with the dialog editor) that contains a Notebook Control and Pushbuttons in its client area. The notebook below should demontrate this. By the way, the screen capture was taken from my Program Commander/2 (PC/2) program, which is available from my homepage, and it's Freeware!
This will probably not be new to you, however I would like to summarize this from the point of the Z-Order and owner chain, as this is the basis to understand how to enhance the processing. The controls in the above notebook are created in the following order:
Possible solutionInstance data structureIn order to forward key events from the notebook control to its owner, we have to modify the way the Notebook Control processes such events, in case the notebook is not interested in that event. When working with data that is window dependent (instance data), the proper way is to use the window words to save a pointer to your data. The QWL_USER window word is reserved for the user and would be a good starting point. However, while implementing the advanced processing, it can be useful to store the data in the heap, as it can be accessed by the debugger even when not stepping through a window procedure. In the following code excerpt, the processing supports 3 different dialogs. typedef struct _NOTEBOOKSUBCLASS NOTEBOOKSUBCLASS; /* Structure to save which notebook was subclassed from which previous window procedure */ struct _NOTEBOOKSUBCLASS { HWND hwndNotebook; /* Notebook window handle */ PFNWP pfnwpNotebook; /* Notebook control's window procedure before subclassing */ }; /* Desktop dialog notebook subclassed */ #define DD_SUBCLASSEDNOTEBOOK 0 /* Program Installation dialog notebook subclassed */ #define PI_SUBCLASSEDNOTEBOOK 1 /* Control Configuration dialog notebook subclassed */ #define CC_SUBCLASSEDNOTEBOOK 2 /* As only one instance of the Desktop and Program Installation dialogs is allowed, its save to avoid more complicated per dialog instantiation, but use module class storage */ #define NOTEBOOKSUBCLASSMAX 3 NOTEBOOKSUBCLASS DialogNotebookSubclass[NOTEBOOKSUBCLASSMAX];In this example our application consists of 3 different Dialog Windows for which we want to enhance the message processing. Modifying the notebook processingIn order to forward key events from the notebook control to its owner, we have to modify the way the Notebook Control processes such events, in case the notebook is not interested in that event. In order to change the Notebook Control's default message processing, we have to subclass the control by using the PM API WinSubclassWindow(): /* Subclass notebook's window procedure to add handling of notebook pages, notebook and outer dialog accelerator keys */ DialogNotebookSubclass[DD_SUBCLASSEDNOTEBOOK].hwndNotebook=hwndNB; DialogNotebookSubclass[DD_SUBCLASSEDNOTEBOOK].pfnwpNotebook= WinSubclassWindow(hwndNB, SubclassedNotebookProcedure);Subclassing means, that we add a new window procedure to the existing window procedure of the Notebook Control to be able to add processing logic before the notebook's window procedure. In your own controls, you would add the processing somewhere in the big switch-Statement of your window procedure, however as the notebook (and all other controls shipped with OS/2) is implemented inside PM, you can only get access to the control's window procedure by using WinSubclassWindow(). Another thing to mention here is, that in our example all procedures are coded only once, that is they run in the context of the window that is currently processing the user input. As a consequence special caution needs to be taken not to share variables between different window contexts. The subclassed window procedure of the Notebook Control is especially interested in WM_CHAR messages, as the WM_CHAR message reflects the keyboard input we want to change: The first thing we do in the subclassed window procedure is look for the control structure element that corresponds to the notebook control in whose context we are currently running in. As I said, the most elegant way would be to use the notebook's window words, but the easier to debug list approach is taken here. In detail, the following happens for a WM_CHAR message is routed to the notebook:
Modifying the notebook pages' processingIn the window procedure of the Notebook Pages we also have to catch the key events, by looking for WM_CHAR messages. As the dialog that implements the notebook page is created by us, we also supply the window procedure (or the dialog procedure to be exact), so we just can easily add the processing as shown here: case WM_CHAR: /* * Process the navigation keys on this page and forward all * unprocessed keys to the dialog where the notebook is part of. * \* ProcessPageKey(hwndDlg, WinQueryWindow(hwndNotebook, QW_OWNER), mp1, mp2); break;In order to allow window procedures of different notebook pages to share the same implementation, all WM_CHAR messages are processed by ProcessPageKey(). Note: If you have multiple notebook pages you can write window procedure and share it for all notebook pages, just ensure that the controls of the individual notebook pages contain different identifiers. This function is somewhat more complex. In order to make debugging easier, printf() calls are added to generate debug information. You can either redirect stdout to a file (e.g. by invoking myapp.exe with "myapp > debuginfo"), or you get the very useful IBM EWS-written PMPRINTF package, which replaces the C-library printf() function with one that writes into an OS/2 queue, and using the included PM viewer you can display that queue in real-time. In detail, the following happens for a WM_CHAR message routed to a notebook page's dialog procedure:
Modifying the dialog window's processingThe Dialog Window may process messages either generated while the dialog had the input focus, or messages forwarded up from the owner chain, that is message forwarded by the Notebook Control and by the individual Notebook Pages. In any case, we are interested in the processing of WM_CHAR messages as shown below. Again, as the dialog that implements the dialog window is created by us, we also supply the window procedure (or the dialog procedure to be exact), so we just can easily add the WM_CHAR processing. case WM_CHAR: /* * All window procedure implementing a noteboook page and the notebook * control itself, which is part of this window's client, forward * keystroke messages they don't have handled themselves here. \* { static ULONG ulRecursion=FALSE; return(DispatchKeyToNotebook(&ulRecursion, hwndDlg, hwndNB, DialogNotebookSubclass[DD_SUBCLASSEDNOTEBOOK].pfnwpNotebook, mp1, mp2)); }To share code, again we call a function named DispatchKeyToNotebook(). As a notebook page's window procedure calls the dialog's window procedure by using WinSendMsg() (as shown in ProcessPageKey()) and DispatchKeyToNotebook() again calls the notebook page's window procedure, we have to check for recursion. The reason why we may get into recursion is, that while processing a key in the Dialog Window's dialog procedure, we no longer know where it was generated, that is in the dialog window itself, or sent from one of the owned controls. However, we want our logic to enhance the key processing to work in all cases, so we have to take the recursion into account. DispatchKeyToNotebook In detail, the following happens for a WM_CHAR message routed to the dialog procedure:
CreditsThe code shown here was greatly influenced by some discussions in the OS/2 development fora on the IBMPC conference disk. |