Accelerator Table Decoding for OS/2: Difference between revisions

From OS2World.Com Wiki
Jump to navigation Jump to search
No edit summary
Jugbogdan (talk | contribs)
mNo edit summary
 
(4 intermediate revisions by 2 users not shown)
Line 1: Line 1:
[PM has an affinity for accelerator tables. It loves to translate the WM_CHAR
{{Article
message into a WM_COMMAND or WM_SYSCOMMAND or WM_HELP. It looks everywhere
|Author=Alfred Longyear
for the translation table.]
|Date=1992
|Source=ACLINF.ZIP
}}
[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  
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" ...)
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
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.
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.==
==Accelerator tables come in two forms.==
# You can create them with the aid of the resource compiler and store them in your resources; or
# You can simply build them from the structures supplied by OS/2.


1. You can create them with the aid of the resource compiler and store them
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.
in your resources; or


2. You can simply build them from the structures supplied by OS/2.
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.


There is no reason that a single application NEED use only one accelerator
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.
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
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.
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.


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==
==Accelerator translations==
 
The last accelerator procedure is the WinTranslateAccel. This is the most useful of the lot.
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():
The Microsoft quick help information for version 1 has this for WinTranslateAccel():


Generally, applications do not have to call this function. It is normally
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.
called automatically by WinGetMsg and WinPeekMsg when a WM_CHAR message is
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.
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
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:
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:


Lets assume that you have an accelerator table which you have stored in the resource file. It was defined as:
<pre>
#define MY_UNSHIFTED_FUNCTION  101
#define MY_UNSHIFTED_FUNCTION  101
#define MY_SHIFTED_FUNCTION    102
#define MY_SHIFTED_FUNCTION    102
Line 83: Line 45:
     "a", MY_SHIFTED_FUNCTION,  SHIFT | ALT
     "a", MY_SHIFTED_FUNCTION,  SHIFT | ALT
END
END
</pre>
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.)


When you press a key on the keyboard, the hardware generates an interrupt.
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.)
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.
Sometime later PM looks at the system queue and fetches the WM_CHAR message. It looks at the active window and places it into 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.)
(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.
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".
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
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.
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
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.)
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.
This message is the WM_TRANSLATEACCEL message that it just received.


Eventually, the WM_TRANSLATEACCEL message will filter to the frame window.
Eventually, the WM_TRANSLATEACCEL message will filter to the frame window. The frame window will finally do something with the message.
The frame window will finally do something with the message.


The message processing is done with the aid of a procedure called
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 whether or not it found the WM_CHAR data in the accelerator table. If the procedure returns TRUE, the frame window returns TRUE.
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.


[Moral: If you want to override any system accelerator, you need only put it
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.)]
into your application accelerator table with the proper value.


This override applies to all windows for the application. If you have an
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.
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.)]


 
[Another grey area ....]
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.  
[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
The parent of the application frame window is the desktop. It may be that the
Line 162: Line 85:
given to the WinDispatchMsg procedure.
given to the WinDispatchMsg procedure.


If, instead the character had been a "b" (which is not in your table . . .
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
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.
this case, WinTranslateAccel does not change the message and returns FALSE.
Line 171: Line 94:


Ok, true or false the thread then unwinds. The frame window returns to the
Ok, true or false the thread then unwinds. The frame window returns to the
WinDefWindowProc which returns to the WinDefWindowProc which returns . . . .
WinDefWindowProc which returns to the WinDefWindowProc which returns ....


Eventually, you will end up back in WinGetMsg. The result of the translated
Eventually, you will end up back in WinGetMsg. The result of the translated
Line 177: Line 100:
This is the untranslatable "b" or the WM_COMMAND of 102 when you used the
This is the untranslatable "b" or the WM_COMMAND of 102 when you used the
shift "a".
shift "a".
==Moral Advice==
==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
It is not a good idea to simply process the WM_TRANSLATEACCEL message to see
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
if it is some key combination and if it is to return FALSE without having
wish to have your own table then do it by the rules. Use the WinCreateAccel or WinLoadAccel procedures.
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.==
==Accelerator Tables.==
 
The WinTranslateAccel procedure is fairly simple. It does a top down scan for the first character which matches the appropriate shift combinations.
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.
A common mistake is to have two entries.
Line 197: Line 115:


If you place the items in the table in this order:
If you place the items in the table in this order:
     "a", MY_UNSHIFTED_FUNCTION,        ALT
     "a", MY_UNSHIFTED_FUNCTION,        ALT
     "a", MY_SHIFTED_FUNCTION,  SHIFT | ALT
     "a", MY_SHIFTED_FUNCTION,  SHIFT | ALT
Line 206: Line 123:


The proper order is
The proper order is
     "a", MY_SHIFTED_FUNCTION,  SHIFT | ALT
     "a", MY_SHIFTED_FUNCTION,  SHIFT | ALT
     "a", MY_UNSHIFTED_FUNCTION,        ALT
     "a", MY_UNSHIFTED_FUNCTION,        ALT
Line 225: Line 141:
not in your table. But, if you like F4 to be WM_SYSCOMMAND with SC_CLOSE then
not in your table. But, if you like F4 to be WM_SYSCOMMAND with SC_CLOSE then
put in the entry
put in the entry
   VK_F4, SC_CLOSE, SYSCOMMAND | VIRTUALKEY
   VK_F4, SC_CLOSE, SYSCOMMAND | VIRTUALKEY
and you too can close your application with a simple F4 keypress.
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
The only modifier which should be avoided is the HELP modifier. This will
Line 236: Line 149:


==Questions==
==Questions==
Assume that you have windows such as:
Assume that you have windows such as:
 
<pre>
Standard window S1
Standard window S1
   Client of S1 - C1
   Client of S1 - C1
Line 250: Line 161:
             B1, B2 - WC_PUSHBUTTON windows
             B1, B2 - WC_PUSHBUTTON windows
             Z1 - a custom window, child of X1
             Z1 - a custom window, child of X1
 
</pre>


>> Who sends the WM_TRANSLATEACCEL messages--PM or the default window
>> Who sends the WM_TRANSLATEACCEL messages--PM or the default window
Line 256: Line 167:


PM sends the message (or rather you do from your thread calling WinGetMsg)
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
>> Is that done before or after WM_CHAR is sent to the window
Line 263: Line 173:
The message is sent BEFORE the WM_CHAR message. You will only get a WM_CHAR
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.
message if there are no accelerators for your entry.


>> Is the message passed up the owner chain?  If so, by
>> Is the message passed up the owner chain?  If so, by
Line 271: Line 180:
educated guess. WinDefWindowProc does the passing. (Again another educated
educated guess. WinDefWindowProc does the passing. (Again another educated
guess.)
guess.)


>> Or is it sent specifically to each window in the owner chain?  
>> Or is it sent specifically to each window in the owner chain?  
Line 278: Line 186:
window which will do the standard translations unless you intercept the
window which will do the standard translations unless you intercept the
message.
message.


>> When (if) the accelerator table on S2 is found, who is the message sent
>> When (if) the accelerator table on S2 is found, who is the message sent
Line 292: Line 199:
not found in the table, the message is WM_CHAR. (It probably started as
not found in the table, the message is WM_CHAR. (It probably started as
WM_CHAR and was simply left as such)
WM_CHAR and was simply left as such)


>> The specific problem I have is that an accelerator table entry
>> The specific problem I have is that an accelerator table entry
Line 311: Line 217:
MDI client.
MDI client.


 
THE FOLLOWING CODE IS FROM THE SAMPLE MDIDOC.C. IT IS COPYRIGHTED BY MICROSOFT.
THE FOLLOWING CODE IS FROM THE SAMPLE MDIDOC.C. IT IS COPYRIGHTED BY
<pre>
MICROSOFT.
 
     ctlData = FCF_TITLEBAR  | FCF_MINMAX | FCF_SIZEBORDER |
     ctlData = FCF_TITLEBAR  | FCF_MINMAX | FCF_SIZEBORDER |
               FCF_VERTSCROLL | FCF_HORZSCROLL;
               FCF_VERTSCROLL | FCF_HORZSCROLL;
Line 328: Line 232:
     pfnFrameWndProc = WinSubclassWindow(hwndS2,
     pfnFrameWndProc = WinSubclassWindow(hwndS2,
             (PFNWP)DocFrameWndProc);
             (PFNWP)DocFrameWndProc);
 
</pre>
If you forget to create the MDI frame windows with FS_ACCELTABLE then you
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
will not have accelerators in your application. Even if your outer frame
window does have the accelerator table defined.
window does have the accelerator table defined.


Then in the DocFrameWndProc, the following is performed:
Then in the DocFrameWndProc, the following is performed:
 
<pre>
 
MRESULT EXPENTRY DocFrameWndProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
 
MRESULT EXPENTRY DocFrameWndProc(HWND hwnd, USHORT msg, MPARAM mp1,
                                MPARAM mp2)
     {
     {
     MRESULT mres;
     MRESULT mres;
Line 411: Line 311:
                 }
                 }
         break;
         break;
         ....
         ....
 
</pre>


==Documentation==
==Documentation==
>> I'm asking this question generally because I'm not sure where the problem
>> 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, or whether it is in the code or in my understanding of some detail. If this
Line 443: Line 341:
then you might try Microsoft Developer Relations to see if they can supply
then you might try Microsoft Developer Relations to see if they can supply
you with a copy. [No guarantees.]
you with a copy. [No guarantees.]


Additionally, you can find information documented under the version 1 quick
Additionally, you can find information documented under the version 1 quick
Line 454: Line 351:
If you can find out where it is fully documented then TELL ME. I have a
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).
curiosity as to the correctness of the guesswork (educated as it is).


== New Accelerator tables for dialog procedures in a DLL==
== New Accelerator tables for dialog procedures in a DLL==
>> I need to load an accelerator table for a dialog box. I have 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
>> that creates a dialog box when it is called. In the dialog box are several
Line 487: Line 381:
WinDestroyAccelTable() will destory the accelerator table when you are no
WinDestroyAccelTable() will destory the accelerator table when you are no
longer using it. (After you have restore the previous accelerator table.)
longer using it. (After you have restore the previous accelerator table.)


==Dialog Mnemonics==
==Dialog Mnemonics==
Dialogs call WinDefDlgProc(). The processing for mnemonics is done by the
Dialogs call WinDefDlgProc(). The processing for mnemonics is done by the
control windows itself. They respond to the WM_MATCHMNEMONIC message to match
control windows itself. They respond to the WM_MATCHMNEMONIC message to match
Line 504: Line 394:
a button to delete the item in the list!
a button to delete the item in the list!


 
==The ugly word: Windows 3.x==
==The ugly word: Windows 3.x==
 
Windows does not combine the accelerator processing with the WinGetMsg
Windows does not combine the accelerator processing with the WinGetMsg
procedure. It is a seperate procedure to translate the accelerators. If you
procedure. It is a separate procedure to translate the accelerators. If you
don't wish to translate the functions in the manner described then it is up
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
to you to do your own translation and not call the standard translate message
Line 514: Line 402:


The messaage loop for windows is something like:
The messaage loop for windows is something like:
   while (GetMessage (&msg, NULL, 0, 0))
   while (GetMessage (&msg, NULL, 0, 0))
       {
       {
Line 520: Line 407:
       DispatchMessage  (&msg);          // The standard dispatch routine
       DispatchMessage  (&msg);          // The standard dispatch routine
       }
       }
The accelerators are translated by the TranslateMessage() procedure. If you
The accelerators are translated by the TranslateMessage() procedure. If you
don't wish accelerators translated then don't call TranslateMessage(). If you
don't wish accelerators translated then don't call TranslateMessage(). If you
Line 526: Line 412:


PM merged the GetMessage and the TranslateMessage routines.
PM merged the GetMessage and the TranslateMessage routines.


A. Longyear
A. Longyear


Compuserve: 70165,725
[[Category:Technical‎]]

Latest revision as of 10:40, 18 November 2025

Article Info
Author Alfred Longyear
Date 1992
OS Version
Companion File
Source ACLINF.ZIP

[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 into 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 whether 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 separate 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