1. How do I use the Default Push Button in a dialog?
The Default Push Button is the button considered to have been pressed if the user presses the
ENTER key while the focus is on a control that is not a push button. It is always drawn with a
thick highlighted border. Usually the default push button has the label OK or Close, and if the
user enters information in a different part of the window and presses ENTER, it is as if they
entered the information then clicked the default button. Windows notifies the program that the
default push button has been pressed in the same way as it notifies other push buttons, by sending
WM_COMMAND to the dialog with the control i/d equal to the default push button i/d (see MS KB
Q67655).
The user interface of the Default Push Button works as follows. One push button in the dialog
resource is defined as a DEFPUSHBUTTON control, and this button is drawn with a thick border when
the dialog is shown. If the dialog contains several push buttons the default status moves between
them as the user tabs through them. This behaviour is internal to Windows and is a way of showing
that pressing Enter on a push button overrides the default button. When the focus moves away from
the push buttons the original DEFPUSHBUTTON is shown as the default again. Thus the default button
only moves when the focus moves among other push buttons. By using Spy you can see that the default
status really does change as the focus moves - the original DEFPUSHBUTTON turns into an ordinary
BS_PUSHBUTTON while the button with the focus turns into a BS_DEFPUSHBUTTON. When the focus moves
away from the push buttons, Windows needs a way of knowing which was the original DEFPUSHBUTTON.
The DM_GETDEFID message is used for this purpose.
For default push buttons to work properly, the following things need to happen:
- The dialog resource should contain only one DEFPUSHBUTTON. The push button control i/d does not
matter and it does not have to be IDOK or IDCLOSE. There may be any number of non-default push
buttons.
- The dialog's message loop must call IsDialogMessage, which converts the ENTER key into a
default button press (WM_COMMAND + the button i/d), and handles the movement of the default status
when the focus moves between push buttons. Modal dialogs created with DialogBox / EndDialog do this
automatically. Modeless dialogs created with CreateDialog / DestroyWindow or custom dialogs created
with CreateWindow / DestroyWindow must include a call to IsDialogMessage in their message
loop.
- The dialog's window procedure must respond to the DM_GETDEFID message in order to return the
control i/d of the default push button. Dialogs created with DialogBox / CreateDialog have a window
procedure internal to Windows which does this automatically. If you have Visual C/C++ v1.5, look at
the source code of the internal Windows dialog manager in \samples\defprocs\defdlg.c and note there
is a case statement that responds to DM_GETDEFID. Custom dialogs created with CreateWindow have a
harder job because they have to store the default push button i/d somewhere, probably in the extra
window words or as a global variable. They can't find the default push button by enumerating the
controls since when the focus moves among push buttons the original default button may have been
changed into a non-default button. The only way of responding reliably to DM_GETDEFID in a custom
dialog is to find the default button when the dialog is created and store the identifier
somewhere.
If item (2) above is not attended to, the symptom is the default button does not change with the
focus if you tab between several push buttons. If item (3) above is not done, then although the
default button may be displayed properly when the dialog is created, if you tab around to the
default push button it will lose its default status and not regain it when you tab off the
button.
2. Is there an LB_SETTEXT message?
This is a technical note, of interest only to C programmers using the Windows API.
In the Windows API there is an LB_GETTEXT message which gets the text of a listbox item given a
zero-based index. But there is no equivalent message LB_SETTEXT which sets the text of a listbox
item at a given index. This is puzzling because in Visual Basic there is a List(i) property of the
Listbox control object which is used to get and set the listbox text. Mystr = lb.List(i)
corresponds to the LB_GETTEXT message but we do not know what corresponds to the code lb.List(i)
= Mystr. In other words, how does Visual Basic set the listbox text? Possibly by using an
undocumented Windows message, which would be known to the VB developers.
As an alternative, LB_SETTEXT can be simulated by inserting and deleting the listbox item.
Here's some sample code:
lTotal = SendMessage(hControl, LB_GETCOUNT, 0, 0L);
if ((lIndex >= 0) && (lIndex < lTotal))
{
/* Find what the current selection is */
lCurSel = SendMessage(hControl, LB_GETCURSEL, (WPARAM)0, 0L);
/* Insert new string at List(i) */
SendMessage(hControl, LB_INSERTSTRING, (WPARAM)(lIndex), (LPARAM)szItem);
/* Delete string at List(i+1) */
SendMessage(hControl, LB_DELETESTRING, (WPARAM)(lIndex+1), 0L);
/* Restore the current selection */
SendMessage(hControl, LB_SETCURSEL, (WPARAM)lCurSel, 0L);
}
3. How do I create file associations?
Sometimes it's useful to associate file extensions with a particular application and an icon.
This enables Windows Explorer to show the correct icon and description in the directory listings,
and allows a right-click to open the file. File associations can be done at the API level without
using COM or ActiveX. The file's extension is used as the basis of the association. Associations
are defined by registry settings. Before defining the file type, you need to know the
following:
- File extension. This should be 3 or more characters. It can be less, although this is
not usual.
- File icon. This can be an ICO file, or it can be obtained from a DLL or possibly an EXE
file. For example "%SystemRoot%\icons.dll,-3" obtains the third icon resource from the DLL.
- File description. This appears in Windows Explorer as the description of the file type.
Without a description, Explorer simply follows the extension by the word "file", for example "XYZ
file".
- Application that opens the file. This specifies a command line that opens the file. For
example "c:\mydir\myapp.exe" "%1". The application needs to be coded so that it accepts files on
the command line.
- File identifier. This is a unique identifier, also known as the File type. It is used
internally by the registry for storing the file association, and is the registry key under which
most of the association information is held. The file identifier also makes it possible for
multiple extensions to be treated in the same way. For example both ".txt" and ".text" could have
the same file identifier (txtfile).
This example shows the new registry settings for an application using files with the extension
".xyz":
HKCR\.xyz\(default) = XyzFiles
HKCR\XyzFiles\(default) = "XYZ program file"
HKCR\XyzFiles\DefaultIcon\(default) = "\mydir\xyz.ico"
HKCR\XyzFiles\shell\open\command\(default) = "\mydir\xyzapp.exe" "%1"
HKCR\XyzFiles\shell\print\command\(default) = "\mydir\xyzapp.exe" -p "%1"
"HKCR" is an abbreviation for HKEY_CLASSES_ROOT. The first setting maps the file extension onto
the file identifier (XyzFiles). The second setting is the description of the file association. The
third setting sets up the icon shown by Windows explorer. The last two settings provide "open" and
"print" choices on the properties menu (the menu that is shown when you right-click a file). These
command lines can be whatever you want, but the application must be coded to recognize them and act
accordingly. The print option should be coded so it prints quietly without starting the main menu,
if possible. It should also be possible to make up new actions in addition to "open" and
"print".
4. How do I create a single instance program using the Windows
API?
Most Windows programs are multiple instance - if you start the program twice over, you will have
two separate copies of the program running. But in some cases you want programs to be single
instance. This means if you start the program a second time, the second copy refuses to start and
returns execution to the first copy.
To do this under the Windows API requires a named mutex semaphore using the base name of the EXE
file. The named mutex can only be owned by one application at a time, so if another copy of the
same application starts up, it detects the named mutex and stops running. For example:
HANDLE hMutex;
DWORD dwReturn;
BOOL fGotMutex;
char szAppname[_MAX_PATH];
char szFname[_MAX_FNAME];
GetModuleFileName(
GetSystemInstance(), // application instance
szAppname, // application name
sizeof(szAppname)); // buffer size
splitpath(szAppname, NULL, NULL, szFname, NULL);
strupr(szFname);
hMutex = CreateMutex(
NULL, // no security attribute
FALSE, // don't acquire initial ownership
szFname); // use base name as mutex name
dwReturn = WaitForSingleObject(
hMutex, // handle to wait for
1000); // timeout in mS
fGotMutex = (dwReturn == WAIT_OBJECT_0) || (dwReturn == WAIT_ABANDONED);
if (fGotMutex)
{
// At this point we own the mutex and we are the only
// instance of this program to be running.
// ... application code ...
// Release mutex before we finish
ReleaseMutex(hMutex);
}
CloseHandle(hMutex);
The call to GetModuleFilename gets the full name and path of the EXE file (eg
"c:\apps\myapp.exe"). GetSystemInstance is assumed to be a helper function that returns the current
application instance. The calls to splitpath and strupr get the base name of the EXE file (eg
MYAPP) which is used as the name of the mutex. Mutex names are available globally to all
applications, also they are case sensitive which is why we convert them to upper case. A mutex can
be owned by one application only, which is the basis of this sample code which creates a named
mutex for the application.
The call to CreateMutex returns a handle to the mutex szFname if it already exists, or creates a
new mutex named szFname if it did not exist. The FALSE parameter means CreateMutex does not attempt
to own the mutex, all it is doing is returning a handle to the mutex and creating it if necessary.
Although a mutex can only have one owner, there can be any number of handles to the mutex.
The call to WaitForSingleObject tries to get ownership of the mutex. An owned mutex (known as an
unsignaled mutex) is owned by a single application and cannot be owned by any other application,
but an unowned mutex (signaled mutex) is available and ownership can be gained by any application.
The terms "unsignaled" and "signaled" are used in the SDK documentation but are more confusing than
owned and unowned. WaitForSingleObject attempts to get ownership of the mutex identified by hMutex
and always returns within the time-out period. It is possible to specify an INFINITE timeout which
means WaitForSingleObject blocks until the mutex becomes available. It INFINITE is not used,
WaitForSingleObject returns values as follows:
- WAIT_FAILED - if hMutex is invalid. WaitForSingleObject will not own the mutex on return.
- WAIT_TIMEOUT - if WaitForSingleObject waited for the specified period but the mutex is owned
elsewhere and did not become available during the wait. WaitForSingleObject will not own the mutex
on return.
- WAIT_ABANDONED - if the mutex was owned elsewhere, but the owning thread terminated without
releasing the mutex. In this case Windows automatically releases the mutex when the owner
terminates, and WaitForSingleObject owns the mutex on return.
- WAIT_OBJECT_0 - if the mutex was unowned when WaitForSingleObject was called.
WaitForSingleObject owns the mutex on return.
As a result of these return codes, the return codes WAIT_ABANDONED or WAIT_OBJECT_0 mean the
mutex became available, and ownership was acquired by WaitForSingleObject on behalf of this
application.
The comment marked "...application code..." represents the rest of the application, and all code
called from here is single instance code. Any other applications of the same base name using
WaitForSingleObject to gain ownership of the mutex would fail with the return code
WAIT_TIMEOUT.
The call to ReleaseMutex releases the mutex which changes it back to the unowned (signaled)
state. At this point it could be successfully acquired by another application using the same mutex
name. CloseHandle is called to close the hMutex handle. It is not essential to call CloseHandle if
the application is about to terminate, because Windows automatically closes the handles on
application clean-up. Also Windows will automatically destroy the mutex if there are no handles to
it.
Therefore it would still be correct (but sloppy) to omit calls to ReleaseMutex and CloseHandle
above. If another application was blocked in a WaitForSingleObject call waiting for the named mutex
to become available, and this application quit without calling ReleaseMutex and CloseHandle, the
mutex would become available, and the other WaitForSingleObject call would acquire the mutex and
return WAIT_ABANDONED. But it is neater to call ReleaseMutex and CloseHandle as above.
The information that follows is kept for historical interest only. 32 bit Windows has been
available from 1995, and 16 bit code is obsolete.
In 16 bit Windows it is possible to make a single instance program by checking the hPrevInstance
parameter of WinMain, which is non-NULL if there is an instance of the same program (same EXE file)
running. This technique does not work under Win32, which always passes a NULL parameter for
hPrevInstance.
5. How do I handle missing DLLs after installing software (missing
MSVCIRT.DLL file)?
In some cases, if a program A is installed, then adding and later removing a program B can cause
A to stop working properly afterwards. This is usually because B replaces some DLLs required by A.
For example Microsoft Visual C/C++ Developer Environment had previously been installed and was
working reliably, but after installing some software called ResEdit, Visual C/C++ failed to load,
giving the error message:
"A required DLL file, MSVCIRT.DLL was not found"
After reinstalling Visual C/C++ from the CD, the same error happened. Even after using
Add/Remove programs to remove all traces of Visual C/C++, then reinstalling it, the error still
happened. The previously installed program was called ResEdit, and it had an installation program
based on Install Shield. The MSVCIRT.DLL was not found anywhere, it is not included in the standard
distribution of Windows (ie not anywhere in the list of Windows files), it was not included with
ResEdit, nor is on the Visual C/C++ CD. Why did Visual C/C++ want this DLL which is nowhere to be
found, when it worked fine before?
It turns out Visual C/C++ uses MSVCRT40.DLL, which is on the Visual C/C++ CD, although it is not
part of the Windows disk set. By grepping most files on the hard disk for "msvcirt" it turned out
MSVCRT40.DLL makes calls to routines in the missing MSVCIRT.DLL, and that the installation of
ResEdit replaced the original old MSVCRT40.DLL (which does not want MSVCIRT.DLL) with a newer
version of MSVCRT40.DLL that does. Replacing the "new" version of MSVCRT40.DLL with the "old"
version from the Visual C/C++ CD fixed the problem and allows Visual C/C++ to run properly.
After checking MSDN it appears the MSVCRT40.DLL file should not be distributed by applications,
as to do so can break programs written with Visual C/C++ (and presumably Visual C/C++ itself). That
explains what happened in this case. What ResEdit did wrong was to replace the MSVCRT40.DLL file
and not provide the MSVCIRT.DLL file. Refer to the article: "FIX: Wrong Version of
MSVCRT40.DLL(Forwarder DLL) in Windows 95", Article ID: Q154591.
6. What is the Command Line Length Limit under Windows?
The Command Line Length Limit is the longest possible command line that may be passed to
programs under Windows. It varies, depending on the context in which the command line is being
used. Here are the limits:
- CreateProcess() Windows API function The maximum length is 32767 characters, based on
fields in the UNICODE_STRING structure.
- Cmd.exe When using the Windows NT / 2000 / XP command processor, the maximum length is
8192 characters.
- ShellExecute() Windows API function The maximum length is 2048 characters as given by
the INTERNET_MAX_URL_LENGTH defined constant.
- ShellExecute() Windows 95 API function Under Windows 95 the maximum length is 255
characters as defined by MAX_PATH.
- MS-DOS command line When using MS-DOS or the command.com MS-DOS boxes under Windows 95,
Windows 98 or Windows ME, the maximum command line length is 127 characters.
- Environment variables There is also a maximum size allowed for Environment variables
under cmd.exe - this is 32767 characters, including the names of the variables. Under MS-DOS the
maximum size allowed for environment variables can be set to a lesser limit using the /E command
line option.
This information is based on The Old New Thing blog which
is a very useful source of low-level information under Windows.
|