Ask Dr. GUI #3

(Remember, he's not a real doctor.)

Bob Gunderson
Microsoft Developer Network Technology Group

Got a question? Dr. GUI's busy operating schedule won't allow him to answer all questions, but the Good Doctor will answer as many as he can on the next Microsoft Developer Network CD. Send your questions to:

Dr. GUI
Microsoft Developer Network
One Microsoft Way
Redmond, WA 98052-6399

Fax: 206-936-7329, Attn: Developer Network
Internet: devnetwk@microsoft.com

Dear Dr. GUI:

I seem to recall that there is a set of dynamic data exchange (DDE) topics and items that the shell application must support for setup programs to work right. For the life of me, I can't seem to find it in the Windows version 3.1 manuals or the online Help file. Is it still documented? Also, if I wanted to build my own shell application, what subset of these DDE commands do I need to support?

Dr. GUI replies:

Don't worry, it's still documented. Go over to your bookshelf and find the manual titled Programmer's Reference, Volume 1: Overview that came with your Windows version 3.1 Software Development Kit (SDK). You know, the one that is still in the shrink-wrap because you didn't want to look uncool by having a book with the word Overview on your desk. Turn to Chapter 17. Now, put it back on your bookshelf because there is a better way to get the information. Fire up the Windows version 3.1 SDK Reference Help file and search for "Shell Dynamic Data Exchange Interface Overview (3.1)." This contains the same information that is in the book plus some extras.

If you are really intent on making a Program Manager clone, you need to provide all the DDE services that the Program Manager provides. Most application installation programs nowadays add Program Manager groups and group items during installation. These installation programs will expect the Program Manager to be running. It's anyone's guess what will happen to installation applications if your Program Manager clone doesn't give the same DDE responses as the real Program Manager.

Have you noticed this neat new feature in Windows version 3.1: Minimized MS-DOS applications use the same icon and icon title that you gave the application in the Program Manager. A new set of DDE commands between the Program Manager and WINOLDAP, the part of Windows that manages MS-DOS applications, accounts for this. Any Program Manager clone should support this feature. The description of this DDE interface can be found only in the Help file, about halfway down the Help file "Shell Dynamic Data Exchange Interface Overview" topic. As far as I can tell, this information never made it into the printed manuals. Take note of the warning about the possibility of the interface changing in future versions of Windows.

Dear Dr. GUI:

I'd like to use CTRL+> and CTRL+< as accelerators. I can make the following entries in my resource file:

190, IDM_THISCMD, VIRTKEY, CONTROL

188, IDM_THATCMD, VIRTKEY, CONTROL

Since these keys are OEM-specific, I'm concerned it won't work with all keyboards. Is there a better way to do this?

Dr. GUI replies:

Your concern is justified. Indeed, this won't work with all keyboards, especially non-U.S. keyboards. The values 190 and 188 that you use in your example are the virtual-key code values for the > and < keys on a standard U.S. keyboard. For most European keyboards, you enter the < and > characters with the same key on the keyboard, one with the SHIFT key and one without it. For these non-U.S. keyboards, the virtual-key code will be the same for both characters.

What would be ideal is to have an accelerator table with entries like:

">", IDM_THISCMD, ASCII, CONTROL

"<", IDM_THATCMD, ASCII, CONTROL

But that won't work because you can only use the ALT, SHIFT, and CONTROL options on accelerator table entries that use virtual keys. The options can't be used with ASCII entries. The resource compiler isn't going to complain, but the resulting resource table simply won't work.

An easy way to get around this problem is to build your own accelerator handler. To understand how to do this, a quick overview of what TranslateAccelerator does is in order. TranslateAccelerator just sits in your WinMain loop and watches for WM_KEYDOWN and WM_CHAR messages. When it sees one, it looks in your accelerator table to see if you have an accelerator defined for that key. If a match is found, it generates a WM_COMMAND message and sends it to your window. By testing the return value from TranslateAccelerator, your application throws away WM_KEYDOWN and WM_CHAR messages that match a defined accelerator. That's why your window never sees the WM_KEYDOWN or the WM_CHAR messages for accelerator keys.

One more bit of trivia: Because modal dialogs have their own message-processing loops that don't call TranslateAccelerator, accelerators don't work in modal dialog boxes. If you need to use an accelerator table in a dialog box, use a modeless dialog box.

The only trick to creating your own accelerator handler is that the WM_COMMAND message must be sent on a WM_KEYDOWN message rather than on a WM_CHAR message. Unfortunately, at WM_KEYDOWN time, you have only the virtual-key code for the character, not the character itself. (TranslateMessage creates a WM_CHAR message from a WM_KEYDOWN message.) But the virtual-key code for the keys in question will change based on the keyboard in use. What you really need is the virtual-key code of the character based on the current keyboard driver. No problem. There is a little-used function, VkKeyScan, that does just this. Pass it an ASCII character, and it gives you back the virtual-key code and whether a SHIFT key is needed to obtain the character.

So, what you need to do is replace the call to the standard TranslateAccelerator function in your WinMain loop with a call to your own custom accelerator handler. For example:

// Initialize global(s) for the key(s) you are interested in.

wKeyInfo = VkKeyScan('<');

// Standard WinMain message loop with a small twist.

while (GetMessage((LPMSG) &msgMain, NULL, 0, 0))

{

if (!TranslateMyAccelerator(hwndMain, (LPMSG)&msgMain)) {

TranslateMessage((LPMSG) &msgMain);

DispatchMessage((LPMSG) &msgMain);

}

}

Then add your own accelerator handler:

BOOL TranslateMyAccelerator(HWND hwnd, LPMSG lpmsg)

{

// Return immediately if this isn't a WM_KEYDOWN message.

if (lpmsg->message != WM_KEYDOWN)

return FALSE;

// Check the virtual-key code against what we are looking for.

if (lpmsg->wParam != LOBYTE(wKeyInfo))

return FALSE;

// Insure that the CONTROL key is down (for this example).

if (!(GetKeyState(VK_CONTROL) & 0x8000))

return FALSE;

// Depending upon the keyboard in use, the SHIFT key may have to

// be pressed to get the "<" character. The high byte from

// VkKeyScan will indicate this. (Unique to this example.)

if (HIBYTE(wKeyInfo) == 1)

if (!GetKeyState(VK_SHIFT) & 0x8000)

return FALSE;

else

if (GetKeyState(VK_SHIFT) & 0x8000)

return FALSE;

SendMessage(hwnd, WM_COMMAND, IDM_THISCMD, MAKELPARAM(0, 1));

return TRUE;

}

This example only looks for the CTRL+< key sequence; you will have to modify the code if you want to look for other key sequences.

Dear Dr. GUI:

I'm writing an MS-DOS–based application that is doing some hardware polling. What is the accepted method to yield control in an enhanced mode MS-DOS session? Is there a defined API to do this?

Dr. GUI replies:

There is an INT 2Fh enhanced mode API that allows an MS-DOS application to release the rest of its time slice. It can be used by non-Windows applications when they want to go idle, such as while waiting for the user to type on the keyboard. Releasing the time slice permits the enhanced mode scheduler to allow another MS-DOS application or the Windows Virtual Machine (VM) to run. This API is documented in the Windows Device Driver Kit (DDK) documentation. In the Windows version 3.0 DDK, it's located in the Virtual Device Adaptation Guide, Appendix D, section D.1.2. In the Windows version 3.1 DDK, it's located in the Device Driver Adaptation Guide, Appendix C, section C.2 under the title "Interrupt 2Fh Function 1680h."

Now, I won't make you go out and buy the DDK just to find out how to use the API. The following code fragment is from the DDK documentation and illustrates how it works:

mov ax,352Fh ;Get Interrupt Vector

int 21h ;Get DOS vector for 2Fh

mov ax,es ;This vector will be 0:0 if the service

or ax,bx ;is not available

jz Skip_Idle_Call ;Skip this if not available

mov ax,1680h ;otherwise, Release Current VM Time-Slice

int 2Fh

Skip_Idle_Call:

Dear Dr. GUI:

I have a question about the bRevert parameter to GetSystemMenu. The Windows version 3.0 SDK Programmer's Reference, Volume 1 states that setting this parameter to a nonzero value resets the System menu and then returns a handle to the original, unmodified version of the System menu. The Windows version 3.1 SDK Programmer's Reference, Volume 2: Functions says that the return value is undefined in this case. Which one is correct?

Dr. GUI replies:

The Windows version 3.1 SDK Programmer's Reference, Volume 2 is correct. This was an error in the Windows version 3.0 documentation. The code for GetSystemMenu did not change between Windows versions 3.0 and 3.1. The following code will destroy the current modified version of the System menu for your window and get a handle to the original, unmodified version:

// Destroy modified System menu and revert to unmodified copy.

GetSystemMenu(hWnd, TRUE);

// Get handle to unmodified copy of System menu.

hMenuOriginal = GetSystemMenu(hWnd, FALSE);

Dear Dr. GUI:

I noticed that the WM_PAINTICON message is no longer documented in the Windows version 3.1 SDK. Why? What are the workarounds?

Dr. GUI replies:

This unnecessary message was extremely confusing to developers and caused some version compatibility problems for Windows. Here's a quick overview of icons and how to display them.

Icons are used to represent applications on the desktop, in Program Manager groups, and in the new Windows version 3.1 ALT+TAB task switcher. Applications associate icons with their windows in two different ways:

The easiest and most common way to associate with a window is by specifying a class icon when registering the main window class (the hIcon field of the WNDCLASS structure). If the icon is defined in the application's resource file, it will be available for use by the Program Manager when the application is added to a group. Windows automatically draws class icons on the desktop when the window is minimized. Windows also displays class icons next to the window caption in the new ALT+TAB task switch window. When using a class icon, make sure you specify a valid icon when the class is first registered, otherwise the new ALT+TAB task switcher won't use the correct icon for your application. Setting the class icon when the application is first minimized, rather than when the class is registered, is a common mistake that results in the incorrect icon being displayed.

The second method of icon association is obtained using owner-drawn icons, where the application handles the painting of its own icon. For example, the Windows Clock application uses an owner-drawn icon to display the time when its window is iconic. Other applications choose to display different icon images during different phases of processing.

When using owner-drawn icons, applications still need to include at least one normal static icon as an ICON resource. This nonanimated icon represents the application in Program Manager groups and in the new ALT+TAB task switcher.

The following code demonstrates how to implement owner-drawn icons. Most of this will be old hat to experienced developers, but please look at the WM_QUERYDRAGICON handler below. Make sure that you register the application's class with a NULL hIcon in the WNDCLASS structure when calling RegisterClass. Then in the window procedure do the following:

HANDLE hIcon; // Icon for this application initialized

// someplace else (using LoadIcon).

// You might also want to use the new Windows

// version 3.1 DDE capabilities of the Program

// Manager to get the icon the user has associated

// with your application. Doing this will give you

// changeable icons, just as MS-DOS applications do.

// The paint routine

case WM_PAINT: {

PAINTSTRUCT ps;

if (IsIconic(hWnd)) {

BeginPaint(hWnd, &ps);

// Erase the background of the window with what's on the

// desktop. This is so the desktop bitmap will show

// through the transparent areas of the icon.

DefWindowProc(hWnd, WM_ICONERASEBKGND, (WORD)ps.hdc, 0L);

// Now draw the icon. For example, Clock may want to do

// a TextOut of the current time instead.

DrawIcon(ps.hdc, 0,0, hIcon);

EndPaint(hWnd, &ps);

}

else {

// Do your own painting here for a nonminimized window or

// pass it to DefWindowProc.

return(DefWindowProc(hWnd, message, wParam, lParam));

}

}

// There may be some flicker when the icon is drawn. To fix that,

// do the following:

case WM_ERASEBKGND:

if (IsIconic(hWnd))

// Don't erase the background now because we will do

// it at paint time when we paint our icon.

return(TRUE);

else

return(DefWindowProc(hWnd, message, wParam, lParam));

// Basically, this technique lets you return the icon you want to

// use when dragging this iconic application's window around the

// screen. USER.EXE automatically converts color icons to

// monochrome. Also, if the user is ALT+TAB'ing to your application,

// this icon will be displayed beside the window caption in the

// ALT+TAB task switch box.

case WM_QUERYDRAGICON:

return((LONG)(WORD)hIcon);

break;

Dear Dr. GUI:

Would you comment on physical and virtual memory limitation differences between Windows versions 3.0 and 3.1?

Dr. GUI replies:

Windows in enhanced mode has two limits on physical memory. One is an address space limitation; the other is an amount limitation. There is also a limit on total virtual memory:

Windows version 3.1 in enhanced mode cannot use physical memory that has a physical bus address of greater than 7FC00000h (2044 MB). In Windows version 3.0, this limit was 1000000h (16 MB).

Windows version 3.1 in enhanced mode is limited to a total physical memory pool of 256 MB. In Windows version 3.0, this limit was 16 MB.

Windows version 3.1 in enhanced mode will not set up more than 256 MB of virtual memory; that is, the total physical memory and swap-file size combined will never be larger than 256 MB.

These three limits are hard-wired and cannot be changed through any configuration parameters in SYSTEM.INI.

Swap-file size limits total virtual memory. If you want larger free-memory numbers, make the swap file bigger or buy more physical memory, but remember the 256 MB limit on combined physical memory and swap-file size.

Dear Dr. GUI:

The Windows version 3.1 SDK Programmer's Reference, Volume 2: Functions states this in the documentation for SetClipboardData:

"Private data formats in the range CF_PRIVATEFIRST through CF_PRIVATELAST are not automatically freed when the data is removed from the clipboard. Data handles associated with these formats should be freed upon receiving a WM_DESTROYCLIPBOARD message."

The Windows version 3.0 SDK and the WINDOWS.H file state the same thing as well. In spite of this I've noticed that data of private formats (except CF_OWNERDISPLAY) are being freed (using GlobalFree) by Windows. Is this a documentation error or a bug?

Dr. GUI replies:

Your observations are correct: Windows does free private Clipboard format data. Since this has been the case since Windows version 3.0, I guess you have to consider it a documentation bug now. You do, however, have a chance to clean things up yourself by catching the WM_DESTROYCLIPBOARD message, which is sent before the handles to these private formats are actually freed by Windows. You are also correct about the CF_OWNERDISPLAY format. Windows does not free any data in this format, so it is up to your application to free the data on a WM_DESTROYCLIPBOARD message.