home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Professional
/
OS2PRO194.ISO
/
os2
/
prgramer
/
info
/
accel
next >
Wrap
Text File
|
1992-09-10
|
23KB
|
544 lines
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