yoy.be "Why-o-Why"

How to create a shell extension context menu.

2012-11-12 16:41  i3045  delphi  [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.

twitter reddit linkedin facebook