Sources
Delphi Russian Knowledge Base
DRKB - это самая большая и удобная в использовании база знаний по Дельфи в рунете, составленная Виталием Невзоровым

Как зарегистрировать свою команду в контекстном меню проводника?

01.01.2007

Для подобных действий пишется маленький комсервер задача которого лишь реализовать 2 интерфейса IShellExtInit и IContextMenu.
Для чего это делается - операционная система при инициализации меню проверит твою библиотеку на предмет: поддерживает ли она эти интерфейсы и если да - то вызовет нужные их методы. Ну а уж при срабатывании данных методов ты и добавляешь свои пункты меню.
 
Для облегчения отладки, чтобы библиотека выгружалась сразу же как только не используется производим следующие действия:
 
В реестре вот по этому пути HKEY_LOCAL_MASHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer устанавливаем строковое значение AlwaysUnloadDLL равным "1" (если такого значения нет, тогда нужно его создать).
 
Далее пишем код:
 
вот реализация сервера:

 

// Test COM Server Shell Context menu extention
 
library CONTMENU;

 
uses
  ComServ,
  ContextM in 'ContextM.pas';
 
exports
  DllGetClassObject,
  DllCanUnloadNow,
  DllRegisterServer,
  DllUnregisterServer;
 
begin
end.
unit ContextM;

 
interface
 
uses
  Windows, ActiveX, ComObj, ShlObj;
 
type
  TContextMenu = class(TComObject, IShellExtInit, IContextMenu)
  private
    FFileName: array[0..MAX_PATH] of Char;
    TmpFileNames:String;
  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, uType: UINT; pwReserved: PUINT;
      pszName: LPSTR; cchMax: UINT): HResult; stdcall;
  end;
 
resourcestring
  IDC_TEST1 = 'Тестовая строка номер 1';
  IDC_TEST2 = 'Тестовая строка номер 2';
 
const
  Class_ContextMenu: TGUID = '{45C15F61-ACAD-48C6-8D86-321ED8A6CFC6}';
 
implementation
 
uses ComServ, SysUtils, ShellApi, Registry, Graphics;
 
// Тут наше меню инициализируется
// на вход приходит интерфейс IDataObject из которого мы можем получить
// список файлов и папок над которыми будут происходить действия
function TContextMenu.SEIInitialize(pidlFolder: PItemIDList; lpdobj: IDataObject;
  hKeyProgID: HKEY): HResult;
var
  StgMedium: TStgMedium;
  FormatEtc: TFormatEtc;
  FilesCount,I:Integer;
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;
 
  TmpFileNames := '';
  FilesCount := DragQueryFile(StgMedium.hGlobal, $FFFFFFFF, nil, 0);
  for I:= 0 to FilesCount - 1 do
  begin
    DragQueryFile(StgMedium.hGlobal, I, FFileName, SizeOf(FFileName));
    TmpFileNames := TmpFileNames + '"'+FFileName+'" ';
  end;
  Result := NOERROR;
  ReleaseStgMedium(StgMedium);
end;
 
// Создание меню
// по этому событию мы добавляем новые элементы меню...
function TContextMenu.QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst,
          idCmdLast, uFlags: UINT): HResult;
begin
  Result := MakeResult(SEVERITY_SUCCESS, FACILITY_NULL, 0);
 
  if ((uFlags and $0000000F) = CMF_NORMAL) or
     ((uFlags and CMF_EXPLORE) <> 0) then
  begin
    // Разделитель
    InsertMenu(Menu, indexMenu, MF_SEPARATOR or MF_BYPOSITION, 0, nil);
    // первый пункт меню
    InsertMenu(Menu, indexMenu + 1, MF_STRING or MF_BYPOSITION, idCmdFirst,
      PChar(IDC_TEST1));
    // второй пункт меню
    InsertMenu(Menu, indexMenu + 2, MF_STRING or MF_BYPOSITION, idCmdFirst + 1,
      PChar(IDC_TEST2));
    // разделитель
    InsertMenu(Menu, indexMenu + 3, MF_SEPARATOR or MF_BYPOSITION, 0, nil);
    // указываем сколько пунктов меню мы добавили
    // 2 пункта - т.к. разделители не считаются
    Result := MakeResult(SEVERITY_SUCCESS, FACILITY_NULL, 2);
  end;
end;
 
// данная функция срабатывает при нажатии на наш элемент меню
function TContextMenu.InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult;
begin
  Result := E_FAIL;
  if (HiWord(Integer(lpici.lpVerb)) <> 0) then Exit;
  Result := NOERROR;
  // Выбор элементов меню идет по возрастающей в том порядке
  // в каком они были добавлены
  case LoWord(lpici.lpVerb) of
  0: // первый элемент меню
     // тут собственно и нужно делать реакцию на нажатие ;)
    MessageBox(lpici.hWnd, PChar(TmpFileNames), PChar(IDC_TEST1 + ' Pressed'), MB_OK);
  1: // второй элемент меню
    MessageBox(lpici.hWnd, PChar(TmpFileNames), PChar(IDC_TEST2 + ' Pressed'), MB_OK);
  else
    Result := E_INVALIDARG;
  end;
end;
 
// Данная функция вызывается когда статус бар в эксплорере активен
// и в нем отображается краткая информация о подсвеченном пункте меню
function TContextMenu.GetCommandString(idCmd, uType: UINT; pwReserved: PUINT;
  pszName: LPSTR; cchMax: UINT): HRESULT;
begin
  Result := S_OK;
  if uType = GCS_HELPTEXT then
    case idCmd of
      0:
      begin
        StrCopy(pszName, 'Справочная информация по первому пункту меню');
      end;
      1:
      begin
        StrCopy(pszName, 'Справочная информация по второму пункту меню');
      end
      else
        Result := E_INVALIDARG
    end
end;
 
type
  TContextMenuFactory = class(TComObjectFactory)
  public
    procedure UpdateRegistry(Register: Boolean); override;
  end;
 
// Это процедура которая будет выполнятся при вызове библиотеки из командной строки
// regsvr32   C:\CONTMENU.dll  - регистрация библиотеки
// regsvr32   C:\CONTMENU.dll -unregister - снятие библиотеки с регистрации
procedure TContextMenuFactory.UpdateRegistry(Register: Boolean);
var
  ClassID: string;
begin
  if Register then
  begin
    inherited UpdateRegistry(Register);
 
    ClassID := GUIDToString(Class_ContextMenu);
    CreateRegKey('Test\shellex', '', '');
    CreateRegKey('Test\shellex\ContextMenuHandlers', '', '');
    CreateRegKey('Test\shellex\ContextMenuHandlers\ContMenu', '', 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, 'Test Context Menu Shell Extension');
      finally
        Free;
      end;
  end
  else
  begin
    DeleteRegKey('Test\shellex\ContextMenuHandlers\ContMenu');
    DeleteRegKey('Test\shellex\ContextMenuHandlers');
    DeleteRegKey('Test\shellex');
    inherited UpdateRegistry(Register);
  end;
end;
 
initialization
  TContextMenuFactory.Create(ComServer, TContextMenu, Class_ContextMenu,
    '', 'Test Context Menu Shell Extension', ciMultiInstance,
    tmApartment);
end.

 
Вот и все, компилишь этот код и у тебя готовый ком сервер...
Регистрировать билиотеку из своей программы так:

 

// Установка...

 
procedure TForm1.btnRegClick(Sender: TObject);
begin
  with TRegistry.Create do
  try
    RootKey := HKEY_CLASSES_ROOT;
    OpenKey('CLSID\{45C15F61-ACAD-48C6-8D86-321ED8A6CFC6}\InprocServer32', True);
    WriteString('','C:\CONTMENU.dll');
    WriteString('ThreadingModel','Apartment');
    CloseKey;
  finally
    Free;
  end;
 
  with TRegistry.Create do
  try
    RootKey := HKEY_LOCAL_MACHINE;
    OpenKey('SOFTWARE\Classes\CLSID\{45C15F61-ACAD-48C6-8D86-321ED8A6CFC6}\InprocServer32', True);
    WriteString('','C:\CONTMENU.dll');
    WriteString('ThreadingModel','Apartment');
    CloseKey;
  finally
    Free;
  end;
 
  with TRegistry.Create do
  try
    RootKey := HKEY_LOCAL_MACHINE;
    OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved', True);
    WriteString('{45C15F61-ACAD-48C6-8D86-321ED8A6CFC6}', 'Test Context Menu Shell Extension');
    CloseKey;
  finally
    Free;
  end;
 
  with TRegistry.Create do
  try
    RootKey := HKEY_CLASSES_ROOT;
    OpenKey('*\shellex\ContextMenuHandlers\Test', True);
    WriteString('','{45C15F61-ACAD-48C6-8D86-321ED8A6CFC6}');
    CloseKey;
  finally
    Free;
  end;
 
  with TRegistry.Create do
  try
    RootKey := HKEY_CLASSES_ROOT;
    OpenKey('Folder\shellex\ContextMenuHandlers\Test', True);
    WriteString('','{45C15F61-ACAD-48C6-8D86-321ED8A6CFC6}');
    CloseKey;
  finally
    Free;
  end;
end;
 
 
а снимать с регистрации вот так:
 
// Удаление ...
procedure TForm1.btnUnRegClick(Sender: TObject);
begin    
  with TRegistry.Create do
  try
    RootKey := HKEY_CLASSES_ROOT;
    OpenKey('CLSID', True);
    DeleteKey('{45C15F61-ACAD-48C6-8D86-321ED8A6CFC6}');
    CloseKey;
  finally
    Free;
  end;
 
  with TRegistry.Create do
  try
    RootKey := HKEY_LOCAL_MACHINE;
    OpenKey('SOFTWARE\Classes\CLSID', True);
    DeleteKey('{45C15F61-ACAD-48C6-8D86-321ED8A6CFC6}');
    CloseKey;
  finally
    Free;
  end;
 
  with TRegistry.Create do
  try
    RootKey := HKEY_LOCAL_MACHINE;
    OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved', True);
    DeleteValue('{45C15F61-ACAD-48C6-8D86-321ED8A6CFC6}');
    CloseKey;
  finally
    Free;
  end;
 
  with TRegistry.Create do
  try
    RootKey := HKEY_CLASSES_ROOT;
    OpenKey('*\shellex\ContextMenuHandlers', True);
    DeleteKey('Test');
    CloseKey;
  finally
    Free;
  end;
 
  with TRegistry.Create do
  try
    RootKey := HKEY_CLASSES_ROOT;
    OpenKey('Folder\shellex\ContextMenuHandlers', True);
    DeleteKey('Test');
    CloseKey;
  finally
    Free;
  end;
end;

 
Если нужно, чтобы пункты меню возникали только для определенных типов файлов, то при вызове QueryContextMenu нужно проверить какие файлы находятся в TmpFileNames, если данные типы файлов не подходят, то выходить из процедуры с результатом

 

Result := MakeResult(SEVERITY_SUCCESS, FACILITY_NULL, 0);

 

Взято из https://forum.sources.ru

Автор: Rouse_