Starting with Windows Vista, the Common Item Dialog supersedes the older Common File Dialog when used to open or save a file. The Common Item Dialog is used in two variations: the
Open dialog and the
Save dialog. These two dialogs share most of their functionality, but each has its own unique methods.
The following topics are discussed here:
IFileDialog, IFileOpenDialog, and IFileSaveDialog
Windows Vista provides implementations of the Open and Save dialogs: CLSID_FileOpenDialog and CLSID_FileSaveDialog. Those dialog boxes are shown here.
.png)
.png)
IFileOpenDialog and IFileSaveDialog inherit from IFileDialog and share much of their functionality. In addition, the Open dialog supports IFileOpenDialog, and the Save dialog supports IFileSaveDialog.
The Common Item Dialog implementation found in Windows Vista provides several advantages over the implementation provided in earlier versions:
- Supports direct use of the Shell namespace through IShellItem instead of using file system paths.
- Enables simple customization of the dialog, such as setting the label on the OK button, without requiring a hook procedure.
- Supports more extensive customization of the dialog by the addition of a set of data-driven controls that operate without a Win32 dialog template. This customization scheme frees the calling process from user interface (UI) layout. Since any changes to the dialog design continue to use this data model, the dialog implementation is not tied to the specific current version of the dialog.
- Supports caller notification of events within the dialog, such as selection change or file type change. Also enables the calling process to hook certain events in the dialog, such as the parsing.
- Introduces new dialog features such as adding caller-specified places to the Places bar.
- In the Save dialog, developers can take advantage of new metadata features of the Windows Vista Shell.
Additionally, developers can choose to implement the following interfaces:
The Open or Save dialog returns an IShellItem or IShellItemArray object to the calling process. The caller can then use an individual IShellItem object to get a file system path or to open a stream on the item to read or write information.
Flags and options available to the new dialog methods are very similar to the older OFN flags found in the OPENFILENAME structure and used in GetOpenFileName and GetSaveFileName. Many of them are exactly the same, except that they begin with an FOS prefix. The complete list can be found in the IFileDialog::GetOptions and IFileDialog::SetOptions topics. Open and Save dialogs are created by default with the most common flags. For the Open dialog, this is (FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR) and for the Save dialog this is (FOS_OVERWRITEPROMPT | FOS_NOREADONLYRETURN | FOS_PATHMUSTEXIST | FOS_NOCHANGEDIR).
IFileDialog and its descendant interfaces inherit from and extend IModalWindow. IModalWindow::Show takes as its only parameter the handle of the parent window. If IModalWindow::Show returns successfully, there is a valid result. If it returns HRESULT_FROM_WIN32(ERROR_CANCELLED), it means the user cancelled the dialog. It might also legitimately return another error code such as E_OUTOFMEMORY.
Sample Usage
The following sections show example code for a variety of dialog tasks.
Basic Usage
The following example shows how to launch a basic Open dialog that can open items of any type.
HRESULT SimpleInvoke(HWND hwnd)
{
IFileDialog *pfd;
// CoCreate the dialog object.
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pfd));
if (SUCCEEDED(hr))
{
// Show the dialog
hr = pfd->Show(hwnd);
if (SUCCEEDED(hr))
{
// Obtain the result of the user's interaction with the dialog.
IShellItem *psiResult;
hr = pfd->GetResult(&psiResult);
if (SUCCEEDED(hr))
{
// Do something with the result.
psiResult->Release();
}
}
pfd->Release();
}
return hr;
}
Specifying File Types for a Dialog
To set specific file types that the dialog can handle, use the IFileDialog::SetFileTypes method. That method accepts an array of COMDLG_FILTERSPEC structures, each of which represents a file type.
The default extension mechanism in a dialog is unchanged from GetOpenFileName and GetSaveFileName. The file extension that is appended to the text the user types in the file name edit box is initialized when the dialog opens. It should match the default file type (that selected as the dialog opens). If the default file type is "*.*" (all files), the file can be an extension of your choice. If the user chooses a different file type, the extension automatically updates to the first file extension associated with that file type. If the user chooses "*.*" (all files), then the extension reverts to its original value.
Limiting Results to File System Items
The following example extends the previous example to demonstrate how to restrict results to file system items. Note that IFileDialog::SetOptions adds the new flag to a value obtained through IFileDialog::GetOptions. This is the recommended method.
DWORD dwOptions
hr = pfd->GetOptions(&dwOptions);
if (SUCCEEDED(hr))
{
hr = pfd->SetOptions(dwOptions | FOS_FORCEFILESYSTEM);
}
if (SUCCEEDED(hr))
{
IShellItem *psiResult;
hr = pfd->Show(hwnd);
if (SUCCEEDED(hr))
{
hr = pfd->GetResult(&psiResult);
if (SUCCEEDED(hr))
{
WCHAR *pszPath;
hr = psiResult->GetDisplayName(SIGDN_FILESYSPATH, *pszPath);
if (SUCCEEDED(hr))
{
// Do something with the path, such as calling CreateFile.
CoTaskMemFree(pszPath);
}
psiResult->Release();
}
}
}
Controlling the Default Folder
Almost any folder in the Shell namespace can be used as the default folder for the dialog (the folder presented when the user chooses to open or save a file). The following code shows how to open the dialog in the music folder.
IShellItem *psiMusic;
hr = SHCreateItemFromFolderID(CSIDL_MYMUSIC,
NULL,
IID_PPV_ARGS(&psiMusic));
if (SUCCEEDED(hr))
{
hr = pfd->SetDefaultFolder(psiMusic);
psiMusic->Release();
}
The default folder is the folder in which the dialog starts the first time a user opens it from your application. After that, the dialog will open in the last folder a user opened or the last folder they used to save an item. See State Persistence for more details.
You can force the dialog to always show the same folder when it opens, regardless of previous user action, by calling IFileDialog::SetFolder. However, we do not recommended doing this. If you call SetFolder before you display the dialog box, the most recent location that the user saved to or opened from is not shown. Unless there is a very specific reason for this behavior, it is not a good or expected user experience and should be avoided. In almost all instances, IFileDialog::SetDefaultFolder is the better method.
The following example demonstrates the use of IFileDialog::SetFolder.
IShellItem *psiMusic;
hr = SHCreateItemFromFolderID(CSIDL_MYMUSIC,
NULL,
IID_PPV_ARGS(&psiMusic));
if (SUCCEEDED(hr))
{
hr = pfd->SetFolder(psiMusic);
psiMusic->Release();
}
When saving a document for the first time in the Save dialog, you should follow the same guidelines in determining the initial folder as you did in the Open dialog. If the user is editing a previously existing document, open the dialog in the folder where that document is stored, and populate the edit box with that document's name. This can be done with the IFileSaveDialog interface as shown here:
// psiCurrent is the current document.
HRESULT DoSaveAs(IShellItem *psiCurrent)
{
IFileSaveDialog *pSaveDialog;
HRESULT hr = CoCreateInstance(CLSID_FileSaveDialog,
NULL,
cLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pSaveDialog));
if (SUCCEEDED(hr))
{
hr = pSaveDialog->SetSaveAsItem(psiCurrent);
}
...
}
However, if you want to ignore the document's current name and display the default file name in the edit box each time, add the following code:
hr = pSaveDialog->SetSaveName(L"Untitled");
Note that you would not typically include a file extension in this case. File extensions are automatically appended by the dialog according to the file type the user has selected.
Adding Items to the Places Bar
The following example demonstrates the addition of items to the Places bar:
IShellItem *psiMusic;
hr = SHCreateItemFromFolderID(CSIDL_MYMUSIC,
NULL,
IID_PPV_ARGS(&psiMusic));
if (SUCCEEDED(hr))
{
hr = pfd->AddPlace(psiMusic);
psiMusic->Release();
}
State Persistence
Prior to Windows Vista, a state, such as the last visited folder, was saved on a per-process basis. However, that information was used regardless of the particular action. For example, a video editing application would present the same folder in the Render As dialog as is would in the Import Media dialog. In Windows Vista you can be more specific through the use of GUIDs.
GUID guid;
pfd->IdentifyDialog(&guid);
Multiselect Capabilities
Multiselect functionality is available in the Open dialog using the IFileOpenDialog::GetResults method as shown here.
HRESULT MultiselectInvoke(HWND hwnd)
{
IFileOpenDialog *pfd;
// CoCreate the dialog object.
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pfd));
if (SUCCEEDED(hr))
{
// Specify multiselect.
hr = pfd->GetOptions(&dwOptions);
if (SUCCEEDED(hr))
{
hr = pfd->SetOptions(dwOptions | FOS_ALLOWMULTISELECT);
}
if (SUCCEEDED(hr))
{
// Show the Open dialog.
hr = pfd->Show(hwnd);
if (SUCCEEDED(hr))
{
// Obtain the result of the user interaction.
IShellItemArray *psiaResult;
hr = pfd->GetResults(&psiaResults);
if (SUCCEEDED(hr))
{
// Do something with the resulting IShellItemArray
psiaResults->Release();
}
}
}
pfd->Release();
}
return hr;
}
Listening to Events from the Dialog
A calling process can register an IFileDialogEvents interface with the dialog by using the IFileDialog::Advise and IFileDialog::Unadvise methods as shown here.
IFileDialogEvents *pfde = NULL;
DWORD dwCookie = 0;
hr = CMyEventsClass_CreateInstance(IID_PPV_ARGS(&pfde));
if (SUCCEEDED(hr))
{
hr = pfd->Advise(pfde, &dwCookie);
pfde->Release();
}
...
// Unhook the event handler after you are done with the dialog.
if (dwCookie)
{
pfd->Unadvise(dwCookie);
}
The calling process can use events for notification when the user changes the folder, file type, or selection. These events are particularly useful when the calling process has added controls to the dialog (see Customizing the Dialog) and must change the state of those controls in reaction to these events. Useful in all cases is the ability of the calling process to provide custom code to deal with situations such as sharing violations, overwriting files, or determining if a file is valid before the dialog closes. Some of those cases are described in this section.
OnFileOk
This method is called after the user chooses an item, just before the dialog closes. The application can then call IFileDialog::GetResult or IFileOpenDialog::GetResults as would be done once the dialog had closed. If the item chosen is acceptable, they can return S_OK. Otherwise, they return S_FALSE and display UI that tells the user why the chosen item is not valid. If S_FALSE is returned, the dialog does not close.
The calling process can use the window handle of the dialog itself as the parent of the UI. That handle can be obtained by first calling IOleWindow::QueryInterface and then calling IOleWindow::GetWindow with the handle as shown in this example.
HRESULT CMyEventsClass::OnFileOk(IFileDialog *pfd)
{
IShellItem *psiResult;
HRESULT hr = pfd->GetResult(&psiResult);
if (SUCCEEDED(hr))
{
SFGAOF attributes;
hr = psiResult->GetAttributes(SFGAO_COMPRESSED, &attributes);
if (SUCCEEDED(hr))
{
if (attributes & SFGAO_COMPRESSED)
{
// Accept the file.
hr = S_OK;
}
else
{
// Refuse the file.
hr = S_FALSE;
_DisplayMessage(pfd, L"Not a compressed file.");
}
}
psiResult->Release();
}
return hr;
}
HRESULT CMyEventsClass::_DisplayMessage(IFileDialog *pfd, PCWSTR pszMessage)
{
IOleWindow *pWindow;
HRESULT hr = pfd->QueryInterface(IID_PPV_ARGS(&pWindow));
if (SUCCEEDED(hr))
{
HWND hwndDialog;
hr = pWindow->GetWindow(&hwndDialog);
if (SUCCEEDED(hr))
{
MessageBox(hwndDialog, pszMessage, L"An error has occurred", MB_OK);
}
pWindow->Release();
}
return hr;
}
OnShareViolation and OnOverwrite
If the user chooses to overwrite a file in the Save dialog, or if a file being saved or replaced is in use and cannot be written to (a sharing violation), the application can provide custom functionality to override the default behavior of the dialog. By default, when overwriting a file, the dialog displays a prompt allowing the user to verify this action. For sharing violations, by default the dialog displays an error message, it does not close, and the user is required to make another choice. The calling process can override these defaults and display its own UI if desired. The dialog can be instructed to refuse the file and remain open or accept the file and close successfully.
Customizing the Dialog
A variety of controls can be added to the dialog without supplying a Win32 dialog template. These controls include PushButton, ComboBox, EditBox, CheckButton, RadioButton lists, Groups, Separators, and Static Text controls. Call QueryInterface on the dialog object (IFileDialog, IFileOpenDialog, or IFileSaveDialog) to obtain an IFileDialogCustomize pointer. Use that interface to add controls. Each control has an associated caller-supplied ID as well as a visible and enabled state that can be set by the calling process. Some controls, such as PushButton, also have text associated with them.
Multiple controls can be added into a "visual group" that moves as a single unit in the layout of the dialog. Groups can have a label associated with them.
Controls can be added only before the dialog is shown. However, once the dialog is displayed, controls can be hidden or shown as desired, perhaps in response to user action. The following example shows how to add an Encoding ComboBox to a dialog.
// The group control
#define ENCODINGGROUP 0
// The ComboBox control
#define ENCODINGCOMBO 1
// These are the items within the ComboBox.
// Their IDs need be unique only within the ComboBox.
#define FT_ANSI 0
#define FT_UNICODE 1
#define FT_UNICODEBE 2
#define FT_UTF8 3
//
// Add the ComboBox for Encoding through the IFileDialogCustomize interface.
//
HRESULT AddEncodingComboBox(IFileDialogCustomize *pfdc)
{
// We want a label next to the ComboBox, so we put it in a group.
HRESULT hr = pfdc->StartVisualGroup(ENCODINGGROUP, L"&Encoding");
if (SUCCEEDED(hr))
{
// Add the ComboBox:
hr = pfdc->AddComboBox(ENCODINGCOMBO);
if (SUCCEEDED(hr))
{
// Add the four choices to be presented in the ComboBox.
hr = pfdc->AddControlItem(ENCODINGCOMBO, FT_ANSI, L"ANSI");
if (SUCCEEDED(hr))
{
hr = pfdc->AddControlItem(ENCODINGCOMBO, FT_UNICODE, L"Unicode");
if (SUCCEEDED(hr))
{
hr = pfdc->AddControlItem(ENCODINGCOMBO,
FT_UNICODEBE,
L"Unicode big endian");
if (SUCCEEDED(hr))
{
hr = pfdc->AddControlItem(ENCODINGCOMBO,
FT_UTF8,
L"Unicode);
}
}
}
}
if (SUCCEEDED(hr))
{
hr = pfdc->EndVisualGroup();
}
}
return hr;
}
HRESULT HandleUserEncodingSelection(IFileDialogCustomize *pfdc)
{
DWORD dwItem;
// This method can be called at any time before, during or
// after the dialog is shown.
HRESULT hr = pfdc->GetSelectedControlItem(ENCODINGCOMBO, &dwItem);
if (SUCCEEDED(hr))
{
// Process dwItem: FT_ANSI, FT_UNICODE, FT_UNICODEBE or FT_UTF8
}
return hr;
}
Adding Options to the OK Button
Similarly, choices can be added to the Open or Save buttons, which are the OK button for their respective dialog types. The options are accessible through a drop-down list box attached to the button. The first item in the list becomes the text for the button. The following example shows how to provide an Open button with two possibilities: "Open" and "Open as read-only".
#define OPENCHOICES 0
#define OPEN 0
#define OPEN_AS_READONLY 1
HRESULT AddOpenChoices(IFileDialogCustomize *pfdc)
{
hr = pfdc->EnableCommitButtonDropDown(OPENCHOICES);
if (SUCCEEDED(hr))
{
hr = pfdc->AddControlItem(OPENCHOICES, OPEN, L"&Open");
if (SUCCEEDED(hr))
{
hr = pfdc->AddControlItem(OPENCHOICES,
OPEN_AS_READONLY,
L"Open as &read-only");
}
}
return hr;
}
The user's choice can be verified after the dialog returns from the IModalWindow::Show method as you would for a ComboBox, or it can verified as part of the handling by IFileDialogEvents::OnFileOk.
Responding to Events in Added Controls
The events handler provided by the calling process can implement IFileDialogControlEvents in addition to IFileDialogEvents. IFileDialogControlEvents enables the calling process to react to these events:
- PushButton clicked
- CheckButton state changed
- Item selected from a menu, ComboBox, or RadioButton list
- Control activating. This is sent when a menu is about to display a drop-down list, in case the calling process wants to change the items in the list.
Related Topics