На этом шаге мы рассмотрим создание собственных окон просмотра данных в Windows.
Windows Explorer позволяет просматривать не только файловую систему, но и данные, которые не
имеют непосредственного отношения к файловой системе. Хороший пример - стандартные приложения
Microsoft: "Панель управления", "Корзина", "Сетевое окружение", "Принтеры".
Доступ к этим приложениям осуществляется посредством выбора соответствующей пиктограммы в Windows Explorer,
при этом в правой нижней части Windows Explorer показывается содержимое соответствующих хранилищ,
которые никакого отношения к реальной файловой системе не имеют.
Для создания подобного приложения необходимо реализовать интерфейс IShellFolder, который описывает пиктограмму в Windows Explorer и интерфейс IShellView, который создает окно для просмотра данных в правом нижнем углу Windows Explorer. Оба этих интерфейса следует помещать во внутренний сервер автоматизации.
Реализация интерфейса IShellFolder приведена ниже:
unit IShlFold; interface uses Windows,ActiveX,CommCtrl,ShellAPI,RegStr,Messages,ComObj, ComServ,ShlObj,Classes,IShlView,Dialogs; const CLSID_CustomShellFolder:TGUID='{BF029401-68CC-11D2-9B02-0000E844A5C5}'; type TCustomShellFolder=class(TComObject,IShellFolder,IPersistFolder) protected function IPersistFolder.Initialize=PersistInitialize; public {IShellFolder} function ParseDisplayName(hwndOwner:HWND;pbcReserved:Pointer; lpszDisplayName:POLESTR; out pchEaten:ULONG; out ppidl:PItemIDList; var dwAttributes:ULONG):HResult;stdcall; function EnumObjects(hwndOwner:HWND;grfFlags:DWORD; out EnumIDList:IEnumIDList):HResult;stdcall; function BindToObject(pidl:PItemIDList; pbcReserved:Pointer;const riid:TIID;out ppvOut):HResult;stdcall; function BindToStorage(pidl:PItemIDList;pbcReserved:Pointer; const riid:TIID;out ppvObj):HResult;stdcall; function CompareIDs(lParam:LPARAM; pidl1,pidl2:PItemIDList):HResult;stdcall; function CreateViewObject(hwndOwner:HWND; const riid:TIID;out ppvOut):HResult;stdcall; function GetAttributesOf(cidl:UINT;var apidl:PItemIDList; var rgfInOut:UINT):HResult;stdcall; function GetUIObjectOf(hwndOwner:HWND;cidl:UINT;var apidl:PItemIDList; const riid:TIID;prgfInOut:Pointer;out ppvOut):HResult;stdcall; function GetDisplayNameOf(pidl:PItemIDList;uFlags:DWORD; var lpName:TStrRet):HResult;stdcall; function SetNameOf(hwndOwner:HWND;pidl:PItemIDList;lpszName:POLEStr; uFlags:DWORD;var ppidlOut:PItemIDList):HResult;stdcall; {Persist} function GetClassID(out classID:TCLSID):HResult;stdcall; function PersistInitialize(pidl:PItemIDList):HResult; virtual;stdcall; end; implementation uses Registry; type TShellFolderObjectFactory=class(TComObjectFactory) public procedure UpdateRegistry(Register:Boolean);override; end; function TCustomShellFolder.ParseDisplayName(hwndOwner:HWND;pbcReserved:Pointer; lpszDisplayName:POLESTR; out pchEaten:ULONG; out ppidl:PItemIDList; var dwAttributes:ULONG):HResult; begin Result:=E_NOTIMPL; end; function TCustomShellFolder.EnumObjects(hwndOwner:HWND;grfFlags:DWORD; out EnumIDList:IEnumIDList):HResult; begin Result:=E_NOTIMPL; end; function TCustomShellFolder.BindToObject(pidl:PItemIDList;pbcReserved:Pointer; const riid:TIID;out ppvOut):HResult; begin Result:=E_NOTIMPL; end; function TCustomShellFolder.BindToStorage(pidl:PItemIDList; pbcReserved:Pointer;const riid:TIID;out ppvObj):HResult; begin Result:=E_NOTIMPL; end; function TCustomShellFolder.CompareIDs(lParam:LPARAM; pidl1,pidl2:PItemIDList):HResult; begin Result:=E_NOTIMPL; end; function TCustomShellFolder.CreateViewObject(hwndOwner:HWND; const riid:TIID;out ppvOut):HResult; var SV:IShellView; begin try Pointer(ppvOut):=nil; if IsEqualGUID(riid,IShellView) then begin try SV:=CreateComObject(CLSID_CustomShellView) as IShellView; except CoInitialize(nil); SV:=CreateComObject(CLSID_CustomShellView) as IShellView; end; if Assigned(SV) then begin SV._AddRef; Pointer(ppvOut):=Pointer(SV); end; Result:=S_OK; end else Result:=E_NOINTERFACE; except on E:EOleSysError do Result:=E.ErrorCode; else Result:=E_UNEXPECTED; end; end; function TCustomShellFolder.GetAttributesOf(cidl:UINT; var apidl:PItemIDList;var rgfInOut:UINT):HResult; begin Result:=E_NOTIMPL; end; function TCustomShellFolder.GetUIObjectOf(hwndOwner:HWND;cidl:UINT; var apidl:PItemIDList;const riid:TIID;prgfInOut:Pointer;out ppvOut):HResult; begin Result:=E_NOTIMPL; end; function TCustomShellFolder.GetDisplayNameOf(pidl:PItemIDList;uFlags:DWORD; var lpName:TStrRet):HResult; begin Result:=E_NOTIMPL; end; function TCustomShellFolder.SetNameOf(hwndOwner:HWND;pidl:PItemIDList; lpszName:POLEStr;uFlags:DWORD;var ppidlOut:PItemIDList):HResult; begin Result:=E_NOTIMPL; end; {IPersistFolder} function TCustomShellFolder.GetClassID(out classID:TCLSID):HResult; begin classID:=CLSID_CustomShellFolder; Result:=NOERROR; end; function TCustomShellFolder.PersistInitialize(pidl:PItemIDList):HResult; begin Result:=NOERROR; end; {---------------------------------------------} procedure TShellFolderObjectFactory.UpdateRegistry(Register:boolean); var Reg:TRegistry; B:array[0..3] of byte; begin if Register then begin inherited UpdateRegistry(Register); Reg:=nil; try Reg:=TRegistry.Create; Reg.RootKey:=HKEY_CLASSES_ROOT; Reg.OpenKey('CLSID\'+GUIDToString(CLSID_CustomShellFolder)+ '\InprocServer32',True); Reg.WriteString('ThreadingModel','Apartment'); Reg.CloseKey; Reg.OpenKey('CLSID\'+GUIDToString(CLSID_CustomShellFolder)+ '\ShellFolder',True); Reg.WriteString('',''); B[0]:=$40; B[1]:=$01; B[2]:=$00; B[3]:=$20; Reg.WriteBinaryData('Attributes',B,sizeof(B)); Reg.CloseKey; Reg.RootKey:=HKEY_LOCAL_MACHINE; Reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\Explorer\ Desktop\Namespace\'+GUIDToString(CLSID_CustomShellFolder),True); Reg.WriteString('','Shell Test view object'); Reg.CloseKey; finally Reg.Free; end; end else begin Reg:=nil; try Reg:=TRegistry.Create; Reg.Rootkey:=HKEY_LOCAL_MACHINE; Reg.DeleteKey('Software\Microsoft\Windows\CurrentVersion\Explorer\ Desktop\Namespace\'+GUIDToString(CLSID_CustomShellFolder)); Reg.CloseKey; finally if Assigned(Reg) then Reg.Free; end; inherited UpdateRegistry(Register); end; end; initialization TShellFolderObjectFactory.Create(ComServer,TCustomShellFolder, CLSID_CustomShellFolder,'','Inprise fish viewer',ciSingleInstance); end.
IShellFolder содержит реализацию одного метода - CreateView. Этот метод будет вызываться всякий раз, когда в Windows Explorer (Проводнике) будет выбрана пиктограмма, которая описывается в IShellFolder. Имя этой пиктограммы будет соответствовать текстовому описанию фабрики класса TShellObjectFactory - в данном случае 'Inprise fish viewer'. В этом методе необходимо создать интерфейс IShellView и передать указатель на него.
Следует обратить внимание на защищенный блок try-except-end. Если при создании экземпляра интерфейса возникает исключение, это значит, что запрос к интерфейсу IShellView был выполнен не из главного потока Windows Explorer (Проводник, как известно, работает в многопоточном режиме). Соответственно в нем не был произведен вызов метода CoInitialize, поэтому СОМ-объект не может быть создан. В этом случае необходимо вызвать метод CoInitialize перед созданием СОМ-объекта.
Регистрация фабрики класса IShellFolder в системном реестре существенно отличается от регистрации стандартной фабрики класса сервера автоматизации. Прежде всего она должна поддерживать модель Apartment при работе в многопоточном режиме. Начиная с Delphi 4 это достигается добавлением еще одного параметра в конструктор фабрики класса, а в Delphi 3 надо было отдельно описать этот параметр под именем ThreadingModel в секции CLSID\<IID>\InprocServer32 (где <IID> - идентификатор созданного здесь интерфейса IShellFolder - BF029401-68CC-11D2-9B02-0000E844A5C5). Кроме того, необходимо создать подсекцию с именем ShellFolder в секции CLSID\<IID> и поместить туда двоичные данные $40 $01 $00 $20 с именем Attributes. Без установки этого значения пиктограмма не будет показываться в Windows Explorer. Помимо этого, в разделе HKEY_LOCAL_MACHINE системного реестра в секции Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\Namespace\ необходимо создать подсекцию <IID> - в данном случае BF029401-68CC-11D2-9B02-0000E844A5C5. В эту секцию поместить краткое текстовое описание просматриваемых данных.
Все необходимые записи в системный реестр (и их удаление) выполняет, переписанный в данном примере метод UpdateRegistry фабрики класса TComObjectFactory.
Реализация интерфейса IShellView приведена ниже:
unit IShlView; interface uses Windows,ActiveX,ShellAPI,ComObj,ComServ,ShlObj,CommCtrl,UTestF; const CLSID_CustomShellView:TGUID='{BF029403-68CC-11D2-9B02-0000E844A5C5}'; type TCustomShellView=class(TComObject,IShellView) private FFolderSettings:TFolderSettings; FShellBrowser:IShellBrowser; FHWndParent:HWND; FForm:TForm1; public {IOleWindow Methods} function GetWindow(out wnd:HWnd):HResult;stdcall; function ContextSensitiveHelp(fEnterMode:BOOL):HResult;stdcall; {IShellView Methods} function TranslateAccelerator(var Msg:TMsg):HResult;stdcall; function EnableModeless(Enable:Boolean):HResult;stdcall; function UIActivate(State:UINT):HResult;stdcall; function Refresh:HResult;stdcall; function CreateViewWindow(PrevView:IShellView; var FolderSettings:TFolderSettings;ShellBrowser:IShellBrowser; var Rect:TRect;out Wnd:HWND):HResult;stdcall; function DestroyViewWindow:HResult;stdcall; function GetCurrentInfo(out FolderSettings:TFolderSettings):HResult;stdcall; function AddPropertySheetPages(Reseved:DWORD; var lpfnAddPage:TFNAddPropSheetPage;lParam:LPARAM):HResult;stdcall; function SaveViewState:HResult;stdcall; function SelectItem(pidl:PItemIDList;flags:UINT):HResult;stdcall; function GetItemObject(Item:UINT;const iid:TIID; var IPtr:Pointer):HResult;stdcall; end; implementation Uses Dialogs,SysUtils,Forms; type TShellViewFactory=class(TComObjectFactory) public procedure UpdateRegistry(Register:Boolean);override; end; function TCustomShellView.GetWindow(out wnd:HWND):HResult;stdcall; begin if Assigned(FForm) then Wnd:=FForm.Handle else Wnd:=0; Result:=NOERROR; end; function TCustomShellView.ContextSensitiveHelp(fEnterMode:BOOL):HResult;stdcall; begin Result:=E_NOTIMPL; end; function TCustomShellView.TranslateAccelerator(var Msg:TMsg):HResult;stdcall; begin Result:=E_NOTIMPL; end; function TCustomShellView.EnableModeless(Enable:Boolean):HResult;stdcall; begin Result:=E_NOTIMPL; end; function TCustomShellView.UIActivate(State:UINT):HResult;stdcall; var S:string; begin case TSVUIAEnums(State) of SVUIA_DEACTIVATE:S:='Deactivate view'; SVUIA_ACTIVATE_NOFOCUS:S:='Activate view without focus'; SVUIA_ACTIVATE_FOCUS:S:='Activate view with focus'; SVUIA_INPLACEACTIVATE:S:='Activate view for inplace-activation within ActiveX control'; end; FShellBrowser.SetStatusTextSB(StringToOleStr('IShellView.UIActivate:'+S)); Result:=NOERROR; end; function TCustomShellView.Refresh:HResult;stdcall; begin FShellBrowser.SetStatusTextSB(StringToOleStr('IShellView.Refresh')); Result:=E_NOTIMPL; end; function TCustomShellView.CreateViewWindow(PrevView:IShellView; var FolderSettings:TFolderSettings;ShellBrowser:IShellBrowser; var Rect:TRect;out Wnd:HWND):HResult;stdcall; begin FFolderSettings:=FolderSettings; FShellBrowser:=ShellBrowser; if Assigned(FShellBrowser) then FShellBrowser.GetWindow(FHWndParent) else FHWndParent:=0; if not Assigned(FForm) then FForm:=TForm1.Create(nil); Wnd:=FForm.Handle; if FHWndParent<>0 then begin SetParent(Wnd,FHWndParent); with FForm do begin SetWindowPos(Handle,HWND_TOP,Rect.Left,Rect.Top,Rect.Right-Rect.Left, Rect.Bottom-Rect.Top,SWP_SHOWWINDOW); Show; end; end else begin FForm.BorderStyle:=bsDialog; FForm.ShowModal; end; if Wnd<>0 then Result:=NOERROR else Result:=E_UNEXPECTED; end; function TCustomShellView.DestroyViewWindow:HResult;stdcall; begin if Assigned(FForm) then FForm.Free; FForm:=nil; Result:=NOERROR; end; function TCustomShellView.GetCurrentInfo (out FolderSettings:TFolderSettings):HResult;stdcall; begin Result:=E_NOTIMPL; end; function TCustomShellView.AddPropertySheetPages(Reseved:DWORD; var lpfnAddPage:TFNAddPropSheetPage;lParam:LPARAM):HResult;stdcall; begin Result:=E_NOTIMPL; end; function TCustomShellView.SaveViewState:HResult;stdcall; begin Result:=E_NOTIMPL; end; function TCustomShellView.SelectItem(pidl:PItemIDList;flags:UINT):HResult;stdcall; begin Result:=NOERROR; end; function TCustomShellView.GetItemObject(Item:UINT; const iid:TIID; var IPtr:Pointer):HResult;stdcall; begin Result:=E_NOTIMPL; end; {----------------------------------------} procedure TShellViewFactory.UpdateRegistry(Register:Boolean); begin if Register then begin CreateRegKey('CLSID\'+GUIDToString(CLSID_CustomShellView)+ '\InprocServer32','ThreadingModel','Apartment'); inherited UpdateRegistry(Register); end else begin inherited UpdateRegistry(Register); end; end; initialization TShellViewFactory.Create(ComServer,TCustomShellView,CLSID_CustomShellView,'', 'View fish',ciMultiInstance); end.
Главный метод, который необходимо описать в этом интерфейсе, - создание окна для просмотра данных. В частности, этим окном может быть любая форма со значением свойства BorderStyle, равным bsNone. На этой форме могут размещаться любые элементы управления (кроме ActiveX). Подобная форма, показывающая содержимое таблицы BIOLIFE демонстрационной базы DBDEMOS, реализована в модуле UTestF, текст которого приведен ниже:
unit UTestF; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ComCtrls, ShellCtrls, Grids, DBGrids, ExtCtrls, DBCtrls, Buttons, Menus, DB, DBTables; type TForm1 = class(TForm) DBNavigator1: TDBNavigator; DBGrid1: TDBGrid; Table1: TTable; DataSource1: TDataSource; DBImage1: TDBImage; BitBtn1: TBitBtn; procedure BitBtn1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.BitBtn1Click(Sender: TObject); begin if Table1.Active=False Then Table1.Active:=True else Table1.Active:=False; end; end.
Окно для просмотра данных реализуется при вызове метода CreateViewWindow. При этом передается ссылка на рабочую копию Windows Explorer в интерфейсе IShellBrowser. Создаваемая форма становится дочерним окном Windows Explorer и помещается в область, указанную в переменной Rect. Соответственно метод DestroyViewWindow должен разрушить созданную форму. И наконец, метод UIActivate просто показывает тип активации окна просмотра на панели состояния Windows Explorer.
Дополнительные данные, которые должны быть занесены в реестр, описывают тип работы интерфейса в многопоточном режиме. Они выполняются в переписанном методе UpdateRegistry фабрики класса TComObjectFactory.
Данная пара модулей (вместе с модулем реализации формы) должна быть реализована в библиотеке ActiveX. После вызова команды регистрации элемента управления ActiveX в системном реестре и перезагрузки компьютера на рабочем столе Windows появляется папка Inprise Fish Viewer. Она же появится и в Windows Explorer. При попытке открытия этой папки появляется содержимое формы, определенной в модуле UTestF (рис. 1).
Рис.1. Окно просмотра нефайловых данных в Windows Explorer (Проводнике)
На следующем шаге мы рассмотрим OLE-реализацию метода drag-and-drop (реализацию контейнера).