65.9K
CodeProject 正在变化。 阅读更多。
Home

Vista 中的 C++ 实用功能:使用新的 Vista 文件对话框

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (56投票s)

2006年12月7日

18分钟阅读

viewsIcon

347018

downloadIcon

4686

如何使用WTL在Vista中使用新的文件打开和文件保存对话框。

目录

引言

在本篇Vista增强功能文章中,我将演示如何使用Vista新的文件对话框 - 即用于文件打开和文件保存操作的内置对话框。

本文使用Visual Studio 2005、WTL 7.5以及Windows SDK,为RTM版本的Vista编写。有关在哪里下载这些组件的更多信息,请参阅第一篇Vista增强功能文章的介绍

在Vista中,微软再次更新了通用文件对话框,使其与常规的Explorer窗口相似。文件打开和文件保存对话框现在具有内置的搜索字段,以及在所有Explorer窗口中都存在的“收藏夹链接”/树状视图窗格。一些使用旧的通用文件对话框API的应用程序会自动获得新的对话框,但出于向后兼容的考虑,Vista在某些情况下仍会显示旧的对话框。

如果应用程序将 OPENFILENAME 结构体的 lStructSize 成员设置为表示它是为Windows 2000之前的操作系统编写的,那么它将继续获得Windows 95样式的对话框。显示文件名的列表视图控件将具有Vista功能(例如更大的缩略图视图),但对话框的其余部分将保持不变。下面是一个由VC6显示的打开文件对话框,它通过添加“另存为”组合框来自定义对话框。

如果应用程序是为Windows 2000/XP文件对话框编写的,它将继续获得该版本的对话框。对于许多现有的MFC和WTL应用程序,这将会发生,因为这些类库使用了一个在Vista中不存在的回调函数。为了保持兼容性,Vista将为这些应用程序显示Windows 2000样式的对话框。

最后,对于一些现有的应用程序以及所有使用新的文件对话框API的新应用程序,Vista将显示全新的通用对话框。

本文将介绍新的API,并演示如何在您的应用程序中使用新的对话框,即使您正在使用WTL这样的GUI类库。

使用Windows API的文件对话框

如果一个应用程序调用 GetOpenFileName() 或 GetSaveFileName() API来显示一个文件对话框,并且完全没有自定义该对话框,那么Vista将显示新的文件对话框,因为没有破坏应用程序的风险。Vista用于确定应用程序是否自定义了对话框的标准是:OPENFILENAME 结构体的 lpfnHook 成员是否存储了挂钩函数地址,或者 lpTemplateName 成员是否存储了自定义对话框模板。

由于MFC和WTL使用挂钩函数向应用程序提供通知,如果您使用任一库的 CFileDialog 类,您将获得Windows 2000样式的对话框。因此,要获得Vista样式的对话框,您需要使用替换 GetOpenFileName() 和 GetSaveFileName() API的新COM接口。

文件对话框COM接口

基本接口

文件对话框有两个基本接口:IModalWindow 和 IFileDialog。 IModalWindow 只有一个方法:Show()。 Show() 方法显示对话框,使用给定的窗口作为对话框的父窗口,就像MFC和ATL对话框类中的DoModal()一样。如果对话框显示成功且用户选择了文件,Show()返回S_OK,否则返回失败的HRESULT。如果用户取消了对话框,Show()返回HRESULT_FROM_WIN32(ERROR_CANCELLED)。

IFileDialog 包含对文件打开和文件保存对话框通用的方法。IFileDialog 有许多方法,它们执行的任务与以前版本的Windows中设置OPENFILENAME结构体成员的任务相同。IFileDialog 提供了一个基于COM的API,并消除了容易出错的约定,例如双空终止字符串。下面是IFileDialog方法的快速概述。

SetFileTypes(), SetFileTypeIndex()
用于填充文件类型组合框。
GetFileTypeIndex()
用于确定用户选择了哪种文件类型。
Advise(), Unadvise()
用于开始和停止监听文件对话框的事件,这类似于相应的IConnectionPoint方法。
GetOptions(), SetOptions()
用于读取或写入一组控制对话框行为的标志,类似于OPENFILENAME的Flags和FlagsEx成员。
SetDefaultFolder()
设置对话框首次出现时显示的文件夹。
SetTitle()
设置对话框标题栏中的文本。
SetOkButtonLabel(), SetFileNameLabel()
分别设置“打开/保存”按钮的文本以及“文件名”编辑框旁边的静态控件的文本。
GetFolder(), SetFolder()
用于获取或设置对话框当前显示的文件夹。在对话框显示之前也可以调用GetFolder(),在这种情况下,它的作用与SetDefaultFolder()相同。
AddPlace()
将一个项目添加到“收藏夹链接”部分(对话框左侧窗格中的文件夹列表)。
SetDefaultExtension()
设置一个默认扩展名,如果用户没有输入扩展名,则会自动添加,类似于OPENFILENAME的lpstrDefExt成员。
SetClientGuid(), GetClientGuid(), ClearClientData()
Vista通常会为每个应用程序保存一些状态信息,因此文件对话框的默认文件夹将是上一个对话框所显示的同一个文件夹。应用程序可以通过创建每个状态的GUID并将该GUID传递给SetClientGuid()来告诉Vista维护多个这样的状态。ClearClientData()清除与该GUID关联的状态。
SetFilter()
如果应用程序想要过滤掉其文件对话框中的项目,它可以创建一个实现IShellItemFilter的COM对象,并将该IShellItem接口传递给SetFilter()。
GetCurrentSelection()
获取当前在对话框中选中的项目。
GetFileName(), SetFileName()
用于获取或设置对话框“文件名”编辑框中的文本。
GetResult()
如果用户选择了一个文件并点击了OK,GetResult()返回选中的项目。
Close()
关闭对话框并指定Show()应该返回的值,类似于普通对话框的EndDialog() API。

GetResult()等函数使用IShellItem接口来标识文件,而不是文件路径或PIDL。您可以将IShellItem视为SHITEMID或PIDL的COM版本,因为它可以表示shell命名空间中的任何项目,而不仅仅是文件系统对象。我稍后将介绍IShellItem。

文件打开对话框接口

IFileOpenDialog继承自IFileDialog,并增加了两个用于多选对话框的方法。

GetSelectedItems()
代替GetCurrentSelection()使用。
GetResults()
代替GetResult()使用。

每个方法返回一个IShellItemArray接口,其中包含每个选中项的一个IShellItem。这两个方法在单选对话框中也同样有效,所以如果您愿意,可以在所有文件打开对话框中使用它们。

文件保存对话框接口

IFileSaveDialog继承自IFileDialog,并增加了一些用于设置正在保存的文件属性的方法。

SetSaveAsItem()
设置对话框打开时的初始文件夹和文件名。
SetProperties(), SetCollectedProperties(), GetProperties(), ApplyProperties()
用于读写正在保存的文件的shell属性。shell属性是Vista的一个新功能,本文不作介绍,但Ben Karas的博客有许多关于使用属性的文章,如果您想了解更多。

其他接口

还有三个接口将在本文后面介绍。

IFileDialogEvents
用于接收文件对话框中内置控件触发的事件。
IFileDialogCustomize
用于向文件对话框添加控件。
IFileDialogControlEvents
用于接收通过IFileDialogCustomize添加的控件触发的事件。

直接使用文件对话框接口

基本文件打开对话框示例

下面是一个使用文件打开对话框选择单个文件的简单示例。此代码几乎没有进行自定义,只是设置了对话框的标题,并在文件类型列表中放入了三个项目。

文件类型列表使用了一种新的系统,比OPENFILENAME结构体中使用的双空终止字符串容易得多。列表中的每个项目都由一个COMDLG_FILTERSPEC结构体描述。

struct COMDLG_FILTERSPEC
{
  LPCWSTR pszName;
  LPCWSTR pszSpec;
};

pszName 包含显示在组合框中的字符串,例如“文本文件”。pszSpec 是与该项目一起使用的通配符模式,例如“*.txt”。

以下是创建文件对话框COM对象并进行初始化的设置步骤。

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

初始化完成后,我们调用Show()来显示对话框。

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

如果hr包含一个成功的HRESULT,我们调用GetResult()来获取用户所选文件上的IShellItem接口。要获取该文件的路径,我们调用IShellItem::GetDisplayName()并传递SIGDN_FILESYSPATH标志,这表明我们想要文件的文件系统路径。如果用户选择了非文件系统对象,GetDisplayName()将失败。我们还有责任释放GetDisplayName()返回的字符串。

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

这是带有展开的文件类型组合框的对话框。

请注意,Vista会自动将通配符模式附加到文件类型描述(COMDLG_FILTERSPEC结构体的pszName成员)的末尾。这与早期操作系统不同,早期操作系统中,描述包含模式是一种约定。如果您使用包含模式的描述,Vista会尝试通过查看描述的末尾来弥补这一点。如果模式出现在描述的末尾,Vista将不会再次添加模式。例如,如果您的字符串是

描述:所有文件 (*.*)
模式:*.*

Vista将不会在描述中添加另一个“(*.*)”。描述中的模式不必括在括号中,但大多数应用程序确实使用括号,所以Vista会检查它们。

请注意,两个通配符模式必须*完全*匹配(不包括括号),所以如果您使用这样的字符串:

描述:Word文件 (*.doc, *.docx)
模式:*.doc;*.docx

Vista仍然会将“(*.doc;*.docx)”附加到描述中,因为两个通配符模式不完全匹配。

尽管上面的代码没有调用SetOptions(),但一些选项默认是开启的。文件打开对话框自动设置了FOS_FILEMUSTEXIST,所以用户在文件不存在时无法输入文件名。

基本文件保存对话框示例

文件保存对话框的设置步骤类似。本示例演示了如何提示用户保存一些数据,默认格式为带有.txt扩展名的文本文件。本示例还使用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 );
        }
      }
    }
}

这是显示默认文件名的对话框。

与文件打开对话框一样,文件保存对话框也有一些默认开启的选项。其中包括FOS_PATHMUSTEXIST,所以用户必须输入一个已经存在的目录名。

多选文件打开对话框示例

最后,这是一个使用多选文件打开对话框的示例。设置步骤与前面的文件打开示例基本相同。为了允许多选,我们使用SetOptions()设置了FOS_ALLOWMULTISELECT标志。请注意,我们必须先调用GetOptions(),然后添加FOS_ALLOWMULTISELECT,因为如前所述,一些选项是默认开启的。

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

如果Show()成功,我们调用GetResults()来获取一个包含所有选中文件的数组。我们使用IShellItemArray::GetCount()获取数组的大小,然后循环并获取每个选中文件的路径。

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

处理文件对话框事件

在Vista之前,应用程序可以通过在OPENFILENAME结构体的lpfnHook成员中指定一个回调函数来获得文件对话框事件的通知。在Vista中,应用程序创建一个实现IFileDialogEvents接口的COM对象,并将该接口传递给文件对话框。当某些事件发生时,文件对话框会调用IFileDialogEvents方法,让应用程序对事件做出响应。

IFileDialogEvents接口

IFileDialogEvents有七个方法,在各种事件发生时会被调用。

OnFolderChanging(), OnFolderChange()
在对话框导航到不同文件夹之前和之后调用。如果应用程序想阻止更改,OnFolderChanging()可以做到。
OnSelectionChange()
在用户在文件列表中选择不同文件后调用。
OnTypeChange()
当用户更改文件类型组合框中的选择时调用。
OnFileOk()
当用户选择一个文件并点击“打开”或“保存”按钮时调用。应用程序可以通过从此方法返回S_FALSE来阻止文件对话框关闭。
OnOverwrite()
当用户选择一个已存在的文件时,由文件保存对话框调用。应用程序可以在此方法中显示自己的UI,或让对话框显示默认UI。
OnShareViolation()
在打开或保存操作期间发生共享错误时调用。应用程序可以在此方法中显示自己的UI,或让对话框显示默认UI。

处理事件

我们可以创建一个实现IFileDialogEvents的COM对象的新C++类来处理事件。

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

我在这里展示一个方法,OnFolderChanging(),它会打印一个显示新文件夹路径的跟踪消息。您可以查看示例项目来了解其余方法,它们都会打印类似的跟踪消息。

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

现在我们有了这个类,我们需要告诉文件对话框我们想接收事件。让我们回到第一个文件打开对话框示例,并添加一个事件处理程序。新代码以粗体显示。

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

我们首先创建一个CDlgEventHandler对象,使用CComObjectStackEx模板类来管理COM对象的生命周期。(CComObjectStackEx与CComObjectStack类似,只是它支持QueryInterface())。然后,我们调用Advise()并传递我们CDlgEventHandler对象上的IFileDialogEvents接口。如果Advise()成功,我们在Show()返回后调用Unadvise()进行清理。

文件对话框自定义

在Vista之前,自定义文件对话框是通过一个复杂的流程完成的,该流程涉及创建一个自定义对话框模板。当应用程序使用此方法时,它与特定版本的通用对话框紧密耦合,这意味着应用程序无法利用Windows后续版本中引入的功能,例如Places Bar和可调整大小的对话框。例如,Paint Shop Pro 5中的文件打开对话框看起来是这样的。

除了列表视图控件之外,对话框中的所有元素仍然看起来像原始的Windows 95对话框。Windows无法自动启用新功能,因为这样做会改变对话框的布局并破坏PSP的自定义模板。

Vista支持对新文件对话框的自定义,但与旧方法相比有两个主要区别:

  1. 自定义是通过COM接口完成的,该接口不向应用程序暴露文件对话框的任何内部细节。可以推测,今天编写的自定义将在未来具有不同文件对话框的Windows版本中工作。
  2. 应用程序只能添加一组预定义的控件和文本标签,而不是任意的UI元素。

控件系统

应用程序可以使用一些简单的控件:静态文本控件、按钮、复选框、编辑框和分隔符。还有三个控件是其他项目的容器:单选按钮组、组合框和显示弹出菜单的按钮。还有一个称为“视觉分组”的功能,它是一种将控件分组在对话框中的方法。视觉分组有一个文本标签,并且可以包含其他控件(包括本身是容器的控件)。

添加控件后,只能以有限的方式修改它。控件可以被启用或禁用、隐藏或显示,并且容器中的项目可以被选中。容器具有更多的灵活性,因为它们的内​​容可以随时更改。控件由应用程序管理的DWORD标识符标识。

添加简单控件

要添加控件,我们首先按照前面介绍的方式创建文件对话框COM对象,然后查询IFileDialogCustomize接口。我们可以使用以下方法添加控件:

AddCheckButton()
添加一个复选框。复选框的初始状态可以是选中或未选中,取决于应用程序的需要。
AddEditBox()
添加一个编辑框。应用程序还可以传递一个字符串,用作编辑框的初始文本。
AddPushButton()
添加一个按钮。
AddSeparator()
添加一个分隔符,一个类似于菜单中使用的分隔符的蚀刻线。
AddText()
添加一个静态文本控件。

下面是如何添加一个静态文本控件和一个按钮的示例。

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

这是带有这两个附加控件的保存对话框的外观。

添加容器控件

要添加容器控件,我们首先调用AddComboBox()、AddRadioButtonList()或AddMenu()来创建容器。然后,我们为要显示在容器中的每个项目调用一次AddControlItem()。下面是一个如何添加菜单按钮的示例。

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

这是带有显示弹出菜单的按钮的外观。

单选按钮和组合框的处理方式略有不同。AddComboBox()和AddRadioButtonList()没有字符串参数,因为这些控件没有标签。此外,我们可以调用SetSelectedControlItem()来选中容器中的一个项目。下面是一个添加单选按钮组的代码片段。

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

这是带有两个附加单选按钮的对话框。

使用视觉分组

视觉分组中的控件在对话框中并排放置。组还带有一个标签,可用于传达控件将执行的操作或它们控制的功能。要创建视觉分组,我们首先调用StartVisualGroup(),传递组的ID和标签。所有添加的控件都会自动放入该组,直到调用EndVisualGroup()完成该组。

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

这是带有单选按钮组的对话框。

使控件突出显示

还有一个额外的选项是使控件*突出显示*。这样做会将控件移到“打开”或“保存”按钮旁边。但是,并非所有控件都可以这样移动。只有复选框、按钮、组合框和菜单按钮可以突出显示。包含一个此类控件的视觉分组也可以突出显示。我们可以通过此调用使按钮控件突出显示。

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

然后,按钮会被移到“保存”旁边。

请注意,如果应用程序只向对话框添加了一个控件,并且该控件是可突出显示的类型,那么它会自动突出显示。

处理其他控件的事件

IFileDialogControlEvents有四个方法,在与您使用IFileDialogCustomize添加的控件相关的各种事件发生时会被调用。

OnButtonClicked()
点击按钮时调用。
OnCheckButtonToggled()
点击复选框时调用。
OnControlActivating()
打开组合框或点击菜单按钮(显示弹出菜单)时调用。
OnItemSelected()
点击单选按钮、组合框项目或弹出菜单项时调用。

示例项目在同一个C++类中实现了IFileDialogControlEvents和IFileDialogEvents。尽管我们将IFileDialogEvents接口传递给Advise(),但文件对话框在需要时会查询IFileDialogControlEvents。

您可以在示例项目中看到这些事件处理程序的实际应用。我有点用力过猛,添加了*所有*可能的控件类型到对话框中。当然,您在实际代码中不会这样做,但这确实是一个很好的演示。

其他文件对话框功能

设置初始文件夹

Vista会保留应用程序显示的文件对话框的一些状态信息。其中一个记录的是最近使用的文件对话框显示的文件夹。默认情况下,应用程序显示的下一个文件对话框将从同一个文件夹开始。应用程序可以通过在Show()之前调用IFileDialog::SetFolder()来覆盖此默认设置。

此代码片段显示了如何让对话框从“我的图片”目录开始。它使用了两个新的Vista API:SHGetKnownFolderPath()和SHCreateItemFromParsingName()。SHGetKnownFolderPath()是SHGetSpecialFolderPath()的替代品,而SHCreateItemFromParsingName()获取文件夹路径并创建一个IShellItem,我们可以将其传递给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 );
    }

保留状态数据

如果您的应用程序有许多加载/保存功能,不仅仅是简单的文档,您可能希望为每个功能单独保留文件对话框状态数据。您可以通过为要保留的每个状态创建一个GUID,并在Show()之前调用IFileDialog::SetClientGuid()来做到这一点。

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

使用示例项目

示例项目是一个基于对话框的应用程序,它允许您以三种方式显示文件打开对话框:GetOpenFileName() API、WTL的CFileDialog以及IFileOpenDialog接口。您在这些对话框中选择的文件会显示在对话框的列表控件中。

第四个按钮显示一个文件保存对话框,如果您选择一个文件,应用程序会将列表控件中的所有文件名写入该文件。此对话框显示了更实际的自定义用法:它有一个组合框,您可以在其中选择要用于文本文件的编码。

参考文献

  • Kenny Kerr的博客上的《面向开发者的Windows Vista - 第6部分 - 新的文件对话框》。

版权和许可

本文是受版权保护的材料,©2006 Michael Dunn。我知道这并不能阻止人们在网络上到处复制它,但我必须说出来。如果您有兴趣翻译本文,请给我发邮件告知。我不认为我会拒绝任何人翻译的许可,但我希望知道翻译情况,以便在此处发布链接。

本文附带的演示代码已发布到公共领域。我这样发布是为了让代码能够惠及所有人。(我不将文章本身设为公共领域,因为文章仅在CodeProject上提供有助于我自己的知名度和CodeProject网站。)如果您在自己的应用程序中使用演示代码,发送邮件让我知道(只是为了满足我对人们是否受益于我的代码的好奇心)是值得赞赏的,但不是必需的。在您自己的源代码中注明出处也是值得赞赏的,但不是必需的。

修订历史

  • 2006年12月7日:文章首次发布。
  • 2006年12月26日:代码在RTM Vista版本上进行测试,并相应更新了介绍。

系列导航:« 监控计算机电源状态 | 使用任务对话框显示友好消息 »

© . All rights reserved.