Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WTL

Vista Goodies in C++: Using the New Vista File Dialogs

4.96/5 (62 votes)
29 Dec 200618 min read 1   4.7K  
How to use Vista's new file open and file save dialogs with WTL.

Contents

Introduction

In this Vista Goodies article, I'll demonstrate how to use Vista's new file dialogs - the built-in dialogs that you use for File-Open and File-Save operations.

This article is written for the RTM version of Vista, using Visual Studio 2005, WTL 7.5, and the Windows SDK. See the introduction in the first Vista Goodies article for more information on where you can download those components.

In Vista, Microsoft updated the common file dialogs again, this time to make them similar to regular Explorer windows. The file-open and file-save dialogs now have a built-in search field as well as the Favorite Links/tree view pane that are in all Explorer windows. Some applications that use the old common file dialog APIs will automatically get the new dialogs, but for backward compatibility reasons, Vista will still show the older dialogs in some situations.

If an app sets the lStructSize member of the OPENFILENAME struct to indicate that it was written for an OS prior to Windows 2000, it will continue to get the Windows 95-style dialog. The list view control that displays the filenames will have Vista features (such as larger thumbnail views), but the rest of the dialog will be unchanged. Here is a file-open dialog shown by VC6, which customizes the dialog by adding the Open as combo box:

Image 1

If an app was written for the Windows 2000/XP file dialog, it will continue to get that version of the dialog. This will happen for many existing MFC and WTL apps, because those class libraries use a callback function that is not present in Vista. To maintain compatibility, Vista will display the Windows 2000-style dialog for those apps:

Image 2

Finally, for some existing apps and all new apps that use the new file dialog APIs, Vista will show the brand-new common dialogs:

Image 3

This article will cover the new APIs, and demonstrate how to use the new dialogs in your app, even if you are using a GUI class library like WTL.

Using the File Dialogs with Windows APIs

If an app calls the GetOpenFileName() or GetSaveFileName() API to show a file dialog, and does not customize the dialog at all, then Vista will show the new file dialog since there is no danger of breaking the app. The criteria that Vista uses to determine if the app customizes the dialog is whether there is a hook function address stored in the lpfnHook member of the OPENFILENAME struct, or a custom dialog template stored in the lpTemplateName member.

Since MFC and WTL use a hook function to provide notifications to the app, if you use either library's CFileDialog class, you will get the Windows 2000-style dialog. Therefore, to get the Vista-style dialog, you'll need to use the new COM interfaces that replace the GetOpenFileName() and GetSaveFileName() APIs.

File Dialog COM Interfaces

Base interfaces

There are two base interfaces for file dialogs: IModalWindow and IFileDialog. IModalWindow just has one method: Show(). Show() shows the dialog using a given window as the dialog's parent, just like DoModal() in the MFC and ATL dialog classes. Show() returns S_OK if the dialog was shown and the user selected a file, or a failure HRESULT otherwise. If the user cancels the dialog, Show() returns HRESULT_FROM_WIN32(ERROR_CANCELLED).

IFileDialog contains the methods that are common to the file-open and file-save dialogs. IFileDialog has a lot of methods that do the same tasks that were accomplished by setting members of an OPENFILENAME struct in previous versions of Windows. IFileDialog provides a COM-based API and does away with error-prone conventions such as double-null-terminated strings. Here is a quick overview of the IFileDialog methods:

SetFileTypes(), SetFileTypeIndex()
Used for filling in the file types combo box.
GetFileTypeIndex()
Used to determine which file type the user selected.
Advise(), Unadvise()
Used to start and stop listening to events from a file dialog, analogous to the corresponding IConnectionPoint methods.
GetOptions(), SetOptions()
Used to read or write a set of flags that control the dialog's behavior, analogous to the Flags and FlagsEx members of OPENFILENAME.
SetDefaultFolder()
Sets the folder that the dialog will show when it first appears.
SetTitle()
Sets the text in the dialog's caption bar.
SetOkButtonLabel(), SetFileNameLabel()
Sets the text for the Open/Save button and the static control next to the File name edit box, respectively.
GetFolder(), SetFolder()
Used to get or set the folder that the dialog is currently displaying. GetFolder() may also be called before the dialog is shown, in which case it does the same thing as SetDefaultFolder().
AddPlace()
Adds an item to the Favorite Links section (the list of folders in the left pane of the dialog).
SetDefaultExtension()
Sets a default extension to be added to the filename if the user does not enter an extension, analogous to the lpstrDefExt member of OPENFILENAME.
SetClientGuid(), GetClientGuid(), ClearClientData()
Vista normally saves some state information on a per-app basis, so that a file dialog's default folder will be the same folder that the previous dialog was displaying. An app can tell Vista to maintain several such states by creating one GUID per state and passing that GUID to SetClientGuid(). ClearClientData() clears the state associated with that GUID.
SetFilter()
If the app wants to filter out items from its file dialogs, it can create a COM object that implements IShellItemFilter, and pass that IShellItem interface to SetFilter().
GetCurrentSelection()
Gets the item that is currently selected in the dialog.
GetFileName(), SetFileName()
Used to get or set the text in the dialog's File name edit box.
GetResult()
If the user selects a file and clicks OK, GetResult() returns the item that was selected.
Close()
Closes the dialog and specifies what value Show() should return, analogous to what the EndDialog() API does for regular dialogs.

Functions such as GetResult() identify files with an IShellItem interface, instead of a file path or PIDL. You can think of IShellItem as a COM version of a SHITEMID or PIDL, in that it can represent any item in the shell namespace, not just file system objects. I will cover IShellItem later on.

File-Open Dialog Interfaces

IFileOpenDialog inherits from IFileDialog, and has two additional methods for use in multiple-select dialogs:

GetSelectedItems()
Used in place of GetCurrentSelection().
GetResults()
Used in place of GetResult().

Each method returns an IShellItemArray interface that contains one IShellItem for each selected item. Those two methods work in single-select dialogs as well, so you can use them in all of your file-open dialogs if you want.

File-Save Dialog Interfaces

IFileSaveDialog inherits from IFileDialog, and adds a few methods for setting attributes of the file being saved:

SetSaveAsItem()
Sets the initial folder and file name to be shown when the dialog opens.
SetProperties(), SetCollectedProperties(), GetProperties(), ApplyProperties()
Used to read and write shell properties on the file being saved. Shell properties are a new feature of Vista and will not be covered in this article, but Ben Karas's blog has many articles on using properties if you want to learn more about them.

Other Interfaces

There are three other interfaces that will be covered later in this article:

IFileDialogEvents
Used to receive events fired by the built-in controls in a file dialog.
IFileDialogCustomize
Used to add controls to a file dialog.
IFileDialogControlEvents
Used to receive events fired from controls that are added via IFileDialogCustomize.

Using the File Dialog Interfaces Directly

Basic File-Open Dialog Example

Here is a simple example of how to use the file-open dialog to select one file. This code does very little customization, just setting the dialog's caption and putting three items in the file type list.

The file type list uses a new system that is far easier than the double-null-terminated strings used in the OPENFILENAME struct. Each item in the list is described by a COMDLG_FILTERSPEC struct:

struct COMDLG_FILTERSPEC
{
  LPCWSTR pszName;
  LPCWSTR pszSpec;
};

pszName contains the string to show in the combo box, for example "Text files". pszSpec is the wildcard pattern to use with that item, for example "*.txt".

Here are the setup steps to create a file dialog COM object and initialize it:

void CMainDlg::OnFileOpen()
{
HRESULT hr;
CComPtr<IFileOpenDialog> pDlg;
COMDLG_FILTERSPEC aFileTypes[] = {
    { L"Text files", L"*.txt" },
    { L"Executable files", L"*.exe;*.dll" }, 
    { L"All files", L"*.*" }
  };
 
  // Create the file-open dialog COM object.
  hr = pDlg.CoCreateInstance ( __uuidof(FileOpenDialog) );
 
  if ( FAILED(hr) )
    return;
 
  // Set the dialog's caption text and the available file types.
  // NOTE: Error handling omitted here for clarity.
  pDlg->SetFileTypes ( _countof(aFileTypes), aFileTypes );
  pDlg->SetTitle ( L"A Single-Selection Dialog" );

After the initialization is done, we call Show() to display the dialog:

// Show the dialog.
hr = pDlg->Show ( m_hWnd );

If hr contains a success HRESULT, we call GetResult() to get an IShellItem interface on the file that the user selected. To get the path to that file, we call IShellItem::GetDisplayName() and pass the SIGDN_FILESYSPATH flag, which indicates that we want the file system path to the item. If the user chose something that isn't a file system object, GetDisplayName() will fail. It's also our responsibility to free the string that GetDisplayName() returns.

  // If the user chose a file, show a message box with the
  // full path to the file.
  if ( SUCCEEDED(hr) )
    {
    CComPtr<IShellItem> pItem;
 
    hr = pDlg->GetResult ( &pItem );
 
    if ( SUCCEEDED(hr) )
      {
      LPOLESTR pwsz = NULL;
 
      hr = pItem->GetDisplayName ( SIGDN_FILESYSPATH, &pwsz );
 
      if ( SUCCEEDED(hr) )
        {
        MessageBox ( pwsz );
        CoTaskMemFree ( pwsz );
        }
      }
    }
}

Here's the dialog with the file type combo box expanded:

Image 4

Note that Vista automatically appends the wildcard pattern to the file type description (the pszName member of COMDLG_FILTERSPEC). This is different from earlier OSes, where it was a convention for the description to include the pattern. If you use a description that contains a pattern, Vista will try to compensate for this by looking at the end of the description. If the pattern appears at the end of the description, Vista will not add the pattern again. For example, if your strings are:

Description: All files (*.*)
Pattern: *.*

Vista will not add another "(*.*)" to the description. The pattern in the description does not have to be in parentheses, however most apps do use parentheses, so that's why Vista checks for them.

Note that the two wildcard patterns must match exactly (not counting the parentheses), so if you use strings such as:

Description: Word files (*.doc, *.docx)
Pattern: *.doc;*.docx

Vista will still append "(*.doc;*.docx)" to the description, because the two wildcard patterns don't match exactly.

Even though the code given above doesn't call SetOptions(), a few options are on by default. The file-open dialog automatically has FOS_FILEMUSTEXIST set, so the user cannot enter a filename when that file doesn't exist.

Basic File-Save Dialog Example

The setup steps for a file-save dialog are similar. This example shows how to prompt the user to save some data, with the default format being a text file with a .txt extension. This example also changes the Save button text using IFileDialog::SetOkButtonLabel().

void CMainDlg::OnFileSave()
{
HRESULT hr;
CComPtr<IFileSaveDialog> pDlg;
COMDLG_FILTERSPEC aFileTypes[] = {
    { L"Text files", L"*.txt" },
    { L"All files", L"*.*" }
  };
 
  // Create the file-save dialog COM object.
  hr = pDlg.CoCreateInstance ( __uuidof(FileSaveDialog) );
 
  if ( FAILED(hr) )
    return;
 
  // Set the dialog's caption text, file types, Save button label,
  // default file name, and default extension.
  // NOTE: Error handling omitted here for clarity.
  pDlg->SetFileTypes ( _countof(aFileTypes), aFileTypes );
  pDlg->SetTitle ( L"A File-Save Dialog" );
  pDlg->SetOkButtonLabel ( L"D&o It!" );
  pDlg->SetFileName ( L"mystuff.txt" );
  pDlg->SetDefaultExtension ( L"txt" );
 
  // Show the dialog.
  hr = pDlg->Show ( m_hWnd );
 
  // If the user chose a file, save the user's data to that file.
  if ( SUCCEEDED(hr) )
    {
    CComPtr<IShellItem> pItem;
 
    hr = pDlg->GetResult ( &pItem );
 
    if ( SUCCEEDED(hr) )
      {
      LPOLESTR pwsz = NULL;
 
      hr = pItem->GetDisplayName ( SIGDN_FILESYSPATH, &pwsz );
 
      if ( SUCCEEDED(hr) )
        {
        //TODO: Save the file here, 'pwsz' has the full path
        CoTaskMemFree ( pwsz );
        }
      }
    }
}

Here's the dialog, showing the default file name:

Image 5

As with the file-open dialog, the file-save dialog has some options on by default. Among these is FOS_PATHMUSTEXIST, so the user must enter a directory name that already exists.

Multi-Select File Open Dialog Example

Finally, here's an example that uses a multi-select file-open dialog. The setup steps are mostly the same as in the previous file-open example. To allow multiple selection, we set the FOS_ALLOWMULTISELECT flag using SetOptions(). Notice that we have to call GetOptions() first and then add FOS_ALLOWMULTISELECT, because some options are on by default, as mentioned earlier.

CComPtr<IFileOpenDialog> pDlg;
 
  // Previous setup steps omitted...
 
DWORD dwFlags = 0;
 
  pDlg->GetOptions ( &dwFlags );
  pDlg->SetOptions ( dwFlags | FOS_ALLOWMULTISELECT );
 
  // Show the dialog.
  hr = pDlg->Show ( m_hWnd );

If Show() succeeds, we call GetResults() to get an array containing all the selected files. We get the size of that array with IShellItemArray::GetCount(), the loop through and get the path of each selected file.

// If the user chose any files, loop thru the array of files.
if ( SUCCEEDED(hr) )
  {
  CComPtr<IShellItemArray> pItemArray;

  hr = pDlg->GetResults ( &pItemArray );

  if ( SUCCEEDED(hr) )
    {
    DWORD cSelItems;

    // Get the number of selected files.
    hr = pItemArray->GetCount ( &cSelItems );

    if ( SUCCEEDED(hr) )
      {
      for ( DWORD j = 0; j < cSelItems; j++ )
        {
        CComPtr<IShellItem> pItem;

        // Get an IShellItem interface on the next file.
        hr = pItemArray->GetItemAt ( j, &pItem );

        if ( SUCCEEDED(hr) )
          {
          LPOLESTR pwsz = NULL;

          // Get its file system path.
          hr = pItem->GetDisplayName ( SIGDN_FILESYSPATH, &pwsz );

          if ( SUCCEEDED(hr) )
            {
            MessageBox ( pwsz );
            CoTaskMemFree ( pwsz );
            }
          }
        }
      }
    }
  }

Handling File Dialog Events

Prior to Vista, an app could be notified of file dialog events by specifying a callback function in the lpfnHook member of the OPENFILENAME struct. In Vista, the app creates a COM object that implements the IFileDialogEvents interface, and passes that interface to the file dialog. When certain events occur, the file dialog calls IFileDialogEvents methods to let the app act on the events.

The IFileDialogEvents Interface

IFileDialogEvents has seven methods that are called when various events occur:

OnFolderChanging(), OnFolderChange()
Called before and after the dialog navigates to a different folder. If the app wants to prevent the change, OnFolderChanging() can do so.
OnSelectionChange()
Called after the user selects a different file in the file list.
OnTypeChange()
Called when the user changes the selection in the file types combo box.
OnFileOk()
Called when the user selects a file and clicks the Open or Save button. The app can prevent the file dialog from closing by returning S_FALSE from this method.
OnOverwrite()
Called by a file-save dialog if the user selects a file that already exists. The app can show its own UI in this method, or let the dialog show the default UI.
OnShareViolation()
Called when a sharing error occurs during an open or save operation. The app can show its own UI in this method, or let the dialog show the default UI.

Handling Events

We can handle events by creating a new C++ class for a COM object that implements IFileDialogEvents:

class CDlgEventHandler :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CDlgEventHandler>,
    public IFileDialogEvents
{
public:
  CDlgEventHandler();
  ~CDlgEventHandler();
 
  BEGIN_COM_MAP(CDlgEventHandler)
    COM_INTERFACE_ENTRY(IFileDialogEvents)
  END_COM_MAP()
 
  // IFileDialogEvents
  STDMETHODIMP OnFileOk(IFileDialog* pfd);
  STDMETHODIMP OnFolderChanging(IFileDialog* pfd, IShellItem* psiFolder);
  STDMETHODIMP OnFolderChange(IFileDialog* pfd);
  STDMETHODIMP OnSelectionChange(IFileDialog* pfd);
  STDMETHODIMP OnShareViolation(IFileDialog* pfd, IShellItem* psi,
                                FDE_SHAREVIOLATION_RESPONSE* pResponse);
  STDMETHODIMP OnTypeChange(IFileDialog* pfd);
  STDMETHODIMP OnOverwrite(IFileDialog* pfd, IShellItem* psi,
                           FDE_OVERWRITE_RESPONSE* pResponse);
};

I'll show one method here, OnFolderChanging(), that prints a trace message showing the new folder path. You can look at the sample project to see the remaining methods, all of which print similar trace messages.

// Helper method to get a file path from an IShellItem:
bool PathFromShellItem (
  IShellItem* pItem, CString& sPath )
{
HRESULT hr;
LPOLESTR pwsz = NULL;
 
  hr = pItem->GetDisplayName ( SIGDN_FILESYSPATH, &pwsz );
 
  if ( FAILED(hr) )
    return false;
 
  sPath = pwsz;
  CoTaskMemFree ( pwsz );
  return true;
}
 
STDMETHODIMP CDlgEventHandler::OnFolderChanging (
  IFileDialog* pfd, IShellItem* psiFolder )
{
CString sPath;
 
  ATLTRACE("OnFolderChanging called\n");
 
  if ( PathFromShellItem ( psiFolder, sPath ) )
    ATLTRACE("Changing to folder: %ls\n", (LPCWSTR) sPath);
 
  return S_OK;  // allow the change
}

Now that we have this class, we need to tell the file dialog that we want to get events. Let's go back to the first file-open dialog example and add an event handler. The new code is shown in bold:

void CMainDlg::OnFileOpen()
{
HRESULT hr;
CComPtr<IFileOpenDialog> pDlg;
 
  // Create the file-open dialog COM object.
  hr = pDlg.CoCreateInstance ( __uuidof(FileOpenDialog) );
 
  if ( FAILED(hr) )
    return;
 
  // Instantiate a COM object and listen for events
CComObjectStackEx<CDlgEventHandler> obj;
CComQIPtr<IFileDialogEvents> pEvents = obj.GetUnknown();
DWORD dwCookie;
bool bAdvised;
 
  hr = pDlg->Advise ( pEvents, &dwCookie );
 
  bAdvised = SUCCEEDED(hr);
 
  hr = pDlg->Show ( m_hWnd );
 
  // Call Unadvise() to stop listening
  if ( bAdvised )
    pDlg->Unadvise ( dwCookie );
 
  //...
}

We first create a CDlgEventHandler object, using the CComObjectStackEx template class to manage the COM object's lifetime. (CComObjectStackEx is like CComObjectStack, except it supports QueryInterface().) We then call Advise() and pass it an IFileDialogEvents interface on our CDlgEventHandler object. If Advise() succeeds, we clean up by calling Unadvise() after Show() returns.

File Dialog Customization

Before Vista, customizing a file dialog was done via a tricky process that involved creating a custom dialog template. When an app used this method, it was tightly coupled to a particular version of the common dialog, which meant that the app could not take advantage of features introduced in later versions of Windows, such as the Places bar and resizeable dialogs. For example, this is how the file-open dialog looks in Paint Shop Pro 5:

Image 6

Every element in the dialog, aside from the list view control, still looks like the original Windows 95 dialog. Windows can't automatically enable newer features, because doing so would change the layout of the dialog and break PSP's custom template.

Vista supports customization of the new file dialogs, but there are two main differences compared to the old method:

  1. Customization is done through a COM interface, which does not expose the app to any internal details of the file dialog. Presumably, customizations written today will work in future versions of Windows that have different file dialogs.
  2. An app can only add a set of pre-defined controls and text labels, instead of arbitrary UI elements.

The Control System

There are a few simple controls available to applications: static text controls, push buttons, check boxes, edit boxes, and separators. There are also three controls that are containers for other items: radio button groups, combo boxes, and buttons that show popup menus. There is also a feature called a visual group, which is a way to group controls together in the dialog. A visual group has a text label and can contain other controls (including controls that are themselves containers).

Once a control has been added, it can be modified only in limited ways. A control can be enabled or disabled, hidden or shown, and an item in a container can be selected. Containers have a little more flexibility, in that their contents can be changed at any time. Controls are identified by DWORD identifiers that the app manages.

Adding Simple Controls

To add controls, we begin by creating the file dialog COM object as we saw earlier, then querying it for the IFileDialogCustomize interface. We can add controls using these methods:

AddCheckButton()
Adds a check box. The initial state of the check box can be checked or unchecked, as the app desires.
AddEditBox()
Adds an edit box. The app can also pass a string, which will be used for the edit box's initial text.
AddPushButton()
Adds a button.
AddSeparator()
Adds a separator, an etched line similar to the separators used in menus.
AddText()
Adds a static text control.

Here is an example of how to add a static text control and a button:

void CMainDlg::OnFileSave()
{
HRESULT hr;
CComPtr<IFileSaveDialog> pDlg;
 
  // Create the file-save dialog COM object.
  hr = pDlg.CoCreateInstance ( __uuidof(FileSaveDialog) );
 
  if ( FAILED(hr) )
    return;
 
  // Get an IFileDialogCustomize interface and add some controls.
CComQIPtr<IFileDialogCustomize> pfdc = pDlg;
 
  if ( !pfdc )
    return;
 
  pfdc->AddText(1000, L"A label");
  pfdc->AddPushButton(1001, L"My Button");
 
  // rest of dialog setup omitted...
}

Here's how the save dialog looks with these two additional controls:

Image 7

Adding Container Controls

In order to add a container control, we first call AddComboBox(), AddRadioButtonList(), or AddMenu() to create the container. Then we call AddControlItem() once for each item to be shown in the container. Here is an example showing how to add a menu button:

CComPtr<IFileOpenDialog> pDlg;
 
  // dialog creation steps omitted...
 
CComQIPtr<IFileDialogCustomize> pfdc = pDlg;
const DWORD dwMenuID = 1100;
 
  if ( !pfdc )
    return;
 
  pfdc->AddMenu(dwMenuID, L"A new menu button");
  pfdc->AddControlItem(dwMenuID, 1101, L"Menu command 1");
  pfdc->AddControlItem(dwMenuID, 1102, L"Menu command 2");

And here's how the button looks with the popup menu displayed:

Image 8

Radio buttons and combo boxes are handled a bit differently. AddComboBox() and AddRadioButtonList() do not have a string parameter, since there is no label for those controls. Also, we can call SetSelectedControlItem() to select one item in the container. Here is a snippet that adds a radio button group:

CComPtr<IFileOpenDialog> pDlg;
 
  // dialog creation steps omitted...
 
CComQIPtr<IFileDialogCustomize> pfdc = pDlg;
const DWORD dwRadioGroupID = 1200;
 
  if ( !pfdc )
    return;
 
  pfdc->AddRadioButtonList(dwRadioGroupID);
  pfdc->AddControlItem(dwRadioGroupID, 1201, L"Veronica");
  pfdc->AddControlItem(dwRadioGroupID, 1202, L"Mars");
  pfdc->SetSelectedControlItem(dwRadioGroupID, 1202);

And here's the dialog with the two additional radio buttons:

Image 9

Using Visual Groups

The controls in a visual group are positioned together in the dialog. The group also has a label, which can be used to convey what the controls will do or what feature they control. To create a visual group, we start by calling StartVisualGroup(), passing the group's ID and label. All controls that are added are automatically put into that group, until the call to EndVisualGroup() that completes the group.

CComPtr<IFileOpenDialog> pDlg;
 
  // dialog creation steps omitted...
 
CComQIPtr<IFileDialogCustomize> pfdc = pDlg;
const DWORD dwRadioGroupID = 1200,
            dwVisualGroupID = 1300;
 
  if ( !pfdc )
    return;
 
  pfdc->StartVisualGroup(dwVisualGroupID, L"Favorite show?");
 
  pfdc->AddRadioButtonList(dwRadioGroupID);
  pfdc->AddControlItem(dwRadioGroupID, 1301, L"The Daily Show");
  pfdc->AddControlItem(dwRadioGroupID, 1302, L"The Colbert Report");
  pfdc->SetSelectedControlItem(dwRadioGroupID, 1302);
 
  pfdc->EndVisualGroup();

Here's the dialog with the radio buttons in a group:

Image 10

Making a Control Prominent

One additional option is to make a control prominent. Doing so moves the control down next to the Open or Save button. However, not all controls can be moved this way. Only check boxes, buttons, combo boxes, and menu buttons can be made prominent. A visual group that contains only one such control can also be made prominent. We can make the push button control prominent with this call:

// NOTE: 1001 is the button's control ID
pfdc->AddPushButton(1001, L"My Button");
pfdc->MakeProminent(1001);

The button is then moved down next to Save:

Image 11

Note that if an app adds only one control to the dialog, and it is a type of control that can be made prominent, it will automatically be made prominent.

Handling Events From Additional Controls

IFileDialogControlEvents has four methods that are called when various events occur that relate to the controls you add with IFileDialogCustomize:

OnButtonClicked()
Called when a push button is clicked.
OnCheckButtonToggled()
Called when a check box is clicked.
OnControlActivating()
Called when a combo box is opened or a menu button is clicked (to show the popup menu).
OnItemSelected()
Called when a radio button, combo box item, or popup menu item is clicked.

The sample project implements IFileDialogControlEvents and IFileDialogEvents in the same C++ class. Even though we pass a IFileDialogEvents interface to Advise(), the file dialog will query for IFileDialogControlEvents when necessary.

You can see these event handlers in action in the sample project. I kind of went overboard and added every possible type of control to the dialog. You wouldn't do this in real code, of course, but it does make for a good demo.

Image 12

Other File Dialog Features

Setting the Initial Folder

Vista keeps some state information about the file dialogs that an app shows. One thing that is recorded is the folder that the most-recently-used file dialog was displaying. By default, the next file dialog the app shows will begin in that same folder. An app can override this default by calling IFileDialog::SetFolder() before Show().

This code snippet shows how to make the dialog begin in the My Pictures directory. It uses two new Vista APIs, SHGetKnownFolderPath() and SHCreateItemFromParsingName(). SHGetKnownFolderPath() is a replacement for SHGetSpecialFolderPath(), and SHCreateItemFromParsingName() takes the folder path and creates an IShellItem that we can pass to SetFolder().

CComPtr<IFileOpenDialog> pDlg;
 
  // dialog creation steps omitted...
 
CComPtr<IShellItem> psiFolder;
LPWSTR wszPath = NULL;
 
  hr = SHGetKnownFolderPath ( FOLDERID_Pictures, KF_FLAG_CREATE,
                              NULL, &wszPath );
 
  if ( SUCCEEDED(hr) )
    {
    hr = SHCreateItemFromParsingName ( wszPath, NULL,
                                       IID_PPV_ARGS(&psiFolder) );
 
    if ( SUCCEEDED(hr) )
      pDlg->SetFolder ( psiFolder );
 
    CoTaskMemFree ( wszPath );
    }

Keeping State Data

If your app has many load/save features, beyond simple documents, you might want to keep file dialog state data separately for each feature. You can do this by creating a GUID for each state you want to keep, and calling IFileDialog::SetClientGuid() before Show().

CComPtr<IFileOpenDialog> pDlg;
 
  // dialog creation steps omitted...
 
  // Tell the dlg to load the state data associated with this GUID:
  // {7D5FE367-E148-4a96-B326-42EF237A3662}
  // NOTE: Be sure to change this to your own GUID if you reuse this code~!
  // This is not strictly necessary for our app (normally you'd want loads
  // and saves to share the same state data) but I'm putting this in for the demo.
static const GUID guidFileOpen = {
    0x7D5FE367, 0xE148, 0x4A96, { 0xB3, 0x26, 0x42, 0xEF,
    0x23, 0x7A, 0x36, 0x62 } };
 
  hr = pDlg->SetClientGuid ( guidFileOpen );

Using the Sample Project

The sample project is a dialog-based app that lets you show a file-open dialog in three ways: the GetOpenFileName() API, WTL's CFileDialog, and the IFileOpenDialog interface. The files you select in those dialogs are shown in the dialog's list control.

Image 13

The fourth button shows a file-save dialog, and if you select a file, the app writes all the filenames in the list control to that file. This dialog shows a more real-world usage of customization: it has a combo box where you can select the encoding to be used for the text file.

References

Copyright and License

This article is copyrighted material, ©2006 by Michael Dunn. I realize this isn't going to stop people from copying it all around the 'net, but I have to say it anyway. If you are interested in doing a translation of this article, please email me to let me know. I don't foresee denying anyone permission to do a translation, I would just like to be aware of the translation so I can post a link to it here.

The demo code that accompanies this article is released to the public domain. I release it this way so that the code can benefit everyone. (I don't make the article itself public domain because having the article available only on CodeProject helps both my own visibility and the CodeProject site.) If you use the demo code in your own application, an email letting me know would be appreciated (just to satisfy my curiosity about whether folks are benefiting from my code) but is not required. Attribution in your own source code is also appreciated but not required.

Revision History

  • December 7, 2006: Article first published.
  • December 26, 2006: Code tested on the RTM Vista build, intro updated accordingly.

Series navigation: « Monitoring the Computer's Power Status | Showing Friendly Messages with Task Dialogs »

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here