Редагувати

Поділитися через


Common Item Dialog

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.

While this newer version is named the Common Item Dialog, it continues to be called the Common File Dialog in most documentation. Unless you are dealing specifically with an older version of Windows, you should assume that any mention of the Common File Dialog refers to this Common Item Dialog.

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.

screen shot of the open dialog box

screen shot of the save as dialog box

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 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. Show takes as its only parameter the handle of the parent window. If Show returns successfully, there is a valid result. If it returns HRESULT_FROM_WIN32(ERROR_CANCELLED), it means the user canceled 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.

Most of the sample code can be found in the Windows SDK Common File Dialog Sample.

Basic Usage

The following example illustrates how to launch an Open dialog. In this example, it is restricted to Microsoft Word documents.

Note

Several examples in this topic use the CDialogEventHandler_CreateInstance helper function to create an instance of the IFileDialogEvents implementation. To use this function in your own code, copy the source code for the CDialogEventHandler_CreateInstance function from the Common File Dialog Sample, from which all of the examples in this topic are taken.

 

HRESULT BasicFileOpen()
{
    // CoCreate the File Open Dialog object.
    IFileDialog *pfd = NULL;
    HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, 
                      NULL, 
                      CLSCTX_INPROC_SERVER, 
                      IID_PPV_ARGS(&pfd));
    if (SUCCEEDED(hr))
    {
        // Create an event handling object, and hook it up to the dialog.
        IFileDialogEvents *pfde = NULL;
        hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&pfde));
        if (SUCCEEDED(hr))
        {
            // Hook up the event handler.
            DWORD dwCookie;
            hr = pfd->Advise(pfde, &dwCookie);
            if (SUCCEEDED(hr))
            {
                // Set the options on the dialog.
                DWORD dwFlags;

                // Before setting, always get the options first in order 
                // not to override existing options.
                hr = pfd->GetOptions(&dwFlags);
                if (SUCCEEDED(hr))
                {
                    // In this case, get shell items only for file system items.
                    hr = pfd->SetOptions(dwFlags | FOS_FORCEFILESYSTEM);
                    if (SUCCEEDED(hr))
                    {
                        // Set the file types to display only. 
                        // Notice that this is a 1-based array.
                        hr = pfd->SetFileTypes(ARRAYSIZE(c_rgSaveTypes), c_rgSaveTypes);
                        if (SUCCEEDED(hr))
                        {
                            // Set the selected file type index to Word Docs for this example.
                            hr = pfd->SetFileTypeIndex(INDEX_WORDDOC);
                            if (SUCCEEDED(hr))
                            {
                                // Set the default extension to be ".doc" file.
                                hr = pfd->SetDefaultExtension(L"doc;docx");
                                if (SUCCEEDED(hr))
                                {
                                    // Show the dialog
                                    hr = pfd->Show(NULL);
                                    if (SUCCEEDED(hr))
                                    {
                                        // Obtain the result once the user clicks 
                                        // the 'Open' button.
                                        // The result is an IShellItem object.
                                        IShellItem *psiResult;
                                        hr = pfd->GetResult(&psiResult);
                                        if (SUCCEEDED(hr))
                                        {
                                            // We are just going to print out the 
                                            // name of the file for sample sake.
                                            PWSTR pszFilePath = NULL;
                                            hr = psiResult->GetDisplayName(SIGDN_FILESYSPATH, 
                                                               &pszFilePath);
                                            if (SUCCEEDED(hr))
                                            {
                                                TaskDialog(NULL,
                                                           NULL,
                                                           L"CommonFileDialogApp",
                                                           pszFilePath,
                                                           NULL,
                                                           TDCBF_OK_BUTTON,
                                                           TD_INFORMATION_ICON,
                                                           NULL);
                                                CoTaskMemFree(pszFilePath);
                                            }
                                            psiResult->Release();
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                // Unhook the event handler.
                pfd->Unadvise(dwCookie);
            }
            pfde->Release();
        }
        pfd->Release();
    }
    return hr;
}

Limiting Results to File System Items

The following example, taken from above, demonstrates 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.

                // Set the options on the dialog.
                DWORD dwFlags;

                // Before setting, always get the options first in order 
                // not to override existing options.
                hr = pfd->GetOptions(&dwFlags);
                if (SUCCEEDED(hr))
                {
                    // In this case, get shell items only for file system items.
                    hr = pfd->SetOptions(dwFlags | FOS_FORCEFILESYSTEM);

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 name 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 name extension associated with that file type. If the user chooses "*.*" (all files), then the extension reverts to its original value.

The following example illustrates how this was done above.

                        // Set the file types to display only. 
                        // Notice that this is a 1-based array.
                        hr = pfd->SetFileTypes(ARRAYSIZE(c_rgSaveTypes), c_rgSaveTypes);
                        if (SUCCEEDED(hr))
                        {
                            // Set the selected file type index to Word Docs for this example.
                            hr = pfd->SetFileTypeIndex(INDEX_WORDDOC);
                            if (SUCCEEDED(hr))
                            {
                                // Set the default extension to be ".doc" file.
                                hr = pfd->SetDefaultExtension(L"doc;docx");

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). Call IFileDialog::SetDefaultFolder prior to calling Show to do so.

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.

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. Call IFileSaveDialog::SetSaveAsItem with the current item prior to calling Show.

Adding Items to the Places Bar

The following example demonstrates the addition of items to the Places bar:

HRESULT AddItemsToCommonPlaces()
{
    // CoCreate the File Open Dialog object.
    IFileDialog *pfd = NULL;
    HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, 
                      NULL, 
                      CLSCTX_INPROC_SERVER, 
                      IID_PPV_ARGS(&pfd));
    if (SUCCEEDED(hr))
    {
        // Always use known folders instead of hard-coding physical file paths.
        // In this case we are using Public Music KnownFolder.
        IKnownFolderManager *pkfm = NULL;
        hr = CoCreateInstance(CLSID_KnownFolderManager, 
                      NULL, 
                      CLSCTX_INPROC_SERVER, 
                      IID_PPV_ARGS(&pkfm));
        if (SUCCEEDED(hr))
        {
            // Get the known folder.
            IKnownFolder *pKnownFolder = NULL;
            hr = pkfm->GetFolder(FOLDERID_PublicMusic, &pKnownFolder);
            if (SUCCEEDED(hr))
            {
                // File Dialog APIs need an IShellItem that represents the location.
                IShellItem *psi = NULL;
                hr = pKnownFolder->GetShellItem(0, IID_PPV_ARGS(&psi));
                if (SUCCEEDED(hr))
                {
                    // Add the place to the bottom of default list in Common File Dialog.
                    hr = pfd->AddPlace(psi, FDAP_BOTTOM);
                    if (SUCCEEDED(hr))
                    {
                        // Show the File Dialog.
                        hr = pfd->Show(NULL);
                        if (SUCCEEDED(hr))
                        {
                            //
                            // You can add your own code here to handle the results.
                            //
                        }
                    }
                    psi->Release();
                }
                pKnownFolder->Release();
            }
            pkfm->Release();
        }
        pfd->Release();
    }
    return hr;
}

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 it would in the Import Media dialog. In Windows Vista you can be more specific through the use of GUIDs. To assign a GUID to the dialog, call iFileDialog::SetClientGuid.

Multiselect Capabilities

Multiselect functionality is available in the Open dialog using the GetResults method as shown here.

HRESULT MultiselectInvoke()
{
    IFileOpenDialog *pfd;
    
    // CoCreate the dialog object.
    HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, 
                                  NULL, 
                                  CLSCTX_INPROC_SERVER, 
                                  IID_PPV_ARGS(&pfd));

    if (SUCCEEDED(hr))
    {
        DWORD dwOptions;
        // 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(NULL);

            if (SUCCEEDED(hr))
            {
                // Obtain the result of the user interaction.
                IShellItemArray *psiaResults;
                hr = pfd->GetResults(&psiaResults);
                
                if (SUCCEEDED(hr))
                {
                    //
                    // You can add your own code here to handle the results.
                    //
                    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.

This is taken from the Basic Usage sample.

        // Create an event handling object, and hook it up to the dialog.
        IFileDialogEvents *pfde = NULL;
        hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&pfde));
        if (SUCCEEDED(hr))
        {
            // Hook up the event handler.
            DWORD dwCookie;
            hr = pfd->Advise(pfde, &dwCookie);

The bulk of the dialog processing would be placed here.

                // Unhook the event handler.
                pfd->Unadvise(dwCookie);
            }
            pfde->Release();
        }
        pfd->Release();
    }
    return hr;
}

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 CDialogEventHandler::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 CDialogEventHandler::_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 examples show how to add a radio button list to the dialog.

// Controls
#define CONTROL_GROUP           2000
#define CONTROL_RADIOBUTTONLIST 2
#define CONTROL_RADIOBUTTON1    1
#define CONTROL_RADIOBUTTON2    2       // It is OK for this to have the same ID
                    // as CONTROL_RADIOBUTTONLIST, because it 
                    // is a child control under CONTROL_RADIOBUTTONLIST


// This code snippet demonstrates how to add custom controls in the Common File Dialog.
HRESULT AddCustomControls()
{
    // CoCreate the File Open Dialog object.
    IFileDialog *pfd = NULL;
    HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, 
                                  NULL, 
                                  CLSCTX_INPROC_SERVER, 
                                  IID_PPV_ARGS(&pfd));
    if (SUCCEEDED(hr))
    {
        // Create an event handling object, and hook it up to the dialog.
        IFileDialogEvents   *pfde       = NULL;
        DWORD               dwCookie    = 0;
        hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&pfde));
        if (SUCCEEDED(hr))
        {
            // Hook up the event handler.
            hr = pfd->Advise(pfde, &dwCookie);
            if (SUCCEEDED(hr))
            {
                // Set up a Customization.
                IFileDialogCustomize *pfdc = NULL;
                hr = pfd->QueryInterface(IID_PPV_ARGS(&pfdc));
                if (SUCCEEDED(hr))
                {
                    // Create a Visual Group.
                    hr = pfdc->StartVisualGroup(CONTROL_GROUP, L"Sample Group");
                    if (SUCCEEDED(hr))
                    {
                        // Add a radio-button list.
                        hr = pfdc->AddRadioButtonList(CONTROL_RADIOBUTTONLIST);
                        if (SUCCEEDED(hr))
                        {
                            // Set the state of the added radio-button list.
                            hr = pfdc->SetControlState(CONTROL_RADIOBUTTONLIST, 
                                               CDCS_VISIBLE | CDCS_ENABLED);
                            if (SUCCEEDED(hr))
                            {
                                // Add individual buttons to the radio-button list.
                                hr = pfdc->AddControlItem(CONTROL_RADIOBUTTONLIST,
                                                          CONTROL_RADIOBUTTON1,
                                                          L"Change Title to ABC");
                                if (SUCCEEDED(hr))
                                {
                                    hr = pfdc->AddControlItem(CONTROL_RADIOBUTTONLIST,
                                                              CONTROL_RADIOBUTTON2,
                                                              L"Change Title to XYZ");
                                    if (SUCCEEDED(hr))
                                    {
                                        // Set the default selection to option 1.
                                        hr = pfdc->SetSelectedControlItem(CONTROL_RADIOBUTTONLIST,
                                                                          CONTROL_RADIOBUTTON1);
                                    }
                                }
                            }
                        }
                        // End the visual group.
                        pfdc->EndVisualGroup();
                    }
                    pfdc->Release();
                }

                if (FAILED(hr))
                {
                    // Unadvise here in case we encounter failures 
                    // before we get a chance to show the dialog.
                    pfd->Unadvise(dwCookie);
                }
            }
            pfde->Release();
        }

        if (SUCCEEDED(hr))
        {
            // Now show the dialog.
            hr = pfd->Show(NULL);
            if (SUCCEEDED(hr))
            {
                //
                // You can add your own code here to handle the results.
                //
            }
            // Unhook the event handler.
            pfd->Unadvise(dwCookie);
        }
        pfd->Release();
    }
    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".

// OpenChoices options
#define OPENCHOICES 0
#define OPEN 0
#define OPEN_AS_READONLY 1


HRESULT AddOpenChoices()
{
    // CoCreate the File Open Dialog object.
    IFileDialog *pfd = NULL;
    HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, 
                      NULL, 
                      CLSCTX_INPROC_SERVER, 
                      IID_PPV_ARGS(&pfd));
    if (SUCCEEDED(hr))
    {
        // Create an event handling object, and hook it up to the dialog.
        IFileDialogEvents   *pfde       = NULL;
        DWORD               dwCookie    = 0;
        hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&pfde));
        if (SUCCEEDED(hr))
        {
            // Hook up the event handler.
            hr = pfd->Advise(pfde, &dwCookie);
            if (SUCCEEDED(hr))
            {
                // Set up a Customization.
                IFileDialogCustomize *pfdc = NULL;
                hr = pfd->QueryInterface(IID_PPV_ARGS(&pfdc));
                if (SUCCEEDED(hr))
                {
                    hr = pfdc->EnableOpenDropDown(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");
                    }
                    if (SUCCEEDED(hr))
                    {
                        pfd->Show(NULL);
                    }
                }
                pfdc->Release();
            }
            pfd->Unadvise(dwCookie);
        }
        pfde->Release();
    }
    pfd->Release();
    return hr;
}

The user's choice can be verified after the dialog returns from the 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.

Full Samples

The following are complete, downloadable C++ samples from the Windows Software Development Kit (SDK) which demonstrate the use of and interaction with the Common Item Dialog.

IID_PPV_ARGS