Глава 27
Использование возможностей Shell API
Разобравшись с механизмами СОМ, вам наверняка захочется "испытать радость общения" с объектами, имеющимися в составе ОС. Microsoft уверенно идет к тому, чтобы все составные части своих операционных систем, как и прочих продуктов, превратить в СОМ-объекты. В этом направлении сделаны большие шаги, и оболочка Windows и ее файловая система предоставляют интерфейсы СОМ. В Windows 2000, судя по заверениям представителей фирмы, все новые возможности представлены и доступны в виде интерфейсов.
В качестве примера работы с интерфейсом shellLink вместе с Delphi поставляется приложение Virtual ListView. Но, во-первых, в нем безо всякого документирования вводятся достаточно сложные структуры и интерфейсы; во-вторых, оно содержит только минимум функций для работы с объектами. В этой главе мы постараемся объяснить применяемые там приемы.
Интерфейсы функций и СОМ-объектов Shell содержатся в модулях shellapi . раs и shlobj. pas, которые имеются в поставке Delphi.
Понятие пространства имен
Необходимость как-то упорядочить все те сущности, с которыми имеет дело современная ОС, всегда вставала перед разработчиками. Довольно успешный подход к этому реализован в Windows 95 и NT 4. Вооружившись идеями объектного подхода, в Microsoft разбили интерфейс ОС на две части: средства поддержки пространства имен и средства его просмотра.
Под пространством имен оболочки (Shell Namespace) мы будем понимать иерархически упорядоченную совокупность имен всех объектов, которые могут быть просмотрены через средство просмотра — файлы, устройства памяти, принтеры, сетевые ресурсы. В этой совокупности могут встречаться как реально существующие объекты (папки файловой системы), так и виртуальные объекты (папки Принтеры, Мои компьютер и т. п.). Типовым средством просмотра пространства имен является Explorer (Проводник), но можно заменить его на другое средство, в том числе собственноручно разработанное.
Обе составные части являются совокупностями СОМ-объектов, они обладают полиморфизмом и легко расширяемы. Об использовании этих объектов и функций API оболочки ОС и пойдет речь в данной главе.
Размещение значка приложения в области System Tray
Часто программисту приходится сталкиваться с задачей написания приложения, работающего в фоновом режиме и не нуждающегося в месте на Панели задач. Если вы посмотрите на правый нижний угол рабочего стола Windows, то наверняка найдете там приложения, для которых эта проблема решена: часы, переключатель раскладок клавиатуры, регулятор громкости и т. п. Ясно, что, как бы вы ни увеличивали и ни уменьшали формы своего приложения, попасть туда обычным путем не удастся. Способ для этого предоставляет Shell API.
Те картинки, которые находятся на System Tray — это действительно просто картинки, а не свернутые окна. Они управляются и располагаются панелью System Tray. Она же берет на себя еще две функции: показ подсказки для каждого из значков и оповещение приложения, создавшего значок, обо всех перемещениях мыши над ним.
Весь API SystemTray состоит из 1 (одной) функции:
function Shell_NotifyIcon(dwMessage: DWORD; IpData: PNotifyIconData):
BOOL;
PNotifyIconData = TNotifyIconData;
TNotifyIconData = record
cbSize: DWORD;
Wnd: HWND;
uID: UINT;
uFlags: UINT;
uCallbackMessage: UINT;
hicon: HICON;
szTip: array [0..63] of AnsiChar;
end;
Параметр dwMessage определяет одну из операций: nim_add означает добавление значка в область, nim_delete — удаление, nim_modify — изменение. Ход операции зависит от того, какие поля структуры TNotifyIconData будут заполнены.
Обязательным для заполнения является поле cbsize — там содержится размер структуры. Поле wnd должно содержать дескриптор окна, которое будет оповещаться о событиях, связанных со значком. Идентификатор сообщения
Windows, которое вы хотите получать от системы о перемещениях мыши над значком, запишите в поле uCallbackMessage. Если вы хотите, чтобы при этих перемещениях над вашим значком показывалась подсказка, то задайте ее текст в поле szTip. В поле uid задается номер значка — каждое приложение может поместить на System Tray сколько угодно значков. Дальнейшие операции вы будете производить, задавая этот номер. Дескриптор помещаемого значка должен быть задан в поле hicon. Здесь вы можете задать значок, связанный с вашим приложением, или загрузить свой — из ресурсов.
Изменить главный значок приложения можно в диалоговом окне Project/Options, на странице Application. Он будет доступен через свойство Application. Icon. Тут же можно отредактировать и строку для подсказки — свойство Application. Title.
Наконец, в поле uFlags вы должны сообщить системе, что именно вы от нее хотите, или, другими словами, какие из полей hicon, uCallbackMessage и szTip вы на самом деле заполнили. В этом поле предусмотрена комбинация трех флагов: nif_icon, nif_message и nif_tip. Вы можете заполнить, скажем, поле szTip, но если вы при этом не установили флаг nif_tip, созданный вами значок не будет иметь строки с подсказкой.
Два приведенных ниже метода иллюстрируют сказанное. Первый из них создает значок на System Tray, а второй — уничтожает его.
const WM_MYTRAYNOTIFY = WM_USER + 123;
procedure TFormI.CreateTrayIcon(n:Integer) ;
var nidata : TNotifyIconData;
begin with nidata do begin cbSize := SizeOf(TNotifyIconData);
Wnd := Self.Handle;
uID := n;
uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP;
uCallBackMessage := WM_MYTRAYNOTIFY;
hicon := Application.Icon.Handle;
szTip := 'THis is Traylcon Example';
end;
Shell_NotifyIcon(NIM_ADD, enidata) ;
end;
procedure TForml.DeleteTrayIcon(n:Integer);
var nidata : TNotifyIconData;
begin with nidata do begin cbSize := SizeOf(TNotifyIconData);
Wnd := Self.Handle;
uID := n;
end;
Shell_NotifyIcon(NIMJDELETE, @nidata);
end;
He забывайте уничтожать созданные вами значки на System Tray. Это не делается автоматически, даже при закрытии приложения. Значок будет удален только после перезагрузки системы.
Внешний вид значка, помещенного нами на System Tray, ничем не отличается от значков других приложений.
Сообщение, задаваемое в поле uCailbackMessage, по сути дела является единственной ниточкой, связывающей вас со значком после его создания. Оно объединяет в себе несколько сообщений. Когда к вам пришло такое сообщение (в примере, рассмотренном выше, оно имеет идентификатор wm_mytraynotify), поля в переданной в обработчик структуре типа TMеssаgе распределены так: параметр wparam содержит номер значка (тот самый, что задавался в поле uid при его создании), а параметр Lparam — идентификатор сообщения от мыши, вроде wm__mousemove, wm_lbuttondown и т. п. К сожалению, остальная информация из этих сообщений теряется. Координаты мыши в момент события придется узнать, вызвав функцию API
GetCursorPos:
procedure TFormI.WMICON(var msg: TMessage) ;
var P : TPoint;
begin
case msg.LParam of WM_LBUTTONDOWN:
begin
GetCursorPos(p) ;
SetForegroundWindow(Application.MainForm.Handle) ;
PopupMenul.Popup(P.X, P. Y) ;
end;
WM_LBUTTONUP :
end;
end;
Обратите внимание, что при показе всплывающего меню недостаточно просто вызвать метод popup. При этом нужно вынести главную форму приложения на передний план, в противном случае она не получит сообщений от меню.
Теперь решим еще две задачи. Во-первых, как сделать, чтобы приложение минимизировалось не на Панель задач (TaskBar), а на System Tray? И более того — как сразу запустить его в минимизированном виде, а показывать главную форму только по наступлении определенного события (приходу почты, наступлению определенного времени и т. п.)?
Ответ на первый вопрос прост. Если минимизировать не только окно главной формы приложения (Application. MainForm. Handle), но и окно приложения (Application. Handle), то приложение полностью исчезнет "с экранов радаров". В этот самый момент нужно создать значок на панели System Tray. В его всплывающем меню должен быть пункт, при выборе которого оба окна восстанавливаются, а значок удаляется.
Чтобы приложение запустилось сразу в минимизированном виде и без главной формы, следует к вышесказанному добавить установку свойства Application. showMainForm в False. Здесь возникает одна сложность, если главная форма создавалась в невидимом состоянии, ее компоненты будут также созданы невидимыми. Поэтому при первом ее показе установим их свойство visible в значение True. Чтобы не повторять это дважды, установим флаг — глобальную переменную shownOnce:
procedure TFormI.HideMainForm;
begin
Application.ShowMainForm :=- False;
ShowWindow(Application.Handle, SW_HIDE);
ShowWindow(Application.MainForm.Handle, SW_HIDE);
end;
procedure TForml.RestoreMainForm;
var i,j : Integer;
begin
Application.ShowMainForm := True;
ShowWindow(Application.Handle, SW_RESTORE);
ShowWindow(Application.MainForm.Handle, SW_RESTORE);
if not ShownOnce then begin
for I := 0 to Application.MainForm.ComponentCount -1 do
if Application.MainForm.Components[I] is TWinControl then with Application.MainForm.Components[I] as TWinControl do
if Visible then
begin
ShowWindow(Handle, SW_SHOWDEFAULT);
for J := 0 to ComponentCount -1 do if Components[J] is TWinControl then
ShowWindow((Components[J] as TWinControl).Handle, SW_SHOWDEFAULT) ;
end;
ShownOnce := True;
end;
end;
procedure TForml.WMSYSCOMMAND(var msg: TMessage);
begin inherited;
if (Msg.wParam=SC_MINIMIZE) then
begin HideMainForm;
CreateTrayIcon(l);
end;
end;
procedure TForrol.FileOpenItemlClick(Sender: TObject);
begin
RestoreMainForm;
DeleteTrayIcon(l);
end;
Теперь у вас в руках полноценный набор средств для работы с System Tray.
В заключение необходимо добавить, что все описанное реализуется не в операционной системе, а в оболочке ОС — Проводнике (Explorer). В принципе, и Windows NT 4/2000, и Windows 95/98 допускают замену оболочки ОС на другие, например, DashBoard или LightStep. Там функции System Tray могут быть не реализованы или реализованы через другие API. Впрочем, случаи замены оболочки достаточно редки.
Интерфейс IShellLink
Этот интерфейс представляет собой средство для создания и управления ярлыками (shortcuts). Все читатели этой главы наверняка создавали и перемещали ярлыки для наиболее нужных программ, файлов и папок — на рабочем столе, в главном меню и так далее. С точки зрения ОС эти действия — не что иное, как создание и изменение свойств СОМ-объекта.
Каждый ярлык содержит следующую информацию:
Для всех этих свойств ярлыка в интерфейсе дано по паре методов — один для чтения, другой для установки значения:
IShellLink = interface(lUnknown) ( s1 )
[SID_IShellLinkA] function GetPathfpszFile: PAnsiChar;
cchMaxPath: Integer;
var pfd: TWin32FindData;
fFlags: DWORD): HResult; stdcall;
function GetIDList(var ppidi: PItemIDList): HResult; stdcall;
function SetIDList(pidi: PItemIDList): HResult; stdcall;
function GetDescription(pszName: PAnsiChar; cchMaxName: Integer):
HResult; stdcall;
function SetDescriptionfpszNaitie: PAnsiChar): HResult; stdcall;
function GetWorkingDirectory(pszDir: PAnsiChar;
cchMaxPath: Integer):
HResult; stdcall;
function SetWorkingDirectory(pszDir: PAnsiChar): HResult; stdcall;
function GetArguments(pszArgs: PAnsiChar; cchMaxPath: Integer): HResult; stdcall;
function SetArguments(pszArgs: PAnsiChar): HResult; stdcall;
function GetHotkey(var pwHotkey: Word): HResult; stdcall;
function SetHotkey(wHotkey: Word): HResult; stdcall;
function GetShowCmdfout piShowCmd: Integer): HResult; stdcall;
function SetShowCmdfiShouQnd: Integer): HResult; stdcall;
function GetIconLocation(pszIconPath: PAnsiChar; cchIconPath:
Integer;
out piIcon: Integer): HResult; stdcall;
function SetIconLocationfpszIconPath: PAnsiChar; ilcon: Integer):
HResult; stdcall;
function SetRelativePath(pszPathRel: PAnsiChar; dwReserved: DWORD):
HResult; stdcall;
function Resolve(Wnd: HWND; fFlags: DWORD): HResult; stdcall;
function SetPathfpszFile: PAnsiChar): HResult; stdcall;
end;
Сохраним ярлык для данной программы-примера где-нибудь на диске, скажем, в той же самой папке. Для этого создадим новый объект NewLink класса CLSiD_sheliLink, предоставляющий нам нужный интерфейс:
procedure TForml.ButtonlClick(Sender: TObject);
var NewLink : IShellLink;
fn, fp : string;
ws : WideString;
hRes : THandle;
pf : IPersistFile;
begin NewLink := CreateComObject(CLSID_ShellLink) as IShellLink;
fn := ParamStr(0);
NewLink.SetPath(pchar(fn));
fp := ExtractFilePath(fn);
NewLink.SetWorkingDirectory(pchar(fp));
NewLink.SetDescription(pChar
(Application.Title));
ws := fp+Application.Title+'.Ink';
hRes := NewLink.Querylnterface(IID IPersistFile, pf);
if Succeeded(hRes) then pf.Save(pWideChar(ws),False);
end;
В этом примере, помимо IShellLink, нужно получить доступ к интерфейсу IPersistFile, который умеет записывать данные. Задав параметры ярлыка, мы записываем его на диск. При этом проверяется тот факт, что созданный нами объект поддерживает интерфейс IPersistFile. Если указатель на этот интерфейс получен, вызывается его метод save.
Среди перечисленных выше методов IShellLink, особое внимание уделим методу Resolve. Он понадобится при получении указателя на интерфейс уже существующих ярлыков. Windows пытается вести себя "разумно" и отслеживает перемещения и переименования объекта, на который указывает существующий IShellLink. Но если вы записали содержимое ярлыка в поток (или на диск), то отследить соответствие ярлыка объекту должны сами, вызвав метод Resolve. Если объект, на который ссылается ярлык, по-прежнему находится на своем месте, метод немедленно завершается с нормальным кодом возврата. Если файл или объект перемещен или переименован, начинается его поиск (рис. 27.1).
Рис. 27.1. Поиск объекта, на который указывает ярлык
Знакомая картина, не правда ли? Особенно часто она наблюдается в том случае, если пользователь не выработал у себя привычки правильно деинсталлировать раздобытый где-то "софт", стирая его "по старинке". Между тем, за привычным диалоговым окном на рисунке стоит вызов метода ishellLink. Resolve, Если в пределах досягаемости поиска окажется файл с тем же именем и размерами, ярлык будет автоматически переадресован на него; в противном случае пользователю будет предложено использовать ближайший по характеристикам файл из просмотренных. Если вы вообще не хотите, чтобы пользователь вмешивался в процесс отыскания соответствия, при вызове метода Resolve в параметре fFlags укажите значение slr_no_ui — диалоговое окно появляться в этом случае не будет.
Если вы внимательно изучили рабочий стол своего компьютера, то должны были заметить там ярлыки, ссылающиеся не на файлы, а на специальные объекты — "Мой компьютер", "Сетевое окружение", "Принтеры" и т. п. Чтобы создать такой ярлык самому, нужно обращение к методу SetIDList. В качестве параметра ему передается структура PItemiDList (pidi). О том, где ее взять и как заполнить, рассказано в следующем разделе.
Интерфейс IShell Folder
Этот интерфейс соответствует папке — одному из основных элементов пространства имен Проводника. Зачем было вводить термин "папка", когда существовали уже общепринятые "каталог" и "директория"? В отличие от последних двух, папка может быть не просто обычным элементом файловой системы. Она может быть виртуальной — как папки "принтеры", "документы" или "панель управления". Любая папка может содержать коллекцию объектов из состава пространства имен.
Получив указатель на интерфейс isheiiFoider, соответствующий папке, вы можете работать с ней, как с объектом СОМ. "Верхушкой" (корневой папкой) пространства имен является папка Рабочий стол (Desktop). Получить интерфейс isheiiFoider этой папки можно путем вызова функции:
function SHGetDesktopFolder(var ppshf: IShellFolder): HResult;
Логика работы с описываемым интерфейсом такова: сначала нужно получить интерфейс нужной папки, а затем можно переходить к работе с ее содержимым. Содержимое представляет собой список, а каждый элемент папки представлен структурой PitemiDList. Эта структура не типизирована; ее единственное обязательное поле содержит длину в байтах, зная которую можно переместиться к следующему элементу. То есть, получается обычная цепочка. Все остальные поля заполняются соответствующими функциями и методами интерфейса isheiiFoider.
Все служебные функции работы со структурами PitemiDList — создание, уничтожение, копирование, перемещение по цепочке и т. п. — содержатся в примере Virtual ListView, поставляемом с Delphi. Если вы намерены писать программы,. работающие с isheiiFoider, целесообразно взять их на заметку. В дальнейшем для простоты эти структуры будем именовать pidl.
Рассмотрим функции интерфейса isheiiFoider. Под "текущей папкой" в табл. 27.1 понимается та папка, которая в данный момент представляет интерфейс isheiiFoider.
Таблица 27.1. Функции интерфейса isheiiFoider
Метод |
Описание |
Function ParseDisplay-Name(hwndOwner: HWND; pbcReserved: Pointer; IpszDisplayName: POLESTR; out pchEaten: ULONG; out ppidi: PitemiDList; var dwAttributes: ULONG): HResult; |
Эта функция позволяет получить указатель на элемент ppidi, зная только его полное имя (с путем) IpszDisplayName
|
function EnumObjects(hwndOwner: HWND; grfFlags: DWORD; out Enu-mIDList: lEnumIDList): HResult; function BindToObject(pidl: PitemiDList; pbcReserved: Pointer; const riid: TIID; out ppvOut : Pointer): HResult; |
Возвращает указатель на специальный интерфейс lenumlDList, предназначенный для организации цикла по всем элементам списка в текущей папке Возвращает интерфейс папки pidl, которая должна находиться в текущей папке (на которую ссылается интерфейс, вызвавший этот метод) |
function CompareIDs(IParam: LPARAM; pidll, pidl2: PItemIDList): HResult; |
Сравнивает два первых элемента в списках pidll и pidl2 |
function CreateViewOb-ject(hwndOwner: HWND; const riid: TIID; out ppvOut: Pointer): HResult; |
Создает визуальный объект для текущей папки и возвращает указатель на него в параметре ppvOut |
function GetAttributesOf(cidi: UINT; var apidi: PItemIDList; var rgfInOut: UINT): HResult; |
Возвращает атрибуты элемента под номером cidi в списке apidi. Результат — набор флагов, устанавливаемых в параметре rgfInOut |
function GetUIObjectOf(hwndOwner: HWND; cidi: UINT; var apidi: PItemIDList; const riid: TIID; prgfInOut: Pointer; out ppvOut: Pointer): HResult; |
Создает объект пользовательского интерфейса, связанный с элементом списка aplidi под номером cidi |
function GetDisplayNameOf(pidi: PItemIDList,- uFlags: DWORD; var IpName: TStrRet): HResult; |
Возвращает имя элемента pidi. Полнота возвращаемой информации определяется параметром uFlags |
function SetNameOf(hwndOwner: HWND; pidi: PItemIDList; IpszName: POLEStr; uFlags: DWORD; var ppid-lOut: PItemIDList): HResult; |
Задает новое имя IpszName для списка pidi. При этом возвращается новый указатель на список — ppidlOut |
Два метода — ParseDisplayName И GetDisplayNameOf — взаимно дополняют
друг друга. Первый из них нужен, если вы имеете указатель на isheilFoider и хотите связать его с конкретной папкой. На практике это сводится к задаче в три действия:
function SHGetSpecialFolderLocation(hwndOwner: HWND; nFolder: Integer;
var ppidi: PItemIDList): HResult;
В параметре nFolder вы задаете константу, соответствующую выбранной специальной папке. На выходе будет указатель на элемент ppidi, соответствующий этой папке.
Во многих функциях Shell API и методах его интерфейсов встречается параметр hwndOwner. Он должен задавать дескриптор окна на тот случай, если придется выводить диалоговое окно или окно с сообщением об ошибке.
Возможные значения параметра nFolder перечислены в табл. 27.2. В комментариях к ним "виртуальная" папка является особым объектом, который предоставляется пользователю при помощи Shell API. Просто "папка" реально существует где-то в файловой системе.
Таблица 27.2. Константы, определяющие специальные папки
Значение |
Комментарий |
CSIDLBITBUCKET |
Корзина (Recycle bin) — специальная папка для удаленных файлов. Пути к Recycle bin нет в системном реестре во избежание перемещения или удаления, и его не узнать иным методом |
CSIDL CONTROLS |
Панель инструментов (Control Panel) — виртуальная папка, содержащая значки апплетов Панели инструментов |
CSIDLJ3ESKTOP |
Виртуальная папка Рабочий стол (desktop), корневая в пространстве имен |
CSIDL DESKTOPDIRECTORY |
Папка файловой системы, реально содержащая объекты рабочего стола |
CSIDL DRIVES |
Виртуальная папка Мой компьютер (My Computer), содержащая элементы для всех накопителей на компьютере подключенных сетевых устройств, папки Принтеры, Панель инструментов, Удаленный доступ к сети |
CSIDLFONTS |
Виртуальная папка Шрифты |
CSIDLNETHOOD |
Папка, содержащая объекты сетевого окружения |
CSIDL NETWORK |
Виртуальная папка Сетевое окружение (Network Neighborhood) |
CSIDL PERSONAL |
Папка Мои документы |
CSIDL PRINTERS |
Виртуальная папка Принтеры (Printers) |
CSIDLPROGRAMS |
Папка Программы из Главного меню, содержащая папки установленных на компьютере программ |
CSIDLRECENT |
Папка, содержащая ссылки на последние использовавшиеся документы (Recent) |
CSIDL SENDTO |
Папка, содержащая элементы контекстного меню Send To... |
3SIDLSTARTMENU |
Папка, содержащая элементы главного меню Пуск (Start) |
CSIDL STARTUP |
Папка, содержащая элементы меню Автозапуск (Startup) |
CSIDL TEMPLATES |
Папка, содержащая шаблоны типовых документов |
Третий вариант получить pidi нужной папки — интерактивный, с помощью функции Shell API. function SHBrowseForFolder(var Ipbi: TBrowseInfo): PItemIDList;
Перед ее вызовом следует заполнить структуру типа TBrowseInfo, содержащую в частности pidi того элемента, который будет корневым. После вызова функции пользователь увидит перед собой диалоговое окно выбора папки (рис. 27.2).
Рис. 27.2. Диалоговое окно выбора папки, созданное при вызове функции ShBrowseForFolder
В данном примере корневой служит виртуальная папка Мой компьютер. Пользователю предоставляется возможность выбрать одну из папок файловой системы (за это отвечает флаг TBrowseinfo.uiFiags, равный
BIF_RETURNONLYFSDIRS).
На выходе функция возвращает pidl папки, имя которой извлекается из него вызовом еще одной функции Shell — ShGetPathFromList.
procedure TFoml.ButtonlClickfSender: TObject);
var
BI : TBrowseInfo;
Image : integer;
StartPIDL, ResPIDL : PItemIDEist;
S, Path : Array[0..max_path-l] Of WideChar;
begin
OleCheckfSHGetSpecialFolderLocation(Handle, CSIDLJ3RIVES, StartPIDL) );
With BI do Begin
hwndOwner = Application.Handle;
pszDisplayName = @S;
IpszTitle = 'Выберите необходимую папку';
ulFlags = BIF_RETURNONLYFSDIRS;
pidlRoot = StartPIDL;
Ipfn = nil;
ilmage = 1;
end;
ResPIDL := SHBrowseForFolder(BI);
if SHGETPathFromIDList(ResPIDL, @Patht[0]) then Labell.Caption := StrPas(@Path[0]) ;
end;
Полученное имя здесь отображается при помощи компонента Labell.
Memol.Clear;
try 01eCheck( SHGetDesktopFolder(DeskTop) );
if not Succeeded; DeskTop.ParseDisplayName(Self.Handle,nil, StringToWideChar ( Editl.Text,ws, MAX_PATH),n, pidi, attr) ) then begin
ShowMessage('Неизвестное имя');
Exit;
end;
OleCheckf DeskTop.BindToObject( pidi,nil, IID_IShellFolder, Pointer(NewShellFolder))) ;
0ldCheck( NewShellFolder.EnumObjects( Self.Handle, SHCONTF_FOLDERS or SHCONTF_MONFOLDERS, Enumerator )) ;
while Enumerator.Next(1, pidi, Numpidls) = S_OK do begin NewShellFolder.GetDisplayNameOf(PIDL, SHGDN_FORPARSING, StrRet);
case StrRet.uType of STRRET_CSTR:
s := StrRet.cStr;
STRRET_OFFSET:
begin
P := @PIDL.mkid.abID[StrRet.uOffset - SizeOf(PIDL.mkid.cb)];
SetString(s, P, PIDL.mkid.cb — StrRet.uOffset);
end;
STRRET_WSTR':
s := StrRet.pOleStr;
end;//case
Memol.Lines.Add(s);
end;
except on E:E01eSysError do ShowMessage(' ');
end;
В этом примере имя нужной папки извлекается из компонента Edit1. Получив IshellFolder и затем IEnumiDList, программа заполняет полученными именами файлов список Memol. Lines.
Помимо названия, из большинства объектов файловой системы можно "вытащить" массу полезной информации. Чаще всего задаются вопросом: а как извлечь значок, соответствующий данному файлу, или хранящийся в нем?
Способов для достижения этой цели несколько. Самый простой — через вызов функции:
function SHGetFiieInfo(pszPath: PAnsiChar; dwFileAttributea: DWORD; var psfi: TSHFileInfo; cbFileInfo, uFiags: UINT): DWORD;
Параметр pszpath может быть указателем как на строку с именем файла, так и на структуру вида pidl. Функция заполняет структуру psfi (тип TSHFileInfo) длиной cbFileInfo байт. В зависимости от значения слова флагов (параметр uF,lags), на выходе может быть разнообразная информация. В частности, если в параметре uFiags заданы значения shgfi_sysiconindex и shgfi_icon, то в структуру psfi будет записан номер значка для данного файла в системном списке изображений, а результатом выполнения функции будет дескриптор этого списка. Воспользоваться им можно (например, для панели инструментов) так:
procedure TFormI. FomCreate (Sender: TObj ect) ;
var
Filelnfo: TSHFileInfo;
ImageLi s tHandle: THandle ;
begin
ImageListHandle := SHGetFileInfo('С:\', 0,
Filelnfo,
SizeOf(Filelnfo), SHGFI_SYSICONINDEX or SHGFI_ICON);
SendMessage(ToolBarl.Handle, TB_SETIMAGELIST, 0, ImageListHandle);
end;
Точно так же можно извлечь значок, соответствующий конкретному файлу. В составе Shell есть другие функции, созданные для извлечения значков:
Добавление пунктов в системное контекстное меню
Вы обращали внимание на то, что некоторые приложения после установки добавляют в системное контекстное меню свои собственные пункты? Так поступают многие архиваторы, антивирусные средства и другие утилиты. Эта возможность предоставляется оболочкой Windows.
Когда пользователь щелкает правой кнопкой мыши на любом объекте в пространстве имен, система создает контекстное меню из двух частей: стандартного меню для объектов данного типа и пунктов меню, добавляемых зарегистрированными обработчиками. Зарегистрированные обработчики — это СОМ-серверы, запускаемые в адресном пространстве процесса (inprocess servers) и реализованные в виде динамических библиотек.
Ваш СОМ-объект, который расширяет системное контекстное меню, должен поддерживать как минимум два интерфейса — IShellExtinit и IContextMenu. Существуют и два новых. интерфейса — icontextMenu2 и iContextMenu3, но они вносят в логику работы контекстных меню лишь небольшие дополнения и здесь рассмотрены не будут. Интерфейс isheiiExtinit отвечает за инициализацию меню, IContextMenu — за выполнение основных функций.
Методы интерфейса IContextMenu приведены в табл. 27.3.
Таблица 27.3. Методы интерфейса IConvextMenu
Метод |
Описание |
unction GetCornitiandString(idCmd, uType: UINT; pwReserved: POINT; PszName: LPSTR; cchMax: UINT): HResult; stdcall; |
Возвращает описание добавленного пункта меню (подсказку или полное название) |
function QueryContextMenu (Menu: HMENU; indexMenu, idCmdFirst, idCmdLast, uFlags: UINT): HRe5ult; stdcall; |
Добавляет пункт к системному контекстному меню |
function InvokeCommand(var Ipici: TCMInvokeCommandInfo): HResult; stdcall; |
Осуществляет вызов обработчика |
Рассмотрим их подробнее. Параметры метода QueryContextMenu означают следующее:
Для иллюстрации объектов — расширений контекстного меню выберем пример ContMenu (поставляется с Delphi в папке demos\activex\shellext). В этом примере для объектов типа "проект Delphi" добавляется возможность запуска компилятора в командной строке. При вызове метода QueryContextMenu нужный пункт добавляется с помощью функции
InsertMenu:
function TContextMenu.QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst,
idCmdLast, uFiags: UINT): HResult;
begin
Result := 0; /I or use MakeResult(SEVERITY_SUCCESS, FACILITY_NULL, 0) ;
if ((uFiags and $OOOOOOOF) = CMF_NORMAL) or
((uFiags and CMFJSXPLORE) <> 0) then
begin
// Add one menu item to context menu
InsertMenu(Menu, indexMenu, MF_STRING or MF_BYPOSITION, idCmdFirst, 'Compile...');
Result := 1; // or use MakeResult(SEVERITY_SUCCESS, FACILITY_NULL, 1) end;
end;
Метод GetCommandString предоставляет системе данные о пункте меню, в частности, текст подсказки; эта подсказка будет отображаться в строке состояния Проводника, когда курсор находится в нужном месте меню.
Параметры GetCommandString просты. Первый, idCmd, соответствует идентификатору пункта меню, второй — uType — запрос на тип информации (gcs_helptext — текст подсказки, gcs_verb — полное название пункта меню). Наконец, параметры pszName и ссhМах задают буфер, в который будут копироваться текстовые данные. Полное название необходимо системе, что-
бы с его помощью вызывать предусмотренные в пункте действия программно. В примере ContMenu возврат названия (то есть обработка запроса gcs_verb) не предусмотрена, а в ответ на запрос gcs_helptext возвращается текстовая строка 'Compile the selected Delphi project'.
Наиболее сложным является метод InvokeComroand. Он вызывается при выборе пользователем вставленного вами пункта меню. По сути дела, метод invokecoromand представляет собой прямой аналог, обработчика onclick обычных пунктов меню (объектов TMenuitem) в Delphi.
Единственным параметром метода является структура типа TCMinvokeCommandinfo, поля которой имеют такое предназначение:
Отдельно следует остановиться на описании параметра ipVerb. Как уже говорилось, он может представлять из себя как идентификатор пункта меню, так и его текст — строку, заканчивающуюся нулем. Чтобы выяснить это, нужно проверить старшее слово этого 32-разрядного параметра на равенство нулю. В примере ContMenu вызов по тексту не предусмотрен:
if (HiWord(Integer(Ipici.IpVerb)) <> 0) then
begin
Exit;
end;
Для создания расширения контекстного меню мы должны породить объект, поддерживающий эти интерфейсы. К сожалению, мастера, предусмотрен-
ные в Delphi, не позволяют в автоматизированном режиме создавать объек ты, реализующие уже существующие интерфейсы. Поэтому и описание, и реализацию методов придется делать "по старинке", вручную. В примере ContMenu описание объекта таково:
TContextMenu = class(TComObjact, IShellExtInit, IContextMenu) private
FFileName: array[0..MAX_PATH] of Char;
protected
{ IShellExtInit }
function IShellExtInit.Initialize = SEIInitialize; // Avoid compiler warning
function SEIInitialize(pidlFolder: PItemIDList; Ipdobj: IDataObject;
hKeyProgID: HKEY): HResult; stdcall;
{ IContextMenu }
function QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst, idCmdLast,
uFlags: UINT): HResult; stdcall;
function InvokeCommand (var Ipici: TCMInvokeCoitimandInfo) : HResult;
stdcall;
function GetCoinmandString(idCmd, uType: UINT; pwReserved: PUINT;
pszName: LPSTR; cchMax: UINT): HResult; stdcall;
end;
Вас может насторожить конструкция, описывающая переименование метода initialize интерфейса IShellExtInit. На самом деле одноименный метод имеется у объекта TComObject, и приведенный синтаксис как раз и предназначен для выхода из подобных ситуаций.
Последняя часть работы — регистрация созданного обработчика. Самое подходящее место для этого — метод updateRegistry фабрики класса. Разработчики примера ContMenu породили класс TContextMenuFactory, который при регистрации СОМ-сервера регистрирует создаваемые фабрикой объекты:
ClassID := GUIDToString(Class_ContextMenu) ;
CreateRegKey('DelphiProject\shellex', '', '');
CreateRegKey('DelphiProject\shellex
\ContextMenuHandlers', '', '') ;
CreateRegKey('DeIphiProject\she 11 ex\ContextMenuHandlers\ContMenu', '', ClassID);
Пример ContMenu иллюстрирует "дельфийский" подход к созданию серверов СОМ, через соответствующие объекты из иерархии объектов Delphi. Но в папке shellext вы найдете еще один пример создания расширения для контекстного меню, сделанный целиком — и только — с использованием
интерфейсов и функций СОМ. Присмотритесь к этому примеру внимательнее, если хотите глубже понимать внутреннюю структуру СОМ-объектов.
Резюме
Несколько тем, затронутых в этой главе, могут дать лишь начальные представления о принципах работы с оболочкой Windows. Вы можете изучить составляющие ее объекты практически сколь угодно глубоко — были бы потребность да желание. Разумеется, чтобы не "наломать дров", перед этим надо отдать себе отчет в полном и правильном понимании механизмов СОМ. Надеемся, эта глава, вместе с предьщущей — " Основы и реализация СОМ в Delphi 5" и последующей — "Использование ActiveX", поможет вам в этом.