Using the Common Dialogs Under Windows 95

Nancy Winnick Cluts
Microsoft Developer Network Technology Group

Created: October 25, 1994

Click to open or copy the files in the CMNDLG32 sample application for this technical article.

Abstract

The Microsoft® Windows® 95 operating system has a new set of common dialog boxes that conform to the look and feel of the Windows 95 shell. In general, if you are already an old hand at programming the common dialogs supported in Microsoft Windows version 3.1 and Microsoft Windows NT™ (versions 3.1 and 3.5), you will have little or no work to do to support these new dialog boxes. Aside from the new look of the dialog boxes, some new functionality has been added and some changes have been made. This article is intended to give the reader a "walking tour" of the new common dialogs, offer information about how to use the new functions provided for the Windows 95 common dialogs, and offer a solution for those who are using templates and will be using one executable to run under both Windows NT 3.5 and Windows 95.

Note   The information contained in this article about the new common dialogs for Windows 95 is based on preliminary information that is subject to change before the final release of Windows 95. I will update this article with any new information about the common dialogs as I receive it.

Introduction

If you read the title of this article and the phrase "Oh no! Don't tell me that I have to change my common dialog code!" sprang into your head (or screamed from your lips), don't worry. The first thing you should know about the common dialogs and the Microsoft® Windows® 95 operating system is that, if you are using them in their simple form, you should not have to make any changes for the dialogs to work. A simple recompile will include the correct dialog resources (if you are including the templates and want the new-look dialogs) and allow you to "automagically" get the new look and feel. If you aren't using templates or you want the old-look dialogs, you don't even have to recompile to see them. For example, the common dialog sample that I wrote for Windows NT many moons ago worked without a hitch, with no changes and no recompilation.

If you are using a template and want to ensure that you can still use the common dialogs, you can do that too. In fact, some of the changes made to the templates actually make it easier for the developer (in my opinion, of course) to use them. I am going to take you on a walking tour of the new dialog boxes and let you in on the changes that have been made to them. I am also including a sample application that you can use to exercise with these new common dialog boxes yourself.

So, lace up those walking shoes and follow me.

First Stop: Open A File and Save File As

The first stop in our walking tour is the Open A File and Save File As common dialog boxes. For those of you who have not used the common dialog boxes before, Open A File and Save File As use the same dialog box template, and both use the OPENFILENAME structure. When you want to display the Open A File dialog box, you fill out the OPENFILENAME structure and call the GetOpenFileName function; when you want to display the Save File As dialog box, you also fill out the OPENFILENAME structure, but instead call the GetSaveFileName function.

From the viewpoint of a developer, the Open A File and Save File As common dialog boxes present some of the most significant changes, as shown in the following figures. You will notice that these dialog boxes support long filenames (see Figure 2) and offer a graphical representation of the current folder, using a list view control. Figure 1 shows the Open A File dialog box in what is referred to as "list" mode.

Figure 1. Open A File common dialog box (list mode)

If you click the rightmost toolbar button, you will see the information for all the objects in the currently selected folder displayed in the list view's "report" mode. Report mode displays the details about each object (file) in the folder (directory). In Figure 2, the Save File As dialog box, we see not only the name of the object but also the size, type, and last modification date of the object.

Figure 2. File Save As common dialog box (report mode)

New Flags

The OPENFILENAME structure supports three new flags:

Tapping into GetOpenFileName via WM_NOTIFY

Under Windows 95, the WM_NOTIFY message is sent to the hook procedure for the Open A File and Save File As dialog boxes whenever actions such as selection or a change are made. If you want the ability to monitor what is happening within the dialog box, you should enable the hook into the dialog box via the OFN_ENABLEHOOK flag and supply the name of the hook procedure. The hook procedure will receive the WM_NOTIFY message with the OFNOTIFY structure packaged into its LPARAM.

The OFNOTIFY structure contains the following members:

Once your hook procedure receives the WM_NOTIFY message, it can determine the current action via the .code member of the NMHDR structure. The code can be one of the following values:

CDN_INITDONE Sent when initialization has finished via processing within the WM_INITDIALOG message. All controls have been moved.
CDN_SELCHANGE Sent when the current selection has changed. The user has clicked a file or folder in the file list.
CDN_FOLDERCHANGE Sent when the current folder has changed.
CDN_SHAREVIOLATION Sent when a sharing violation has occurred.
CDN_HELP Sent when the user has clicked the Help button.
CDN_FILEOK Sent when the user has clicked the OK button. To prevent the common dialog from exiting, the result should be nonzero via a call to SetWindowLong(hdlg, DWL_MSGRESULT, lResult).
CDN_TYPECHANGE Sent when the file type changes via the "Files of type" combo box.

Messages and Macros

Along with the new notifications, there are also some new messages (with handy-dandy associated macros) that the developer can use to retrieve information about the status of the Open A File and Save File As dialog boxes. This section lists (I can hear Nigel screaming already, "No! Not another list!") these new messages, with an explanation of the parameters they require and their primary function.

CDM_GETSPEC

wParam = cbmax;       \\ maximum number of characters of the text buffer
lParam = psz;         \\ pointer to a text buffer

Description: Gets the current file specification.

Parameters: wParam (int cbmax) is the maximum number of characters of the text buffer (including the NULL terminator). lParam (LPSTR psz) is a pointer to the text buffer that will be filled in with the file specification.

Return value: The number of characters used in the buffer, or 0 if an error occurred.

Macro: (int) CommDlg_OpenSave_GetSpec(hDlg, psz, cbmax);

CDM_GETFILEPATH

wParam = cbmax;       \\ maximum number of characters of the text buffer
lParam = psz;         \\ pointer to a text buffer

Description: Gets the current file path.

Parameters: wParam (int cbmax) is the maximum number of characters of the text buffer (including the NULL terminator). lParam (LPSTR psz) is a pointer to the text buffer that will be filled in with the path information.

Return value: The number of characters used in the buffer, or 0 if an error occurred.

Macro: (int) CommDlg_OpenSave_GetFilePath(hDlg, psz, cbmax);

CDM_GETFOLDERPATH

wParam = cbmax;       \\ maximum number of characters of the text buffer
lParam = psz;         \\ pointer to a text buffer 

Description: Gets the current folder path.

Parameters: wParam (int cbmax) is the maximum number of characters of the text buffer (including the NULL terminator). lParam (LPSTR psz) is a pointer to the text buffer that will be filled in with the current folder's path information.

Return value: The number of characters used in the buffer, or 0 if an error occurred.

Macro: (int) CommDlg_OpenSave_GetFolderPath(hDlg, psz, cbmax);

CDM_GETFOLDERIDLIST

wParam = cbmax;       \\ size of the ITEMIDLIST buffer
lParam = pidl;        \\ pointer to the ITEMIDLIST buffer 

Description: Gets the ITEMIDLIST for the current folder.

Parameters: wParam (int cbmax) is the size of the ITEMIDLIST buffer. lParam (LPVOID pidl) is a pointer to the ITEMIDLIST buffer that will be filled in.

Return value: The length of the buffer used, or 0 if an error occurred.

Macro: (int) CommDlg_OpenSave_GetFolderIDList(hDlg, pidl, cbmax);

Using the OFN_EXPLORER Flag

In the information above, I glazed over a new flag, OFN_EXPLORER. I really shouldn't have done that because there is more to it than meets the eye. In previous versions of Windows (version 3.1, and Windows NT 3.1 and 3.5), if you wanted to include the template for a common dialog box to change it in some way, you needed to actually have a copy of that .DLG file and #include it in your resource file. Well, if you want to include the new Open A File template, you no longer need to do this. You simply include the OFN_EXPLORER flag ( | OFN_EXPLORER) and create a dialog template that includes only the items you want to add to the dialog box. If the OFN_EXPLORER flag is set in the .flags field of the OPENFILENAME structure, the .hInstance, .lpfnHook, and .lpTemplateName fields will be interpreted as follows:

GetDlgItemText(GetParent(hDlg), cmb1, buf, MAX_PATH);

For example, the dialog template below was used to add some fields to the Save File As dialog box:

IDD_COMDLG32 DIALOG DISCARDABLE  0, 0, 300, 74
STYLE WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | DS_3DLOOK
FONT 8, "MS Sans Serif"
BEGIN
    LTEXT           "Path:",-1,28,4,36,8
    LTEXT           "This is to the left",-1,4,16,20,40
    LTEXT           "Selected:",-1,32,49,40,8
    EDITTEXT        IDE_SELECTED,74,47,200,12,ES_AUTOHSCROLL
    LTEXT           "This is to the right.",-1,232,20,65,8
    LTEXT           "",stc32,28,16,204,31
    EDITTEXT        IDE_PATH,65,2,200,12,ES_AUTOHSCROLL
END

Once I created this template, I used the notification code in the section above to trap the notifications and to update the new fields that I added. I also needed to include the flag to enable the template, OFN_ENABLETEMPLATE, when I filled in the OPENFILENAME structure.

In the code above, notice the next-to-last resource. It is a control with an ID of stc32. In the common dialog handler, this ID has a special purpose: to let the common dialog handler know where to place all of the standard controls. If there is no stc32 control, the common dialog handler assumes all new controls added by the application-defined template should be placed below the standard controls. If the developer includes the stc32 control, the common dialog handler will look at the size of this control. If it is too small to hold all the standard controls, the common dialog handler will move the controls to the right of or below the stc32 control to make room for the new controls.

Example: Using the New Notifications and Macros

Since I've given you this list of new notifications and messages, it seems only fair that I should illustrate how you can use them. Here is a small code snippet from the COMDLG32 sample (from the Windows 95 SDK) that demonstrates how you can trap the dialog box notifications and use some of the new messages. This sample dialog box contains two extra edit boxes that display the currently selected file and the currently open folder.

BOOL NEAR PASCAL TestNotify(HWND hDlg, LPOFNOTIFY pofn)
{
  switch (pofn->hdr.code)
  {
    // The selection has changed.
    case CDN_SELCHANGE:
    {
      char szFile[MAX_PATH];

      // Get the file specification from the common dialog.
      if (CommDlg_OpenSave_GetSpec(GetParent(hDlg),
        szFile, sizeof(szFile)) <= sizeof(szFile))
      {
        // Set the dialog item to reflect this.
        SetDlgItemText(hDlg, IDE_SELECTED, szFile);
      }

      // Get the path of the selected file.
      if (CommDlg_OpenSave_GetFilePath(GetParent(hDlg),
        szFile, sizeof(szFile)) <= sizeof(szFile))
      {
        // Display this path in the appropriate box.
        SetDlgItemText(hDlg, IDE_PATH, szFile);
      }
    }
    break;

    // A new folder has been opened.
    case CDN_FOLDERCHANGE:
    {
      char szFile[MAX_PATH];

      if (CommDlg_OpenSave_GetFolderPath(GetParent(hDlg),
        szFile, sizeof(szFile)) <= sizeof(szFile))
      {
        // Display this new path in the appropriate box.
        SetDlgItemText(hDlg, IDE_SELECTED, szFile);
      }
    }
    break;

    // The "Help" pushbutton has been pressed.
    case CDN_HELP:
      MessageBox(hDlg, "Got the Help button notify.", "ComDlg32 Test", MB_OK);
      break;

    // The 'OK' pushbutton has been pressed.
    case CDN_FILEOK:
      // Update the appropriate box.
      SetDlgItemText(hDlg,IDE_SELECTED, pofn->lpOFN->lpstrFile);
      SetWindowLong(hDlg, DWL_MSGRESULT, 1L);
      break;

    // Received a sharing violation.
    case CDN_SHAREVIOLATION:
      // Update the appropriate box.
      SetDlgItemText(hDlg, IDE_SELECTED, pofn->pszFile);
      MessageBox(hDlg, "Got a sharing violation notify.", "ComDlg32 Test", 
         MB_OK);
      break;
  }

  return(TRUE);
}

Font

Next on our walking tour is the Font dialog box. As you can see in Figure 3, this dialog box has not changed significantly from the previous versions of Windows (so this will be a really short stop). The new dialog box has the three-dimensional look and the new Help and Close buttons in the caption bar.

Figure 3. Font common dialog box

There are (literally) a couple of new flags that you can specify when you fill out the CHOOSEFONT structure:

Color

As with the Font dialog box, the Color dialog box will also be a short stop. Figure 4 shows what the new dialog box looks like (looks a lot like the previous version, doesn't it?). Here again we have the new three-dimensional look and the Help and Close buttons in the caption bar.

Figure 4. Color common dialog box

This dialog box also has two new support flags:

Note   At the time of the writing of this article, the CC_ANYCOLOR option was not implemented.

Find and Replace

We aren't even going to stop here long enough for a bathroom break. These two dialog boxes haven't changed functionally at all. I am including the screen shots purely for the sake of consistency. Notice that the only real difference is the three-dimensional look and the Help and Close buttons.

Figure 5. Find common dialog box

Figure 6. Replace common dialog box

Print

Three different dialog boxes provide for printing by the common dialog library. The first dialog box that we'll look at, Print Setup, looks and behaves much like the Windows 3.1 version. It allows you to view and change information about the printers that you have installed, set the paper size and source, and set the orientation. The one difference in the dialog boxes is that the new one, shown in Figure 7, has a button that will bring up the printer properties property sheet for the currently selected printer.

Figure 7. Print Setup common dialog box

Figure 8 shows the property sheet that is displayed when the user queries the printer properties. There are six different pages of information about the printer that you can view or change. These pages are brought into the foreground by clicking the associated tab. So, if you wanted to change the paper that is used, you would click the tab labeled Paper. (This dialog box may change before the final release of Windows 95.)

Figure 8. Printer properties for the Print Setup common dialog box

A new common dialog box for Windows 95 in the area of printing is Page Setup. This dialog box (Figure 9) allows the user to set the paper size, source, orientation, and margins for printing. In the previous versions of Windows, the code for page setup was included in the dialog template for the Print common dialog box. The picture of the page shown at the top of the dialog box gives the user an idea of what the printed output will look like.

Figure 9. Page Setup common dialog box

From a programming standpoint, using the Page Setup dialog box is very much like programming the other common dialogs: you fill out a structure and make a function call. The new structure provided for the Page Setup dialog box is PAGESETUPDLG.

typedef struct tagPSDA
{
    DWORD     lStructSize;
    HWND      hwndOwner;
    HGLOBAL   hDevMode;
    HGLOBAL   hDevNames;
    DWORD     Flags;
    POINT     ptPaperSize;
    RECT      rtMinMargin;
    RECT      rtMargin;
    HINSTANCE hInstance;
    LPARAM    lCustData;
    LPPAGESETUPHOOK lpfnPageSetupHook;
    LPPAGEPAINTHOOK lpfnPagePaintHook;
    LPCSTR    lpPageSetupTemplateName;
    HGLOBAL   hPageSetupTemplate;
} PAGESETUPDLG, * LPPAGESETUPDLG;

The following table explains the parameters of the PAGESETUPDLG structure. Because of the amount of information included here, I'm ignoring all conventions and am breaking some of the parameter and flag names. Remember that in code they're one word.

Parameter Description
lStructSize The size of the PAGESETUPDLG structure.
hwndOwner The handle to the window that owns this dialog box.
hDevMode The handle to a movable global memory object that contains a DEVMODE structure. If this member is NULL, the system will allocate memory for the structure, initialize it, and return the handle to it.
hDevNames The handle to a global memory object that contains a DEVNAMES structure. This structure specifies information such as the driver name, printer name, and output port name. If this member is NULL, the system will allocate memory for the structure, initialize it, and return the handle to it.
Flags The dialog box initialization flags. This member can be a combination of the following values:
  Value Meaning
PSD_MINMARGINS Use the data in the rtMinMargin member for the minimum margins.
PSD_MARGINS Use the data in the rtMargin member for the margins.
PSD_INTHOUSANDTHSOF
INCHES
Specify margins in thousandths of inches.
PSD_INHUNDREDTHSOF
MILLIMETERS
Specify margins in hundredths of millimeters.
PSD_DISABLEMARGINS Disable the Margins edit boxes.
PSD_DISABLEPRINTER Disable the Printer button.
PSD_NOWARNING Prevent the warning message from being displayed when there is no default printer.
PSD_DISABLEORIENTATION Disable the orientation radio buttons.
PSD_RETURNDEFAULT Cause PageSetupDlg to return DEVMODE and DEVNAMES structures that are initialized for the system default printer without displaying a dialog box. This works as PrintDlg does with the same flag, PD_RETURNDEFAULT, specified.
PSD_DISABLEPAPER Disable the paper selection combo box.
PSD_SHOWHELP Show the Help button.
PSD_ENABLEPAGESETUPHOOK Enable the hook function specified by lpfnPageSetupHook.
PSD_ENABLEPAGESETUP
TEMPLATE
Cause the system to create the dialog box by using the dialog template box identified by hInstance and lpPageSetupTemplateName.
PSD_ENABLEPAGESETUP
TEMPLATEHANDLE
Indicate that a dialog box template has already been loaded and the template that should be used is identified by hPageSetupTemplate.
PSD_DEFAULTMINMARGINS Set the minimum margins to the default of 0", 0", 0", 0".
PSD_ENABLEPAGEPAINTHOOK Enable the hook function specified by lpfnPagePaintHook.
PSD_DISABLEPAGEPAINTING Do not show the page as painted.
ptPaperSize The paper size. This member will be filled in with the user's chosen paper size.
rtMinMargin The minimum margin.
rtMargin The margin chosen by the user.
hInstance The instance that owns this dialog box.
lCustData Points to an application's custom data for the dialog box. This information is passed to the hook function pointed to by lpfnHook in the lParam parameter of the WM_INITDIALOG message.
lpfnPageSetupHook Points to the exported function that hooks dialog messages if the application alters the Page Setup dialog box. This member is ignored unless the Flags member specifies PSD_ENABLEPAGESETUPHOOK.
lpfnPagePaintHook Points to the exported function that hooks dialog messages that are used in painting the Page Setup dialog box. This member is ignored unless the Flags member specifies PSD_ENABLEPAGEPAINTHOOK.
lpPageSetup
TemplateName
The name of the dialog box resource to be used instead of the default Page Setup dialog. An application must specify PSD_ENABLEPAGESETUPTEMPLATE in the Flags member to enable the function; otherwise, the system ignores this member.
hPageSetup
Template
A handle to the global memory object that contains the preloaded dialog box template to be used instead of the default Page Setup dialog. An application must specify PSD_ENABLEPAGESETUPTEMPLATEHANDLE in the Flags member to enable the function; otherwise, the system ignores this member.

Example: Using the Page Setup Common Dialog

When you have filled in the PAGESETUPDLG structure, a call to PageSetupDlg with a pointer to that structure will bring up the Page Setup common dialog box. The sample that accompanies this article, CMNDLG32, demonstrates how you can use this new dialog box (and all of the new common dialog boxes). The CMNDLG32 sample allows you to look at all of the common dialog boxes in their standard form, using a hook, or using a template. The code below demonstrates how the CMNDLG32 sample fills out the structure and brings up the Page Setup dialog box. You will notice that this code has a switch statement based upon the mode that the user chooses (standard, with a hook, or custom).

void PageSetup( HWND hWnd )
{
    // Initialize PAGESETUPDLG structure
    psDlg.lStructSize = sizeof(PAGESETUPDLG);
    psDlg.hwndOwner = hWnd;
    psDlg.hDevMode = (HANDLE)NULL;
    psDlg.hDevNames = (HANDLE)NULL;
    psDlg.hInstance = (HANDLE)hInst;
    psDlg.lCustData = (LPARAM)NULL;
    psDlg.hPageSetupTemplate = (HGLOBAL)NULL;

    switch( wMode )
    {
        case IDM_STANDARD:
            psDlg.Flags = PSD_DEFAULTMINMARGINS | PSD_DISABLEPAGEPAINTING |
              PSD_DISABLEPRINTER;
            psDlg.lpfnPageSetupHook = (LPPAGESETUPHOOK)(FARPROC)NULL;
            psDlg.lpPageSetupTemplateName = (LPTSTR)NULL;
            psDlg.lpfnPagePaintHook = (LPPAGEPAINTHOOK)(FARPROC)NULL;
            break;

        case IDM_HOOK:
            psDlg.Flags = PSD_DEFAULTMARGINS | PSD_ENABLEPAGESETUPHOOK;
            psDlg.lpfnPageSetupHook = (LPPAGESETUPHOOK)(FARPROC)PageSetupHook;
            psDlg.lpPageSetupTemplateName = (LPTSTR)NULL;
            psDlg.lpfnPagePaintHook = (LPPAGEPAINTHOOK)(FARPROC)NULL;
            break;

        case IDM_CUSTOM:
            psDlg.Flags = PSD_DEFAULTMARGINS | PSD_ENABLEPAGESETUPHOOK |
              PSD_ENABLEPAGESETUPTEMPLATE;
            psDlg.lpfnPageSetupHook = (LPPAGESETUPHOOK)(FARPROC)PageSetupHook;
            psDlg.lpPageSetupTemplateName = (LPTSTR)PRNSETUPDLGORD95;
            psDlg.lpfnPagePaintHook = (LPPAGEPAINTHOOK)(FARPROC)NULL;
           break;

    }

    // Call the Page Setup common dialog
    if (PageSetupDlg(&psDlg) == FALSE)
        ProcessCDError(CommDlgExtendedError(), hWnd );
}

This concludes the walking tour portion of this article.

Using a Common Code Base

Now that you have seen the new dialog boxes, you may be wondering how you can use a common code base between your Windows NT and Windows 95 application. One of the methods that I use to support both Windows NT and Windows 95 and use dialog templates is to query the version of the operating system and then use the appropriate flags and boxes dependent upon the version. This check (complements of Robert Hess) is done as follows:

  DWORD dwVersion;

  dwVersion = GetVersion();
  if (dwVersion < 0x80000000)
    bNewShell = FALSE;
  else
    bNewShell = TRUE;

Later on in my code, when I open the File Open dialog box, I use this flag to let me know which flags to use:

OpenFileName.Flags = OFN_SHOWHELP | OFN_ENABLEHOOK |OFN_HIDEREADONLY | 
     OFN_ENABLETEMPLATE;
OpenFileName.lpfnHook = (LPOFNHOOKPROC)FileOpenHookProc;
if (bNewShell)
{
   // Include the OFN_EXPLORER flag to get the new look.
   OpenFileName.Flags |= OFN_EXPLORER;
   // Use the new template sans the Open File controls.
    OpenFileName.lpTemplateName = (LPTSTR)MAKEINTRESOURCE(IDD_OPENSAVE);
}
else
  // Running under Windows NT, use the old look template.
  OpenFileName.lpTemplateName = (LPTSTR)MAKEINTRESOURCE(FILEOPENORD);

Summary

I hope you enjoyed our walking tour of the new common dialogs, and I hope I have answered some of your questions about working with them. With the information contained in this article and the accompanying sample, CMNDLG32, you should be able to easily use the common dialogs in your own application. If you need more information about any of the structures used in the samples or listed in this article, a good source of information is the Win32 API Programmer's Reference. The new flags and information for Windows 95 will not be in the version of the programmer's reference for Windows NT 3.5, but they will be documented in the software development kit that comes with Windows 95.