Extension du shell - Menu contextuel
Date de publication : 24/02/2005
Par
Pierre Castelain (Contributions)
Tutoriel sur l'ajout d'éléments dans le menu contextuel du shell de Windows en utilisant la technique des extensions du shell
I. Introduction
II. Principe
III. Mise en oeuvre
I. Introduction
Ce petit tutoriel est une reproduction d'une réponse faite directement dans un fil de discussion du forum
Delphi de
developpez.com. Il a pour but de montrer comment
ajouter un ou plusieurs éléments de menu dans le menu contextuel du shell de Windows. Cette méthode est
plus complexe que le simple ajout de commandes dans la ruche correspondant au type de fichier
dans la base de registre, mais elle présente de nombreux avantages. En particulier la possibilité
de tester les fichiers sélectionnés avant l'ouverture du menu ainsi que celle de modifier le texte
de l'item de menu en fonction des fichiers sélectionnés.
II. Principe
Une extension du shell est un objet COM qui implémente certaines interfaces spécifiques
(IShellExtInit et IContextMenu). Pour que cet objet soit utilisé par le shell, il faut
bien sur que la dll qui le contient soit enregistrée (regsvr32 ou directement depuis
Delphi par le menu Exécuter->Recenser le serveur ActiveX). De plus, il faut que
l'extension soit également inscrite dans la base de registre (voir la méthode
UpdateRegistry).
Lorsque l'utilisateur sélectionne des fichiers et fait un clic droit, le shell instancie
un objet de notre classe et appelle les méthodes suivantes:
- Initialize : qui permet d'obtenir le nom des fichiers sélectionnés (entre autres),
- QueryContextMenu : qui permet à notre objet d'ajouter ces items de menu dans le menu contextuel,
- GetCommandString : appelée pour obtenir une chaîne de description de notre extension,
- InvokeCommand : quand l'utilisateur clique sur un de nos items.
III. Mise en oeuvre
Voila pour la théorie, maintenant un peu de pratique.
Il faut commencer par créer une bibliothèque ActiveX (menu Fichier->Nouveau->ActiveX). Nous
allons l'appeler ShellExtTest.
Ensuite, il nous faut un objet COM (menu Fichier->Nouveau->ActiveX) que nous appellerons
ShellExtTester (Delphi va automatiquement ajouter un I devant le nom de l'interface).
La nouvelle unité créée sera enregistrée sous le nom ShellExtTesterImpl.
Il ne reste plus qu'à recopier le code suivant (légèrement commenté):
unit ShellExtTesterImpl;
interface
uses
Windows, ActiveX, Classes, ComObj, ShellExtTest_TLB, StdVcl,
ShlObj;
type
TShellExtTester = class(TTypedComObject, IShellExtTester, IShellExtInit,
IContextMenu)
private
FFilenames: array of PChar;
protected
function IShellExtInit.Initialize = ShellInit;
function ShellInit(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;
public
destructor Destroy; override;
end;
TShellExtTesterFactory = class(TComObjectFactory)
public
procedure UpdateRegistry(Register: Boolean); override;
end;
implementation
uses
ComServ, ShellAPI, SysUtils, Registry, Dialogs;
destructor TShellExtTester.Destroy;
var
i: integer;
begin
for i:=0 to Length(FFilenames) - 1 do
StrDispose(FFilenames[i]);
inherited;
end;
function TShellExtTester.GetCommandString(idCmd, uType: UINT; pwReserved: PUINT;
pszName: LPSTR; cchMax: UINT): HResult;
begin
if uType = GCS_HELPTEXT then
begin
StrLCopy(pszName, 'Un test d''extension du shell', cchMax);
result:= NOERROR;
end
else
result:= E_INVALIDARG;
end;
function TShellExtTester.InvokeCommand(
var lpici: TCMInvokeCommandInfo): HResult;
var
i: integer;
s: string;
begin
if (HiWord(Integer(lpici.lpVerb)) <> 0) then
begin
result := E_FAIL;
Exit;
end;
if Length(FFileNames) > 0 then
begin
s:= '';
for i:=0 to Length(FFilenames)-1 do
s:= s + FFilenames[i] + #13#10;
MessageDlg(s, mtInformation, [mbOK], 0);
end;
result := NOERROR;
end;
function TShellExtTester.QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst,
idCmdLast, uFlags: UINT): HResult;
begin
if (uFlags and CMF_DEFAULTONLY)=0 then
begin
InsertMenu(Menu, IndexMenu, MF_STRING or MF_BYPOSITION, idCmdFirst,
'Tester l''extension du shell');
result:= 1;
end
else
result:= NOERROR;
end;
function TShellExtTester.ShellInit(pidlFolder: PItemIDList; lpdobj: IDataObject;
hKeyProgID: HKEY): HResult;
var
StgMedium: TStgMedium;
FormatEtc: TFormatEtc;
filesnum, i: integer;
filename: PChar;
nameLength: UInt;
begin
if lpdobj = nil then
begin
Result := E_INVALIDARG;
Exit;
end;
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;
try
filesnum:= DragQueryFile(StgMedium.hGlobal, $FFFFFFFF, nil, 0);
if filesnum > 0 then
begin
SetLength(FFileNames, filesnum);
for i:= 0 to filesnum-1 do
begin
nameLength:= DragQueryFile(StgMedium.hGlobal, i, nil, 0) + 1;
filename:= StrAlloc(nameLength);
DragQueryFile(StgMedium.hGlobal, i, filename, nameLength);
FFilenames[i]:= filename;
Result := NOERROR;
end;
end
else
begin
SetLength(FFileNames, 0);
Result := E_FAIL;
end;
finally
ReleaseStgMedium(StgMedium);
end;
end;
procedure TShellExtTesterFactory.UpdateRegistry(Register: Boolean);
var
ClassID: string;
DocumentName: string;
begin
if Register then
begin
inherited UpdateRegistry(Register);
ClassID := GUIDToString(Class_ShellExtTester);
with TRegistry.Create do
try
RootKey:= HKEY_CLASSES_ROOT;
if OpenKey('.txt', false) then
DocumentName:= ReadString('');
finally
Free;
end;
CreateRegKey(DocumentName+'\shellex', '', '');
CreateRegKey(DocumentName+'\shellex\ContextMenuHandlers', '', '');
CreateRegKey(DocumentName+'\shellex\ContextMenuHandlers\ShellExtTester', '', ClassID);
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, 'ShellExtTester Context Menu Extension');
finally
Free;
end;
end
else
begin
DeleteRegKey(DocumentName+'\shellex\ContextMenuHandlers\ShellExtTester');
inherited UpdateRegistry(Register);
end;
end;
initialization
TShellExtTesterFactory.Create(ComServer, TShellExtTester, Class_ShellExtTester,
'ShellExtTester', 'Test d''extension du shell', ciMultiInstance, tmApartment);
end.
Voila, c'est fini. Il ne reste plus qu'à compiler et à recenser le serveur
(par le menu exécuter ou par RegScr32).
Attention, une fois recensé vous ne pourrez pas recompiler si le shell est ouvert,
c'est à dire si une fenêtre de l'explorateur est ouverte.
|