Explorer Context Menu
Опубликовано 31.05.2010 г. 23:27
Никто из вас не задумывался как в своем приложении реализовать контекстное меню, которое мы видим кликнув правой кнопкой на файле (папке, диске, и т.п.) в проводнике? А я вот недавно задумался, причем решить данную задачу с первого подхода не получилось (:
Зачем может понадобится такое меню в вашем приложении? В большинстве случаев наверное и не надо, но почему бы, если вы показываете пользователю список каких либо файлов, не дать ему возможность использовать привычное меню кликнув на файл? Для того чтобы осуществить вызов такого меню нам потребуется объект IContextMenu (ShlObj.pas). Этот интерфейс в свою очередь мы можем получить от объекта IShellFolder, представляющего родительскую директорию, в которой расположен интересующий нас объект, представленный структурой PItemIdList. Если быть точным, то PItemIDList содержит список структур описывающих различные Shell-объекты (TShItemID). Хотя поскольку не являюсь знатоком Shell-программирования, то до точности здесь далеко. Далее мы создадим меню с помощью функции CreatePopupMenu(), и отобразим его. Теперь нам понадобится узнать, какое действие выбрал пользователь, и воспроизвести его. Вообще во всей этой цепочке мне показались странными две вещи, первая - почему меню мы не можем получить прямо от объекта описывающего файл, почему мы должны получать его от родительской папки? И второе - почему после того как мы отобразили меню, оно само не выполняет нужные действия. В некоторых ситуациях мне не требуется никак обрабатывать эти нажатия пунктов меню. Итого, наш алгоритм выходит таков: при нажатии на кнопку формы, будем выбирать файл с помощью диалога, а потом показывать контекстное меню для него и выполнять полученную команду. Более детально:
- Сначала сохраним позицию клика мыши, где отобразить меню, чтобы диалог выбора файла нам ее не менял.
- С помощью TOpenDialog выберем файл - targetFile.
- Получим PItemIDList для выбранного файла - pidl
- Получим интерфейс IShellFolder для родительской папки - sh
- Получим относительный PItemIDList для нашего файла - pidlChild (относительно родительской папки)
- Получим интерфейc IContextMenu от родителя для файла - iMenu, с помощью функции GetUIObjectOf
- создадим дескриптор меню menu: HMenu с помощью CreatePopupMenu()
- попросим iMenu выдать нам элементы контекстного меню в наше hMenu.
- Отобразим меню с помощью trackPopupMenu(). Функция может возвращать либо bool Либо LongInt. Если указан флаг TPM_RETURNCMD то будет возвращен номер пункта меню. Также флаги устанавливают позиционирование меню, относительно точки его появления, и анимацию.
- Определим номер пункта меню, если оно было вызвано, если не было, то выходим
- Заполним структуру icInfo : TCMInvokeCommandInfo для вызова команды меню
- Вызовем выбранную команду с помощью метода InvokeCommand()
procedure TMainForm.ShowMenuButtonClick(Sender: TObject); const SCRATCH_QCM_FIRST = 1; SCRATCH_QCM_LAST = $7FFF; var targetFile : string; pidl, pidlChild : PItemIdList; sf : IShellFolder; iMenu : IContextMenu; menu : HMenu; cmdBool : LongBool; cmd : Cardinal; icInfo : TCMInvokeCommandInfo; hr : HResult; mPoint : TPoint; begin mPoint := mouse.CursorPos; with TOpenDialog.Create(self) do begin if not execute() then exit; targetFile := filename; free(); end; SHParseDisplayName(pChar(targetFile), IBindCtx(nil^), pidl, 0, cardinal(nil^)) ; SHBindToParent(pidl, IID_IShellFolder, pointer(sf), pidlChild); sf.GetUIObjectOf(handle, 1, pidlChild, IID_IContextMenu, nil, iMenu); menu := CreatePopupMenu(); iMenu.QueryContextMenu(menu, 0, SCRATCH_QCM_FIRST, SCRATCH_QCM_LAST, CMF_NORMAL); cmdBool := TrackPopupMenu(menu, TPM_RETURNCMD, mPoint.x ,mPoint.y, 0, handle, 0); cmd := LongInt(cmdBool); if not cmdBool then exit; zeroMemory(@icInfo,sizeof(icInfo)); icInfo.cbSize := sizeof(icInfo); icInfo.hwnd := handle; icInfo.nShow := SW_SHOWNORMAL; icInfo.lpVerb := MakeIntResourceA(cmd - SCRATCH_QCM_FIRST); hr := iMenu.InvokeCommand(icInfo); if hr <> S_OK then begin MessageDlg('Failed to invoke command',mtError,[mbOK],0); end; end;Отметим одну немаловажную вещь, один из параметров структуры icInfo - lpVerb является строкой задающей команду к выполнению, как например в функции ShellExecute. Поэтому в принципе мы можем использовать данный подход для отображение окна свойств файла, например: не отображая само меню (т.е пропустив часть TrackPopupMenu()), просто заполнив структуру icInfo с заданным lpVerb - 'properties', и попросим также с помощью invokeCommand ее выполнить. Никогда не делайте такого в действительности, если вам требуется получить окно свойств файла, просто откройте его. Контекстное меню здесь ни при чем. Для этого нам потребуется функция ShellExecuteEx()
procedure TMainForm.PropertiesButtonClick(Sender: TObject); var info : TShellExecuteInfo; begin zeroMemory(@info,sizeof(info)); info.cbSize := sizeof(info); info.Wnd := handle; info.lpVerb := 'properties'; info.lpFile := 'С:\Windows\'; info.fMask := SEE_MASK_INVOKEIDLIST; info.nShow := SW_SHOWNORMAL; ShellExecuteEx(@info); end;По ходу изложения сего рассказа я понял ответ на второй свой второй вопрос, который был мне изначально не понятен - почему мне надо самому обрабатывать команду. IContextMenu не умеет сам показывать меню, для этого мы создаем и используем собственное меню. Т.е мы передаем визуальное управление другому компоненту, который определит какой пункт использовал пользователь. После чего получив обратно код команды, можем выполнить операцию.
01.06.2010 в 02:11
01.06.2010 в 10:53
хотя вообще надо было делать по части №2 (: но как то я до нее не дошел (:
01.06.2010 в 19:00
01.06.2010 в 19:18
24.08.2011 в 16:21
24.08.2011 в 22:25