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

编写外壳扩展的 Complete Idiot's Guide - 第 VII 部分

starIconstarIconstarIconstarIconstarIcon

5.00/5 (30投票s)

2000 年 9 月 1 日

viewsIcon

544719

downloadIcon

5155

本教程介绍如何在上下文菜单 Shell 扩展中使用拥有者绘制菜单,以及如何制作一个响应目录背景右键单击的上下文菜单扩展。

目录

引言

在本期的《完全傻瓜指南》中,我将回答一些读者的请求,并讨论两个主题:在上下文菜单扩展中使用拥有者绘制菜单,以及制作一个在用户右键单击目录窗口背景时触发的上下文菜单扩展。您应该阅读并理解第一部分第二部分,以便掌握上下文菜单扩展的基础知识。

请记住,VC 7(可能还有 VC 8)用户在编译前需要更改一些设置。请参阅 第一部分的 README 部分 以了解详细信息。

扩展 1 - 拥有者绘制的菜单项

在本节中,我将只介绍实现拥有者绘制菜单所需的额外工作。

由于此扩展将有一个拥有者绘制的菜单,因此它必须具有图形显示功能。我决定模仿 PicaView 程序[1]的功能:在上下文菜单中显示 BMP 文件的缩略图。PicaView 的菜单项外观如下:

 [PicaView owner-drawn menu item - 23K]

此扩展将为 BMP 文件创建缩略图,但为了使代码保持简单,我不会担心精确地调整比例或颜色。修复这些问题留给读者作为练习。;)

初始化接口

现在您应该熟悉设置步骤了,因此我将跳过通过 VC 向导的说明。如果您正在按照向导操作,请创建一个新的 ATL COM 应用程序(启用 MFC 支持),命名为 _BmpViewerExt_,并使用 C++ 实现类 CBmpCtxMenuExt

与之前的上下文菜单扩展一样,此扩展将实现 IShellExtInit 接口。要将 IShellExtInit 添加到我们的 COM 对象,请打开 _BmpCtxMenuExt.h_ 并添加此处以粗体显示的行。此外,还列出了几个成员变量,我们将在稍后绘制菜单项时使用它们。

#include <comdef.h>
 
/////////////////////////////////////////////////////////////////////////////
// CBmpCtxMenuExt
 
class CBmpCtxMenuExt : 
  public CComObjectRootEx<CComSingleThreadModel>,
  public CComCoClass<CBmpCtxMenuExt, &CLSID_BmpCtxMenuExt>,
  public IShellExtInit
{
  BEGIN_COM_MAP(CBmpCtxMenuExt)
    COM_INTERFACE_ENTRY(IShellExtInit)
  END_COM_MAP()
 
public:
  // IShellExtInit
  STDMETHODIMP Initialize(LPCITEMIDLIST, LPDATAOBJECT, HKEY);
 
protected:
  TCHAR   m_szFile[MAX_PATH];
  CBitmap m_bmp;
  UINT    m_uOurItemID;
 
  LONG m_lItemWidth, m_lItemHeight;
  LONG m_lBmpWidth, m_lBmpHeight;
 
  static const LONG m_lMaxThumbnailSize;
  static const LONG m_l3DBorderWidth;
  static const LONG m_lMenuItemSpacing;
  static const LONG m_lTotalBorderSpace;
 
  // Helper functions for handling the menu-related messages.
  STDMETHODIMP MenuMessageHandler(UINT, WPARAM, LPARAM, LRESULT*);
  STDMETHODIMP OnMeasureItem(MEASUREITEMSTRUCT*, LRESULT*);
  STDMETHODIMP OnDrawItem(DRAWITEMSTRUCT*, LRESULT*);
};

我们在 IShellExtInit::Initialize() 中要做的就是获取被右键单击的文件的名称,如果其扩展名为 .BMP,则为其创建一个缩略图。

在 _BmpCtxMenuExt.cpp_ 文件中,为这些静态变量添加声明。这些变量控制缩略图及其边框的视觉方面。您可以随意更改它们,并观察它们如何影响菜单中最终结果。

const LONG CBmpCtxMenuExt::m_lMaxThumbnailSize = 64;
const LONG CBmpCtxMenuExt::m_l3DBorderWidth    = 2;
const LONG CBmpCtxMenuExt::m_lMenuItemSpacing  = 4;
const LONG CBmpCtxMenuExt::m_lTotalBorderSpace = 
               2*(m_lMenuItemSpacing+m_l3DBorderWidth);

这些常量具有以下含义:

  • m_lMaxThumbnailSize:如果位图在任一维度上大于此数字,它将被压缩以适应一个正方形,该正方形的每条边长为 m_lMaxThumbnailSize 像素。如果位图在两个维度上都小于此数字,它将按原样绘制。
  • m_l3DBorderWidth:围绕缩略图绘制的 3D 边框的厚度(以像素为单位)。
  • m_lMenuItemSpacing:在 3D 边框周围留出的空白像素数。这会在缩略图和周围的菜单项之间提供一些空间。

此外,请添加 IShellExtInit::Initialize() 函数的定义:

STDMETHODIMP CBmpCtxMenuExt::Initialize (
  LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDO,
  HKEY hkeyProgID )
{
  AFX_MANAGE_STATE(AfxGetStaticModuleState());
 
COleDataObject dataobj;
HGLOBAL        hglobal;
HDROP          hdrop;
bool           bOK = false;
 
  dataobj.Attach ( pDO, FALSE );
 
  // Get the first selected file name.  I'll keep this simple and just check
  // the first name to see if it's a .BMP.
  hglobal = dataobj.GetGlobalData ( CF_HDROP );
 
  if ( NULL == hglobal )
    return E_INVALIDARG;
 
  hdrop = (HDROP) GlobalLock ( hglobal );
 
  if ( NULL == hdrop )
    return E_INVALIDARG;
 
  // Get the name of the first selected file.
  if ( DragQueryFile ( hdrop, 0, m_szFile, MAX_PATH ))
    {
    // Is its extension .BMP?
    if ( PathMatchSpec ( m_szFile, _T("*.bmp") ))
      {
      // Load the bitmap and attach our CBitmap object to it.
      HBITMAP hbm = (HBITMAP) LoadImage ( NULL, m_szFile, IMAGE_BITMAP,
                                          0, 0, LR_LOADFROMFILE );
 
      if ( NULL != hbm )
        {
        // We loaded the bitmap, so attach the CBitmap to it.
        m_bmp.Attach ( hbm );
        bOK = true;
        }
      }
    }
 
  GlobalUnlock ( hglobal );
  return bOK ? S_OK : E_FAIL;
}

这里的东西很直接。我们从文件中加载位图,并将其附加到 CBitmap 对象,以便稍后使用。

与上下文菜单交互

与以前一样,如果 IShellExtInit::Initialize() 返回 S_OK,Explorer 就会查询我们的扩展以获取 IContextMenu 接口。为了让我们的扩展能够实现其拥有者绘制菜单项,它还会查询 IContextMenu3IContextMenu3IContextMenu 添加了一个方法,我们将使用它来进行拥有者绘制。

确实存在 IContextMenu2 接口,文档声称它受 Shell 版本 4.00 支持。然而,我在 Windows 95 上进行测试时,Shell 从未查询 IContextMenu2,因此最终结果是您无法在 Shell 版本 4.00 上执行拥有者绘制的菜单项。(NT 4 可能有所不同;我无法访问 NT 4 系统进行检查。)相反,您可以让菜单项显示位图,但这会导致在选中该项时外观不尽人意。(这就是 PicaView 在 4.00 版本上的做法。)

IContextMenu3 继承自 IContextMenu2,并将 HandleMenuMsg2() 方法添加到 IContextMenu。此方法使我们有机会响应两个菜单消息:WM_MEASUREITEMWM_DRAWITEM。文档说明 HandleMenuMsg2() 会被调用,以便它也可以处理 WM_INITMENUPOPUPWM_MENUCHAR,但在我的测试中,HandleMenuMsg2() 从未收到过这些消息。

由于 IContextMenu3 继承自 IContextMenu2(它本身继承自 IContextMenu),我们只需要让我们的 C++ 类继承自 IContextMenu3。打开 _BmpCtxMenuExt.h_ 并添加此处以粗体显示的行:

class CBmpCtxMenuExt :
  public CComObjectRootEx<CComSingleThreadModel>,
  public CComCoClass<CBmpCtxMenuExt, &CLSID_BmpCtxMenuExt>,
  public IShellExtInit,
  public IContextMenu3
{
  BEGIN_COM_MAP(CSimpleShlExt)
    COM_INTERFACE_ENTRY(IShellExtInit)
    COM_INTERFACE_ENTRY(IContextMenu)
    COM_INTERFACE_ENTRY(IContextMenu2)
    COM_INTERFACE_ENTRY(IContextMenu3)
  END_COM_MAP()
  
public:
  // IContextMenu
  STDMETHODIMP QueryContextMenu(HMENU, UINT, UINT, UINT, UINT);
  STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO);
  STDMETHODIMP GetCommandString(UINT_PTR, UINT, UINT*, LPSTR, UINT);
 
  // IContextMenu2
  STDMETHODIMP HandleMenuMsg(UINT, WPARAM, LPARAM);
 
  // IContextMenu3
  STDMETHODIMP HandleMenuMsg2(UINT, WPARAM, LPARAM, LRESULT*);

修改上下文菜单

与以前一样,我们在三个 IContextMenu 方法中进行工作。我们在 QueryContextMenu() 中添加菜单项。我们首先检查 Shell 版本。如果它是 4.71 或更高版本,则我们可以添加一个拥有者绘制的菜单项。否则,我们添加一个显示位图的项。在后一种情况下,添加该项是我们所要做的所有事情;菜单会负责显示位图。

首先,这是检查 Shell 版本的代码。它调用导出的 DllGetVersion() 函数来检索版本。如果该导出不存在,则 DLL 是 4.00 版本,因为 DllGetVersion() 在 4.00 版本中不存在。

STDMETHODIMP CBmpCtxMenuExt::QueryContextMenu (
  HMENU hmenu, UINT uIndex, UINT uidCmdFirst,
  UINT uidCmdLast, UINT uFlags )
{
  // If the flags include CMF_DEFAULTONLY then we shouldn't do anything.
  if ( uFlags & CMF_DEFAULTONLY )
    return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
 
bool bUseOwnerDraw = false;
HINSTANCE hinstShell;
 
  hinstShell = GetModuleHandle ( _T("shell32") );
 
  if ( NULL != hinstShell )
    {
    DLLGETVERSIONPROC pProc;
 
    pProc = (DLLGETVERSIONPROC) GetProcAddress(hinstShell, "DllGetVersion");
 
    if ( NULL != pProc )
      {
      DLLVERSIONINFO rInfo = { sizeof(DLLVERSIONINFO) };
 
      if ( SUCCEEDED( pProc ( &rInfo ) ))
        {
        if ( rInfo.dwMajorVersion > 4 ||
             (rInfo.dwMajorVersion == 4 &&  
              rInfo.dwMinorVersion >= 71) )
          bUseOwnerDraw = true;
        }
      }
    }

此时,bUseOwnerDraw 指示是否可以使用拥有者绘制的项。如果为 true,我们插入一个拥有者绘制的项(请参见设置 mii.fType 的行)。如果为 false,我们添加一个位图项,然后告知菜单要显示的位图的句柄。

MENUITEMINFO mii = {0};
 
  mii.cbSize = sizeof(MENUITEMINFO);
  mii.fMask  = MIIM_ID | MIIM_TYPE;
  mii.fType  = bUseOwnerDraw ? MFT_OWNERDRAW : MFT_BITMAP;
  mii.wID    = uidCmdFirst;
 
  if ( !bUseOwnerDraw )
    {
    // NOTE: This will put the full-sized bitmap in the menu, which is
    // obviously a bit less than optimal.  Scaling the bitmap down
    // to a thumbnail is left as an exercise.
    mii.dwTypeData = (LPTSTR) m_bmp.GetSafeHandle();
    }
 
  InsertMenuItem ( hmenu, uIndex, TRUE, &mii );
 
  // Store the menu item's ID so we can check against it later when
  // WM_MEASUREITEM/WM_DRAWITEM are sent.
  m_uOurItemID = uidCmdFirst;
 
  // Tell the shell we added 1 top-level menu item.
  return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 1);
}

我们将菜单项 ID 存储在 m_uOurItemID 中,以便稍后收到其他消息时能够知道该 ID。由于我们只有一个菜单项,因此这不是必需的,但它仍然是良好实践,并且如果您有多个项,则绝对需要。

在状态栏中显示即时帮助

显示即时帮助与早期扩展中的情况没有区别。Explorer 调用 GetCommandString() 来检索将在状态栏中显示的字符串。

#include <atlconv.h>  // for ATL string conversion macros
  
STDMETHODIMP CBmpCtxMenuExt::GetCommandString (
  UINT uCmd, UINT uFlags, UINT* puReserved,
  LPSTR pszName, UINT cchMax )
{
USES_CONVERSION;
static LPCTSTR szHelpString =
         _T("Select this thumbnail to view the entire picture.");
 
  // Check idCmd, it must be 0 since we have only one menu item.
  if ( 0 != uCmd )
      return E_INVALIDARG;
 
  // If Explorer is asking for a help string, copy our string into the
  // supplied buffer.
  if ( uFlags & GCS_HELPTEXT )
    {
    if ( uFlags & GCS_UNICODE )
      {
      // We need to cast pszName to a Unicode string, and then use the
      // Unicode string copy API.
      lstrcpynW ( (LPWSTR) pszName, T2CW(szHelpString), cchMax );
      }
    else
      {
      // Use the ANSI string copy API to return the help string.
      lstrcpynA ( pszName, T2CA(szHelpString), cchMax );
      }
    }
 
  return S_OK;
}

执行用户选择

IContextMenu 中的最后一个方法是 InvokeCommand()。如果用户单击我们添加的菜单项,则调用此方法。该扩展将调用 ShellExecute() 来启动与 .BMP 文件关联的程序打开该位图。

STDMETHODIMP CBmpCtxMenuExt::InvokeCommand (
  LPCMINVOKECOMMANDINFO pInfo )
{
  // If lpVerb really points to a string, ignore this
  // call and bail out.
  if ( 0 != HIWORD(pInfo->lpVerb) )
    return E_INVALIDARG;
 
  // The command ID must be 0 since we only have one menu item.
  if ( 0 != LOWORD(pInfo->lpVerb) )
    return E_INVALIDARG;
 
  // Open the bitmap in the default paint program.
int nRet;
 
  nRet = (int) ShellExecute ( pInfo->hwnd, _T("open"), m_szFile,
                              NULL, NULL, SW_SHOWNORMAL );
 
  return (nRet > 32) ? S_OK : E_FAIL;
}

绘制菜单项

好的,我敢打赌您现在已经厌倦了看到所有之前的代码了。是时候进入新奇有趣的部分了!IContextMenu2IContextMenu3 添加的两个方法如下所示。它们只是调用一个辅助函数,该函数又调用一个消息处理程序。这是我设计的用于避免两种不同版本消息处理程序(一种用于 IContextMenu2,一种用于 IContextMenu3)的方法。关于 HandleMenuMsg2()LRESULT* 参数有一些奇怪之处,将在下面的注释中进行解释。

STDMETHODIMP CBmpCtxMenuExt::HandleMenuMsg (
  UINT uMsg, WPARAM wParam, LPARAM lParam )
{
  AFX_MANAGE_STATE(AfxGetStaticModuleState());  // init MFC
 
  // res is a dummy LRESULT variable.  It's not used (IContextMenu2::HandleMenuMsg()
  // doesn't provide a way to return values), but it's here so that the code 
  // in MenuMessageHandler() can be the same regardless of which interface it
  // gets called through (IContextMenu2 or 3).
LRESULT res;
 
  return MenuMessageHandler ( uMsg, wParam, lParam, &res );
}
 
STDMETHODIMP CBmpCtxMenuExt::HandleMenuMsg2 (
  UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult )
{
  AFX_MANAGE_STATE(AfxGetStaticModuleState());  // init MFC
 
  // For messages that have no return value, pResult is NULL.
  // If it is NULL, I create a dummy LRESULT variable, so the code in
  // MenuMessageHandler() will always have a valid pResult pointer.
  if ( NULL == pResult )
    {
    LRESULT res;
    return MenuMessageHandler ( uMsg, wParam, lParam, &res );
    }
  else
    return MenuMessageHandler ( uMsg, wParam, lParam, pResult );
}

MenuMessageHandler() 只是将 WM_MEASUREITEMWM_DRAWITEM 分派到单独的消息处理函数。

HRESULT CBmpCtxMenuExt::MenuMessageHandler (
  UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult )
{
  switch ( uMsg )
    {
    case WM_MEASUREITEM:
      return OnMeasureItem ( (MEASUREITEMSTRUCT*) lParam, pResult );
    break;
 
    case WM_DRAWITEM:
      return OnDrawItem ( (DRAWITEMSTRUCT*) lParam, pResult );
    break;
    }
 
  return S_OK;
}

如我前面指出的,文档说 Shell 应该让我们的扩展处理 WM_INITMENUPOPUPWM_MENUCHAR,但在我的测试中从未收到过这些消息。

处理 WM_MEASUREITEM

Shell 向我们的扩展发送 WM_MEASUREITEM 消息,以请求我们菜单项的尺寸。我们首先检查是否正在为我们自己的菜单项调用。如果该测试通过,我们获取位图的尺寸,然后计算整个菜单项的大小。

首先是位图尺寸:

HRESULT CBmpCtxMenuExt::OnMeasureItem (
  MEASUREITEMSTRUCT* pmis, LRESULT* pResult )
{
BITMAP bm;
LONG   lThumbWidth, lThumbHeight;
 
  // Check that we're getting called for our own menu item.
  if ( m_uOurItemID != pmis->itemID )
    return S_OK;
 
  m_bmp.GetBitmap ( &bm );
 
  m_lBmpWidth = bm.bmWidth;
  m_lBmpHeight = bm.bmHeight;

接下来,我们计算缩略图大小,并从中计算整个菜单项的大小。如果位图小于最大缩略图尺寸(在示例项目中是 64x64),则按原样绘制。否则,它将以 64x64 的分辨率绘制。这可能会稍微扭曲位图的外观;让缩略图看起来好看留作练习。

  // Calculate the bitmap thumbnail size.
  lThumbWidth = (m_lBmpWidth <= m_lMaxThumbnailSize) ? m_lBmpWidth : 
                 m_lMaxThumbnailSize;
 
  lThumbHeight = (m_lBmpHeight <= m_lMaxThumbnailSize) ? m_lBmpHeight :
                  m_lMaxThumbnailSize;
 
  // Calculate the size of the menu item, which is the size of the thumbnail +
  // the border and padding (which provides some space at the edges of the item).
  m_lItemWidth = lThumbWidth + m_lTotalBorderSpace;
  m_lItemHeight = lThumbHeight + m_lTotalBorderSpace;

现在我们有了菜单项的大小,我们将尺寸存储在与消息一起收到的 MENUITEMSTRUCT 中。Explorer 将在菜单中为我们的项保留足够的空间。

  pmis->itemWidth = m_lItemWidth;
  pmis->itemHeight = m_lItemHeight;
 
  *pResult = TRUE;  // we handled the message
  return S_OK;
}

处理 WM_DRAWITEM

当我们收到 WM_DRAWITEM 消息时,Explorer 请求我们实际绘制菜单项。我们首先计算绘制缩略图周围 3D 边框的 RECT。此 RECT 不一定与我们从 WM_MEASUREITEM 处理程序返回的大小相同,因为如果菜单中有其他更大的项,菜单可能会比该大小宽。

HRESULT CBmpCtxMenuExt::OnDrawItem (
  DRAWITEMSTRUCT* pdis, LRESULT* pResult )
{
CDC   dcBmpSrc;
CDC*  pdcMenu = CDC::FromHandle ( pdis->hDC );
CRect rcItem ( pdis->rcItem );  // RECT of our menu item
CRect rcDraw;                   // RECT in which we'll be drawing
 
  // Check that we're getting called for our own menu item.
  if ( m_uOurItemID != pdis->itemID )
    return S_OK;
            
  // rcDraw will first be set to the RECT that we set in WM_MEASUREITEM.
  // It will get deflated as we go along.
  rcDraw.left = rcItem.CenterPoint().x - m_lItemWidth/2;
  rcDraw.top = rcItem.CenterPoint().y - m_lItemHeight/2;
  rcDraw.right = rcDraw.left + m_lItemWidth;
  rcDraw.bottom = rcDraw.top + m_lItemHeight;
 
  // Shrink rcDraw to account for the padding space around
  // the thumbnail.
  rcDraw.DeflateRect ( m_lMenuItemSpacing, m_lMenuItemSpacing );

第一个绘制步骤是为菜单项的背景着色。DRAWITEMSTRUCTitemState 成员指示我们的项是否被选中。这决定了我们用于背景的颜色。

  // Fill in the background of the menu item.
  if ( pdis->itemState & ODS_SELECTED )
    pdcMenu->FillSolidRect ( rcItem, GetSysColor(COLOR_HIGHLIGHT) );
  else
    pdcMenu->FillSolidRect ( rcItem, GetSysColor(COLOR_MENU) );

接下来,我们绘制一个凹陷边框,使缩略图看起来像是凹入菜单中。

  // Draw the sunken 3D border.
  for ( int i = 1; i <= m_l3DBorderWidth; i++ )
    {
    pdcMenu->Draw3dRect ( rcDraw, GetSysColor ( COLOR_3DDKSHADOW ),
                          GetSysColor(COLOR_3DHILIGHT) );
 
    rcDraw.DeflateRect ( 1, 1 );
    }

最后一步是绘制缩略图本身。我选择了简单的路线,进行了简单的 StretchBlt() 调用。结果并不总是那么好看,但我的目标是保持代码简单。

  // Create a new DC and select the original bitmap into it.
CBitmap* pOldBmp;
 
  dcBmpSrc.CreateCompatibleDC ( &dc );
  pOldBmp = dcBmpSrc.SelectObject ( &m_bmp );
 
  // Blit the bitmap to the menu DC.
  pdcMenu->StretchBlt ( rcDraw.left, rcDraw.top, rcDraw.Width(), rcDraw.Height(),
                        &dcBmpSrc, 0, 0, m_lBmpWidth, m_lBmpHeight, SRCCOPY );
 
  dcBmpSrc.SelectObject ( pOldBmp );
 
  *pResult = TRUE;  // we handled the message
  return S_OK;
}

请注意,在实际的 Shell 扩展中,使用无闪烁的绘图类是一个好主意,这样鼠标悬停在菜单项上时,菜单项就不会闪烁。

这里是一些菜单的屏幕截图!首先,这是拥有者绘制的菜单,处于未选中和选中状态。

 [Owner-drawn menu - 18K]  [Owner-drawn menu, selected - 18K]

这是 Shell 版本 4.00 上的菜单。请注意,选中状态会反转所有颜色,使其有点难看。

 [Bitmap menu item - 4K]  [Bitmap menu item, selected - 4K]

注册 Shell 扩展

注册我们的位图查看器与我们其他的上下文菜单扩展的注册方式相同。这是用于完成此工作的 RGS 脚本:

HKCR
{
  NoRemove Paint.Picture
  {
    NoRemove ShellEx
    {
      NoRemove ContextMenuHandlers
      {
        BitmapPreview = s '{D6F469CD-3DC7-408F-BB5F-74A1CA2647C9}'
      }
    }
  }
}

请注意,“Paint.Picture”文件类型在此处是硬编码的。如果您不使用 Paint 作为 .BMP 文件的默认查看器,您需要将“Paint.Picture”更改为存储在 HKCR\.bmp 键的默认值中的任何内容。无需赘述,在生产代码中,您应该在 DllRegisterServer() 中执行此注册,以便能够检查“Paint.Picture”是否是正确的写入键。我将在第一部分中对这个主题有更多说明。

扩展 2 - 处理目录窗口中的右键单击

在 Shell 版本 4.71 及更高版本中,您可以修改右键单击桌面或查看文件系统目录的任何 Explorer 窗口时显示的上下文菜单。编程此类扩展与其他上下文菜单扩展类似。两个主要区别是:

  1. IShellExtInit::Initialize() 的参数使用方式不同。
  2. 该扩展在不同的键下注册。

我不会再次介绍扩展所需的所有步骤。如果您想查看完整内容,请查看示例项目。

IShellExtInit::Initialize() 中的差异

Initialize() 有一个 pidlFolder 参数,到目前为止,我们一直忽略它,因为它一直是 NULL。现在,终于,该参数有了用途!它是右键单击发生的目录的 PIDL。第二个参数(IDataObject*)是 NULL,因为没有要操作的选定文件。

这是示例项目中 Initialize() 的实现:

STDMETHODIMP CBkgndCtxMenuExt::Initialize (
  LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDO,
  HKEY hkeyProgID )
{
  // We get the conventional path of the directory from its PIDL with the
  // SHGetPathFromIDList() API.
  return SHGetPathFromIDList(pidlFolder, m_szDirClickedIn) ? S_OK : E_INVALIDARG;
}

SHGetPathFromIDList() 函数返回文件夹的完整路径,我们将其存储起来以备后用。它返回一个 BOOL 值表示成功或失败。

IShellExtInit::GetCommandString() 中的差异

从 XP 开始,Explorer 会检查从 GetCommandString() 返回的动词名称,并删除具有相同动词的任何项。我们的 GetCommandString() 必须检查 GCS_VERB 的标志,并为我们的每个菜单项返回一个不同的动词名称。

STDMETHODIMP CBkgndCtxMenuExt::GetCommandString (
    UINT uCmd, UINT uFlags, UINT* puReserved,
    LPSTR pszName, UINT cchMax )
{
USES_CONVERSION;
static LPCTSTR szItem0Verb = _T("CPBkgndExt0");
static LPCTSTR szItem1Verb = _T("CPBkgndExt1");
 
  // Check idCmd, it must be 0 or 1 since we have two menu items.
  if ( uCmd > 1 )
    return E_INVALIDARG;
 
  // Omitted: code to return a fly-by help string
 
  // Return verb names for our two items.
  if ( GCS_VERBA == uFlags )
    lstrcpynA ( pszName, T2CA((0 == uCmd) ? szItem0Verb : szItem1Verb), cchMax );
  else if ( GCS_VERBW == uFlags )
    lstrcpynW ( (LPWSTR) pszName, T2CW((0 == uCmd) ? szItem0Verb : szItem1Verb), cchMax );
 
  return S_OK;
}

注册中的差异

此类型的扩展在不同的键下注册:HKCR\Directory\Background\ShellEx\ContextMenuHandlers。这是用于执行此操作的 RGS 脚本:

HKCR
{
  NoRemove Directory
  {
    NoRemove Background
    {
      NoRemove ShellEx
      {
        NoRemove ContextMenuHandlers
        {
          ForceRemove SimpleBkgndExtension = s '{9E5E1445-6CEA-4761-8E45-AA19F654571E}'
        }
      }
    }
  }
}

除了这两个差异之外,该扩展的工作方式与其他上下文菜单扩展相同。但是,在 IContextMenu::QueryContextMenu() 中有一个例外。在 Windows 98 和 2000 上,uIndex 参数似乎始终为 -1 (0xFFFFFFFF)。将 -1 作为 InsertMenu() 的索引意味着新项位于菜单的底部。但是,如果增加 uIndex,它将变为零,这意味着如果您再次将 uIndex 传递给 InsertMenu(),第二个项将出现在菜单的*顶部*。请查看示例项目中的 QueryContextMenu() 代码,了解如何正确地将项添加到正确的位置。

这是修改后的上下文菜单的样子,在末尾添加了两个项。请注意,恕我直言,以这种方式将项添加到菜单末尾存在严重的用户体验问题。当用户想要选择“属性”项时,右键单击然后选择最后一个菜单项是一个习惯性的动作。当我们的扩展出现并在“属性”之后添加项时,我们就会破坏用户的习惯,这可能会导致沮丧并产生一些糟糕的电子邮件。;)

 [modified context menu - 42K]

待续...

接下来,在第八部分中,我将演示一个列处理程序扩展,它可以向 Explorer 的详细信息视图添加列。

版权和许可

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

随本文一起提供的演示代码已发布到公共领域。我以这种方式发布它,以便代码可以惠及所有人。(我不会将本文本身发布到公共领域,因为在 CodeProject 上提供本文有助于提高我个人的知名度和 CodeProject 网站。)如果您在自己的应用程序中使用演示代码,请发送电子邮件告知我(只是为了满足我对人们是否从我的代码中受益的好奇心),但这并非必需。在您自己的源代码中注明出处也表示赞赏,但并非必需。

修订历史

2000 年 9 月 1 日:文章首次发布。
2000 年 9 月 14 日:更新了某些内容。;)
2006 年 5 月 30 日:更新以涵盖 VC 7.1 的更改,清理代码片段,示例项目在 XP 上进行主题化。

系列导航:« 第六部分 | 第八部分 »

结束注释

[1] PicaView 由 ACD Systems 开发,但已不再可用。

© . All rights reserved.