Creating ContextMenu Handlers
Creating Context Menu Handlers
When a user right-clicks a Shell object,the Shell displays its context menu. For file system objects there are a numberof standard items, such as Cut and Copy, that are on the menu bydefault. If the object is a file that is a member of a class, additionalitems can be specified in the registry.Finally, the Shell checks the registry to see if the file class is associatedwith any context menu handlers. If it is, the Shell queries the handlerfor additional context menu items.
A context menu handler is a shell extension handlerthat adds commands to an existing context menu. Context menu handlers areassociated with a particular file class and are called any time a context menuis displayed for a member of the class. While you can add items to a file classcontext menu with the registry, the items will be the same for all members ofthe class. By implementing and registering such a handler, you can dynamicallyadd items to an object's context menu, customized for the particular object.
A closely related Shell extension handleris the drag-and-drop handler. It is a context menu handler that addsitems to the context menu that is displayed when a user drags and drops a filewith the right mouse button.
The procedures for implementing andregistering a Shell extension handler are discussed in Creating ShellExtension Handlers. This document focuses on those aspects ofimplementation that are specific to context menu handlers.
The following topics are discussed.
· How Context Menu HandlersWork
· Registering a Context Menu Handler
· Creating Drag-and-Drop Handlers
Context menu handlers are a type of Shellextension handler. Like all such handlers, they are in-process Component ObjectModel (COM) objects implemented as DLLs. Context menu handlers must export twointerfaces in addition to IUnknown: IShellExtInit andIContextMenu. Optionally,a context menu handler can also export IContextMenu2 andIContextMenu3.These interfaces handle the messaging needed to implement owner-drawn menuitems. For more information on owner-drawn menu items, see the CreatingOwner-Drawn Menu Items section under Using Menus.
The IShellExtInit interface is usedby the Shell to initialize the handler. When the Shell calls IShellExtInit::Initialize,it passes in a data object with the object's name and the pointer to an itemidentifier list (PIDL) of the folder that contains the file. The hKeyProgIDparameter is not used with context menu handlers. The IShellExtInit::Initializemethod must extract the file name from the data object and store the name andthe folder's PIDL for later use. For further details, see Implementing IShellExtInit.
The remainder of the operation takes placethrough the handler's IContextMenu interface. The Shell first calls IContextMenu::QueryContextMenu.It passes in an HMENU handle that the method can use to add items to thecontext menu. If the user selects one of the commands, IContextMenu::GetCommandStringis called to retrieve the Help string that will be displayed on the MicrosoftWindows Explorer status bar. If the user clicks one of the handler's items, theShell calls IContextMenu::InvokeCommand.The handler can then execute the appropriate command.
Registeringa Context Menu Handler
Context menu handlers are associated witheither a file class or a folder. For file classes, the handler is registeredunder the following subkey.
· HKEY_CLASSES_ROOT
o Program ID
§ shellex
§ ContextMenuHandlers
Create a subkey under ContextMenuHandlersnamed for the handler, and set the subkey's default value to the string form ofthe handler's class identifier (CLSID) GUID.
You can also associate a context menuhandler with different kinds of folders. Register the handler the same way youwould for a file class, but under the following subkey, where FolderTypeis the name of the type of folder.
· HKEY_CLASSES_ROOT
o FolderType
§ shellex
§ ContextMenuHandlers
For a discussion of how to register Shellextension handlers and more information about which folder types you canregister handlers for, see Registering Shell Extension Handlers.
If a file class has a context menuassociated with it, double-clicking an object normally launches the defaultcommand. The handler's IContextMenu::QueryContextMenu method is notcalled. To specify that the handler's IContextMenu::QueryContextMenumethod should be called when an object is double-clicked, create a shellex\MayChangeDefaultMenusubkey under the handler's CLSID key. When an object associated with thehandler is double-clicked, IContextMenu::QueryContextMenu will be calledwith the CMF_DEFAULTONLY flag set in the uFlags parameter.
Note Setting the MayChangeDefaultMenukey forces the system to load the handler's DLL when an associated item isdouble-clicked. If your handler does not change the default verb, you shouldnot set MayChangeDefaultMenu . Doing so causes the system to load yourDLL unnecessarily. Context menu handlers should set this key only if they mightneed to change the context menu's default verb.
The following example illustrates registryentries that enable a context menu handler for an example .myp file class. Thehandler's CLSID key includes a MayChangeDefaultMenu subkey toguarantee that the handler is called when the user double-clicks a relatedobject.
· HKEY_CLASSES_ROOT
o
o .myp
(Default) = MyProgram.1
o CLSID
§ {00000000-1111-2222-3333-444444444444}
§ InProcServer32
(Default) = C:\MyDir\MyCommand.dll
ThreadingModel = Apartment
§ shellex
§ MayChangeDefaultMenu
o MyProgram.1
(Default) = MyProgram Application
§ shellex
§ ContextMenuHandler
MyCommand = {00000000-1111-2222-3333-444444444444}
Most of the operation described in How Context Menu HandlersWork is handled by the IContextMenuinterface. This section discusses how to implement its three methods.
The Shell calls IContextMenu::QueryContextMenuto allow the context menu handler to add its menu items to the menu. It passesin the HMENU handle in the hmenu parameter. The indexMenuparameter is set to the index to be used for the first menu item that is to beadded.
Any menu items that are added by thehandler must have identifiers that fall between the idCmdFirst and idCmdLastparameters. Typically, the first command identifier is set to idCmdFirst,which is incremented by one (1) for each additional command. This practiceensures that you don't exceed idCmdLast and maximizes the number ofavailable identifiers in case the Shell calls more than one handler.
An item identifier's command offsetis the difference between the identifier and idCmdFirst. Store theoffset of each item that your handler adds to the context menu because theShell might use it to identify the item if it subsequently calls IContextMenu::GetCommandStringor IContextMenu::InvokeCommand.
You should also assign a verb to eachcommand you add. A verb is a language-independent string that can be usedinstead of the offset to identify the command when IContextMenu::InvokeCommandis called. It is also used by functions such as ShellExecuteEx toexecute context menu commands.
Only three of the flags that can be passedin through the uFlags parameter are relevant to context menu handlers.
| CMF_DEFAULTONLY | The user has selected the default command, usually by double-clicking the object. IContextMenu::QueryContextMenu should return control to the Shell without modifying the menu. |
| CMF_NODEFAULT | No item in the menu should be the default item. The method should add its commands to the menu. |
| CMF_NORMAL | The context menu will be displayed normally. The method should add its commands to the menu. |
Use either InsertMenu or InsertMenuItem toadd menu items to the list. Then return an HRESULT value with theseverity set to SEVERITY_SUCCESS. Set the code value to the offset of thelargest command identifier that was assigned, plus one (1). For example, assumethat idCmdFirst is set to 5 and you add three items to the menu withcommand identifiers of 5, 7, and 8. The return value should beMAKE_HRESULT(SEVERITY_SUCCESS, 0, 8 - 5 + 1).
The following example shows a simpleimplementation of IContextMenu::QueryContextMenu that inserts a singlecommand. The identifier offset for the command is IDM_DISPLAY, which is set tozero. The m_pszVerb and m_pwszVerb variables are privatevariables used to store the associated language-independent verb string in bothUnicode and ANSI formats.
#define IDM_DISPLAY 0
STDMETHODIMP CMenuExtension::QueryContextMenu(HMENUhMenu,
UINT indexMenu,
UINT idCmdFirst,
UINT idCmdLast,
UINT uFlags)
{
HRESULT hr;
if(!(CMF_DEFAULTONLY & uFlags))
{
InsertMenu(hMenu,
indexMenu,
MF_STRING | MF_BYPOSITION,
idCmdFirst + IDM_DISPLAY,
"&Display File Name");
// TODO:Add error handling to verify HRESULT return values.
hr =StringCbCopyA(m_pszVerb, sizeof(m_pszVerb), "display");
hr =StringCbCopyW(m_pwszVerb, sizeof(m_pwszVerb), L"display");
returnMAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(IDM_DISPLAY + 1));
}
returnMAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0));
}
If a user highlights one of the items addedby a context menu handler, the handler's IContextMenu::GetCommandStringmethod is called to request a Help text string that will be displayed on theWindows Explorer status bar. This method can also be called to request the verbstring that is assigned to a command. Either ANSI or Unicode verb strings canbe requested.
The idCmd parameter holds theidentifier offset of the command that was defined when IContextMenu::QueryContextMenuwas called. If a Help string is requested, uFlags will be set to either GCS_HELPTEXTA or GCS_HELPTEXTW, depending on whether an ANSI or Unicodestring is desired. Copy the Help string to the pszName buffer, castingit to an LPWSTR for the Unicode case. The verb string is requested bysetting uFlags to either GCS_VERBA or GCS_VERBW. Copy the appropriate string to pszName,just as with the Help string. The GCS_VALIDATEA and GCS_VALIDATEW flags are not used by context menu handlers.
The following example shows a simpleimplementation of IContextMenu::GetCommandString that corresponds to theIContextMenu::QueryContextMenu example given in the QueryContextMenu Methodsection. Since the handler adds only one menu item, there is only one set ofstrings that can be returned. The method simply tests whether idCmd isvalid and, if it is, returns the requested ANSI or Unicode string.
The StringCchCopyNfunction is used to copy the requested string to pszName to ensure thatthe copied string doesn't exceed the size of the buffer given by uMaxNameLen.The ANSI and Unicode versions of the function are used explicitly, because theformat defined when the handler is compiled might not match the requestedformat.
STDMETHODIMP CMenuExtension::GetCommandString(UINTidCommand,
UINTuFlags,
LPUINT lpReserved,
LPSTR pszName,
UINT uMaxNameLen)
{
HRESULT hr = E_INVALIDARG;
if(idCommand !=IDM_DISPLAY)
{
return hr;
}
switch(uFlags)
{
caseGCS_HELPTEXTA:
hr =StringCchCopyNA(pszName,
lstrlen(pszStr)/sizeof(pszStr(0)),
"DisplayFile Name",
uMaxNameLen);
break;
caseGCS_HELPTEXTW:
hr =StringCchCopyNW((LPWSTR)pszName,
lstrlen(pszStr)/sizeof(pszStr(0)),
L"Display File Name",
uMaxNameLen);
break;
caseGCS_VERBA:
hr =StringCchbCopyNA(pszName,
lstrlen(pszStr)/sizeof(pszStr(0)),
m_pszVerb,
uMaxNameLen);
break;
caseGCS_VERBW:
hr =StringCchCopyNW((LPWSTR)pszName,
lstrlen(pszStr)/sizeof(pszStr(0)),
m_pwszVerb,
uMaxNameLen);
break;
default:
hr =S_OK;
break;
}
return hr;
}
This method is called when a user clicks amenu item to tell the handler to run the associated command. The piciparameter points to a structure that contains the needed information.
Although pici is declared inShlobj.h as a CMINVOKECOMMANDINFOstructure, in practice it often points to a CMINVOKECOMMANDINFOEXstructure. This structure is an extended version of CMINVOKECOMMANDINFOand has several additional members that allow Unicode strings to be passed.
Check the cbSize member of picito determine which structure was passed in. If it is a CMINVOKECOMMANDINFOEXstructure and the fMask member has the CMIC_MASK_UNICODE flag set, cast pici to CMINVOKECOMMANDINFOEX.This allows your application to use the Unicode information contained in thelast five members of the structure.
The structure's lpVerb or lpVerbWmember is used to identify the command to be executed. There are two ways toidentify commands.
· The command's verb string
· The command's identifier offset
To distinguish between these two cases,check the high-order word of lpVerb for the ANSI case or lpVerbWfor the Unicode case. If the high-order word is nonzero, lpVerb or lpVerbWholds a verb string. If the high-order word is zero, the command offset is inthe low-order word of lpVerb.
The following example shows a simpleimplementation of IContextMenu::InvokeCommand that corresponds to the IContextMenu::QueryContextMenuand IContextMenu::GetCommandString samples given in the previoussections. The method first determines which structure is being passed in. Itthen determines whether the command is identified by its offset or verb. If lpVerbor lpVerbW holds a valid verb or offset, the method displays a messagebox.
STDMETHODIMPCShellExtension::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
{
BOOL fEx =FALSE;
BOOL fUnicode =FALSE;
if(lpcmi->cbSize == sizeof(CMINVOKECOMMANDINFOEX))
{
fEx = TRUE;
if((lpcmi->fMask & CMIC_MASK_UNICODE))
{
fUnicode = TRUE;
}
}
if( !fUnicode&& HIWORD(lpcmi->lpVerb))
{
if(StrCmpIA(lpcmi->lpVerb, m_pszVerb))
{
return E_FAIL;
}
}
else if(fUnicode && HIWORD(((CMINVOKECOMMANDINFOEX *) lpcmi)->lpVerbW))
{
if(StrCmpIW(((CMINVOKECOMMANDINFOEX *)lpcmi)->lpVerbW, m_pwszVerb))
{
returnE_FAIL;
}
}
elseif(LOWORD(lpcmi->lpVerb) != IDM_DISPLAY)
{
returnE_FAIL;
}
else
{
MessageBox(lpcmi->hwnd,
"The File Name",
"File Name",
MB_OK|MB_ICONINFORMATION);
}
return S_OK;
}
CreatingDrag-and-Drop Handlers
When a user right-clicks a Shell object todrag an object, a context menu is displayed when the user attempts to drop theobject. The following illustration shows a typical drag-and-drop context menu.

A drag-and-drop handler is a context menuhandler that can add items to this context menu. Drag-and-drop handlers aretypically registered under the following subkey.
· HKEY_CLASSES_ROOT
o Directory
§ shellex
§ DragDropHandlers
Add a subkey under the DragDropHandlerssubkey named for the drag-and-drop handler, and set the subkey's default valueto the string form of the handler's CLSID GUID. The following exampleenables the MyDD drag-and-drop handler.
· HKEY_CLASSES_ROOT
o Directory
§ shellex
§ DragDropHandlers
§ MyDD
(Default) = {MyDD CLSID GUID}
The basic procedure for implementing adrag-and-drop handler is the same as for conventional context menu handlers. However,context menu handlers normally use only the IDataObject pointer passed to thehandler's IShellExtInit::Initialize method to extract the object's name.A drag-and-drop handler could implement a more sophisticated data handler tomodify the behavior of the dragged object.
Windows外殼擴展編程 www.applevb.com
在Windows下的一些軟件提供了這樣的功能:當安裝了這些軟件之后,當在Windows的Explore中鼠標右鍵單擊文件或者文件夾后,在彈出菜單中就會(huì )多出與該軟件操作相關(guān)的菜單項,點(diǎn)擊該項就會(huì )激活相應的程序對用戶(hù)選中的文件進(jìn)行相應的操作。例如安裝了Winzip之后,當用戶(hù)選中一個(gè)文件夾后單擊右鍵,在彈出菜單中就會(huì )多出一個(gè)Add ToZip和一個(gè) Add To xxx.zip的選項,其中xxx為選中的文件夾的名稱(chēng)。只要單擊上面的兩個(gè)菜單項中的一個(gè),就可以方便的壓縮目錄了。這樣的功能稱(chēng)為Windows外殼擴展(Shell Extensions)
外殼擴展概述
下面是與外殼擴展相關(guān)的三個(gè)重要術(shù)語(yǔ):
(1)文件對象(File Object)
文件對象是外殼中的一項,大家最熟識的文件對象是文件和目錄,此外,打印機、控制面板程序、共享網(wǎng)
絡(luò )等也都是文件對象。
(2)文件類(lèi)(File Class)
文件類(lèi)是具有某種共同特性的文件對象的集合,比如,擴展名相同的文件屬于同一文件類(lèi)。
(3)處理程序(Handler)
處理程序是具體實(shí)現某個(gè)外殼擴展的代碼。
Windows支持七種類(lèi)型的外殼擴展(稱(chēng)為Handler),它們相應的作用簡(jiǎn)述如下:
(1)Context menu handlers向特定類(lèi)型的文件對象增添上下文相關(guān)菜單;
(2)Drag-and-drop handlers用來(lái)支持當用戶(hù)對某種類(lèi)型的文件對象進(jìn)行拖放操作時(shí)的OLE數據傳輸;
(3)Icon handlers用來(lái)向某個(gè)文件對象提供一個(gè)特有的圖標,也可以給某一類(lèi)文件對象指定圖標;
(4)Property sheet handlers給文件對象增添屬性頁(yè),屬性頁(yè)可以為同一類(lèi)文件對象所共有,也可以給一個(gè)
文件對象指定特有的屬性頁(yè);
(5)Copy-hook handlers在文件夾對象或者打印機對象被拷貝、移動(dòng)、刪除和重命名時(shí),就會(huì )被系統調用,
通過(guò)為Windows增加Copy-hook handlers,可以允許或者禁止其中的某些操作;
(6)Drop target handlers在一個(gè)對象被拖放到另一個(gè)對象上時(shí),就會(huì )被系統被調用;
(7)Data object handlers在文件被拖放、拷貝或者粘貼時(shí),就會(huì )被系統被調用。
Windows的所有外殼擴展都是基于COM(ComponentObject Model) 組件模型的,外殼是通過(guò)接口(Interface)來(lái)訪(fǎng)問(wèn)對象的。外殼擴展被設計成32位的進(jìn)程中服務(wù)器程序,并且都是以動(dòng)態(tài)鏈接庫的形式為操作系統提供服務(wù)的。因此,如果要對Windows的用戶(hù)界面進(jìn)行擴充的話(huà),則具備寫(xiě)COM對象的一些知識是十分必要的。
寫(xiě)好外殼擴展程序后,必須將它們注冊才能生效。所有的外殼擴展都必須在Windows注冊表的HKEY_CLASSES_ROOT\CLSID鍵之下進(jìn)行注冊。在該鍵下面可以找到許多名字像{
注冊表HKEY_CLASSES_ROOT主鍵下有幾個(gè)特殊的子鍵,如*、Folder、Drive以及Printer。如果把外殼擴展注冊在*子鍵下,那么這個(gè)外殼擴展將對Windows中所有類(lèi)型的文件有效;如果把外殼擴展注冊在Folder子鍵下,則對所有目錄有效。
上面提到的在WindowsExplore中在鼠標右鍵菜單中添加菜單項(我們成為上下文相關(guān)菜單)的操作屬于外殼擴展的第一類(lèi),即Contextmenu handlers向特定類(lèi)型的文件對象增添上下文相關(guān)菜單。要動(dòng)態(tài)地在上下文相關(guān)菜單中增添菜單項,可以通過(guò)寫(xiě)Context Menu Handler來(lái)實(shí)現。
編寫(xiě)Context Menu Handler必須實(shí)現IShellExtInit和IContextMenu兩個(gè)接口。除了IUnknown接口所定義的函數之外,Context Menu Handler還需要用到QueryContextMenu、InvokeCommand和GetCommandString這三個(gè)非常重要的成員函數。
(1)QueryContextMenu函數:每當系統要顯示一個(gè)文件對象的上下文相關(guān)菜單時(shí),它首先要調用該函數。為了在上下文相關(guān)菜單中添加菜單
項,我們在該函數中調用InsertMenu函數。
(2)InvokeCommand函數:當用戶(hù)選定了某個(gè)ContextMenu Handler登記過(guò)的菜單項后,該函數將會(huì )被調用,系統將會(huì )傳給該函數一個(gè)指向
LPCMINVOKECOMMANDINFO結構的指針。在該函數中要執行與所選菜單項相對應的操作。
(3)GetCommandString函數:當鼠標指針移到一個(gè)上下文相關(guān)菜單項上時(shí),在當前窗口的狀態(tài)條上將會(huì )出現與該菜單項相關(guān)的幫助信息,此
信息就是系統通過(guò)調用該函數獲取的。
下面我通過(guò)具體的例程來(lái)說(shuō)明編寫(xiě)一個(gè)比較完整的上下文菜單程序,這個(gè)程序是一個(gè)文件操作程序,當安裝并注冊了外殼擴展的服務(wù)器動(dòng)態(tài)連接庫之后,當選擇一個(gè)或者多個(gè)文件并單擊鼠標右鍵后,在右鍵菜單中就會(huì )多出一個(gè)“執行文件操作”的上下文菜單,點(diǎn)擊菜單就會(huì )彈出相應的程序執行文件操作。
在整個(gè)程序的編寫(xiě)中,外殼擴展的服務(wù)器動(dòng)態(tài)連接庫是有Delphi4.0編寫(xiě)的,而動(dòng)態(tài)連接庫調用的文件操作程序是由VB6編寫(xiě)的。下面首先介紹服務(wù)器動(dòng)態(tài)連接庫的編寫(xiě):
服務(wù)器動(dòng)態(tài)連接庫的工程文件內容如下:
library contextmenu;
uses
ComServ,
ContextMenuHandler in 'Unit2.pas';
// contmenu_TLB in 'contmenu_TLB.pas';
exports
DllGetClassObject,
DllCanUnloadNow,
DllRegisterServer,
DllUnregisterServer;
{$R *.TLB}
{$R *.RES}
begin
end.
將工程文件保存為contextmenu.dpr。
服務(wù)器動(dòng)態(tài)連接庫的單位文件內容如下:
unit ContextMenuHandler;
interface
uses Windows,ActiveX,ComObj,ShlObj,Classes;
type
TContextMenu = class(TComObject,IShellExtInit,IContextMenu)
private
FFileName: array[0..MAX_PATH] of Char;
protected
function IShellExtInit.Initialize =SEIInitialize; // Avoid compiler warning
function SEIInitialize(pidlFolder: PItemIDList;lpdobj: IDataObject;
hKeyProgID: HKEY): HResult; stdcall;
function QueryContextMenu(Menu: HMENU;indexMenu, idCmdFirst, idCmdLast,
uFlags: UINT): HResult; stdcall;
function InvokeCommand(var lpici:TCMInvokeCommandInfo): HResult; stdcall;
function GetCommandString(idCmd, uType: UINT;pwReserved: PUINT;
pszName: LPSTR; cchMax: UINT): HResult; stdcall;
end;
const
Class_ContextMenu: TGUID ='{19741013-C829-11D1-8233-0020AF3E
{全局唯一標識符(GUID)是一個(gè)16字節(128為)的值,它唯一地標識一個(gè)接口(interface)}
var
FileList:TStringList;
Buffer:array[1..1024]of char;
implementation
uses ComServ, SysUtils, ShellApi, Registry,UnitForm;
function TContextMenu.SEIInitialize(pidlFolder:PItemIDList; lpdobj: IDataObject;
hKeyProgID: HKEY): HResult;
var
StgMedium: TStgMedium;
FormatEtc: TFormatEtc;
FileNumber,i:Integer;
begin
file://如果lpdobj等于Nil,則本調用失敗
if (lpdobj = nil) then begin
Result := E_INVALIDARG;
Exit;
end;
file://首先初始化并清空FileList以添加文件
FileList:=TStringList.Create;
FileList.Clear;
file://初始化剪貼版格式文件
with FormatEtc do begin
cfFormat := CF_HDROP;
ptd := nil;
dwAspect := DVASPECT_CONTENT;
lindex := -1;
tymed := TYMED_HGLOBAL;
end;
Result := lpdobj.GetData(FormatEtc, StgMedium);
if Failed(Result) then Exit;
file://首先查詢(xún)用戶(hù)選中的文件的個(gè)數
FileNumber := DragQueryFile(StgMedium.hGlobal,$FFFFFFFF,nil,0);
file://循環(huán)讀取,將所有用戶(hù)選中的文件保存到FileList中
for i:=0 to FileNumber-1 do begin
DragQueryFile(StgMedium.hGlobal, i, FFileName,SizeOf(FFileName));
FileList.Add(FFileName);
Result := NOERROR;
end;
ReleaseStgMedium(StgMedium);
end;
function TContextMenu.QueryContextMenu(Menu: HMENU;indexMenu, idCmdFirst,
idCmdLast, uFlags: UINT): HResult;
begin
Result := 0;
if ((uFlags and $)= CMF_NORMAL) or
((uFlags and CMF_EXPLORE) <> 0) then begin
// 往Context Menu中加入一個(gè)菜單項
InsertMenu(Menu, indexMenu, MF_STRING or MF_BYPOSITION,idCmdFirst,
PChar('執行文件操作'));
// 返回增加菜單項的個(gè)數
Result := 1;
end;
end;
function TContextMenu.InvokeCommand(var lpici:TCMInvokeCommandInfo): HResult;
var
// sFile:TFileStream;
charSavePath:array[0..1023]of char;
sSaveFile:String;
i:Integer;
F: TextFile;
FirstLine: string;
begin
// 首先確定該過(guò)程是被系統而不是被一個(gè)程序所調用
if (HiWord(Integer(lpici.lpVerb)) <> 0) then
begin
Result := E_FAIL;
Exit;
end;
// 確定傳遞的參數的有效性
if (LoWord(lpici.lpVerb) <> 0) then begin
Result := E_INVALIDARG;
Exit;
end;
file://建立一個(gè)臨時(shí)文件保存用戶(hù)選中的文件名
GetTempPath(1024,charSavePath);
sSaveFile:=charSavePath+'chen0001.tmp';
AssignFile(F,sSaveFile); { nextfile in Files property }
ReWrite(F);
file://將文件名保存到臨時(shí)文件中
for i:= 0 to FileList.Count -1 do begin
FirstLine:=FileList.Strings[i];
Writeln(F,FirstLine); { Readthe first line out of the file }
end;
CloseFile(F);
file://調用文件操作程序對用戶(hù)選中的文件進(jìn)行操作
ShellExecute(0,nil,'c:\FileOP.exe',PChar(sSaveFile),charSavePath,SW_NORMAL);
Result := NOERROR;
end;
function TContextMenu.GetCommandString(idCmd, uType: UINT;pwReserved: PUINT;
pszName: LPSTR; cchMax: UINT):HRESULT;
begin
if (idCmd = 0) then begin
if (uType = GCS_HELPTEXT) then
{返回該菜單項的幫助信息,此幫助信息將在用戶(hù)把鼠標移動(dòng)到該菜單項時(shí)出現在狀態(tài)條上。}
StrCopy(pszName, PChar('點(diǎn)擊該菜單項將執行文件操作'));
Result := NOERROR;
end
else
Result := E_INVALIDARG;
end;
type
TContextMenuFactory = class(TComObjectFactory)
public
procedure UpdateRegistry(Register: Boolean); override;
end;
procedure TContextMenuFactory.UpdateRegistry(Register:Boolean);
var
ClassID: string;
begin
if Register then begin
inherited UpdateRegistry(Register);
ClassID := GUIDToString(Class_ContextMenu);
CreateRegKey('*\shellex', '', '');
CreateRegKey('*\shellex\ContextMenuHandlers','', '');
CreateRegKey('*\shellex\ContextMenuHandlers\OpenWithWordPad', '', ClassID);
file://如果操作系統為WindowsNT的話(huà)
if (Win32Platform = VER_PLATFORM_WIN32_NT) then
with TRegistry.Create do
try
RootKey := HKEY_LOCAL_MACHINE;
OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions', True);
OpenKey('Approved', True);
WriteString(ClassID, 'ContextMenu Shell Extension');
finally
Free;
end;
end
else begin
DeleteRegKey('*\shellex\ContextMenuHandlers\FileOpreation');
DeleteRegKey('*\shellex\ContextMenuHandlers');
// DeleteRegKey('*\shellex');
inherited UpdateRegistry(Register);
end;
end;
initialization
TContextMenuFactory.Create(ComServer, TContextMenu, Class_ContextMenu,
'', 'Context Menu Shell Extension', ciMultiInstance,tmApartment);
end.
將該單位文件保存為unit2.pas,文件同contextmenu.dpr位于同一個(gè)目錄下。
打開(kāi)Delphi,選菜單中的 file | open project 打開(kāi)contextmenu.dpr文件,然后選 Project | build contextmenu菜單項編譯連接程序,如果編譯成功的話(huà),會(huì )建立一個(gè)contextmenu.dll的動(dòng)態(tài)連接庫文件,這個(gè)文件就是服務(wù)器動(dòng)態(tài)連接庫。
下面來(lái)建立文件操作程序。打開(kāi)VB,建立一個(gè)新的工程文件,在Form1中加入一個(gè)ListBox控件和三個(gè)CommandButton控件,將ListBox的MultiSelect屬性設置為2。然后在Form1的代碼窗口中加入以下代碼:
Option Explicit
Private Type BrowseInfo
hwndOwner As Long
pIDLRoot As Long
pszDisplayName As Long
lpszTitle As Long
ulFlags As Long
lpfnCallback As Long
lParam As Long
iImage As Long
End Type
Private Type SHFILEOPSTRUCT
hwnd As Long
wFunc As Long '對文件的操作指令
pFrom As String '源文件或路徑
pTo As String '目的文件或路徑
fFlags As Integer '操作標志
fAnyOperationsAborted As Long
hNameMappings As Long
lpszProgressTitle As String
End Type
Const FO_COPY = &H2
Const FO_DELETE = &H3
Const FO_MOVE = &H1
Const FO_RENAME = &H4
Const FOF_ALLOWUNDO = &H40
Const BIF_RETURNONLYFSDIRS = 1
Const MAX_PATH = 260
Private Declare Function ShellAbout Lib"shell32.dll" Alias _
"ShellAboutA" (ByVal hwndAs Long, ByVal szApp As _
String, ByVal szOtherStuff AsString, ByVal hIcon As Long) _
As Long
Private Declare Sub CoTaskMemFree Lib "ole32.dll" (ByVal hMem AsLong)
Private Declare Function lstrcat Lib "kernel32" Alias _
"lstrcatA" (ByVallpString1 As String, ByVal lpString2 _
As String) As Long
Private Declare Function SHBrowseForFolder Lib "shell32" (lpbi _
As BrowseInfo) As Long
Private Declare Function SHGetPathFromIDList Lib "shell32" _
(ByVal pidList As Long, ByVallpBuffer As String) As Long
Private Declare Function SHFileOperation Lib "shell32" _
(lpFileOp As SHFILEOPSTRUCT) As Long
Private Declare Function GetWindowsDirectory _
Lib "kernel32" Alias"GetWindowsDirectoryA" _
(ByVal lpBuffer As String, ByValnSize As _
Long) As Long
Dim DirString As String
Dim sFile As String
Sub UpdateList()
'UpdateList函數檢查列表框中的文件是否存在,如果不存在,就將其
'從文件列表中刪除
Dim bEndList As Boolean
Dim i As Integer
bEndList = True
i = 0
While bEndList
'檢查文件是否存在,如果不存在就刪除
If Dir$(List1.List(i)) ="" Then
List1.RemoveItem (i)
Else '如果文件存在就轉移到下一個(gè)列表項
i = i + 1
If i >List1.ListCount - 1 Then
bEndList = False
End If
End If
Wend
Command1.Enabled = False
Command2.Enabled = False
Command3.Enabled = False
End Sub
Function BrowseForFolder(hwndOwner As Long, sPrompt AsString) As String
Dim iNull As Integer
Dim lpIDList As Long
Dim lResult As Long
Dim sPath As String
Dim udtBI As BrowseInfo
'初試化udtBI結構
With udtBI
.hwndOwner = hwndOwner
.lpszTitle = lstrcat(sPrompt,"")
.ulFlags = BIF_RETURNONLYFSDIRS
End With
'彈出文件夾查看窗口
lpIDList = SHBrowseForFolder(udtBI)
If lpIDList Then
sPath = String$(MAX_PATH, 0)
lResult =SHGetPathFromIDList(lpIDList, sPath)
Call CoTaskMemFree(lpIDList)
iNull = InStr(sPath, vbNullChar)
If iNull Then sPath = Left$(sPath,iNull - 1)
End If
BrowseForFolder = sPath
End Function
Private Sub Command1_Click() '執行文件拷貝操作
Dim sPath As String
Dim tCopy As SHFILEOPSTRUCT
Dim i As Integer
'選擇拷貝到的文件夾
sPath = BrowseForFolder(Form1.hwnd, "選擇拷貝到的文件夾")
If sPath <> "" Then
With tCopy
.hwnd =Form1.hwnd
.lpszProgressTitle= "正在拷貝"
.pTo = sPath
.fFlags =FOF_ALLOWUNDO
.wFunc =FO_COPY
End With
For i = 0 To List1.ListCount - 1
IfList1.Selected(i) Then '如果文件被選中則拷貝文件
tCopy.pFrom = List1.List(i)
SHFileOperation tCopy
End If
Next i
UpdateList
End If
Kill sFile
End Sub
Private Sub Command2_Click() '執行文件移動(dòng)操作
Dim sPath As String
Dim tCopy As SHFILEOPSTRUCT
Dim i As Integer
'選擇移動(dòng)到的文件夾
sPath = BrowseForFolder(Form1.hwnd, "選擇轉移到的文件夾")
If sPath <> "" Then
With tCopy
.hwnd =Form1.hwnd
.lpszProgressTitle = "正在移動(dòng)"
.pTo = sPath
.fFlags =FOF_ALLOWUNDO
.wFunc =FO_MOVE
End With
For i = 0 To List1.ListCount - 1
IfList1.Selected(i) Then '如果文件被選中則拷貝文件
tCopy.pFrom = List1.List(i)
SHFileOperation tCopy
End If
Next i
UpdateList
End If
Kill sFile
End Sub
Private Sub Command3_Click() '執行文件刪除操作
Dim sPath As String
Dim tCopy As SHFILEOPSTRUCT
Dim i As Integer
With tCopy
.hwnd = Form1.hwnd
.lpszProgressTitle = "正在刪除"
.pTo = sPath
.fFlags = FOF_ALLOWUNDO
.wFunc = FO_DELETE
End With
For i = 0 To List1.ListCount - 1
If List1.Selected(i) Then
tCopy.pFrom= List1.List(i)
SHFileOperation tCopy
End If
Next i
UpdateList
Kill sFile
End Sub
Private Sub Form_Load()
Dim hFileHandle As Long
Dim TextLine As String
Command1.Caption = "拷貝"
Command2.Caption = "移動(dòng)"
Command3.Caption = "刪除"
Command1.Enabled = False
Command2.Enabled = False
Command3.Enabled = False
'sFile接受由Windows外殼擴展庫contextmenu.dll傳遞過(guò)來(lái)的文件參數
sFile = Command$
hFileHandle = FreeFile
Open sFile For Input As hFileHandle
Do While Not EOF(hFileHandle)
Line Input #1, TextLine
If Dir$(TextLine) <>"" Then
List1.AddItem TextLine
End If
Loop
Close hFileHandle
End Sub
Private Sub Form_Unload(Cancel As Integer)
If Dir$(sFile) <> "" Then
Kill sFile
End If
End Sub
Private Sub List1_Click()
If Not Command1.Enabled Then
Command1.Enabled = True
Command2.Enabled = True
Command3.Enabled = True
End If
End Sub
保存文件并將工程文件編譯為FileOP.exe文件,將文件拷貝到C盤(pán)根目錄下。然后注冊contextmenu.dll,注冊的方法是,在DOS窗口中進(jìn)入Windows\system子目錄,輸入 Regsvr32 x:\xxxxx\contextmenu.dll 。其中x:\xxxxx\為Contextmenu.dll文件所在的驅動(dòng)器和目錄。如果注冊成功,系統會(huì )彈出對話(huà)框,顯示 DllRegisterServer in ..\xxx\contextmenu.dll Success 提示注冊成功。
注冊成功后,再選擇文件并單擊右鍵,就會(huì )發(fā)現在彈出菜單中多了一個(gè)“執行文件操作”的菜單項,點(diǎn)擊該項,系統就會(huì )調用FileOP.exe執行文件操作,在窗口的列表框中會(huì )出現用戶(hù)選擇的文件名,點(diǎn)擊相應的文件并點(diǎn)擊“拷貝”、“移動(dòng)”或“刪除”按鈕就可以對列表框中的選中的文件進(jìn)行相應的操作。
由 ATL 想起的外殼擴展編程(一)
作者/李曉飛
下載源代碼
好久沒(méi)有給VC知識庫發(fā)稿了,實(shí)在不好意思,由于前段時(shí)間實(shí)在太忙所以一直沒(méi)有時(shí)間閑下心來(lái)寫(xiě)點(diǎn)東西,期間也有不少朋友給我來(lái)信討論問(wèn)題,我很感謝大家對我的支持,我歡迎大家繼續來(lái)信,共同交流,共同進(jìn)步!這次我想和大家一起討論一下 Windows 的 Shell 擴展編程,首先在閱讀以下內容之前我還是推薦大家看一下《COM技術(shù)內幕》這本大作,不過(guò)即使您沒(méi)有有關(guān)的基礎知識其實(shí)也是無(wú)所謂的,因為以下講解是傻瓜式講解。




| 處理器類(lèi)型 | 何時(shí)觸發(fā) | 所做處理 |
| Context menu 處理器 | 當用戶(hù)鼠標右擊文件或文件夾時(shí)觸發(fā)。但是在Shell V4.71+中,用戶(hù)在文件夾目錄的空白處點(diǎn)擊鼠標右鍵也會(huì )觸發(fā)該事件。 | 加入上下文菜單項。 |
| Property sheet 處理器 | 當用戶(hù)鼠標右擊文件,選擇文件"屬性"菜單彈出文件屬性對話(huà)框時(shí)觸發(fā)。 | 加入用戶(hù)自定義屬性頁(yè)。 |
| Drag and drop 處理器 | 當用戶(hù)在文件夾或桌面中用鼠標右鍵Drag/Drop文件或文件夾時(shí)觸發(fā)。 | 加入上下文菜單項。 |
| Drop處理器 | 當某一數據對象被Drag Over/Dropped Into某一文件時(shí)觸發(fā)。 | 加入任何用戶(hù)自定義動(dòng)作。 |
| QueryInfo 處理器(Shell V4.71+) | 當用戶(hù)鼠標滑過(guò)某一個(gè)文件或某一Shell對象時(shí)觸發(fā)。 | 加入用戶(hù)自定義提示信息(ToolTips)。 |
也許有人會(huì )問(wèn)我實(shí)現它們困難嗎?答案是:比較簡(jiǎn)單。實(shí)現它是不是必須得去看那些枯燥乏味的ATL模板類(lèi),或者生硬死板的 MFC 宏定義呢?答案是否定的。也許以上的問(wèn)題阻礙了大多數COM初學(xué)者的學(xué)習欲望,其實(shí)我剛接觸ATL時(shí)多的是迷惘,常常抱怨 ATL 的知識太深奧,MFC的構架太生硬,一般我是不太喜歡用#define來(lái)定義程序的全部(請參閱effective C++)。言歸正傳,我們再回到今天的話(huà)題上來(lái),那么為實(shí)現 圖1.1 所示功能可以通過(guò)哪些途徑呢?答案有二,第一:注冊表編程。第二:Shell Extension COM編程。通過(guò)注冊表方式實(shí)現其實(shí)十分簡(jiǎn)單,請參閱COM 組件注冊表實(shí)現,在這里本文不做重復介紹,再者也不是本文的主題所在。在以下的內容中我會(huì )以第一類(lèi)Shell 擴展編程---" Context Menu 處理器" 為例來(lái)講解 Handler 的實(shí)現過(guò)程。










3. HRESULT Initialize(
4. LPCITEMIDLIST pidlFolder,
5. LPDATAOBJECT lpdobj,
6. HKEY hkeyProgID
);
在 Initialize 函數中,我們要做的事情就是獲取用戶(hù)鼠標右鍵點(diǎn)擊的文件名稱(chēng),但是有可能用戶(hù)選擇了多個(gè)文件,這里為了簡(jiǎn)單起見(jiàn)我們僅獲取文件列表中的第一個(gè)文件。在這里我們得補充一點(diǎn)內容:當用戶(hù)在一個(gè)擁有 WS_EX_ACCEPTFILES 風(fēng)格的窗體中Drag/Drop 文件時(shí)這些文件名會(huì )以同一種格式存儲,而且文件完整路徑的獲取也都以DragQueryFile API函數來(lái)實(shí)現。但是 DragQueryFile 需要傳入一個(gè) HDROP 句柄,該句柄即為 Drag/Drop 文件名稱(chēng)列表數據句柄(開(kāi)始存放數據的內存區域首指針)。而 HDROP 句柄的可以通過(guò)接口 " DATAOBJECTlpdobj" 的成員函數"GetData" 來(lái)獲取。以下為獲取第一個(gè)Drag/Drop 文件的完整文件路徑的具體代碼:
//數據存儲格式
FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };//數據存儲內存句柄(常用于IDataObject和IAdviseSink接口的數據傳輸操作)
STGMEDIUM stg = { TYMED_HGLOBAL };if(FAILED(pDataObj->GetData(&fmt, &stg)))
{//如果獲取數據內存句柄失敗則返回E_INVALIDARG,
//返回E_INVALIDARG則Explorer不會(huì )再調用我們的Shell擴展接口
return E_INVALIDARG;
}
//獲取實(shí)際數據內存句柄
HDROP hDrop = (HDROP)GlobalLock(stg.hGlobal);
if(NULL==hDrop)
{//在COM程序中養成良好的檢錯習慣是很重要的?。?!
return E_INVALIDARG;
}
//獲取用戶(hù)Drag/Drop的文件數目
int nDropCount = ::DragQueryFile((HDROP)stg.hGlobal,
0xFFFFFFFF, NULL, 0);
//本示例程序僅獲取第一個(gè)Drag/Drop文件完整路徑
//以下注釋代碼為獲取所有文件完整路徑的實(shí)現代碼:
//for(int i = 0; i < nDropCount; ++i){//循環(huán)獲取每個(gè)Drag/Drop文件的完整文件名
// ::DragQueryFile((HDROP)stg.hGlobal, i, m_pzDropFile, MAX_PATH);
//}
//如果用戶(hù)Drag/Drop的文件數目不為一個(gè)則不予處理
if(1==nDropCount)
{//pzDropFile為組件類(lèi)內部的private變量
//它用來(lái)保存用戶(hù)Drag/Drop的文件完整文件名
memset(m_pzDropFile, 0x0, MAX_PATH*sizeof(TCHAR));
::DragQueryFile((HDROP)stg.hGlobal, 0, m_pzDropFile, MAX_PATH);
}
//釋放內存句柄
::ReleaseStgMedium(&mdmSTG);
至此IShellExtInit 接口已經(jīng)完全實(shí)現,從此我們也可以看出進(jìn)程內組件編程的一些特點(diǎn),大體總結如下:"新建自己的接口,然后繼承某些接口,最后一一實(shí)現這些接口的所有虛成員函數或加入自己的成員函數,最后就是組件的注冊"?!? IContextMenu 接口 :該接口和 "Context Menu 處理器" 一一對應,說(shuō)到此我們也順便說(shuō)一下 Shell 擴展接口編程中和(表一)中所列處理器各自對應的COM接口:
(表二)
| 處理器類(lèi)型 | COM 接口 |
| Context menu 處理器 | IContextMenu |
| Property sheet 處理器 | IShellPropSheetExt |
| Drag and drop 處理器 | IContextMenu |
| Drop 處理器 | IDropTarget |
| QueryInfo 處理器(Shell V4.71+) | IQueryInfo |
其中 "Drag and drop 處理器" 的除了 COM 接口IContextMenu 實(shí)現外還得需要注冊表的特殊注冊才可以實(shí)現。其中 IContextMenu 接口有三個(gè)虛成員函數需要我們的組件來(lái)實(shí)現,其函數原型分別如下:
HRESULT QueryContextMenu(
HMENU hmenu,
UINT indexMenu,
UINT idCmdFirst,
UINT idCmdLast,
UINT uFlags
);
注:在QueryContextMenu成員函數中我們可以加入自己的菜單項,插入菜單項其實(shí)很簡(jiǎn)單,我們可以通過(guò) InsertMenu API 函數來(lái)實(shí)現,如下代碼所示:
::InsertMenu(hmenu, indexMenu, MF_STRING | MF_BYPOSITION,
idCmdFirst, IDM_REG_MNU_TXT);
QueryContextMenu 的處理過(guò)程十分簡(jiǎn)單,在這里無(wú)須多說(shuō)。
HRESULT GetCommandString(
UINT idCmd,
UINT uFlags,
UINT *pwReserved,
LPSTR pszName,
UINT cchMax
);
注:GetCommandString成員函數為 Explorer提供了在狀態(tài)欄顯示菜單命令提示信息的方法。在這個(gè)方法中 "LPSTR pszName" 是我們要關(guān)注的參數,我們只要根據 "UINT uFlags" 參數來(lái)填充 "LPSTR pszName" 參數即可。在這里可能會(huì )涉及到 ANSI 和 UNICODE 之間相互轉換的知識,不過(guò)在這里我要提醒大家的是:在 COM 編程中盡可能使用兼容的 TCHAR 類(lèi)型,同時(shí)對字符操作也盡量不要使用 C 類(lèi)的 <string.h> 和<stdio.h> 等等函數庫,因為這樣會(huì )使您無(wú)法通過(guò) "Win32Release Mindependency " 或其他 UINCode/Release 版本的編譯過(guò)程。
HRESULT InvokeCommand(
LPCMINVOKECOMMANDINFO pici
);
InvokeCommand 函數實(shí)現最終菜單項命令的執行。在"LPCMINVOKECOMMANDINFO pici" 參數中包含了當前用戶(hù)執行的菜單項ID和其他一些標志信息,如下代碼可獲取菜單項的ID:
//如果 nFlag 不為0則說(shuō)明 pici->lpVerb 指向一個(gè)以''\0''結尾的字符串
int nFlag = HIWORD(pici->lpVerb);
//用戶(hù)當前點(diǎn)擊的菜單項ID
int nMnuId = LOWORD(lpici->lpVerb);
一旦獲取了菜單項ID那么我們就可以根據不同的菜單項來(lái)執行相應的動(dòng)作,如圖1.2 所示的 "Register Component" 和 "UnRegister Component" 菜單項所對應的 "注冊/反注冊進(jìn)程內組件" 動(dòng)作。
8. //聲明組件注冊所用的注冊表REG資源
9. //其中IDR_SIMPLESHLEXT為注冊表資源ID
10.DECLARE_REGISTRY_RESOURCEID(IDR_SIMPLESHLEXT)
11.//AddRef和Release成員函數的實(shí)現
12.DECLARE_PROTECT_FINAL_CONSTRUCT()
13.//組件接口映射部分,該部分映射主要是告訴QueryInterface能返回哪些接口給外部
14.BEGIN_COM_MAP(CSimpleShlExt)
15. COM_INTERFACE_ENTRY(ISimpleShlExt)
16. COM_INTERFACE_ENTRY(IDispatch)
17. COM_INTERFACE_ENTRY(IShellExtInit) //IShellExtInit接口
18. COM_INTERFACE_ENTRY(IContextMenu) //IContextMenu接口
END_COM_MAP()
o HKCR
o {o NoRemove dllfile
o {o NoRemove ShellEx
o {o NoRemove ContextMenuHandlers
o {o //類(lèi)的GUID字符串
o ForceRemove SimpleShlExt = s''{ - 訪(fǎng)問(wèn)許可權的注冊則在DllRegisterServer DLL輸出函數中完成。
其實(shí)現只需要使用注冊表的Win32 API函數即可;相應的反注冊組件時(shí)則應在 DllUnregisterServer中刪除相應的注冊表鍵值即可。
好了,"Context Menu 處理器" 的實(shí)現到此完畢,還是老規矩如有問(wèn)題請直接來(lái)信,我期待大家的來(lái)信,同時(shí)在以后的時(shí)間里我會(huì )繼續討論Shell擴展編程,并會(huì )對我以前的文章大家所存在的疑點(diǎn)做出合適的回答,再次感謝大家的支持和鼓勵
聯(lián)系客服