How to create a shell extension context menu.
2012-11-12 16:41 i3045 [permalink]
I've put it up here, but since it's a pretty good boiler plate code, I'll put it up here as well. Include a unit like this one into an ActiveX library. I especially had a though time to get the values right that are based on idCmdFirst and get sent back when a menu item is invoked. Something that gives pretty strange results if you don't get it right.
unit demoContextMenu; interface uses Windows, Classes, ActiveX, ComObj, ShlObj; type PItemIDList=LPCITEMIDLIST; { TContextMenu } TContextMenu = class(TComObject, IShellExtInit, IContextMenu) private Files:TStringList; protected { IShellExtInit } function IShellExtInit.Initialize = SEIInitialize; function SEIInitialize(pidlFolder: PItemIDList; lpdobj: IDataObject; hKeyProgID: HKEY): HResult; stdcall; { IContextMenu } function QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst, idCmdLast, uFlags: UINT): HResult; stdcall; function InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult; stdcall; function GetCommandString(idCmd: UINT_Ptr; uType: UINT; pwReserved: PUINT; pszName: LPSTR; cchMax: UINT): HResult; stdcall; public procedure Initialize; override; destructor Destroy; override; end; const Class_ContextMenu: TGUID = '{put a new GUID here by pressing Ctrl+Shift+G}'; implementation uses ComServ, SysUtils, Registry; procedure TContextMenu.Initialize; begin inherited; Files:=TStringList.Create; end; destructor TContextMenu.Destroy; begin Files.Free; inherited; end; function TContextMenu.SEIInitialize(pidlFolder: PItemIDList; lpdobj: IDataObject; hKeyProgID: HKEY): HResult; stdcall; var StgMedium: TStgMedium; FormatEtc: TFormatEtc; i,c:integer; s:string; begin if lpdobj=nil then Result:=E_INVALIDARG else begin FormatEtc.cfFormat:=CF_HDROP; FormatEtc.ptd:=nil; FormatEtc.dwAspect:=DVASPECT_CONTENT; FormatEtc.lindex:=-1; FormatEtc.tymed:=TYMED_HGLOBAL; Result:=lpdobj.GetData(FormatEtc,StgMedium); if not(Failed(Result)) then begin c:=DragQueryFile(StgMedium.hGlobal,$FFFFFFFF,nil,0); for i:=0 to c-1 do begin SetLength(s,1024); SetLength(s,DragQueryFile(StgMedium.hGlobal,i,PChar(s),1024)); Files.Add(s); end; ReleaseStgMedium(StgMedium); Result:=NOERROR; end; end; end; function TContextMenu.QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst, idCmdLast, uFlags: UINT): HResult; stdcall; var h:HMENU; i:integer; begin i:=1; h:=CreatePopupMenu; AppendMenu(h,MF_STRING,idCmdFirst+i,'Menu item one'); inc(i); AppendMenu(h,MF_STRING,idCmdFirst+i,'Menu item two'); inc(i); AppendMenu(h,MF_STRING,idCmdFirst+i,'Menu item three'); inc(i); InsertMenu(Menu,indexMenu, MF_BYPOSITION or MF_POPUP or MF_STRING,h,'DemoContextMenu'); Result:=i; end; function TContextMenu.InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult; stdcall; begin Result := E_FAIL; //not called by application if HiWord(Integer(lpici.lpVerb))=0 then begin Result := NOERROR; case LoWord(Integer(lpici.lpVerb)) of 1:;//perform action one (use data in Files:TStringList) 2:;//perform action two 3:;//perform action three else Result := E_INVALIDARG; end; end; end; function TContextMenu.GetCommandString(idCmd: UINT_Ptr; uType: UINT; pwReserved: PUINT; pszName: LPSTR; cchMax: UINT): HResult; stdcall; begin if idCmd=0 then begin if (uType=GCS_HELPTEXTW) then StrCopy(pszName,'Perform one of several functions on files'); 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; r:TRegistry; begin if Register then begin inherited UpdateRegistry(Register); ClassID := GUIDToString(Class_ContextMenu); CreateRegKey('*\shellex', '', ''); CreateRegKey('*\shellex\ContextMenuHandlers', '', ''); CreateRegKey('*\shellex\ContextMenuHandlers\DemoContextMenu', '', ClassID); CreateRegKey('Folder\shellex', '', ''); CreateRegKey('Folder\shellex\ContextMenuHandlers', '', ''); CreateRegKey('Folder\shellex\ContextMenuHandlers\DemoContextMenu', '', ClassID); if Win32Platform=VER_PLATFORM_WIN32_NT then begin r:=TRegistry.Create; try r.RootKey:=HKEY_LOCAL_MACHINE; r.OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions',True); r.OpenKey('Approved',True); r.WriteString(ClassID,'DemoContextMenu Shell Extension'); finally r.Free; end; end; end else begin DeleteRegKey('Folder\shellex\ContextMenuHandlers\DemoContextMenu'); DeleteRegKey('*\shellex\ContextMenuHandlers\DemoContextMenu'); inherited UpdateRegistry(Register); end; end; initialization TContextMenuFactory.Create(ComServer, TContextMenu, Class_ContextMenu, '', 'DemoContextMenu Shell Extension', ciMultiInstance, tmApartment); end.