程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 用C++ Builder創建上下文菜單擴展處理器

用C++ Builder創建上下文菜單擴展處理器

編輯:關於C++

當用戶右擊一個shell對象時,shell會顯示它的上下文菜單。文件系統對象有大量的標准菜單項,如"剪切"和"拷貝",這些都是缺省的菜單項。如果對象是一個文件,是文件類的成員,就能夠在注冊表裡指定附加的菜單項。Shell檢查注冊表,看看文件類型是否與一些上下文菜單handler相關聯,如果是,shell會咨詢這些handler是否添加額外的菜單項。

上下文菜單handler是一種shell擴展handler,它添加命令到已有的上下文菜單中。上下文菜單handler都與特定的文件類相關聯,並且在顯示這類文件的成員的上下文菜單時調用。通過實現和注冊這樣一個handler,能夠動態地添加菜單項到對象的上下文菜單上,從而為特殊的對象定制菜單。

上下文菜單Handler的工作原理

作為一種shell擴展handler,上下文菜單handler同所有其它handler一樣, 是進程內COM 對象,即對象作為動態連接庫 (DLL)實現。除了IUnknown接口外,上下文菜單還必須導出IShellExtInit和IContextMenu接口,作為選擇,上下文菜單也能導出IContextMenu2和IContextMenu3,這些接口可以實現自畫菜單項。

IShellExtInit接口僅僅被shell用來初始化handler,主要的操作通過handler的IContextMenu接口進行。Shell首先調用IContextMenu::QueryContextMenu,傳送一個HMENU句柄,這個方法用它來增加上下文菜單。如果用戶亮選了這些新添加的某個命令項, IContextMenu::GetCommandString將被調用,以取得這條菜單的幫助信息,把它顯示在資源管理器的狀態條上。如果用戶單擊了handler的條目,shell調用IContextMenu::InvokeCommand,從而handler能夠執行合適的操作。

實現IContextMenu接口

1、實現QueryContextMenu方法

Shell通過調用IContextMenu::QueryContextMenu,允許handler把它的菜單項添加到菜單中。QueryContextMenu共有5個參數,各參數作用如下:

1) Hmenu:HMENU類型,表示上下文菜單的句柄。
2) IndexMenu:第一個被添加的菜單索引。
3) IdCmdFirst:添加的菜單ID初值。
4) idCmdLast:添加的菜單ID最大值。
5) uFlags:與上下文菜單相關的狀態標志,共有3種,如下:

CMF_DEFAULTONLY 用戶選擇了缺省的命令,通常是通過雙擊對象產生。QueryContextMenu 在把控制返回給shell前不應該修改菜單。

CMF_NODEFAULT 菜單沒有缺省的條目,這個方法應該把它的命令加到菜單中。

CMF_NORMAL 上下文菜單將被正常顯示,這個方法應該把它的命令加到菜單中。

必須注意的是,任何添加的菜單項的ID必須落在idCmdFirst和idCmdLast兩個參數中間,通常,添加的第一個菜單項ID設為idCmdFirst,以後每添加一個菜單項,就把ID加1,這樣,即使shell調用了不止一個handler,也可以確保菜單項的ID不超過idCmdLast和可能的ID最大值。
在ID和idCmdFirst之間,菜單項ID的command offset(命令偏移)是不同的,應該保存handler添加到上下文菜單中的每個菜單項的offset,因為如果shell按順序調用GetCommandString或者InvokeCommand,可以使用它來鑒別菜單項的ID。

還應該為每一個添加的命令賦予一個verb。Verb是語言獨立的字符串,當調用InvokeCommand時,常常用verb來代替偏移以鑒別命令。
QueryContextMenu 方法使用InsertMenu或InsertMenuItem 添加新的菜單項,然後返回一個嚴格設置為SEVERITY_SUCCESS的HRESULT值,把它的值設置為被分配的最大的命令ID。例如,假如idCmdFirst是5,添加了3個菜單項,ID分別是5,7,8,則返回值應該是MAKE_HRESULT(SEVERITY_SUCCESS, 0, 8 - 5 + 1)。

以下是一個QueryContextMenu實例:

HRESULT __stdcall TAddContextMenuImpl::QueryContextMenu(HMENU hmenu,
     UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
  if(!(CMF_DEFAULTONLY & uFlags))
  {
   InsertMenu(hmenu, indexMenu, MF_STRING | MF_BYPOSITION,idCmdFirst,
   _T("選擇打開方式..."));
   return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 1);
  }
  return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0));
}

2、實現GetCommandString 方法

如果用戶高亮了一個handler添加的菜單項,shell將調用handler的GetCommandString方法。這個方法需要傳遞菜單項的偏移值(ID)、指定信息類型的標志、一個預留的參數、一個字符串緩沖區以及緩沖區的大小。

一般,這個方法可以不用處理,以下示例程序直接返回S_OK。

HRESULT __stdcall TAddContextMenuImpl::GetCommandString(UINT idCmd, UINT uFlags,
UINT *pwReserved, LPSTR pszName, UINT cchMax)
{
  return S_OK;
}

3、實現InvokeCommand方法

當在上下文菜單中選擇一個菜單項時,shell就會調用InvokeCommand,告訴handler運行相關聯的命令。在Shlobj.h中,參數pici被聲明為CMINVOKECOMMANDINFO結構,但實際上,它經常指向CMINVOKECOMMANDINFOEX結構,這個結構是CMINVOKECOMMANDINFO的擴展版本,有幾個成員允許傳遞Unicode字符串。

CMINVOKECOMMANDINFO的成員簡介如下:

1) cbSize :結構的大小。

2) fMask :為0,或下列標志的組合。

CMIC_MASK_ASYNCOK 在返回之前等待DDE會話結束

CMIC_MASK_FLAG_NO_UI 當執行命令時,系統防止顯示用戶接口元素(如錯誤信息)

CMIC_MASK_HOTKEY dwHotKey 成員有效

CMIC_MASK_ICON hIcon成員有效

CMIC_MASK_NO_CONSOLE 如果上下文菜單handler必須創建新進程,正常情況下將創建一個控制台,設置CMIC_MASK_NO_CONSOLE標志可以禁止創建新的控制台

3) hwnd :擁有上下文菜單窗口的句柄,handler可以使用這個句柄顯示自己的信息提示框和對話框。

4) lpVerb :32位值,高位字包含0,低位字是命令的菜單ID偏移。當用戶選擇一個菜單命令時,Shell用MAKEINTRESOURCE宏產生這個值,如果高位字不是0,那麼這個成員指向一個以NULL結尾的字符串,指出命令的語言無關的名稱,即上文的verb。典型情況下,當命令被一個應用程序激活時,這個成員是一個字符串。系統提供了下面幾個預定義的常數值:

值:CMDSTR_NEWFOLDER  字符串:"NewFolder"

值:CMDSTR_VIEWDETAILS  字符串:"ViewDetails"

值:CMDSTR_VIEWLIST  字符串:"ViewList"

5) lpParameters :命令傳送的參數字符串,對於shell擴展插入的菜單項,這個成員總是NULL。

6) lpDirectory :目錄名稱,對於shell擴展插入的菜單項,這個成員總是NULL。

7) nShow :顯示窗口或啟動應用程序時,傳遞給ShowWindow函數的參數。

8) dwHotKey :分配給被命令激活的應用程序的熱鍵。如果fMask 不是CMIC_MASK_HOTKEY,這個成員被忽略。

9) hIcon :被命令激活的應用程序使用的圖標。如果fMask 不是CMIC_MASK_ICON,這個成員被忽略。

以下示例先打開一個"選擇文件"的對話框,然後用所選擇的程序打開在資源管理器中被選擇的文件。為了簡化,假定在資源管理器只選擇了一個文件。

HRESULT __stdcall TAddContextMenuImpl::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
{
  if(HIWORD(pici->lpVerb)==0)
  {
   if(LOWORD(pici->lpVerb)==0) // 添加的第一個菜單項
   {
     TOpenDialog *Dlg=new TOpenDialog(NULL);
     Dlg->Title="打開\"";
     Dlg->Title=Dlg->Title+g_szFilePath+"\"";
     Dlg->Options.Clear();
     Dlg->Options << ofFileMustExist << ofPathMustExist << ofNoChangeDir;
     if(Dlg->Execute())
     {
      ShellExecute(pici->hwnd,"open",Dlg->FileName.c_str(),g_szFilePath,NULL,SW_SHOW);      }
     return S_OK;
   }
  }
  return S_FALSE;
}

注冊上下文菜單Handler

上下文菜單與文件類或者文件夾相關聯。對於文件類,handler注冊在文件類的HKEY_CLASSES_ROOT\ProgID\Shellex\ContextMenuHandlers子鍵下。在ContextMenuHandlers下創建一個以handler子鍵,把子鍵的缺省值設置為handler的CLSID的字符串值,就可以完成注冊。

也能夠把handler關聯到文件夾,注冊的方法與上面類似,不過是在HKEY_CLASSES_ROOT\FolderType\Shellex\ContextMenuHandlers增加子鍵, 其中的FolderType 是文件夾類型的名稱。

如果一個文件類有上下文菜單與它關聯,那麼雙擊一個對象將自動啟動缺省的命令,而不會調用handler的QueryContextMenu方法。當對象被雙擊時,為了指定調用handler的QueryContextMenu方法,必須在handler的CLSID鍵下創建一個ShellEx\MayChangeDefaultMenu的子鍵。這樣,當與handler關聯的對象被雙擊時,QueryContextMenu 被調用,而且uFlags參數會包含CMF_DEFAULTONLY 標志。

注意,如果設置了MayChangeDefaultMenu鍵,當一個關聯的項目被雙擊時,會強制系統載入handler的DLL。如果handler不改變缺省動作,就不應該設置MayChangeDefaultMenu,否則會引起系統不必要地載入這個DLL。僅僅當在可能改變上下文菜單的缺省動作時,才應該在設置上下文菜單handler的這個值。

創建工程

作為Borland的產品,用C++ Builder創建shell擴展的過程與Delphi有類似之處,但它畢竟是C++語言,所以也有與VC類似之的地方。

1. 選擇File菜單的New菜單項,翻到New Items對話框的ActiveX頁,雙擊ActiveX Library項,創建一個新的COM工程,把工程命名為MyContextMenu。從New Items 對話框的ActiveX頁選擇COM Object項,將打開COM Server向導。把"COClass"改為AddContextMenu,選擇Apartment線程模式。其它不要改寫。C++ Builder自動產生一個接口和一個類。默認的類名是TAddContextMenuImpl,采用自動生成的IAddContextMenu接口。我們必須自己添加新的接口IShellExtInit和IContextMenu,如下所示,粗體是添加的內容:

#include <shlobj.h> // 聲明IShellExtInit和IContextMenu的頭文件
class ATL_NO_VTABLE TAddContextMenuImpl :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<TAddContextMenuImpl, &CLSID_AddContextMenu>,
public IShellExtInit,
public IContextMenu,
public IAddContextMenu
{
  private:
  char g_szFilePath[MAX_PATH];
  public:
  … …
  BEGIN_COM_MAP(TAddContextMenuImpl)
  COM_INTERFACE_ENTRY(IAddContextMenu)
  COM_INTERFACE_ENTRY(IContextMenu) // 導出IContextMenu接口
  COM_INTERFACE_ENTRY(IShellExtInit) // 導出IShellExtInit接口
  END_COM_MAP()
  … …
};

2. 實現IShellExtInit接口的Initialize方法,在類定義中增加如下內容:

STDMETHOD (Initialize)(LPCITEMIDLIST pidlFolder,LPDATAOBJECT lpdobj,HKEY hkeyProgID);

Initialize方法的代碼如下,從lpdobj對象中取出資源管理器中選擇的文件名,程序假定只選擇了一個文件。

HRESULT __stdcall TAddContextMenuImpl::
Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT lpdobj, HKEY hkeyProgID)
{
  FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
  STGMEDIUM stg = { TYMED_HGLOBAL };
  HDROP hDrop;
  if (FAILED(lpdobj->GetData(&fmt, &stg))) return E_FAIL;
  hDrop = (HDROP)GlobalLock(stg.hGlobal);
  if ( hDrop == NULL)
  {
    ReleaseStgMedium(&stg);
    return E_OUTOFMEMORY;
  }
  DragQueryFile(hDrop, 0, g_szFilePath, MAX_PATH);
  GlobalUnlock(stg.hGlobal);
  ReleaseStgMedium(&stg);
  return S_OK;
}

3. 實現IContextMenu接口的各個方法,內容如上文所示,聲明如下:

public:
STDMETHOD (QueryContextMenu)(HMENU hmenu,UINT indexMenu,UINT idCmdFirst,UINT idCmdLast,UINT uFlags);
STDMETHOD (InvokeCommand)(LPCMINVOKECOMMANDINFO pici);
STDMETHOD (GetCommandString)(UINT idCmd,UINT uFlags,UINT *pwReserved,LPSTR pszName,UINT cchMax);

最後,把工程編譯為DLL文件,運行菜單[Run->Register ActiveX Server],把DLL注冊。與Delphi和VC相比,C++ Builder似乎有些缺陷。首先,它實現時太過復雜,生成的文件一大堆。最麻煩的是,它無法實現自動注冊為shell擴展,它沒有VC的rgs文件,像Delphi那樣改寫UpdateRegistry函數,怎麼也不行,好像這個函數沒有調用一樣。無奈,只好自己動手向注冊表添加必須的項目(如圖)。但是,C++ Builder給出了3個CLSID,很迷惑人,正確的CLSID應該是類AddContextMenu的,C++ Builder給它命名為CLSID_AddContextMenu。

注冊後,在資源管理器右擊任何文件,如readme.txt,都將打開一個選擇文件的對話框,然後shell用選擇的文件打開readme.txt。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved