| |
GNavigia selbst programmieren
|
![]() Abb. 2: C# Entwicklungsumgebung mit den
wichtigsten Einstellungen
|
|
Die KonfigurationIch habe mir zum Ziel gesetzt, die Entwicklung einer Erweiterung ohne Administratorrechte zu ermöglichen. Das erfordert, dass die DLL nicht registriert werden muss, wie das bei den Servern für die Hintergrundbilder notwendig ist. Um sie GNavigia bekannt zu machen, bedienen wir uns der Konfigurationsdatei, die jeder .NET Komponente beigestellt werden kann. Diese Datei liegt im selben Verzeichnis wie GNavigia.exe selbst. Sie ist eine XML-Datei namens GNavigia.exe.config. Diese Datei können Sie in jedem herkömmlichen XML-Editor bearbeiten, brauchen für diesen Vorgang ggf. aber einmalig Administratorrechte. Machen Sie eine Sicherungskopie ihrer Änderungen, da diese Datei bei der nächsten Installation von GNavigia überschrieben werden wird! |
|
![]() Abb. 3: Die Datei GNavigia.exe.config
|
|
| Im ersten Schritt gibt es für Sie zwei
interessante Einträge, log4net und GNavigia.GpsElementHandler.
Letzterer lädt Ihre DLL! Befassen Sie sich aber auch mit
log4net. Der Grund: Wenn Sie die Protokollausgabe Ihrer
Software über diesen Weg ausgeben, haben Sie genau eine Datei, die
Sie beobachten müssen, nämlich die, die in der log4net
section unter value des Parameters File angegeben
ist. Zudem ist die Ausgabe synchron zu der von GNavigia,
was die Beurteilung der Abläufe vereinfacht und erfordert zugleich
nur sehr wenig Overhead. Sie werden log4net im Quellcode
wiederbegegnen. An dieser Stelle sollten Sie zunächst nur den
Dateinamen ändern, falls Ihnen die Vorgabe nicht zusagt. In die Konfigurationsdatei wird unter appSettings der Schlüssel GNavigia.GpsElementHandler eingetragen mit dem Wert: Ihre Client-DLL, vollständiger Pfadname. Aus Darstellungsgründen habe ich den Pfad stark abgekürzt. Wichtig ist, dass die keys alle eindeutig sein müssen! Daher müssen Sie für eine zweite DLL deren key um beliebige (gültige) Zeichen erweitern. GNavigia lädt hemmungslos alles, was mit GNavigia.GpsElementHandler beginnt. Zerstören Sie bei Änderungen nicht die Struktur der XML-Datei, da sonst auch GNavigia selbst nicht mehr richtig funktioniert. Um log4net nutzen zu können, müssen Sie den Verweis auf die DLL hinzufügen. Auch diese finden Sie im Installationsverzeichnis von GNavigia. |
|
Der Quellcode - Teil I
Wenn Sie beim Anlegen des Projekts keine Fehler gemacht haben, sollten
Sie eine Quellcodedatei antreffen, die rudimentär ausgefüllt
ist. Sie können die Datei GNavigiaGpsElementsClient.cs
als Bestandteil dieser Dokumentation laden, allerdings kann der Inhalt
von der Beschreibung hier abweichen, da ich mir vorbehalte, die Datei
für weitere Themen zu modifizieren. Die Laufendhaltung der
zahlreichen Bildschirmfotos ist mit vertretbarem Aufwand ohnehin nicht
möglich. |
|
![]() Abb. 3: Die Datei
|
|
| Um innerhalb der Klasse mit der Variablen log
auf die Ausgabe zugreifen zu können, wird ein logger
angelegt mit vollständigem Namen, d.h. inkl. Namespace. Dadurch
entfallen using-Direktiven. Die Deklaration besagt meist protected,
sodass Sie nicht mehr darüber nachdenken müssen, wenn Sie
eine Klasse von dieser Klasse ableiten wollen, aber das steht ihnen
frei. log4net kennt Methoden zur Ausgabe von Zeichenketten (log.Debug,
log.Info) und zur Formatierung derselben, die dann z. B. log.InfoFormat
heißen und die Parameter an String.Format weiterreichen. |
|
![]() Abb. 4: Der Block Logger (log4net)
|
|
| Wenn Sie nie mit Schnittstellen gearbeitet
haben, dann wird es Zeit, das jetzt zu tun. Schlagen Sie die Hilfe auf
und befragen Sie die .NET Dokumentation. Fakt ist, wenn Sie behaupten,
dass Sie eine Klasse von einer Schnittstelle abgeleitet haben,
dann wacht der Compiler darüber, dass Sie die dort geforderten
Methoden auch implementieren. Sie wissen, dass C# keine Mehrfachvererbung kennt. Schnittstellen werden zwar wie Basisklassen notiert, sind aber keine. Daher können Sie beliebig viele Schnittstellen angeben. Und ich versichere Ihnen, dass Sie das zur Nutzung zukünftiger Funktionalität auch müssen! Das wichtigste an Schnittstellen ist, dass sie sich nicht mehr verändern, wenn sie einmal veröffentlicht sind. Daran halte ich mich jetzt. Schnittstellen, die geändert werden müssten, erhalten Pendants, es gibt dann also neue Schnittstellen. Es gibt zwei Arten, Schnittstellen zu erzeugen, implizit und explizit. Ich setze die Theorie als bekannt voraus. In diesem Beispiel werden wir Schnittstellenmethoden grundsätzlich explizit deklarieren, also private (aber ohne Schlüsselwort) und mit dem Präfix des Schnittstellennamens. Im vorliegenden Fall haben wir es mit genau einer Schnittstelle zu tun, IGpsElementClient, und deren Methoden. Damit der Bildschirm nicht überläuft, schauen wir sie uns Methode für Methode an. Die Variablen, die im Spiel sind, werden wir analysieren, sobald deren Typ wichtig wird. Zunächst sind das meist Zeichenketten. |
|
![]() Abb. 5: Konstruktor |
|
| Der Quasi-Konstruktor tut nichts,
außer
einer Ausgabe ins Logfile und der Sicherung der Serverreferenz, die
über eine Schnittstelle realisisert wird, die der Server zur
Verfügung stellt. Hier sehen wir auch den Vorteil der Reflexion:
Wir können die erste Zeile in jede Methode kopieren und bekommen
immer den richtigen Namen ausgegeben. GNavigia
nutzt Reflexion übrigens auch für eine
extrem einfache Art
der Implementierung von Undo/Redo. Des weiteren erkennt man, dass ich Klassenvariable immer mit "m_" anfangen lasse. Für den Konstruktor ist return true Pflicht, sonst wird die Verbindung nicht weiter verfolgt! |
|
![]() Abb. 6: Das Client-Menü
|
|
| Vielleicht sollte ich kurz erklären, wie GNavigia die Verbindung zum Client herstellt. Der Name der DLL wird aus der Konfiguration gelesen, die DLL dynamisch geladen und untersucht, welche Klasse das IGpsElementClient Interface implementiert. Dann wird eine Instanz der Klasse erzeugt und die Methode Initialize am Interface aufgerufen. Dabei wird eine Referenz auf das IGpsElementServer Interface des Servers an den Client übergeben. Dieser merkt sich die Referenz und ruft später seinerseits Methoden an diesem Interface auf, u. a. werden wir die Liste aller Wegpunkte benötigen, die dem Client auf diese Art und Weise bereitgestellt wird. | |
| Nach erfolgreicher Initialisierung
fragt der Server den
Client zunächst einmal, ob dieser ein Menü bereitstellen
möchte, was dieser immer mit einer gültigen
Menüreferenz beantworten sollte. Das Menü des Clients muss
zugleich den benötigten EventHandler bereitstellen. Was der en
detail tut, sehen wir uns im Teil II an, wo wir auf die Änderung
von Attributen an GPS-Elementen näher eingehen und das IGpsElementServer
Interface betrachten werden. Zunächst definieren wir
eine OnMenuItemClicked Methode, die wir allen MenuItem
Einträgen mitgeben. In der Methode unterscheiden wir die
Menüpunkte anhand des Namens, daher müssen wir hier den
Menüpunkten eindeutige Namen zuordnen. Das Ampersand kennzeichnet
den Mnemonic, also den unterstrichenen Buchstaben im Menü. Zuletzt
erzeugen wir das Hauptmenü, das die Bezeichnung Client bekommt,
und weisen diesem die Liste der Untermenüpunkte zu. Zugleich
erzeugen wir eine OnMenuPopup Methode, die uns später in
die Lage versetzen wird, vor dem Aufklappen des Menüs diejenigen
Menüpunkte auszugrauen, die ohnehin nicht erreichbar sind. Wenn
Sie nur einen einzigen Menüpunkt haben, brauchen Sie auch keine
Menühierarchie. Geben Sie einen einzelnen Menüpunkt
zurück. |
|
![]() Abb. 7: OnMenuPopup-Event |
|
| Eine OnMenuPopup Methode ist in für den Fall nutzlos, da Sie ein einzelnes MenuItem zurückgeben. Das Menü wird von GNavigia nämlich wie folgt berücksichtigt: Besitzt das Menü Unterpunkte, so wird es nach dem Menü Extras im Hauptmenü der Applikation angezeigt, es sei denn, Sie hätten den zweiten Parameter, die Referenz fAddToMainMenu auf false gesetzt. In diesem Fall und falls es keine Unterpunkte gibt, wird das Menü am Ende des Menüs Extras eingereiht. Das erste der verfügbaren Menüs wird, falls mehrere Clients in der Konfiguration eingetragen sind, mit einer Trennlinie von den vorausgehenden Menüpunkten abgehoben. Die Unterbringung im Hauptmenü ist in Abbildung 6 rechts unten eingearbeitet. | |
![]() Abb. 8: Das OnPaint-Event |
|
| Damit der Client in der Lage ist, Objekte selbst
zu zeichnen, benötigt er sowohl Angaben zur Zeichenfläche als
auch zum Zeitpunkt der Aktualisierung der Darstellung. Zeichnen Sie
niemals unaufgefordert! Warten Sie auf das Signal! Das OnPaint-Event macht es Ihnen leicht. Es reicht ein GpsDataEditor.GpsGraphics Objekt in die Methode hinein, das nicht nur die erforderlichen Angaben enthält, sondern auch Methoden, die es erlauben, entsprechend den aktuellen Einstellungen Texte auszugeben. Insbesondere Methoden zur Freistellung von Text sind hier erwähnenswert. Das Ereignis liefert auch einen Hinweis darauf, ob man sich am Anfang oder am Ende der Zeichenfolge befindet. Ist fOnBeginPaint false, zeichnet man on top, sonst vor allen anderen Routinen. Eine eingehendere Beschreibung folgt, wenn Zeichnen Thema der Erläuterungen ist. Für unser Beispiel benötigen wir keine eingenen Zeichenroutinen. |
|
![]() Abb. 9: Die Events OnReadData/OnWriteData |
|
| Damit der Client eigene Daten verwalten
kann, stellt GNavigia eine Möglichkeit bereit, diese Daten
zusammen mit denen der Applikation in die GTD-Dateien zu schreiben. Um
zu verhindern, dass ein Fehler des Clients zum Verlust der
Originaldatei führt, wurde das Speichern überarbeitet. Erst
wenn formal keine Fehler festgestellt wurden, wird die alte Datei durch
die neue ersetzt. Wenn der Client eine nicht mehr konsistente
XML-Struktur hinterlässt, kann das hingegen nicht festgestellt
werden. Prüfen Sie die Datei nach jedem Speichern. Laden Sie sie
in die Entwicklungsumgebung und lassen Sie ggf. spezielle XML-Editoren
Fehler im inneren XML suchen. Der innere XML-Code, der vom Client beim Aufruf von OnWriteData an GNavigia zurückgegeben wird, besteht aus einer einzigen Zeichenkette, die in die GTD-Datei eingebettet wird. Jeder Client kann genau eine Zeichenkette zum Speichern zurückgeben. Das innere XML kann beliebig komplex sein. Das Ergebnis des o. a. Codes ist im folgenden Bild festgehalten, die Zeichenkette im Attribut name ist der, der in der Variablen clientName zurückgegeben wird. |
|
![]() Abb. 10: Das Ergebnis von OnWriteData |
|
| Hinweis: Zurzeit gibt es keine Anfrage an
den Client, ob
seine Daten gespeichert werden müssen. Dieses Problem sollte vor
langer zeit «in
einer der
nächsten Versionen» behoben werden. Das ist leider immer
noch nicht der Fall. Zuletzt werden wir im Teil I einen Blick auf den Konstruktor und die Klassenvariablen werfen. Die Referenz zum Server IGpsElementServer wird zum Zugriff auf die Daten benötigt. Das Interface stellt folgende Methoden zur Verfügung:
|
|
![]() Abb. 11: Klassenvariable und Konstruktor |
|
| Der Konstruktor muss Parameterlos sein. Es gibt
auch keinen Grund, warum Sie eine Überladung schaffen sollten.
Würden Sie dem Konstruktor einen formalen Parameter
hinzufügen, würde der Client ignoriert. Diese Klasse wird
allein für die Kommunikation benötigt und eine Instanz von
Typ GNavigiaGpsElementsClient wird nur von der Applikation
angelegt. Wir nutzen den Konstruktor lediglich zur Ausgabe einer
Nachricht ins Logfile. Am Ende unserer Bemühungen führen wir das Programm aus. Die DLL sollte korrekt erstellt und GNavigia.exe gestartet werden. Wenn Sie das Programm sofort wieder beenden, erhalten Sie die folgende Ausgabe im Logfile: |
|
![]() Abb. 12: Die Datei GNavigia.log
|
|