home *** CD-ROM | disk | FTP | other *** search
- Accelerator table decoding for OS/2
-
- [PM has an affinity for accelerator tables. It loves to translate the WM_CHAR
- message into a WM_COMMAND or WM_SYSCOMMAND or WM_HELP. It looks everywhere
- for the translation table.]
-
- Since there seems to be little information about the accelerator tables used
- for OS/2, I am supplying what information that I have collected. Some of this
- is documented by Microsoft [and almost none by IBM], some is deduced with the
- aid of monitor programs such as SPY, and some is simply deduced (make that
- "intelligently guessed" . . . .)
-
- However, it is not all black. The procedures mentioned here and the tables
- are documented. If IBM later changes the accelerator processing then they
- will break their own programs and rules. There are no secret addresses
- mentioned here. All structures are public. All procedures are public. This is
- safe PM programming.
-
-
- Accelerator tables come in two forms.
-
- 1. You can create them with the aid of the resource compiler and store them
- in your resources; or
-
- 2. You can simply build them from the structures supplied by OS/2.
-
- There is no reason that a single application NEED use only one accelerator
- table. If you have an accelerator table to translate some key combinations to
- various commands then the most common approach is to give it the ID of your
- client window and place the accelerator table in the resource file. Then by
- using the frame control flag, FCF_ACCELTABLE, in creating the standard
- window, the accelerator table is loaded and used.
-
- However, if you wish to have a different accelerator table for various states
- of the program then you may have more than one accelerator table in the
- resource file. The only difficulty is that you have only one chance to call
- the accelerator table the same as the client window ID and thereby have the
- system load and manage the accelerator table. The others are left to you to
- manage.
-
- You can do the management yourself by processing the WM_TRANSLATEACCEL
- messages or by using WinSetAccelTable to set the default accelerator table
- for your application.
-
- If you wish to store multiple accelerator tables in the resource file then
- you can use the WinLoadAccelTable procedure to load them. The result is a
- handle which may be given to the WinTranslateAccel procedure.
- WinDestroyAccelTable will remove the accelerator table handle which was
- loaded by WinLoadAccelTable.
-
- If you wish to create the accelerator tables programmatically, or even
- statically, then use the WinCreateAccelTable procedure to turn the list of
- structures into an accelerator table handle. WinDestroyAccelTable again will
- remove the handle association when you no longer need it.
-
-
- Accelerator translations
-
- The last accelerator procedure is the WinTranslateAccel. This is the most
- useful of the lot.
-
- ----
-
- The Microsoft quick help information for version 1 has this for
- WinTranslateAccel():
-
- Generally, applications do not have to call this function. It is normally
- called automatically by WinGetMsg and WinPeekMsg when a WM_CHAR message is
- received, with the window handle of the active window as the first parameter.
- The standard frame window procedure always passes WM_COMMAND messages to the
- FID_CLIENT window. Since the message is physically changed by
- WinTranslateAccel, applications will not receive the WM_CHAR messages that
- resulted in WM_COMMAND, WM_SYSCOMMAND, or WM_HELP messages.
-
- ----
-
- It takes an accelerator table handle and a message pointer. The result is the
- translated accelerator based upon the WM_CHAR message. It works as follows:
-
- Lets assume that you have an accelerator table which you have stored in the
- resource file. It was defined as:
-
- #define MY_UNSHIFTED_FUNCTION 101
- #define MY_SHIFTED_FUNCTION 102
-
- ACCELTABLE 523
- BEGIN
- "a", MY_UNSHIFTED_FUNCTION, ALT
- "a", MY_SHIFTED_FUNCTION, SHIFT | ALT
- END
-
- When you press a key on the keyboard, the hardware generates an interrupt.
- The interrupt suspends OS/2. OS/2 reads the keyboard and determines that the
- key has been pressed. Let's assume that this is the post with the label "A".
- OS/2 looks in the keyboard translation table for the proper shift state and
- determines that this is the shift letter "a". (You had the shift key down.)
-
- The shifted letter "a" is placed into the system queue as a WM_CHAR message.
- (First guess: I am only guessing that this is a WM_CHAR -- nowhere is it
- documented to the general public. However, it must be some event. I will call
- this the WM_CHAR message for the lack of a better name.)
-
- Sometime later PM looks at the system queue and fetches the WM_CHAR message.
- It looks at the active window and places it in to the message queue for that
- application. If the application was waiting for a message, then it clears the
- generates the event for the semaphore at the same time. (OS/2 will then move
- the thread from BLOCKED to READY and it will be dispatched at the next
- appropriate time. If you want more information about the dispatcher then
- there are several good books on OS theory.)
-
- The application sometime later calls WinGetMsg (or it was in WinGetMsg and
- was blocked on that previous semaphore). WinGetMsg peeks into the application
- queue and sees a WM_CHAR message for the shift "a".
-
- The next step that WinGetMsg does before it returns is to do any accelerator
- translations. This is done by sending a WM_TRANSLATEACCEL message to the
- active window. As part of the message is a pointer to the WM_CHAR message
- that it will supply should the translation not be successful.
-
- Since most applications do not process the WM_TRANSLATEACCEL message it is
- given to our old friend the WinDefWindowProc. The WinDefWindowProc knows what
- to do with the WM_TRANSLATEACCEL message. It passes the buck. It does this by
- doing a WinSendMessage to the parent. (Next Guess: I believe that it uses the
- parent. The docs are contradict the help information. However, since there is
- a parent/child relationship between the frame window and the active window
- while there is not necessarily a owner relationship, it is a good guess that
- it follows the parent rather than the owner relationship.)
-
- This message is the WM_TRANSLATEACCEL message that it just received.
-
- Eventually, the WM_TRANSLATEACCEL message will filter to the frame window.
- The frame window will finally do something with the message.
-
- The message processing is done with the aid of a procedure called
- WinTranslateAccel() This procedure takes the message pointer given in mp1 and
- returns TRUE or FALSE depending upon wether or not it found the WM_CHAR data
- in the accelerator table. If the procedure returns TRUE, the frame window
- returns TRUE.
-
-
- [Moral: If you want to override any system accelerator, you need only put it
- into your application accelerator table with the proper value.
-
- This override applies to all windows for the application. If you have an
- accelerator entry for the enter key, then it applies to every window
- (including dialog entry fields -- isn't that just great! How do you expect to
- complete the field without a return key? If you override F1 then you have
- overridden F1 for all windows, menus, dialogs, etc.)]
-
-
- If the WinTranslateAccel returns FALSE then the frame window looks in the
- system translation queue for a match. If the WinTranslateAccel returns TRUE
- then the frame window returns TRUE.
-
- [Another grey area . . . .]
- [A similar effect may be done if the frame window passed it to its parent.
- The parent of the application frame window is the desktop. It may be that the
- system translation is done in the desktop window procedure rather than the
- frame window. I don't know. I can't see a message sent to the desktop but
- that only means that SPY was not able to capture the event if it occurred.]
-
- Since you have a translator for the shift "a" in your table the
- WinTranslateAccel found the entry. It changed the message from WM_CHAR to
- WM_COMMAND and gave the command value 102. It then returned TRUE. Now your
- shift "a" has been destroyed in favor of a WM_COMMAND message.
-
- The return linkage is then un-rolled and the user gets a WM_COMMAND to be
- given to the WinDispatchMsg procedure.
-
- If, instead the character had been a "b" (which is not in your table . . .
- you like "b"s) then the WinTranslateAccel would not have found the entry. In
- this case, WinTranslateAccel does not change the message and returns FALSE.
-
- The frame window sees the FALSE and then uses the system accelerator table to
- do the translation. There is no translation in the system table entry for a
- shift "b", so it returns FALSE again.
-
- Ok, true or false the thread then unwinds. The frame window returns to the
- WinDefWindowProc which returns to the WinDefWindowProc which returns . . . .
-
- Eventually, you will end up back in WinGetMsg. The result of the translated
- message is then given to the caller to give to the WinDispatchMsg procedure.
- This is the untranslatable "b" or the WM_COMMAND of 102 when you used the
- shift "a".
-
- Moral Advice
-
- It is not a good idea to simply process the WM_TRANSLATEACCEL message to see
- if it is some key combination and if it is to return FALSE without having
- changed the message. That is cheating. The false return from the message
- means that you have processed the message. But you made no changes! If you
- wish to have your own table then do it by the rules. Use the WinCreateAccel
- or WinLoadAccel procedures.
-
-
-
- Accelerator Tables.
-
- The WinTranslateAccel procedure is fairly simple. It does a top down scan for
- the first character which matches the appropriate shift combinations.
-
- A common mistake is to have two entries.
-
- Entry one is ALT "a".
- Entry two is SHIFT ALT "a".
-
- If you place the items in the table in this order:
-
- "a", MY_UNSHIFTED_FUNCTION, ALT
- "a", MY_SHIFTED_FUNCTION, SHIFT | ALT
-
- Then you will never, never, see the translator event for the SHIFT ALT "a".
- This is because the system sees the ALT "a" combination and finds a match on
- this combination. The shift key will become irrelevant in this case.
-
- The proper order is
-
- "a", MY_SHIFTED_FUNCTION, SHIFT | ALT
- "a", MY_UNSHIFTED_FUNCTION, ALT
-
- Which will cause the event without the shift to fail and find the ALT "a" in
- the second position. The shift key needs to be pressed to get the first
- translation.
-
- Rule of thumb: Place the most complicated combinations of key events FIRST.
- The first item in the table should be THE most complicated. Work down from
- there to the unshifted, un-control, un-alt, key codes. Those should be at the
- end of the table.
-
- The translated item may have one of three valid modes (it is stored in two
- bits). By default the translated item will be a WM_COMMAND. If you wish a
- WM_SYSCOMMAND then that selection is available also. However, the
- WM_SYSCOMMAND usually means that the entry was found in the system table and
- not in your table. But, if you like F4 to be WM_SYSCOMMAND with SC_CLOSE then
- put in the entry
-
- VK_F4, SC_CLOSE, SYSCOMMAND | VIRTUALKEY
-
- and you too can close your application with a simple F4 keypress.
-
-
- The only modifier which should be avoided is the HELP modifier. This will
- generate a WM_HELP message and that is another whole story of cascaded
- messages.
-
-
-
- Questions
-
-
- Assume that you have windows such as:
-
- Standard window S1
- Client of S1 - C1
- Standard Window S2 (exactly covers C1 -- a MDI configuration)
- Client of S2 - C2
- A custom window D1 that covers C2
- A dozen windows that are children of D1, including
- X1 - a custom window we have created
- E1 - a WC_ENTRYFIELD window
- B1, B2 - WC_PUSHBUTTON windows
- Z1 - a custom window, child of X1
-
-
- >> Who sends the WM_TRANSLATEACCEL messages--PM or the default window
- >> procedure?
-
- PM sends the message (or rather you do from your thread calling WinGetMsg)
-
-
- >> Is that done before or after WM_CHAR is sent to the window
- >> with the focus?
-
- The message is sent BEFORE the WM_CHAR message. You will only get a WM_CHAR
- message if there are no accelerators for your entry.
-
-
- >> Is the message passed up the owner chain? If so, by
- >> whom?
-
- I believe that it is passed up the PARENT chain. I may be wrong, but it is an
- educated guess. WinDefWindowProc does the passing. (Again another educated
- guess.)
-
-
- >> Or is it sent specifically to each window in the owner chain?
-
- No, it is not "broadcast". The message should eventually find the frame
- window which will do the standard translations unless you intercept the
- message.
-
-
- >> When (if) the accelerator table on S2 is found, who is the message sent
- >> to? S2, the window with the focus, or ?
-
- WM_CHAR, WM_COMMAND, and WM_SYSCOMMANDS are always sent to the window with
- the current focus. WM_HELP messages filter though the help hook and start
- another cascade of messages to eventually display the IPF pannel or be
- ignored.
-
- The result of the translation is one of the four type of messages. It must be
- a WM_COMMAND, WM_SYSCOMMAND, WM_HELP if it is found in the table. If it is
- not found in the table, the message is WM_CHAR. (It probably started as
- WM_CHAR and was simply left as such)
-
-
- >> The specific problem I have is that an accelerator table entry
- >> (Ctrl+F4) attached to S2 is apparently not being processed.
-
- The problem with MDI is that the frame window for the MDI is not really the
- frame with the accelerator tables. The accelerator tables are loaded against
- the frame window of the client window created by WinCreateStdWindow().
-
- Therefore, when the first frame window "up" from the active window is not
- really the owner of the accelerator tables, it will not find the entry in
- your accelerator tables.
-
- If you have access to a PM toolkit for version 1 of OS/2 (from Microsoft)
- then you will find a sample program called MDI. This is a Multiple Document
- Interface. The work done in the MDIDOC.C procedure (near line 510) describes
- the method which must be performed. You must subclass the frame window to the
- MDI client.
-
-
- THE FOLLOWING CODE IS FROM THE SAMPLE MDIDOC.C. IT IS COPYRIGHTED BY
- MICROSOFT.
-
- ctlData = FCF_TITLEBAR | FCF_MINMAX | FCF_SIZEBORDER |
- FCF_VERTSCROLL | FCF_HORZSCROLL;
-
- hwndS2 = WinCreateStdWindow(hwndMDI,
- FS_ICON | FS_ACCELTABLE,
- (VOID FAR *)&ctlData,
- pszClassName, szDocTitle,
- WS_VISIBLE,
- (HMODULE)0, IDR_MDIDOC,
- (HWND FAR *)&hwndC2);
-
- pfnFrameWndProc = WinSubclassWindow(hwndS2,
- (PFNWP)DocFrameWndProc);
-
- If you forget to create the MDI frame windows with FS_ACCELTABLE then you
- will not have accelerators in your application. Even if your outer frame
- window does have the accelerator table defined.
-
-
- Then in the DocFrameWndProc, the following is performed:
-
-
-
- MRESULT EXPENTRY DocFrameWndProc(HWND hwnd, USHORT msg, MPARAM mp1,
- MPARAM mp2)
- {
- MRESULT mres;
- USHORT cFrameCtls;
- HWND hwndParent, hwndClient;
- register NPDOC npdoc;
- RECTL rclClient;
-
- switch (msg) {
-
- case WM_SYSCOMMAND:
- if (SHORT1FROMMP(mp2) == CMDSRC_ACCELERATOR)
- {
-
- /*
- * If the command was sent because of an accelerator
- * we need to see if it goes to the document or the main
- * frame window.
- */
- if ((WinGetKeyState(HWND_DESKTOP, VK_CTRL) & 0x8000))
- {
-
- /*
- * If the control key is down we'll send it
- * to the document's frame since that means
- * it's either ctl-esc or one of the document
- * window's accelerators.
- */
- return (*pfnFrameWndProc)(hwnd, msg, mp1, mp2);
- }
- else
- if (SHORT1FROMMP(mp1) == SC_DOCSYSMENU)
- {
-
- /*
- * If the window is maximized then we want
- * to pull down the system menu on the main
- * menu bar.
- */
- if ((WinQueryWindowULong(hwnd, QWL_STYLE) & WS_MAXIMIZED)
- &&
- (SHORT1FROMMP(mp1) == SC_DOCSYSMENU))
- {
- WinPostMsg(miAabSysMenu.hwndSubMenu, MM_STARTMENUMODE,
- MPFROM2SHORT(TRUE, FALSE), 0L);
- return ((MRESULT) 0);
- }
- else
- {
- WinPostMsg(WinWindowFromID(hwnd, FID_SYSMENU),
- MM_STARTMENUMODE, MPFROM2SHORT(TRUE, FALSE), 0L);
- }
- }
- else
- {
- /*
- * Control isn't down so send it the main
- * frame window.
- */
- return WinSendMsg(hwndMDIFrame, msg, mp1, mp2);
- }
- }
- else
- {
- /*
- * WM_SYSCOMMAND not caused by an accelerator
- * so hwnd is the window we want to send the
- * message to.
- */
- return (*pfnFrameWndProc)(hwnd, msg, mp1, mp2);
- }
- break;
-
- ....
-
-
- Documentation
-
- >> I'm asking this question generally because I'm not sure where the problem
- >> is, or whether it is in the code or in my understanding of some detail. If this
- >> is documented especially clearly anywhere, please tell me where!
-
- A good source of this information is (or was before the divorce) Microsoft
- Online. That was/is an additional cost service if Microsoft. However, now all
- that is present in the databases is information relating to Windows. The OS/2
- information seems to have been archived.
-
- You might find information in the Microsoft Knowledge Base (GO MSKB) on
- Compuserve. There is no guarantee that they are there. They were on MSONLINE
- where I accessed them.
-
- The articles which I found most useful are identified as
-
- Q46125 Accelerator Translation Flow of Control
- Q39339 Using Accelerator Keys within a dialog box within a DLL
- Q58076 Processing Accelerator keystokes when a Dialog Box is Up
-
- and something called "ACCEL".
-
- S12433 PM Accelerators Sample Program
-
- That may be in the Microsoft Library (GO MSL). If you don't find it there
- then you might try Microsoft Developer Relations to see if they can supply
- you with a copy. [No guarantees.]
-
-
- Additionally, you can find information documented under the version 1 quick
- help database, strangely enough at the end of the "Window Procedures
- Overview". It talks about the WM_TRANSLATEACCEL message.
-
- Note that much of the documentation contridicts what SPY tells you is
- *reallly* going on the system. So, take it with a grain of salt.
-
- If you can find out where it is fully documented then TELL ME. I have a
- curiosity as to the correctness of the guesswork (educated as it is).
-
-
-
- New Accelerator tables for dialog procedures in a DLL
-
- >> I need to load an accelerator table for a dialog box. I have a DLL
- >> that creates a dialog box when it is called. In the dialog box are several
- >> buttons. I would like to take the accelerator table table defined in the
- >> DLL's resource file and have it send messages to the dialog procedure.
- >> How?
-
- (This answer comes from originated from a message from Microsoft. It is
- basically obvious, given the statements earlier, so I have restated it
- below.)
-
- This method will work for buttons only. I have no other method for any other
- control. If you find a way then let us all know.
-
- Use the WinLoadAccelTable() procedure to read the accelerator table from your
- DLL's resource file.
-
- To obtain the previous accelerator table, send the parent window a message
- WM_GETACCELTABLE. There are no parameters to this message. The result is the
- accelerator table of the frame. There is no procedure to directly read this
- information, but the message will suffice.
-
- Then use WinSetAccelTable to set the new accelerator table into place.
-
- At the end of the dialog procedure you should restore the previous
- accelerator table using the WinSetAccelTable() procedure.
-
- WinDestroyAccelTable() will destory the accelerator table when you are no
- longer using it. (After you have restore the previous accelerator table.)
-
-
-
-
- Dialog Mnemonics
-
- Dialogs call WinDefDlgProc(). The processing for mnemonics is done by the
- control windows itself. They respond to the WM_MATCHMNEMONIC message to match
- the input character to the mnemonic text. This is a kind of accelerator but
- not really one. They are mnemonics. Do not get them confused.
-
- The processing of a mnemonic is strange indead. It varies depending upon the
- type of control being used. A list box may select the item in the list if it
- matches the first character of an item text. If there is no entry then the
- list box does something stupid. It passes it along. The result may be pushing
- a button to delete the item in the list!
-
-
-
- The ugly word: Windows 3.x
-
- Windows does not combine the accelerator processing with the WinGetMsg
- procedure. It is a seperate procedure to translate the accelerators. If you
- don't wish to translate the functions in the manner described then it is up
- to you to do your own translation and not call the standard translate message
- procedure.
-
- The messaage loop for windows is something like:
-
- while (GetMessage (&msg, NULL, 0, 0))
- {
- TranslateMessage (&msg); // Accelerators et al translated here
- DispatchMessage (&msg); // The standard dispatch routine
- }
-
- The accelerators are translated by the TranslateMessage() procedure. If you
- don't wish accelerators translated then don't call TranslateMessage(). If you
- want something else done to the message then do it yourself.
-
- PM merged the GetMessage and the TranslateMessage routines.
-
-
- A. Longyear
- Compuserve: 70165,725
-