Delphi - A Windows Shell titkai

WinShell 2. rész

forráskód letöltése
Amikor a Windows Intézőjében egy állományon jobb gombbal kattintunk és a Tulajdonságok menüpontot választjuk, akkor megjelenik egy kis dialógus ablak. Ebben az állomány alapvető tulajdonságai láthatók, úgy mint a mérete, neve, elérési útvonala, attribútumai.

Ha viszont a Lomtár tulajdonság ablakát nézzük, vagy akár egy meghajtóét, akkor az alapvető tulajdonságok lapja mellett új lapok is megjelennek. Ebből látható, hogy ez a tulajdonság ablak dinamikusan bővíthető új lapokkal.

Mellékelt példában fény derül arra, hogy saját vagy már meglévő állománynak a tulajdonság ablakába miként vehetünk fel saját lapokat, melyeket az általunk készített alkalmazás kezel. Ezen a lapon keresztül bármilyen alkalmazást megvalósíthatunk, mintha csak egy hagyományos EXE-t készítenénk. A mellékelt példa kipróbálásához az alábbi lépésekre van szükség:
1. Nyissa meg a ShellPropertyPageExt.dpr-t és a Project - Build menüponttal fordítsa le. Ekkor létrejön a ShellPropertyPageExt.dll.
2. Nyissa meg szerkesztésre a mellékelt ShellPropertyPageExt.reg állományt. Például egy Windows Intézővel keresse meg. Jobb gombbal kattintson rá, majd válassza az Edit menüpontot.
3. Ebben az állományban a @="D:\\Dso\\0246\\ShellSecret02\\ShellPropertyPageExt.dll" sorban található elérési útvonalat javítsa ki arra, ahová a mellékelt példaprogramot helyezte. Lényeg az, hogy a ShellPropertyPageExt.dll-nek itt korrektül meg legyen adva a helye. Ügyeljen arra is, hogy az elérési útvonal megadásánál a \ jelet duplázva kell használnia!
4. Mentse és zárja a ShellPropertyPageExt.reg állományt.
5. Kattintson rá a ShellPropertyPageExt.reg állományra. Ekkor a Windows megkérdezi, hogy szeretné-e tartalmát a regisztrációs adatbázishoz hozzáfűzni. Erre igennel válaszoljon.
6. Ezek után kattintson jobb gombbal a mellékelt a.sec2 állományon. A megjelenő menüből válassza a Tulajdonságok (Properties) menüpontot.
7. A megjelenő ablakban válassza a SEC2Page lapot.
8. Itt a Text mezőbe egy tetszőleges szöveget begépelhet.
9. Zárja be az OK-val az ablakot.
10. Ekkor létrejön a c:\_dsolog.txt állomány. Tekintse meg a tartalmát.


Ha a 10. lépésben létrehozott állomány elérési útvonalát szeretné megváltoztatni, akkor a DLL fordítása előtt a LOGFILE='c:\_dsolog.txt'; konstanst írja át.

További fontos tudnivaló, hogy ha a kipróbált DLL-ünket szeretnénk újrafordítani, akkor előtte be kell zárnunk az összes olyan alkalmazást, mely használja a ShellPropertyPageExt.dll-t, mint például a Windows Intézője. E lépés nélkül a DLL-t nem lehet felülírni, így a Delphi-ben a fordításkor a Could not create output file hibaüzenettel találjuk szembe magunkat.

A mostani cikk megértéséhez szükséges lesz a múlt héten leírtak ismerete.


Nézzük, hogyan is készül egy ilyen DLL. A múlt héten elkezdett példaprogramot egyszerűen folytathatjuk. Ott az IShellExtInit, IContextMenu interfészeket használtuk fel ahhoz, hogy a jobb gombra megjelenő menürendszert egyedileg kiegészítsük.

Most megtartva az ottani forráskódot, csak egy újabb interfész felhasználása válik szükségessé. Ez az IShellPropSheetExt lesz. Ennek segítségével lehetőségünk van a tulajdonság ablakba újabb lapokat felvenni és kezelni azokat a saját DLL-ünkön belül.

Az IShellPropSheetExt-nek két metódusa van, melyből csak az egyikre lesz szükségünk, de mivel interfészről van szó, így mind a kettőt deklarálnunk kell.
…
    {Declare IShellPropSheetExt methods here}
function AddPages(lpfnAddPage: TFNADDPROPSHEETPAGE;
     lParam: LPARAM): HResult; stdcall;
function ReplacePage(uPageID: UINT; lpfnReplaceWith: 
    TFNADDPROPSHEETPAGE; lParam: LPARAM): HResult; stdcall; 
…
A ReplacePage függvénnyel lehetőségünk lenne arra, hogy egy már meglévő lapot cseréljük fel sajátunkra, mivel ezt most nem használjuk ki, így a visszatérési értéknek az E_NOTIMPL konstanst adjuk.
function TShellPropertyPage.ReplacePage(uPageID: UINT;
   lpfnReplaceWith: TFNADDPROPSHEETPAGE; lParam: LPARAM): HResult;
begin
  result:=E_NOTIMPL;
end;
Az AddPages függvénnyel van lehetőségünk egy vagy akár több lap egyidejű hozzáadására is. Ehhez viszont szükséges némi előkészület, melyet a Delphi jelenlegi verziói nem igen támogatnak, így vagy külső szoftverre lesz szükségünk, vagy kénytelenek leszünk szövegszerkesztővel létrehozni a szükséges állományt.

Felmerül a kérdés, hogy miként állítható elő a megjelenő új lap. Sajnos nem hozhatunk létre egy Delphi Form-ot, melyből a lap készülne, pedig ez lenne a legegyszerűbb megoldás. A megoldáshoz szükség lesz egy dialógus erőforrás elkészítésére. Mostanában erőforrásként már csak szöveget, esetleg képeket, ikonokat szoktunk leggyakrabban alkalmazni, pedig arra is lehetőségünk van, hogy egy teljes dialógus ablakot helyezzünk el erőforrásként az alkalmazásunkba.

Az erőforrás állomány létrehozásához (*.RES) rendelkezésünkre áll a Delphi-ben egy segédeszköz, melyet a Tools - Image Editor menüpont kiválasztásával indíthatunk el. Ez a program azonban csak Bitmap, Icon és Cursor típusú erőforrásokat kezel, így ezzel a programmal nem vagyunk kisegítve.

Van egy másik segédprogram is a Delphi-hez. Ez a Borland Resource Compiler. Ez egy parancssori program, mely erőforrás állományok fordítására alkalmas. A programot a C:\Program Files\Borland\Delphix\Bin\ könyvtárban találjuk brcc32.exe néven. Futásához azonban szükség van egy RC kiterjesztésű állományra, mely nem más, mint egy egyszerű szöveges állomány, ami akár TXT-ként is kezelhető. Ebben az RC állományban leírhatjuk, hogy milyen erőforrásokat szeretnénk létrehozni. Itt már használhatunk dialóg erőforrást, a brcc32.exe előállítja a szükséges RES állományt, melyet már hozzáadhatunk a DLL-hez.

Most már csak az a kérdés, hogy miként állítjuk elő az RC állományt. Ehhez használhatjuk a Borland Resource Workshop-ot, vagy akár a MS Visual Studio-t is. Ha ezek az alkalmazások nem állnak rendelkezésre, akkor még mindig van megoldás: nyissa meg a mellékelt Dialog.rc állományt egy egyszerű szövegszerkesztővel, vagy akár a Delphi-vel is. Mint látni fogja a dialógban található egyes objektumok, mint például az Edit komponens, egyszerűen szövegesen van leírva megadva néhány paraméter hozzá, mint például annak mérete és pozíciója. Így akár kézzel is előállítható a szükséges RC állomány.

Ha tehát adott az Dialog.rc állomány, akkor a brcc32.exe-vel elkészíthetjük a Dialog.res állományt. Ezt már egyszerűen elhelyezhetjük a DLL-ben: a forráskódban csak hivatkozni kell rá a következő módon:
{$R dialog.res}
Ekkor a Delphi fordítója elhelyezi a Dialog.res erőforrásait a készülő DLL-be.


Térjünk most vissza a DLL készítésére, azon belül is az AddPages függvényre.

Amikor megjelenik egy állomány tulajdonság ablaka, akkor kerül majd meghívásra a DLL-ünk AddPages függvénye. Ez lesz az a pillanat, amikor az erőforrásban tárolt dialóg-ot létre kell hoznunk.

Ezt a CreatePropertySheetPage függvénnyel tehetjük meg, melynek egyetlen paramétert kell átadnunk. Ez a paraméter egy TPropSheetPage típusú struktúra kell hogy legyen, a megfelelő paraméterekkel feltöltve.
…
var
  psp: TPropSheetPage;
…
  psp.dwSize:=SizeOf(TPropSheetPage);
  psp.dwFlags:=PSP_USEREFPARENT+PSP_USETITLE
    +PSP_USECALLBACK;
  psp.hInstance:=hInstance;
  psp.pszTemplate:=MakeIntResource(100);
  psp.pszTitle:='SEC2 Page';
  psp.pfnDlgProc:=@DlgProc;
  psp.pfnCallBack:=@CallBack;
  psp.pcRefParent:=@ComServer.ObjectCount;
  psp.lParam:=integer(Self);
…
A TPropSheetPage struktúra dwFlags mezőjének adott értékben előírjuk, hogy a dialóg létrehozásakor használja a struktúra pcRefParent, pszTitle és a pfnCallBack mezőjét.

A hInstance mezőben meg kell adnunk programunk példányazonosító kódját, melyet a hInstance globális változó tárol minden programban.

A pszTemplate mezőben adjuk meg az erőforrás dialóg azonosítóját. Ha megfigyeljük a Dialog.rc állomány első sorát, akkor látható, hogy a 100-as értéket, mint azonosító számot ott adtuk meg és a pszTemplate-nél hivatkozunk rá.

A pszTitle mezőben a megjelenő lap fejléc szövegét határozhatjuk meg.

Két saját függvényünknek a címét is át kell adnunk a pfnDlgProc és a pfnCallBack mezőben. Ezek szerepét alább részletezzük.

A pcRefParent mezőbe a COM objektumunk számlálójának címét adjuk át.

Az lParam mezőbe egy tetszőleges értéket adhatunk meg későbbi felhasználásra. Mi itt most a saját objektumunkat adjuk meg. Ennek okát az alábbiakban láthatjuk majd.


Miután feltöltöttük a TPropSheetPage struktúrát, hívhatjuk a CreatePropertySheetPage függvényt. Visszatérési értékként a létrejött lap azonosítókódját kapjuk. Ha ez nem nil, akkor a létrehozás sikeres volt. Ekkor az AddPages függvényünk paramétereként kapott (lpfnAddPage) függvény meghívásával adhatjuk hozzá a már létrehozott lapot a megjelenő tulajdonság ablakhoz.
Ha a létrehozás nem volt sikeres, akkor a DestroyPropertySheetPage függvényhívással rögtön meg is szüntetjük a CreatePropertySheetPage által létrehozott lapot.
…
  hpsp:=CreatePropertySheetPage(psp);
  if hpsp<>nil then begin
    if not lpfnAddPage(hpsp, lParam) then begin
      DestroyPropertySheetPage(hpsp);
    end else begin
      _AddRef;
      result:=S_OK;
    end
  end
…
Nézzük most a lap létrehozásánál említett két függvényt, melyet a pfnDlgProc és a pfnCallBack mezőben adtunk meg.

A pfnCallBack-nál megadott függvényünk két esetben kerül meghívásra. Az egyik, amikor a lap létrejön, a másik amikor megszűnik. Ezt, mint két eseményt kezelhetjük és tetszés szerinti kódot futtathatunk.

Hogy mikor mi történik, azt a paraméterként kapott Msg értékének vizsgálatával dönthetjük el. Ha ez PSPCB_CREATE értékkel egyezik, akkor a dialóg ablak létrehozása van folyamatban. Ekkor, ha a függvényünknek hamis értéket adnánk visszatérési értékként, akkor ezzel meggátolhatnánk a lap létrejöttét.
A másik a PSPCB_RELEASE. Ebben az esetben a dialóg ablak megszűnéséről van szó. Ez lesz az a pillanat, amikor meg kell szüntetnünk a létrehozott objektumunkat. Ha visszaemlékszünk a lap létrehozására, ott a TShellPropertyPage struktúra lParam mezőjének magát a lap objektumát adtuk. Mivel a pfnCallBack-nál megadott függvényünknél paraméterként megkapjuk ezt a struktúrát, így az lParam felhasználásával elérhetővé válik az objektum, így meghívhatjuk a _Release metódusát, mely elvégzi az objektum megszüntetését.
function Callback(hWndDlg: HWnd; Msg: Integer;
      var PPSP: TPropSheetPage): Integer; stdcall;
begin
  result:=$FF;
  case Msg of
    PSPCB_CREATE: result:=integer(true);
    PSPCB_RELEASE: TShellPropertyPage(ppsp.lParam)._Release;
  end;
end;
A másik saját függvényünk, melyet a pfnDlgProc mezőben adtunk meg a DlgProc lesz. Ez nem más, mint a dialóg ablakunk ablak kezelő eljárása. Itt a különböző üzenetekre kell megfelelő kóddal válaszolnunk.

Például a WM_INITDIALOG üzenet esetén beállíthatjuk a kezdőértékeit a dialóg ablakunknak. Itt írjuk ki a lap tetejére, hogy melyik állományról is van szó.
…
    WM_INITDIALOG: begin
      …
      SetDlgItemText(hwndDlg, 110, PChar(spp.FileName));
      …
    end;
…
A lap tetején lévő Label-t 110-es azonosítóval láttuk el a Dialog.rc állományban, így a SetDlgItemText függvénynél ezzel tudunk rá hivatkozni.

Amikor a felhasználó bezárja a tulajdonság dialógot, akkor jön létre egy WM_NOTIFY üzenet, melynél tetszőleges kódot futtathatunk. Mellékelt példában egyszerűen egy szöveges állományba írjuk, hogy mit is adott meg a felhasználó a saját lapunkon keresztül.
…
    WM_NOTIFY: begin
      case PNMHdr(lParam)^.code of
        …
        PSN_APPLY: begin
          spp:=TShellPropertyPage(integer(
                GetWindowLong(hwndDlg, DWL_USER)));
          spp.WriteFile(buffer, GetDlgItemText(
               hwndDlg, 102, buffer, 256));
          SetWindowLong(hwndDlg, DWL_MSGRESULT,
               PSNRET_NOERROR);
          result:=true;
        end;
      end;
    end;
…
Ahhoz, hogy az elkészített DLL-ünkről a Windows is tudomást szerezzen, hasonlóan kell eljárnunk a múlt héten ismertetett regisztrációs folyamathoz. Ennek részletes leírása a múlt heti cikk végén megtalálható. A különbség annyi, hogy most nem a ContextMenuHandlers kulcs alá kell bejegyeznünk a DLL-t, hanem a PropertySheetHandlers alá, ahogyan ez a mellékelt ShellPropertyPageExt.reg állományban is látható.

WinShell cikksorozat