Customizing the Explorer Open Dialog

Windows 95 uses a special open dialog that allows users to rename files, create directories, and (of course) open files. Al shows how to customize this dialog, using a Delphi 2.0 component.


September 01, 1996
URL:http://www.drdobbs.com/windows/customizing-the-explorer-open-dialog/184409953

September 1996: Customizing Open Explorer

Customizing the Explorer Open Dialog

The secret is in OFN_EXPLORER

Al Williams

Al, a consultant specializing in software development, training, and documentation, is the author of Steal This Code! (Addison-Wesley, 1995). You can find Al on the Web at http://ourworld .compuserve .com/ homepages/Al_Williams.


One of the most radical changes Windows 95 makes to the traditional Windows user interface is the common file-open dialog. Instead of the old standard, Windows 95 uses a special open dialog that looks like a miniature version of the Explorer (see Figure 1). From this dialog, users can rename files, create directories, and (of course) open files.

Customizing the common dialogs has always been a hassle. Now that the open dialog looks different, the steps you take to customize it are very different, too. In this article I'll explain how to customize the new dialog, and show an example component for Borland's Delphi 2.0. Even if you don't use Delphi, you can still apply these ideas to any other 32-bit language.

Making the Dialog Appear

If you don't take any special steps in a Windows 95 program, you'll get the same old open box that Windows 3.1 and Windows NT use. The secret to making the new dialog box appear is to specify OFN_EXPLORER as a flag in the OPENFILENAME structure. If you are using Delphi, this occurs automatically (unless you have NewStyleControls set to False). If you want the new-style dialog, you don't need to do anything-unless, of course, you want to customize the dialog box.

Many applications need a customized dialog box. You might want to display information about the selected file, or a preview of the file. I like to show the entire path in the open dialog. It is peculiar that the new open dialog doesn't show the entire path to the file in an easy-to-read format. The sample program I present with this article (available electronically) opens BMP, WMF, or ICO files. While opening the file, you'll see the entire path you are browsing. You'll also be able to click a preview button to see a thumbnail of the graphic.

Using a Hook Function

The first step in customizing the open dialog is learning about important events that occur within the dialog. You can install a hook function that acts like an ordinary window procedure by setting the OFN_ENABLEHOOK flag and placing the address of the hook in the lpfnHook field of the OPENFILENAME structure. The hook function looks like an ordinary window procedure. It receives a window handle argument, a message, and the ubiquitous wParam and lParam parameters.

In a hook function, you'll most often want to process WM_NOTIFY messages. This message (which is new to Windows 95) is how many controls notify your program of events. In contrast, if a standard edit control runs out of memory, it sends your program a WM_COMMAND message. That doesn't make sense. Common controls and the newer common dialogs that need to send information that isn't really a command use WM_NOTIFY.

All WM_NOTIFY messages come with a pointer to a structure in lParam. However, this presents a classic chicken-and-egg dilemma. The type of the structure depends on the type of the notification. However, the control embeds the type of the notification in the structure. Confusing, isn't it? The trick is that all notification headers start with the same few bytes (a NMHDR structure; see Table 1). You can treat a pointer to any notification structure as a pointer to a NMHDR. Then you can retrieve the code that identifies the type of the structure and recast the pointer. Object-oriented programmers will recognize this as a crude form of polymorphism.

Table 2 lists the notification codes you may receive from the open dialog. You won't often need to cast the notification structure since the only information sent, in most cases, is that the event occurred. The complete OFNOTIFY structure appears in Table 3.

Windows uses confusing names for some of the notification codes. For example, you might reasonably think that CDN_FOLDERCHANGE occurs when the user changes the current folder. It does do that, but you'll also receive the same notification as the dialog opens to inform you of the initial folder. This is a good thing, of course, but the name is a bit misleading.

Another notification that may not be obvious is CDN_INITDONE. This notice indicates that the dialog has finished setting up its controls. This is a good time for you to initialize your custom controls since you can count on the contents of the original dialog controls.

Customizing the Resource

To add your own controls to the open dialog, you'll need to construct a dialog template. The best way to do this is with a design tool such as Borland's Resource Workshop (which doesn't come with Delphi), or any of Microsoft's C++ or SDK offerings. However, if you don't have access to these, you can construct one by hand using an ASCII text file (look in some of the older Windows programming books for details on how to manually construct RC files).

Your dialog template will contain any additional controls you want to surround the open dialog. You'll also add a control (it doesn't matter what kind) with a special ID of $45F. This control sets the location of the standard open dialog controls. The top left of the special control will be the top left of the open dialog. The system will keep the same distance from the bottom right of the special control to the other controls and the edge of the dialog.

This is easier to observe than to explain. Look at Figure 2, which is a dialog template that contains several controls. Note the relative position between the placeholder control (the empty rectangle) and the rest of the dialog. Figure 3 shows how the open dialog will use this template. Contrast this with Figures 4 and 5. Notice that the dialog template creates the placeholder control without the WS_VISIBLE style. This hides the control. If you don't hide it, it will show up on top of the standard controls.

Your custom dialog template should have the following styles set: DS_CONTROL, DS_3DLOOK, WS_CHILD, and WS_CLIPSIBLINGS. Since several of these styles are new to Windows 95, you may have to add them manually to your RC file (see the listings, available electronically, for an example).

Once you have a resource, you need to do four things to make the open dialog use it:

1. Set the OFN_ENABLETEMPLATE bit in the OPENFILENAME flags.

2. Place the resource name in the lpTemplateName field (also in OPENFILENAME).

3. Set your program's instance handle in the hInstance field.

4.Include the resource file in your project. How you do this depends on the language you are using.

To decorate the open box with static text or other nondynamic controls, just supply a new template. You'll only bother with a hook function if you need to interact with the dialog.

In the more usual case, you'll want to install a hook function and initialize your controls when the dialog sends the CDN_INITDONE notification. If you have any buttons or other controls that send messages, you'll need to process those commands in the hook function also.

Controlling the Dialog

It's easy to manipulate your custom controls in the hook function. The hook function receives a window handle as an argument, but that handle isn't the dialog itself. Instead, it is a child window that represents your customizations. This is true even if you don't provide a customized dialog template.

To interact with the existing controls, you'll need to send messages to the dialog box itself. Table 4 shows the messages you can send to the dialog box. Some of these messages return information; others control the appearance of the dialog.

Of course, you can't send any of these messages to the dialog before it exists or after it has closed. You'll often use these messages while processing a WM_COMMAND message or a notification event from the dialog itself.

A Delphi Component

If you've used the ordinary Delphi file-open component, you'll shudder to think of writing all the code to customize a bare file-open component. However, with a little effort, you can write the ugly mess once and create your own customizable open component, such as CustDlg.PAS (available electronically).

The TCustomOpenDialog component derives directly from TCommonDialog, so it must duplicate much of the code that already exists in TOpenDialog. Although it would have been nice to subclass TOpenDialog, Borland made much of that object private. Without protected members to use, TCustomOpenDialog would have to override all the important functions anyway.

All is not lost, however. Instead of subclassing the object, I simply copied and modified its source code. While this isn't very efficient, it does work. I removed some extraneous code, changed all the names, and added the custom code required.

Component Highlights

The new component differs from the standard component in three ways:

If you aren't programming in Delphi, pay close attention to the DoExecute procedure and the ExplorerHook function. DoExecute is the procedure responsible for creating the dialog box. It sets up the OpenFilename structure with the required fields (mostly from design-time properties). The routine forces the dialog type to OFN_EXPLORER since only this type of dialog works with the customization techniques used. The procedure also sets ExplorerHook as the hook, and if you specify a dialog template ID, it stores that in lpTemplateName and sets the OFN_ENABLETEMPLATE flag. Notice that you don't have to supply a dialog template ID if you don't want to change the dialog's appearance.

The ExplorerHook function is a simple window procedure. It handles four messages: WM_INITDIALOG, WM_NOTIFY, WM_COMMAND, and WM_NCDESTROY. Because the function is a window procedure, it isn't part of any particular object. This poses a problem, since the code needs to examine properties and values from the object in use. If you only use one open dialog, that's not a problem-you could hard code the value or store it in a global variable. However, if you create multiple objects, this wouldn't work well.

To solve this, DoExecute stores a pointer to the current object (Self) in the dialog structure's lCustData field. This is a 32-bit integer that your program can use to store data. During WM_INITDIALOG, the lParam parameter points to the dialog structure. ExplorerHook learns the value of lCustData and attaches it to the dialog window with a window property. It also initializes the dialog object's Wnd property at the same time.

On every call to ExplorerHook, the code reads the property and converts it into a pointer to a TCustomOpenDialog object. When the code detects a WM_NCDESTROY (the last message a window receives), it removes the property.

The remaining code in ExplorerHook is responsible for routing WM_NOTIFY events and WM_COMMAND messages into user-defined events. The routine decodes the WM_NOTIFY code and passes information to the appropriate handler (see Table 5). Except for OnShareViolation, each event handler receives the sending object, the current window handle, the parent window handle, and a pointer to the dialog structure (not the notification structure). The OnShareViolation handler has all these parameters plus the name of the file that caused the violation.

When the default ExplorerHook function detects a notification of CDN_INITDONE, it centers the dialog. I changed that bit of code to respect a Boolean property named Center. This allows you to center the dialog if you like.

The user-defined command handler (OnCommand) gets control when you press a user-defined control. It gets the sending object, the current window handle, the parent window handle, and the control ID. You can use this function to respond to custom buttons or other controls.

The other interesting code in the object provides new properties and methods (see Table 6). Most of these are simple shortcuts for sending the dialog messages. For example, the GetSpec function supplies the Spec property. This simply retrieves the value from the dialog using CDM_ GETSPEC.

One final note on the procedures in the CustDlg unit: These routines rely on Delphi 2.0's ANSI string facility. These new strings (also known as "long strings") convert easily to the PChar type. This is handy because all of the Windows messages require PChars. If you were doing the same thing in an older dialect of Delphi, you'd need to make extensive use of StrPas and StrPCopy.

An Example

Available electronically is a sample program, Custopen, that uses the custom open dialog to view bitmaps, metafiles, and icons (see Figure 1). When you bring up the open dialog, it contains a preview button. Pressing that button shows a preview of the file. The dialog also shows the current directory path and contains some custom text near the top of the box.

You might consider putting the preview window directly on the dialog box. That is possible, but the component used for the preview window is a Delphi component and is difficult to store in a resource template. Instead, you would have to create a placeholder in the template and create the component dynamically at run time.

If you borrow the PREVIEW unit to use in your own code, you should be aware of how it handles exceptions. If the user selects a nongraphic file and clicks preview, an exception occurs. To prevent this from being a problem, I surrounded the offending code in NewPreview with a try block to catch exceptions. However, I don't distinguish between different types of exceptions-I ignore them all. If you were using the code where you might expect different types of exceptions, you'd want to spruce up the exception handling.

I used the standard MDI project to start the program in MAIN.PAS (available electronically). In the CreateMDIChild procedure I added a call to load an image component with the specified file name. The other change required is to FileNewItemClick. I changed this routine to use a TCustomOpenDialog object on the form.

To get the custom dialog template into the project, the MAIN.PAS file has the directive {$R CUSTDLG.RES}. I created CUSTDLG.RC using Borland's resource workshop and compiled it with BRC32 (the Borland resource compiler). The other functions I added to MAIN.PAS are CustomOpenDialog1Command (to handle the preview button), CustomOpenDialog1FolderChange (to update the current directory display), and CustomOpenDialog1InitDone (to change the Cancel button's text). These functions are straightforward.

The only thing to remember as you browse this code is that the dialog template is not a Delphi form; therefore, to set the directory text, the program uses SetDlgItemText. In a language like C, the resource compiler uses the same symbolic constants that the C compiler uses, so you use symbolic names for the control IDs. Although you can create const values for the control IDs, you would have to manually manage them. Rather than bother with that, I simply used constants (99 is the directory name, 100 is the preview button).

In a similar vein, I specified the dialog template by name, not by number. That way, you can simply supply a string to the dialog component. How you do this depends on which dialog editor you use. When you enter the dialog's name with Borland's Resource Workshop, the program will ask you if you want to create a symbol of that name. Just say no. Microsoft's tools will automatically create symbols unless you surround the name with double quotes. As a last resort, you could find the .H file that the RC file includes, edit it, and remove the #define symbol that defines the dialog name.

In the online listings, you'll also find SIMPLE.PAS. This program doesn't do anything except bring up a custom open dialog. Unlike MAIN, SIMPLE doesn't supply a custom dialog template.

Wrap-Up

Using a custom open dialog from Delphi requires some decidedly nonvisual programming. However, if you are careful, you can write this type of code once and encapsulate it in a visual way.

If you are thinking of using any of the messages or techniques in this article with the old-style file-open dialog, forget it. These things only work when OFN_ EXPLORER is set in the options. I haven't decided if I prefer using the new method to customize dialogs or the old method. One thing I have decided is that it is definitely different.

Table 1: NMHDR structure.

Name         Type               Description

hwndFrom     HWND               Window handle of control or dialog

idFrom       Unsigned Integer   ID of control or dialog

code         Unsigned Integer   Type of notification

Table 2: Open dialog notification codes.

Name                 Description

CDN_INITDONE         Initialization complete

CDN_SELCHANGE        Selection changed

CDN_FOLDERCHANGE     Folder changed (see text)

CDN_SHAREVIOLATION   Sharing violation occurred

CDN_HELP             Help requested

CDN_FILEOK           User pressed OK

CDN_TYPECHANGE       User changed file type



Table 3: OFNOTIFY structure.

Field    Type                     Description

hdr      NMHDR (see Table 1)      Standard header

lpOFN    Pointer to OPENFILENAME  Dialog structure

pszFile  Pointer to Char          Name of file if hdr.code = CDN_SHAREVIOLATION



Table 4: Open dialog messages.

Message              wParam             lParam                Description

CDM_GETSPEC          Size of            Pointer to            Get current file spec
                     character buffer   character buffer     

CDM_GETFILEPATH      Size of            Pointer to            Get current file and path
                     character buffer   character buffer     

CDM_GETFOLDERPATH    Size of            Pointer to            Get current folder name
                     character buffer   character buffer     

CDM_GETFOLDERIDLIST  Size of            Pointer to            Shell ID list of items
                     character buffer   character buffer     

CDM_SETCONTROLTEXT   ID of control      Pointer to            Sets control text
                                        character buffer     

CDM_HIDECONTROL      ID of control      Not used              Hides specified control

CDM_SETDEFEXT        Not used           Pointer to            Sets default
                                        character buffer      extension (no '.')

Table 5: Component event handlers.

Handler          Corresponding Message or notification

OnInitDone       CDN_INITDONE

OnFileOK         CDN_FILEOK

OnFolderChange   CDN_FOLDERCHANGE

OnHelp           CDN_HELP

OnSelChange      CDN_SELCHANGE

OnTypeChange     CDN_TYPECHANGE

OnShareViolation CDN_SHAREVIOLATION

OnCommand        WM_COMMAND

Table 6: New component methods and properties.

Name           Type        Description


Wnd            Property    Contains window handle of user dialog

FolderPath     Property    Corresponds to CDM_GETFOLDERPATH (read-only)

FilePath       Property    Corresponds to CDM_GETFILEPATH (read-only)

Spec           Property    Corresponds to CDM_GETSPEC (read-only)

DefExt         Property    Corresponds to CDM_SETDEFEXT (write-only)

HideControl    Procedure   Corresponds to CDM_HIDECONTROL

SetControlText Procedure   Corresponds to CDM_SETCONTROLTEXT

TemplateName   Property    Name of custom template

Center         Property    Determines if dialog auto-centers



Figure 1: The example program.

Figure 2: Typical resource template.

Figure 3: Dialog generated using the template in Figure 2.

Figure 4: Resource template with a small placeholder.

Figure 5: Open dialog generated from the resource template in Figure 4.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.