“通用项”对话框

从 Windows Vista 开始,“通用项”对话框取代了旧的“通用文件”对话框,用于打开或保存文件。 “通用项”对话框有两种变体:“打开”对话框和“保存”对话框。 这两个对话框的大部分功能是一样的,但分别有自己的独特方法。

虽然较新版本名为“通用项”对话框,但它在大多数文档中仍继续称为“通用文件”对话框。 除非你专门处理较旧版本的 Windows,否则应假定“通用文件”对话框的任何提及都引用此“通用项”对话框。

下面讨论以下主题:

IFileDialog、IFileOpenDialog 和 IFileSaveDialog

Windows Vista 提供“打开”“保存”对话框的实现:CLSID_FileOpenDialog 和 CLSID_FileSaveDialog。 下面显示这些对话框。

screen shot of the open dialog box

screen shot of the save as dialog box

IFileOpenDialogIFileSaveDialog 继承自 IFileDialog,并共享其大部分功能。 此外,“打开”对话框支持 IFileOpenDialog“保存”对话框支持 IFileSaveDialog

在 Windows Vista 中找到的“通用项”对话框实现比早期版本中提供的实现具有多种优势:

  • 支持通过 IShellItem 而不是使用文件系统路径直接使用 Shell 命名空间。
  • 启用对话框的简单自定义,例如在“确定”按钮上设置标签,而无需挂钩过程。
  • 通过添加一组不带 Win32 对话框模板操作的数据驱动控件,支持更广泛地自定义对话框。 此自定义方案从 UI 布局释放调用过程。 由于对话框设计的任何更改都会继续使用此数据模型,因此对话框实现不会绑定到对话框的特定当前版本。
  • 支持对话框内事件的调用方通知,例如选择更改或文件类型更改。 此外,调用进程还可以挂钩对话框中的某些事件,例如分析。
  • 引入了新的对话框功能,例如将调用方指定的位置添加到“位置”栏。
  • “保存”对话框中,开发人员可以利用 Windows Vista Shell 的新元数据功能。

此外,开发人员可以选择实现以下接口:

“打开”“保存”对话框将 IShellItemIShellItemArray 对象返回到调用进程。 然后,调用方可以使用单个 IShellItem 对象获取文件系统路径或打开项上的流来读取或写入信息。

可用于新对话框方法的标志和选项与在 OPENFILENAME 结构中找到的较旧 OFN 标志非常相似,并在 getOpenFileNameGetSaveFileName 中使用。 其中许多功能完全相同,只是它们以 FOS 前缀开头。 可以在 IFileDialog::GetOptionsIFileDialog::SetOptions 主题中找到完整列表。 “打开”“保存”对话框默认使用最常见的标志创建。 对于“打开”对话框,这是 (FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR),对于“保存”对话框,这是 (FOS_OVERWRITEPROMPT | FOS_NOREADONLYRETURN | FOS_PATHMUSTEXIST | FOS_NOCHANGEDIR)。

IFileDialog 及其子代接口继承自并扩展 IModalWindowShow 将其作为父窗口句柄的唯一参数。 如果 Show 成功返回,则结果有效。 如果返回 HRESULT_FROM_WIN32(ERROR_CANCELLED),则表示用户取消了对话框。 它还可能合法地返回另一个错误代码,例如 E_OUTOFMEMORY

示例用法

以下部分显示了各种对话框任务的示例代码。

大多数示例代码都可以在 Windows SDK 通用文件对话框示例中找到。

基本用法

以下示例演示如何启动“打开”对话框。 在此示例中,限于 Microsoft Word 文档。

注意

本主题中的几个示例使用 CDialogEventHandler_CreateInstance 帮助程序函数创建 IFileDialogEvents 实现的实例。 若要在自己的代码中使用此函数,请从通用文件对话框示例复制 CDialogEventHandler_CreateInstance 函数的源代码,从中获取本主题中的所有示例。

 

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;
}

将结果限制为文件系统项

上面获取的以下示例演示如何将结果限制为文件系统项。 请注意,IFileDialog::SetOptions 将新标志添加到通过 IFileDialog::GetOptions 获取的值。 这是建议的方法。

                // 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);

指定对话框的文件类型

若要设置对话框可以处理的特定文件类型,请使用 IFileDialog::SetFileTypes 方法。 该方法接受 COMDLG_FILTERSPEC 结构的数组,每个结构都表示文件类型。

对话框中的默认扩展机制与 GetOpenFileNameGetSaveFileName 保持不变。 当对话框打开时,将初始化附加到用户在文件名编辑框中键入的文本的文件扩展名。 它应该与默认文件类型(对话框打开时选择的文件类型)匹配。 如果默认文件类型为“*.*”(所有文件),该文件可以是所选的扩展名。 如果用户选择不同的文件类型,扩展名会自动更新为与该文件类型关联的第一个文件扩展名。 如果用户选择“*.*”(所有文件),则扩展名将还原其原始值。

以下示例演示上述操作的完成方式。

                        // 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");

控制默认文件夹

Shell 命名空间中的任何文件夹几乎都可以用作对话框的默认文件夹(当用户选择打开或保存文件时显示的文件夹)。 调用 Show 之前,先调用 IFileDialog::SetDefaultFolder

默认文件夹是用户首次从应用程序打开对话框时启动的文件夹。 之后,对话框将在用户打开的最后一个文件夹或用于保存项目的最后一个文件夹中打开。 有关详细信息,请参阅状态持久性

通过调用 IFileDialog::SetFolder,可以强制对话框在打开时始终显示同一文件夹,而不考虑以前的用户操作。 但是,我们不建议这样做。 如果在显示对话框之前调用 SetFolder,则不显示用户保存到或打开的最新位置。 除非此行为有非常具体的原因,否则这不是良好的或预期的用户体验,应该避免。 在几乎所有实例中,IFileDialog::SetDefaultFolder 是更好的方法。

“保存”对话框中首次保存文档时,应遵循与在“打开”对话框中确定初始文件夹时相同的准则。 如果用户正在编辑以前存在的文档,请在存储该文档的文件夹中打开对话框,并使用该文档的名称填充编辑框。 在调用 Show 之前,使用当前项调用 IFileSaveDialog::SetSaveAsItem

向位置栏添加项

以下示例演示如何向位置栏添加项:

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;
}

状态持久性

在 Windows Vista 之前,状态(例如上次访问的文件夹)按进程保存。 但是,无论特定操作如何,都使用了该信息。 例如,视频编辑应用程序会在“呈现为”对话框中显示相同的文件夹,就像在“导入媒体”对话框中那样。 在 Windows Vista 中,可以通过使用 GUID 变得更具体。 若要向对话框分配 GUID ,请调用 iFileDialog::SetClientGuid

多选功能

使用 GetResults 方法的“打开”对话框中可以使用多选功能,如下所示。

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;
}

侦听对话框中的事件

调用过程可以使用 IFileDialog::AdviseIFileDialog::Unadvise 方法注册 IFileDialogEvents 接口,如下所示。

这是从基本用法示例获取的。

        // 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);

大量对话处理将放置于此。

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

当用户更改文件夹、文件类型或选择时,调用过程可将事件用于通知。 当调用进程已将控件添加到对话框(请参阅自定义对话框)并且必须更改这些控件的状态以响应这些事件时,这些事件特别有用。 在所有情况下,调用过程都可以提供自定义代码来处理共享冲突、覆盖文件或确定文件在对话框关闭之前是否有效等情况。 本部分介绍其中一些情况。

OnFileOk

此方法在用户选择项目之后、对话框关闭之前调用。 然后,应用程序可以调用 IFileDialog::GetResultIFileOpenDialog::GetResults,将在对话框关闭后执行。 如果选择的项是可接受的,则返回 S_OK。 否则返回 S_FALSE 并在 UI 上显示消息,告知用户所选项无效的原因。 如果返回 S_FALSE,则对话框不会关闭。

调用过程可以使用对话框本身的窗口句柄作为 UI 的父级。 可以通过首先调用 IOleWindow::QueryInterface,然后使用句柄调用 IOleWindow::GetWindow 来获取该句柄,如以下示例所示。

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 和 OnOverwrite

如果用户选择覆盖“保存”对话框中的文件,或者保存或替换的文件正在使用中且无法写入(共享冲突),则应用程序可以提供自定义功能来替代对话框的默认行为。 默认情况下,在覆盖文件时,对话框会显示一个提示,允许用户验证此操作。 对于共享冲突,默认情况下,对话框会显示一条错误消息,但不会关闭,用户需要做出其他选择。 调用进程可以覆盖这些默认值,并根据需要显示自己的 UI。 可以指示对话框拒绝文件并保持打开状态或接受该文件并成功关闭。

自定义对话框

可以向对话框添加各种控件,而无需提供 Win32 对话框模板。 这些控件包括 PushButton、ComboBox、EditBox、CheckButton、RadioButton 列表、组、分隔符和静态文本控件。 对对话框对象(IFileDialogIFileOpenDialogIFileSaveDialog)调用 QueryInterface,以获取 IFileDialogCustomize 指针。 使用该接口添加控件。 每个控件都有关联的调用方提供的 ID,以及可由调用进程设置的可见已启用状态。 某些控件(如 PushButton)还具有关联的文本。

可以将多个控件添加到在对话框布局中作为单一单元移动的“视觉对象组”中。 组可以具有关联的标签。

控件只能在显示对话框之前添加。 但在对话框显示后,控件可以根据需要隐藏或为响应用户操作而显示。 以下示例演示如何向对话框添加单选按钮列表。

// 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;
}

将选项添加到“确定”按钮

同样,可以向“打开”“保存”按钮添加选项,这些按钮对于各自的对话框类型是“确定”按钮。 可通过附加到按钮的下拉列表框访问这些选项。 列表中的第一项将成为按钮的文本。 以下示例演示如何提供具有两种可能性的“打开”按钮:“打开”和“以只读方式打开”。

// 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;
}

对话框从 Show 方法返回后,可以验证用户的选择,就像对 ComboBox 所做的那样,或者可以作为处理的一部分由 IFileDialogEvents::OnFileOk 进行验证。

响应已添加控件中的事件

调用进程提供的事件处理程序除了 IFileDialogEvents 之外,还可以实现 IFileDialogControlEventsIFileDialogControlEvents 可让调用过程响应这些事件:

  • PushButton 已单击
  • CheckButton 状态已更改
  • 已从菜单、组合框或 RadioButton 列表中选择项
  • 控件激活。 当菜单即将显示下拉列表时,如果调用进程想要更改列表中的项,则会发送此消息。

完整示例

以下是 Windows 软件开发工具包 (SDK) 中可下载的完整 C++ 示例,演示了如何使用“通用项”对话框和与之交互。

IID_PPV_ARGS