Session D-HOOK

Hook mal wieder!

Jürgen Wondzinski
ProLib Software GmbH


Einleitung

Im täglichen Umgang mit FoxPro ist der Projektmanager ein ständig benutztes Tool. Und obwohl schon sehr machtig, gibt es doch auch hier noch Raum für Erweiterungswünsche (wär ja auch das Erste Mal, dass was von Microsoft absolut perfekt wäre, gell?)  Wie wärs zB mit ner kleinen Routine, die beim Projekt-Öffnen auch in das betreffende Projektverzeichnis wechselt? Und dabei auch gleich die Drag'nDrop Klassenzuordnung auf die Projektklassen anpasst? Und auch noch die FORM-Basisklasse umsetzt? Und beim Erstellen der EXE automatisch eine ZIP Datei erstellt, für Archivierungszwecke? Oder einen vernünftigen Erstellen-Dialog anbietet (ohne die öden abgeschnittenen Beschriftungen)  Oder gleich nen komplett anders designten Projektmanager? Alles machbar, Herr Nachbar.  Denn natürlich bietet auch hier FoxPro die Möglichkeit, entsprechend selbst Hand anzulegen. Wie also kommen wir ran?

Grundlagenforschung

Des Pudels Kern ist wieder mal die wunderbare offene Architektur von FoxPro. Innerhalb VFP haben wir netterweise vollen Zugriff auf alle momentan geöffneten Projekte, welche als Collection zur Verfügung stehen. Huh? Dder Microsoft-Slang für Collection ist "Sammlung", aber stellen sie sich darunter einfach ein "1-spaltiges Array mit Objektreferenzen" vor. Sicherlich ist Ihnen bekannt, dass man über die Collection namens  _SCREEN.FORMS auf alle laufenden Masken zugreifen kann. Und dass es einen Verweis auf die aktuell aktive Maske mittels _SCREEN.ACTIVEFORM gibt.  Nun, genau das selbe finden wir für Projekte, nur ist die Projekt-Collection nicht der Masterform _SCREEN unterstellt, sondern dem Applikationsobjekt _VFP. Mit _VFP haben sie eigentlich den Zugriff auf die echte FoxPro-Engine, wohingegen _SCREEN nur eine spezialisierte Untermenge, nämlich das sichtbare FoxPro-Hauptfenster, darstellt.  Genaugenommen verbirgt sich hinter _VFP die Automatisierungsschnittstelle von FoxPro; genauso, wie wenn sie

    oVfp = CREATEOBJECT("VisualFoxPro.Application")

eingeben würden. (Mit dieser Programmzeile haben Sie nun ein zweites VFP unsichtbar mitlaufen; im Gegensatz dazu ist _VFP der Verweis auf die aktuelle Instanz.)

Und weil es in FoxPro immer mindestens zwei Wege gibt, so können wir anstelle von _VFP auch mittels APPLICATION zugreifen. (Allerdings lautet die erste Merkregel eines jeden objektorientiert denkenden Programmierers: "Sei Faul!" und daher bevorzuge ich die kürzere "_VFP" Version)

Wichtig ist noch, dass jeder Zugriff auf das _VFP- oder APPLICATION- Objekt über die Automatisierungsschnittstelle (auch OLE oder ActiveX genannt) läuft,  sodass a) Die Geschwindigkeit nicht ganz so gut ist wie beim direkten internen Zugriff (was uns aber hier nicht sonderlich stört, da wir ja als Entwickler eh genug Zeit haben...), und b) eventuelle Programmfehler immer als OLE-Fehler (meistens Nr 1426) zurückgemeldet werden.

So, nu haben wir also VFP im Zugriff. Innerhalb unseres VFP Objektes gibt es prinzipiell drei Collections: Die schon von _SCREEN bekannten FORMS, die OBJECTS (die eigentlich identisch zu FORMS sind, und aus Vereinheitlichungsgründen mit aufgenommen wurden) und die PROJECTS. Jedes offene Projekt ist einfach ein weiterer Eintrag in der Projects Collection (oder klassisch: Array) . Jede Collection hat immer zwei Eigenschaften: COUNT, die quasi die Arraydimension darstellt, und das Array selbst mit dem Namen ITEM.

Folglich könnten wir alle offenen Projekte so durchlaufen:

    FOR i = 1 TO _VFP.PROJECTS.COUNT
       ? _VFP.PROJECTS.ITEM(i).Name
    ENDFOR

Geht aber wieder auch auf anderem Wege: Wesentlich eleganter ist der Zugriff auf das gesamte Collection-Objekt mit dem FOR EACH Konstrukt:

    FOR EACH oProj IN _VFP.PROJECTS
       ? oProj.Name
    ENDFOR

Hier braucht man keine Zählvariable, man muss nicht das ITEM Array auslesen, nicht aufs Ende der Collection aufpassen, und bekommt den jeweiligen gewählten Eintrag direkt als Variable. Sehr praktisch!

Übrigens klappt das FOR EACH mit jedem einspaltigen Array, und der Arrayinhalt muss auch nicht unbedingt ein Objekt sein.

Collections haben noch eine weitere Eigenheit: Man kann die Arrayebene entweder numerisch angeben, oder mit dem NAME Eintrag, also entweder Typ "N" oder "C":

    MODI PROJ Test
    ? _VFP.PROJECTS(1).Name
    ? _VFP.PROJECTS("Test.pjx").Name

Obwohl Projekte in dem NAME Property den kompletten Pfad mitführen, reicht hier der eigentliche Dateiname, um den Eintrag wiederzufinden. Dies klappt auch mit den anderen Collections FORMS und OBJECTS vom _VFP, allerdings nicht mit den gleichlautenden Eigenschaften des _SCREEN Objekts oder anderen VFP-eigenen Containerobjekten (Pageframe, Pages, Buttongroup, Grid usw) ! Hier sehen wir also live den Unterschied zwischen Collection und simplen Array.

Natürlich gibts auch den anonymen Zugriff :

    ? _VFP.ActiveProject.Name

verweist immer auf das Projektfenster, das OnTop ist.

So, nun haben wir genug von der unwahrscheinlich interessanten Collection Theorie, wie geht's weiter? Wir haben also ein Projekt als Objekt im Zugriff, also untersuchen wir das mal.

Das Project Objekt

hat natürlich, wie jedes andere Objekt auch, eine ganze Reihe von PEMs (PEMs = Properties, Events und Methods). Als Methoden gibt's im Prinzip nur die programmatischen Aufrufe der Projekt-Schaltflächen:

Build

Damit wird die APP/DLL/EXE neu erstellt. Die diversen Option des Erstellen Dialogs sind als Parameter hier wiederzufinden.

CleanUp

Führt ein PACK auf die PJX aus. Wenn ein .T. mit übergeben wird, dann werden alle Kompilate mitgelöscht, was ein "Alles neukompilieren" beim nächsten BUILD zwangsweise auslöst.

Close

Naja... Schliesst das Projekt.

Refresh

Damit wird das Projekt komplett neu eingelesen, und der Treeview neu aufgebaut. Notwendig nach programmatischem Hinzufügen von Dateien.

SetMain

Definiert das Hauptprogramm

Unter den Eigenschaften finden wir alles, was sich irgendwo im Projektfenster, im Versionsdialog oder im Projektinfo-Dialog als Wert eingeben lässt (bis auf die Copyright-Infos, seltsamerweise)

Eigenschaft

Zugriff

Typ

Bedeutung

AutoIncrement

S
L
Soll die Versionsnummer hochgezählt werden, wenn EXE oder DLL erstellt wird

BuildDateTime

L
T
DAtum / Zeit der letzten Erstellung

Debug

S
L
Debug-Info für jede Datei generieren?

Encrypted

S
L
Verschlüsseln

HomeDir

S
C
Ausgangsverzeichnis für die Relativen Pfadangaben bei den Dateien

Icon

S
C
Icondatei der EXE

MainClass

L
C
Klassen-Name; nur wenn eine ActiveDoc Klasse als Hauptdatei definiert ist. Wird mit der SETMAIN Methode gesetzt.

MainFile

L
C
Startprogramm / Startklassenbibliothek. Wird mit der SETMAIN Methode gesetzt.

Name

L
C
Projektname, incl Pfad

ProjectHook

S
O
Objektreferenz zum verwendeten ProjectHook.

ProjectHookClass

S
C
Klassenname des ProjektHooks

ProjectHookLibrary

S
C
Klassenbibliothek des ProjektHooks.

SCCProvider

L
C
Name des Quellcodeverwaltungs-Programms

ServerHelpFile

S
C
Hilfedatei für die Typelib  bei COM-Server

ServerProject

S
C
Linker Teil der , mit der COM-Server in die Registry eingetragen werden. Defaultmässig gleich ProjektName

TypeLibCLSID

L
C
Die zugewiesene CLSID für die TypeLib.

TypeLibDesc

S
C
Info für die *.TLB Datei bei COM-Server.

TypeLibName

L
C
Pfad und Name der TLB Datei

VersionComments

S
C
VersionInfo:  Kommentar

VersionCompany

S
C
VersionInfo: Firma

VersionCopyright

S
C
VersionInfo: Copyright

VersionDescription

S
C
VersionInfo: Beschreibung

VersionLanguage

S
C
VersionInfo: Sprache

VersionNumber

S
C
VersionInfo: Nummer; 3x 4Stellen

VersionProduct

S
C
VersionInfo: Produktname

VersionTrademarks

S
C
VersionInfo: Warenzeichen

Visible

S
L
PJ-Manager sichtbar?

Zugriff: L = Nur lesen,  S = Lesen/Schreiben

So, das waren die Methoden und Eigenschaften. Fehlen nur noch die Ereignisse; doch die suchen wir vergebens. Das ist ähnlich dem Problem "Wie kann ich Code zu den _Screen-Ereignissen (zB Resize) zufügen"? Geht nicht, denn der Projektmanager läuft ja schon.  Und genau da kommen nun die ProjektHooks ins Spiel! Die ProjektHook Klasse ist nichts anderes als ein Liste aller Projekt-Ereignisse, für die ich nun Code schreiben kann. Durch Zuweisen der Klasse zu einem Projekt wird nun bei jeder Aktion auch der entsprechende Aufruf an das ProjektHook-Objekt weitergereicht, und wir können darauf reagieren

Und so sieht nun das Projekt-Objekt aus:

  Ausgehend vom Applikationsobjekt, also _VFP, haben wir beliebig viele Projekte (gesammelt als Projects Collection). Diese haben wiederum die Erweiterung durch die Hook-Objekte.

Und dann sehen wir noch zwei weitere Collections pro Projekt: Files und Servers. Über diese kommen Sie dann an die jeweiligen einzelnen Dateien ran, die in jedem Projekt definiert sind.

Die Files und Server Collection sind genaugenommen auch Eigenschaften des Projektobjekts, quasi wie Arrays. Und in diesen Arrays sind nun wieder Verweise (Objektreferenzen) zu den File-Objekten und COM-Server Objekten hinterlegt.

Die Anzahl Dateien ist wieder im COUNT Property der Collection zu finden und um also alle im Projekt verwendeten Dateien aufzulisten, könnten Sie nun schreiben:

    FOR EACH oFile IN _VFP.ActiveProject.Files
       ? oFile.Name
    ENDFOR

Die Files-Collection hat auch noch eine eigene Methode: ADD, mit der sie neue Einträge in der Files-Collection erstellen können. Sie gibt eine Referenz auf das neue File-Objekt zurück. Das File-Objekt kann dann wiederum mit seiner Methode REMOVE (siehe weiter unten) entfernt werden.

Und natürlich haben die Files auch wieder ihre PEMs:

Eigenschaft

Zugriff

Typ

Bedeutung

CodePage

L
N
Codepagenummer, siehe GETCP()

Description

S
C
Beschreibung

Exclude

S
L
.T. , wenn Ausgeschlossen

FileClass

L
C
Bei Forms: Die verwendete Grundklasse

FileClassLibrary

L
C
Bei Forms: Klassenbibliothek für Grundklasse.

LastModified

L
T
Datum/Zeit der letzten Änderung.

Name

L
C
Name, mit vollem Pfad

ReadOnly

L
L
.T. für schreibgeschützt

SCCStatus

L
N
Status der Quellcode-Verwaltung. 0: Keine Kontrolle, ansonsten 1-6 diverse Zustände, siehe VFP Hilfe "SCCStatus-Eigenschaft"

Type

L
C
Datei-Typ; die wichtigsten: V = VCX, P = Programm, R = Report, B = Etikett, K = Form, Q = Abfrage, D = DBF, d = DBC, M = Menü, T = Text, x = sonstiges.

Aufpassen beim Abfragen der Typ-Eigenschaft: Hier wird sowohl "d" als auch "D" codiert; bei Verwendung von SET COLLATE = "GENERAL" ist ja Klein- gleich Grossschreibung.daher unbedingt mit Binärvergleich (zwei Gleichheitszeichen) arbeiten!  IF oFile.Type == "d"

Das Files-Objekt hat natürlich auch Methoden:

AddToSCC

Fügt eine Datei in einem Projekt einer Quellcodeverwaltung hinzu.

CheckIn

Checkt die Änderungen ein, setzt ReadOnly auf .t.

CheckOut

Auschecken aus der Quellcodeverwaltung, dadurch ReadOnly auf .f.

GetLatestVersion

Synchronisiert die lokale Kopie mit der Zentralversion, ohne Checkout

Modify

Ruft den passenden Editor auf (bei Klassen den Klassennamen mitgeben!)
=> QueryModifyFile Event

Remove

Entfernt Datei aus FilesCollection, => QueryRemoveFile Event

RemoveFromSCC

Aus QuellCodeverwaltung rausnehmen

Run

Ausführen (Bei Reports/Etiketten wird Preview gestartet) => QueryRunFile Event

UndoCheckOut

Änderungen verwerfen 

Die dabei ausgelösten QueryXXX Ereignisse werden ebenfalls über unseren ProjectHook abgefangen.

Die dritte Collection sind die diversen COM-Server, die wir in einem Projekt definieren können. Hier gibt es nur Eigenschaften:

Eigenschaft

Zugriff

Typ

Beschreibung

CLSID

L
C
Die CLSID für den Server.

Description

S
C
dazugehörige Beschreibung in der Registry.

HelpContextID

S
N
Hilfetext ID.

Instancing

S
N
Einfach / Mehrfach-Instanziierung: 1 Single, 3 multi-instance.

L
C
Die des Servers in der Registry.

ServerClass

L
C
Klassenname.

ServerClassLibrary

L
C
Klassenbibliothek.

So, damit haben wir jetzt direkten Zugriff auf alle Projekt-, Datei- und COMServer Eigenschaften und wir können auch alle vorhanden Methoden aufrufen (und damit ein Projket komplett fernbedienen).

ProjektHook aktivieren

Zum Reagieren auf die Ereignisse müssen wir nun eine ProjektHook Klasse ankoppeln, voraussetzend, dass wir auch schon eine solche Klasse erstellt haben. Dies kann zum einen pro Projekt im jeweiligen Projekt-Info Dialog definiert werden.Oder aber zentral, für alle zukünftigen Projekte, im Menü unter Extras -> Optionen -> Projekte eingetragen werden. Dies ändert aber nicht mehr schon bestehende Projekte, die sie nun alle einzeln nachbearbeiten müssen. Wenn sie dem aktuell offenen Projekt die ProjekctHook-Info per Eigenschaft-setzen zuweisen, dann wird das Hookobjekt erst aktiviert, wenn das Projekt das nächste Mal geöffnet wird. Sie können aber auch selbst ein Hookobjekt instanziieren und dessen Objektreferenz dem Projekt zuweisen:

    _VFP.ActiveProjekt.ProjektHook = NEWOBJECT("MeiHook","MeiHookLib.VCX")

Dies ist aber nur ein temporärer Vorgang, der nicht dazu führt, dass die Hookinfo dauerhaft im PJX abgelegt wird.

Wenn Sie ein Projket mal ausnahmsweise ohne Hook öffnen wollen, dann verwenden Sie den Zusatz NOPROJEKTHOOK beim MODIFY PROJECT  bzw beim CREATE PROJECT.  Hier gibt es übrigens auch noch den Zusatz NOSHOW, der das Projekt zwar öffnet, aber den Projektmanager nicht anzeigt (also VISIBLE auf .F. setzt). Das ist der Trick um seinen eigenen Projektmanager anstelle des Standardmanagers anzuzeigen.

ProjectHook Ereignisse

Die ProjektHook Klasse hat keine speziellen Eigenschaften und Methoden (ausser den üblichen PEMs, die FoxPro braucht, um die Klassen vom Designer anzusprechen).

Daher sind das einzig Interessante die Ereignisse, die immer aufgerufen werden; egal ob Sie nun interaktiv die Schaltflächen im Projektmanager betätigen, oder programmatisch die zugrundeliegenden Methoden verwenden.

AfterBuild

Aufruf, nachdem das Erstellen fertig ist.

BeforeBuild

Aufruf, bevor das Projekt erstellen anstartet. Mit NODEFAULT wird der Vorgang abgebrochen.

OLEDragOver

Wenn eine Datei per Drag'nDrop über den Projektmanager gezogen wird.

OLEDragDrop

Eine Datei wurde auf den Projektmanager gedroppt. Nach diesem Event kommt dann noch der QueryAddFile

QueryAddFile

Eine Datei wird dem Projekt zugefügt. NODEFAULT verhindert dies.

QueryModifyFile

Aufruf, bevor der jeweilige Editor anstartet. NODEFAULT verhindert das Editieren.

QueryRemoveFile

Aufruf, bevor eine Datei gelöscht wird. NODEFAULT verhindert...

QueryRunFile

Aufruf vor dem Ausführen. Und natürlich verhindert ein NODEFAULT den Vorgang.

Aufpassen: Obwohl alle QUERYxxxx Ereignisse mittels NODEFAULT einen Vorgang abbrechen, und folglich VOR der Aktion ablaufen, wird der QueryAddFile erst aufgerufen, wenn der jeweilige Editor geschlossen wurde, und die Datei tatsächlich dem Projekt zugefügt wird. Ich hatte gehofft, auf diese Weise , dem CREATE FORM beizubringen, zuerst nach der zu verwendenden Basisklasse zu fragen, aber funktioniert so nicht.  Hier fehlt also noch ein BeforeAddFile Event.

Funktionen erweitern

Anhand der nun vollständigen Liste kann man nun seiner Phantasie freien Lauf lassen, und sich entsprechend austoben. Einige Ideen hab ich Ihnen ja schon am Anfang aufgezeigt,  schauen Sie sich dazu auch noch die Beispiele zur Session an.

Die aktuellste Version finden Sie bei mir auf der Webseite unter:

http://www.prolib.de/DevCon00

ProjectHookX

Über kurz oder lang werden Sie feststellen, dass man bei verschiedenen Projekten nicht immer die selben Tätigkeiten braucht. Am Anfang wird man möglichereweise mit einigen DO CASE Schleifen das aufkommende Chaos beherrschen können, aber viel besser wäre es, wenn man verschiedene Hook-Klassen angeben könnte. Allerdings kann das Projekt eben nur einen Hook ansteuern. Hier kommt nun die PublicDomain Erweiterung ProjectHookX ins Spiel, die von Mike und Tone Feltman (F1 Technologies) entwickelt wurde. Sie geben die projectHookX Klasse als zentrale Hookklasse an, diese überprüft dann anhand einer speziellen DBF, welche einzelnen "Sub-Hook"-Klassen dann für das jeweilige Objekt mit angesprochen werden. Im Prinzip wird damit also eine Hook-Collection verwaltet. Sehr praktisch!

vorheriger  Vortrag D-WOOD

zur Übersicht der Gruppe PROG

nächster Vortrag D-MENU

 

dFPUG c/o ISYS GmbH

Frankfurter Str. 21 b

 

D-61476 Kronberg

per Fax an:

+49-6173-950903

oder per e-Mail an:

konferenz@dfpug.de

Texte, Grafiken und Inhalt: ISYS GmbH