Windows[TM] Q&A

Fran Finnegan

Fran Finnegan is chairman of Finnegan O'Malley & Company Inc., a Windows consulting and contract programming firm that is developing E-Mail ManagerÔ, an electronic-mail application.

QI’m developing a workstation program for a bank that includes an access facility and a set of "services" running under WindowsÔ 3.0. The access facility identifies the user (userid/password) and then displays a list of services that the user is authorized for. These services are really separate applications.

I want to display the list of available services the same way that Program ManagerÔ displays group windows. MDI would be a solution, but I don’t feel like having MDI’s overhead just to display icons and a little text below. So I should probably write a custom control. This control would behave much like a list box, except that items would contain a series of icons and text labels. I’m having problems designing this control.

I could perhaps use an owner-draw list box to begin with, but I would like to start with a template of an owner-draw list box that behaves like a list box. I could then modify it to suit my needs. Where can I find such a template? In the list of services, one of the services might be unavailable for some reason. I would like to give some visual clue to the user, like showing grayed text, but the ETO_GRAYED option for ExtTextOut is undocumented. Should I rely on it or use an alternate technique?

The user must not be allowed to start a service without having started the access facility and having been identified and authorized to start a service. Should each service be a separate EXE or a DLL? What are the implications for memory allocation (data segment/stack segment) when using DLLs? What’s your advice?

Pierre Decrocq
Blois Cedex, France

AI agree that MDI is not a good solution. A major problem with MDI is that icons in the client window all look the same (like Program Manager group icons). You probably want a different icon for each service; to do that in MDI would require a window (like Program Manager’s group windows) that would then contain customized text. For your application, that would result in a lot of unnecessary clutter. And I’m sure that users would have a much easier time dealing with a list box-style control than with MDI icons. Visually I’d implement your service chooser as an owner-draw list box.

The easiest way to do what you want is to have your main window really be a dialog box with a single (possibly full-client-area) owner-draw list box. It could also include a Start Service push button. You want something that operates similarly to the Windows Task Manager, which is run by double-clicking on your desktop’s wallpaper or by hitting Ctrl-Esc. (You could even replace the Task Manager with your Service Chooser by editing the SYSTEM.INI [boot] section’s taskman.exe= line, which would allow users to pop up your custom dialog box to select a new service by double-clicking on their wallpaper or hitting Ctrl-Esc.)

For instructions on creating an application that is essentially a dialog box, see Charles Petzold’s Programming Windows (MicrosoftÒ Press, 1990), pp. 479-487. Petzold gives an incredibly simple and elegant way to create such an application. You would have an initial dialog box that collects the userid/password, and another dialog box that is the main application, which then displays the services in the owner-draw list box, whether grayed or not.

The nice thing is that the template you are after is already provided by the Windows operating system. There is no extra piece of code that you need to write or find. The standard Windows ListBox class that manages your dialog box’s owner-draw list box can be used as your template! To write your own would be reinventing code needlessly. Following Petzold’s example, you could have everything but the list box contents done in a matter of hours.

All you would then need is a method for filling in the owner-draw items in the list box. Since you will probably want to have a custom icon for each service, the item height on each line in the LBS_OWNERDRAWFIXED list box will be the height of the standard system icon (GetSystemMetrics’s SM_CYICON) plus a few pixels of margin, and will be set when you receive the WM_MEASUREITEM in the dialog procedure. That height can then be used to select an appropriate font height for the text to be displayed with each icon. Both the icon and the string would be drawn when your dialog procedure receives the WM_DRAWITEM. The user could then run a service by double-clicking the item in the list box, or by selecting an item and hitting your Start Service push button. In response to either, you would then make a WinExec call to run the service.

For services that are not available, which you want to indicate with gray text, I would definitely not use ExtTextOut and ETO_GRAYED. Relying on something that is apparently not documented or functional under Windows1 version 3.0 is risky. (It’s hard enough using the facilities that are documented. )You should probably just use the GrayString function and keep things simple.

If you decide you do not like how the standard list box behaves, you could always subclass it. This approach is a lot easier than trying to write your own list box procedure from scratch.

Each service should be implemented as a normal Windows executable (EXE). Code common to each service would appropriately go into a DLL. Only applications are executable tasks, and only they will have stacks in the default data segment. Memory allocation really shouldn’t be a problem, as long as your program is running on a 386 or better with 4MB or more of memory.

If two of your services need to share a scarce resource (possibly an IBMÒ 3270 session), the management of that resource would appropriately be handled by a DLL. Each service application could ask the DLL if the resource was free and available. If a subsequent service was started that needed the scarce resource, the DLL could even tell the previous user of the scarce resource to shut down.

QI’d like to display a logo banner during my program’s initialization phase. I’d also like to do as much processing as possible that doesn’t require user input during this phase. What would you recommend?

Laura Sackerman
San Francisco, California

AI would create your main window immediately and show it. Then call EnableWindow to disable it. This prevents user input until you have completed initialization. Call UpdateWindow to trigger the painting of the background and the drawing of your logo. Proceed with your initialization.

If initialization takes a long time, periodically go into a PeekMessage loop and process any messages. You won’t get keyboard/mouse input yet because of the disabling, but other windows may get the focus and control. When initialization is done, call EnableWindow to enable your main window (and its children). Go into your main GetMessage loop and you will then get keyboard/mouse input.

If you don’t want to go into a periodic PeekMessage loop, you could use a timer to do your initialization. This was addressed in Windows Q&A, MSJ, Vol. 6, No. 6 for dialog boxes. The same concept applies to main windows, or any windows for that matter. On each timer tick, you do a different part of your initialization. When initialization is done, call EnableWindow to enable your main window.

If you would rather display a modeless About dialog box (without an OK button) instead of just painting a logo, which is what MicrosoftÒ Excel and Word for Windows do, you could implement code similar to Figure 1. Your logo could then be displayed within the dialog box.

Figure 1 Displaying a Modeless Dialog during Startup

In WinMain:

hWnd = CreateWindow(...);

ShowWindow(hWnd, iCmdShow);

UpdateWindow(hWnd);

hDlg = CreateDialogParam(hInst, "ABOUT", hWnd,

(FARPROC)DlgProc, (DWORD)hWnd);

if (!IsIconic(hWnd))

ShowWindow(hDlg, SW_SHOWNA); // NA!

UpdateWindow(hDlg);

o

o // do initialization here

o

DestroyWindow(hDlg);

InvalidateRect(hWnd, NULL, YES);

UpdateWindow(hWnd);

o

o // do main GetMessage loop here

o

In DlgProc:

auto RECT aRtWnd, aRtDlg;

o

o

o

switch (wMsg)

{

case WM_INITDIALOG:

// center dialog box in main app window

GetWindowRect((HWND)lParam,

&aRtWnd);

GetWindowRect(hDlg, &aRtDlg);

OffsetRect(&aRtDlg, -aRtDlg.left,

-aRtDlg.top);

MoveWindow(hDlg,

aRtWnd.left + (aRtWnd.right -

aRtWnd.left - aRtDlg.right ) / 2,

aRtWnd.top + (aRtWnd.bottom -

aRtWnd.top - aRtDlg.bottom) / 2,

aRtDlg.right , aRtDlg.bottom, FALSE);

return TRUE; // did process the message

}

return FALSE; // didn't process the message

QHow does the Yield function work? The comments on page 4-469 of the Windows Programmer’s Reference look like Greek to me. It says, "Applications that contain windows should use a DispatchMessage, PeekMessage, or TranslateMessage loop rather than calling the Yield function directly." From the description of Yield, it seems that you can call it to yield control to other applications. But on page 4-330, comments for PeekMessage say that "The GetMessage, PeekMessage, and WaitMessage functions yield control to other applications. These calls are the only way to let other applications run." This confuses me. Does the Yield function yield control or not? Should I call Yield or PeekMessage?

Also, I sometimes need to wait for two seconds in my program. If I yield control, is it possible to get control back in exactly two seconds?

Jing Zhao
Miami, Florida

AI assume that this is one of those times when a lengthy operation is necessary, such as I/O processing. So that your application is a friendly application that supports cooperative multitasking, it should yield in the midst of the operation (maybe within a loop). It must also keep getting control for it to complete the operation. Although Yield is available to yield control explicitly to other tasks in this situation (under specific circumstances), you should avoid it. Rely on a PeekMessage loop to yield control. The standard PeekMessage loop that serves as a yielding mechanism looks like this:

auto MSG aMsg;

while (PeekMessage(&aMsg, 0, WM_NULL, WM_NULL,

PM_REMOVE | PM_YIELD))

{

TranslateMessage(&aMsg);

DispatchMessage(&aMsg);

}

This call uses the following manifest constant, which I would like to see in WINDOWS.H. Although it’s not strictly required (since it’s defined as 0), I use it to make the code say explicitly what’s happening, which is that PeekMessage is yielding.

#define PM_YIELD 0x0000

In addition to yielding, this PeekMessage loop allows you to periodically check for and process messages, which can be useful for handling messages that may terminate the lengthy operation. (But be careful, though, that you do not actually process a WM_CLOSE from within a yielding PeekMessage loop, or else you’ll get a UAE. The 0 return value from PeekMessage should signify that there are currently no more messages, and that a WM_DESTROY message has not been processed. A WM_DESTROY message might have caused a PostQuitMessage call to post a WM_QUIT intended for your main GetMessage loop.)

This isn’t the only reason why PeekMessage is preferable to Yield. Yield does not allow focus changes to be processed. This is because there may be mouse or keyboard messages in an application’s queue. The high-level USER module serializes these focus-changing events in the queues, so mouse and keyboard events must be processed serially. But Yield does not know which messages are in the queues, as managed by USER, since Yield is a low-level kernel (KRNLx86 module) function. Therefore, the focus cannot be transferred by USER to other applications until your message queue is empty, and Yield does not do the translating and dispatching that the above PeekMessage loop does to empty the queue. Calls to Yield do allow other applications to repaint themselves, but other applications cannot receive the focus from your application until its message queue is emptied.

Also, the Yield function does not yield if there are any messages in the application’s queue. PeekMessage has the same restriction in that it yields only if a message in the specified message-filtering range is not found and there are no messages left in the application queue. When the queue is finally emptied, PeekMessage yields. That’s why the standard PeekMessage loop shown above processes all messages. When there are no messages in the queue, PeekMessage yields before returning to the application and returning a 0 (to indicate there are no more messages). Therefore, you should be using a PeekMessage loop instead of Yield, unless you simply want other applications to be able to repaint themselves when there are no messages in the application queues.

As for getting control back in exactly two seconds, this cannot be guaranteed in Windows. WM_TIMER messages are not generated at timer-interrupt time. When a timer has been triggered in Windows, the kernel does not stop processing the current task to send a WM_TIMER message or call the timer event procedure of another task. Instead, a timer bit is set internally to indicate that the task that owns the timer should be run as soon as the current task yields control. When the timer-owning task eventually runs and calls GetMessage/PeekMessage and if there are no other messages available for that task, Windows processes the timer list to see if any timers for that task have been triggered. If any have been, Windows synthesizes a WM_TIMER message or calls the timer event procedure. Therefore, your application sees the WM_TIMER message two or more seconds later, depending on what other applications are doing in the meantime.

QI came across a problem that only occurred when I was doing many small global allocations. I seemed to be running out of global memory handles long before I ran out of memory. It was driving me mad! I fixed the code by doing just two large allocations, then dividing up the allocated memory manually. What is the maximum number of global allocation handles?

Tony Ortiz
New York, NY

AAll Windows applications share the same Local Descriptor Table (LDT) when running in protected mode. Because all global memory handles are selectors into the LDT, and since the LDT only holds 8K of descriptors, you are currently limited to 8K of global memory handles systemwide. Your method of allocating large chunks of memory and manually suballocating them is the only solution at this time. But even if each application had its own LDT, you would still be subject to the 8K constraint, which is an unfortunate limitation imposed by the IntelÒ 80x86 architecture.

1For ease of reading, "Windows" refers to the Microsoft Windows operating system. "Windows" is a trademark that refers only to this Microsoft product.