|
Antwort |
MathiasSimmack
In diesem Mini-Tutorial geht es um eine Gruppe der Shell-Erweiterungen: die sog. "Property Sheets", die bei einigen Dateitypen eingeblendet werden, wenn man deren Eigenschaften anzeigen lässt. Das gesamte Beispiel basiert auf der Grundlage von Andreas Kosch, die man im Entwickler-Forum downloaden kann. Ich will aber zusätzlich zeigen, welche Units man entfernen muss, damit am Ende eine Datei (DLL) von weniger als 100k entsteht.
Außerdem ist dieser erste Schritt (IMHO) unbedingt wichtig, da man nicht einfach ein vorhandenes, lauffähiges Projekt nehmen und für andere Zwecke erneut verwenden sollte. Das liegt daran, dass die meisten Shell-Erweiterungen intern eine eindeutige GUID benutzen, mit der sie später auch im System identifiziert werden. Logische Folge: wenn ich zwei Shell-Erweiterungen verwende, die die selbe GUID haben, dann kommt es mit großer Wahrscheinlichkeit zu Problemen. Also, ich bitte darum, dieser Anleitung genau zu folgen und für jedes Projekt tatsächlich neuen Code zu erstellen. Die relevanten Code-Teile, die in jeder Erweiterung vorkommen, können dann natürlich problemlos kopiert und wiederverwendet werden. Bevor wir uns an ein solches Projekt wagen, sollten wir uns natürlich darüber im Klaren sein, was wir erreichen wollen. Üblicherweise stellen wir auf den Eigenschaftenseiten ergänzende Informationen zur Verfügung, die beispielsweise auch von einem Programm angezeigt werden. Damit hat der Anwender die Möglichkeit, sich schnell über den Inhalt, den Typ, spezielle Eigenschaften usw. zu informieren, ohne erst das eigentlich passende Programm starten zu müssen. In unserem Beispiel wollen wir eine zusätzliche Seite bei den Textdateien (*.txt) anzeigen lassen, die uns neben dem genauen Dateinamen auch eine Möglichkeit zum Öffnen des Texteditors bietet. Damit wird das Prinzip auf recht einfache Weise verdeutlicht. Hinweis
|
MathiasSimmack |
MathiasSimmack
|
#2
Eine "Eigenschaftenseite" ist in den bekannten Windows-Systemen grundsätzlich als DLL ausgeführt, so dass wir für unser Projekt ebenfalls ein leeres DLL-Grundgerüst wählen müssen. Zu beachten ist allerdings, dass es sich dabei um eine sog. "ActiveX-Bibliothek" handelt, die wir über das Menü "Datei/Neu/ActiveX/ActiveX-Bibliothek" aufrufen können:
Code:
Standardmäßig wird auch die Anweisung "{$R *.res}" in den Quelltext eingetragen. Diesen Eintrag können wir entfernen, da wir ohnehin unseren eigenen Dialog ergänzen werden.
library psheet;
uses ComServ; exports DllGetClassObject, DllCanUnloadNow, DllRegisterServer, DllUnregisterServer; begin end. |
Zitat |
MathiasSimmack
|
#3
Schauen wir uns den Dialog doch gleich mal an! Da das nur ein Beispiel ist, machen wir es uns einfach und lassen uns nur den oder die Dateinamen in einer Listbox anzeigen. Des Weiteren kommt ein Button hinzu, der - durch Klick - die markierten Dateien mit "ShellExecute" startet.
Weil das Beispiel ja heruntergeladen werden kann, spare ich mir an der Stelle einfach mal Screenshot und RC-Quelltext. Hinweis
Nach dem Erstellen der Ressourcendatei (*.res) ist diese natürlich noch in den Projektquelltext aufzunehmen:
Code:
library psheet;
... {$R dialog.RES} begin end. |
Zitat |
MathiasSimmack
|
#4
Jetzt benötigen wir ein COM-Objekt, das wir über das Menü "Datei/Neu/ActiveX/COM-Objekt" in unsere leere DLL einfügen. Der tatsächliche Name und die Beschreibung hängt letzten Endes natürlich vom Einsatzzweck ab. Wichtig wäre aber, dass folgende Optionen unverändert übernommen werden:
(In der Offline-Version dieses Tutorials gibt es einen Screenshot des Dialogs, in dem man die Auswahl noch ein bisschen besser erkennen kann!) So, damit haben wir den Grundlagen-Code, den wir bei jeder Shell-Erweiterung bitte auf diese Art erzeugen! Ich verweise noch mal auf meinen Satz am Anfang, in dem es um die eindeutigen GUIDs ging. Da wir jetzt diesen Grundlagen-Code mit eindeutigen GUIDs besitzen, können wir nun auch problemlos die benötigten Funktionen aus vorhandenen Shell-Erweiterungen nehmen und für unser jeweils aktuelles Projekt anpassen. Das Dialogfenster der Typbibliothek können wir im Hintergrund verschwinden lassen. Uns interessiert nur der Code des COM-Objektes. Zunächst deklarieren wir die Unit "ShlObj.pas" und ergänzen unser COM-Objekt wie folgt:
Code:
Jetzt bekommen wir eine Fehlermeldung beim Kompilieren, weil wir "IShellExtInit" und "IShellPropSheetExt" deklariert, nicht aber deren Eigenschaften benutzt haben. Wir erweitern also:
type
TPSheetTest = class(TTypedComObject, IPSheetTest, IShellExtInit, IShellPropSheetExt { <- beide Angaben ergänzen}) protected {IPSheetTest-Methoden hier deklarieren} end;
Code:
Ebenfalls ergänzt werden muss die Unit "CommCtrl", da wir "TFNAddPropSheetPage" benutzt haben, bzw. benutzen müssen.
protected
function IShellExtInit.Initialize = ShellExtInitialize; function ShellExtInitialize(pidlFolder: PItemIDList; lpdobj: IDataObject; hKeyProgID: HKEY): HResult; stdcall; function AddPages(lpfnAddPage: TFNAddPropSheetPage; lParam: LPARAM): HResult; stdcall; function ReplacePage(uPageID: UINT; lpfnReplaceWith: TFNAddPropSheetPage; lParam: LPARAM): HResult; stdcall; end; Eine dieser Funktion (Methoden) wollen wir bereits mit Leben füllen:
Code:
Die wird uns nämlich in unserem Beispiel nicht weiter interessieren. Es ist eine Dummy-Funktion, die wir allerdings deklarieren und mit Code versehen müssen. Andernfalls meckert der Compiler. Nur brauchen wir sie in unserem Beispiel nicht.
function TPSheetTest.ReplacePage(uPageID: UINT;
lpfnReplaceWith: TFNAddPropSheetPage; lParam: LPARAM): HResult; begin Result := E_NOTIMPL; // Dummy end; |
Zitat |
MathiasSimmack
|
#5
Das Prinzip der Shell-Erweiterung ist folgendes: man markiert im Explorer eine Datei und wählt dann aus dem Kontextmenü die Eigenschaften dieser Datei. Wir brauchen also den Namen. Und das können aber auch mehrere sein, denn die mehrfache Auswahl ist im Windows-Explorer ja ebenfalls möglich.
Als Grundlage kann hier das "ShellExt"-Demo von Borland herangezogen werden. Dabei wird im Kontextmenü einer DPR-Datei der neue Punkt "Compile" erzeugt, der den Delphi-Compiler startet. Und in diesem Beispiel steht:
Code:
Damit haben wir bereits beide Aufgaben erledigt: in der ersten Zeile wird demonstriert, wie man die Anzahl der Dateien bestimmt, und in der zweiten Zeile wird der erste Dateiname gelesen. Wir brauchen dazu aber die Unit "ShellAPI.pas", und dann schreiben wir unsere erste neue Methode:
if (DragQueryFile(StgMedium.hGlobal, $FFFFFFFF, nil, 0) = 1) then begin
DragQueryFile(StgMedium.hGlobal, 0, FFileName, SizeOf(FFileName)); Result := NOERROR; end
Code:
Wer auf Nummer sicher gehen will, kann auch vorher prüfen ob überhaupt Dateien übergeben worden sind. Dazu genügt vor der for-Schleife folgende Zeile:
var
szOpenedTextfile : array of string; function TPSheetTest.ShellExtInitialize(pidlFolder: PItemIDList; lpdobj: IDataObject; hKeyProgID: HKEY): HResult; var StgMedium : TStgMedium; FormatEtc : TFormatEtc; i : integer; FFileName : array[0..MAX_PATH] of Char; begin Result := E_INVALIDARG; if(lpdobj = nil) then exit; 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; // Dateinamen lesen for i := 0 to DragQueryFile(StgMedium.hGlobal, $ffffffff, nil, 0) - 1 do begin DragQueryFile(StgMedium.hGlobal, i, FFileName, sizeof(FFilename)); // in das String-Array eintragen SetLength(szOpenedTextfile,i + 1); szOpenedTextfile[i] := FFileName; end; Result := NOERROR; end;
Code:
Ach so: ich habe hier ein String-Array benutzt. Das hat den Vorteil, dass wir auf die Unit "Classes" und die TStringList verzichten können. Das kommt am Ende dann auch der Größe der DLL entgegen. Wir sollten das Array aber beim Start einmal initialisieren. Das Prinzip entspricht dabei etwa dem Erzeugen und Freigeben von richtigen Stringlisten:
if(DragQueryFile(StgMedium.hGlobal, $ffffffff, nil, 0) = 0) then exit
Code:
initialization
SetLength(szOpenedTextfile,0); finalization SetLength(szOpenedTextfile,0); end. |
Zitat |
MathiasSimmack
|
#6
Um nun endlich unsere eigene Eigenschaftenseite anzeigen zu können, ziehen wir die Funktion "AddPages" heran, bei der wir zwei neue Variablen benötigen. Die eine Variable ist ein Handle auf die erzeugte Seite, die zweite ist eine Struktur, die wir zum Erzeugen der Seite benötigen. Mal ein bisschen Microsoft-Code:
Code:
Nun interessiert uns nicht alles davon, wir brauchen nur:
typedef struct _PROPSHEETPAGE {
DWORD dwSize; // Größe der Struktur DWORD dwFlags; // Flags HINSTANCE hInstance; // Instanz union { LPCSTR pszTemplate; // Dialog-ID LPCDLGTEMPLATE pResource; }; union { HICON hIcon; LPCSTR pszIcon; }; LPCSTR pszTitle; // Titel DLGPROC pfnDlgProc; // Dialog-Funktion LPARAM lParam; LPFNPSPCALLBACK pfnCallback; UINT *pcRefParent; #if (_WIN32_IE >= 0x0500) LPCTSTR pszHeaderTitle; LPCTSTR pszHeaderSubTitle; #endif #if (_WIN32_WINNT >= 0x0501) HANDLE hActCtx; #endif }
Code:
Jetzt können wir die Seite erstellen lassen, wozu wir die Funktion "CreatePropertySheetPage" benutzen:
var
aPSP : TPropSheetPage; begin fillchar(aPSP, sizeof(TPropSheetPage),#0); aPSP.dwSize := sizeof(TPropSheetPage); aPSP.dwFlags := PSP_USETITLE; aPSP.hInstance := hInstance; aPSP.pszTemplate := MakeIntResource(IDD_PROPDLG); aPSP.pszTitle := 'Beispielseite'; // Titel der Seite aPSP.pfnDlgProc := @propdlgproc; // Dialogfunktion aPSP.pfnCallback := nil; aPSP.lParam := 0; end;
Code:
var
hPage : HPropSheetPage; begin ... hPage := CreatePropertySheetPage(aPSP); if(hPage <> nil) then if(lpfnAddPage(hPage,lParam) = FALSE) then DestroyPropertySheetPage(hPage); end; Die Dialog-Funktion Unser Dialog benutzt eine typische Nachrichtenfunktion, in der wir Einfluss auf die Anzeige nehmen können. Diese Funktion ist NonVCL-Entwicklern sicher bekannt - andernfalls empfehle ich einen Blick in Luckies "NonVCL-Tutorials", wobei die Themen Dialogressourcen und Listbox besonders interessant sind, da wir beides in unserem Beispiel benötigen. Schauen wir uns also mal an, wie die Dateinamen aus dem String-Array in unsere Listbox kommen.
Code:
Nichts weltbewegendes, denke ich. - Fehlt noch der Klick auf den Button: hier müssen wir herausfinden, ob und welche Dateien alles markiert sind, und dann übergeben wir sie einfach an "ShellExecute":
WM_INITDIALOG:
begin if(length(szOpenedTextfile) > 0) then for i := 0 to length(szOpenedTextFile) - 1 do begin ZeroMemory(@buffer,sizeof(buffer)); lstrcpy(buffer,pchar(szOpenedTextfile[i])); SendDlgItemMessage(hDlg,IDC_LISTBOX, LB_ADDSTRING,0,integer(@buffer)); end; end;
Code:
Wer mit diesem Code nichts anfangen kann, sollte wirklich einen Blick in die erwähnten Tutorials werfen. Aber ich glaube, in so einem Fall ist eine Shell-Erweiterung vielleicht ein zu hartes Projekt als Einstieg.
WM_COMMAND:
if(HIWORD(wp) = BN_CLICKED) then case LOWORD(wp) of IDC_OPENBTN: begin // Anzahl der Einträge bestimmen items := SendDlgItemMessage(hDlg,IDC_LISTBOX, LB_GETCOUNT,0,0); // ausgewählte Einträge auslesen for i := 0 to items do if(SendDlgItemMessage(hDlg,IDC_LISTBOX,LB_GETSEL,i,0) > 0) then begin ZeroMemory(@buffer,sizeof(buffer)); SendDlgItemMessage(hDlg,IDC_LISTBOX,LB_GETTEXT,i,integer(@buffer)); // und via "ShellExecute" starten ShellExecute(0,nil,buffer,nil,nil,SW_SHOWNORMAL); end; end; end; Noch ein Wort zu "ShellExecute": Wie man sehen kann, verwende ich als zweiten Parameter nil. Ich überlasse also dem System die Entscheidung, was mit den Textdateien passiert. In den meisten Fällen sollte die Standardaktion aber "open" sein, was den Start eines Texteditors und die Anzeige der Datei bedeutet. Es wäre aber auch denkbar, dass die Standardaktion auf Drucken eingestellt ist ... was auch immer ... Also, Obacht! Änderungen übernehmen Schauen wir uns noch eine Eigenschaft unserer "Property Sheet" an, die in dem Fall zwar weniger wichtig ist, die aber ihr möglicherweise brauchen werdet: den "Übernehmen"-Button. Der ist normalerweise deaktiviert, aber durch irgendwelche Änderungen wird er aktiviert. Soll die eigene Shell-Erweiterung auf den Klick dieses Buttons reagieren und irgendwelche Aktionen ausführen, dann ist dazu die Nachricht "WM_NOTIFY" abzufangen:
Code:
WM_NOTIFY:
if(PNMHDR(lp).code = PSN_APPLY) then MessageBox(0,'Übernehmen-Button geklickt','Information', MB_ICONINFORMATION or MB_OK); |
Zitat |
MathiasSimmack
|
#7
Eigentlich wäre unsere neue Eigenschaftenseite damit schon lauffähig. Allerdings müssen wir noch eine kleine Änderung vornehmen, damit wir die Shell-Erweiterung ganz gezielt für einen bestimmten Dateityp (in unserem Fall: die Textdateien) registrieren können. Dazu schauen wir uns den Initialisierungscode der Unit an, in der standardmäßig das steht:
Code:
Wir erstellen stattdessen aber einen neuen Typ auf folgender Basis:
initialization
TTypedComObjectFactory.Create(ComServer, TPSheetTest, Class_PSheetTest, ciMultiInstance, tmApartment); end.
Code:
In der Funktion "UpdateRegistry" können wir nun angeben, wo der Eintrag unserer "Property Sheet" vorgenommen werden soll:
type
TPSheetTestFactory = class(TComObjectFactory) public procedure UpdateRegistry(Register: Boolean); override; end;
Code:
Hinweis
procedure TPSheetTestFactory.UpdateRegistry(Register: Boolean);
const szTestExtension = 'txtfile\shellex\PropertySheetHandlers\'; begin inherited; if Register then CreateRegKey(szTestExtension + ClassName,'',GUIDToString(ClassID)) else DeleteRegKey(szTestExtension + ClassName); end;
Okay, jetzt müssen wir den o.g. Initialisierungscode ändern, damit statt des Standards unsere neue Deklaration verwendet wird:
Code:
Fertig!
initialization
TPSheetTestFactory.Create(ComServer, TPSheetTest, Class_PSheetTest, 'PropertySheetTest', '', ciMultiInstance, tmApartment); end. Die Shell-Erweiterung registrieren und benutzen Registriert wird unsere Eigenschaftenseite durch einen Aufruf des Programms "regsvr32.exe", dem wir den Namen unserer DLL als Parameter übergeben:
Code:
Wenn alles geklappt hat, sollte bei den Eigenschaften der Textdateien jetzt die neue Seite zu sehen sein. Die Auswahl der Dateien in der Listbox und der Button-Klick sollten natürlich auch funktionieren.
regsvr32 psheet.dll
Die Shell-Erweiterung entfernen Entfernt wird unser Shell-Erweiterung durch einen erneuten Aufruf von "regsvr32.exe", wobei wir aber diesmal zusätzlich den Parameter "/u" angeben:
Code:
regsvr32 /u psheet.dll
|
Zitat |
MathiasSimmack
|
#8
Unsere Beispiel-DLL ist damit fertig, aber leider auch ca. 315k groß. Das liegt hauptsächlich an einigen Units, auf die wir aber verzichten können. Auswirkungen auf die DLL und ihre Funktionalität hat das nicht - sie wird eben nur kleiner.
Die erste Änderung nehmen wir in der Unit "*_TLB.pas" vor. Der tatsächliche Dateiname richtet sich dabei nach dem Projektnamen, in meinem Fall also "psheet_TLB.pas". Hier können alle Units - bis auf die "ActiveX.pas" - ausgeklammert werden, und schon ist unsere DLL auf 81,5k geschrumpft. Änderung #2 passiert in der Unit mit dem COM-Objekt-Code. In meinem Fall heißt sie "psheet_IMP.pas". Hier entfernen wir die Units "Classes.pas" und "StdVcl.pas", und unsere DLL ist nach dem Kompilieren nur noch 68k groß. Im Gegensatz zu den 315k ein recht annehmbares Ergebnis. Da ich aber nur ein relativ simples Beispiel geschrieben habe, hängt die tatsächliche Größe eurer eigenen Shell-Erweiterungen natürlich davon ab, was ihr mit ihnen vorhabt. Außerdem bitte ich zu bedenken, dass mein Lösungsweg kein reines NonVCL ist. Möglich also, dass eure Shell-Erweiterungen etwas größer oder aber auch etwas kleiner werden als mein Beispiel. Das war´s. Fehlermeldungen und/oder Ergänzungen bitte hier rein, oder an mich! Gruß, Mathias. |
Zitat |
MathiasSimmack
|
#9
Ich habe dieses Tutorial entfernt (auch aus den Tutorials), weil es kein echtes NonVCL war. Ich hätte euch stattdessen gern an die Demo von Andreas Kosch im Entwickler-Forum verwiesen, aber leider fiel sie einem Serverausfall, -crash o.ä. zum Opfer. Was es genau war, weiß ich nicht. Jedenfalls soll das Beispiel dort nicht mehr zu finden sein.
Darum habe ich mein altes "Tutorial" rausgekramt und stelle es hier inkl. Beispiel online. Allerdings ohne Support. Gruß. |
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |