GNavigia selbst pro­gram­mie­ren

Er­wei­te­run­gen mit­tels C# hin­zufügen - Schritt für Schritt


Auf die­ser Sei­te wer­den wir die Fra­ge be­tra­chen, was Sie ma­chen können, wenn GNavigia trotz sei­ner zahl­rei­chen Funk­tio­nen par­tout nicht an­bie­tet, was Sie sich wünschen. Neh­men wir ein­mal an, dass Sie ei­nen exo­ti­schen GPS-Empfänger be­sit­zen, der Din­ge tut, die ich nicht vor­her­sa­gen konn­te und die ge­wiss nicht zum Stan­dard zählen. Dann können Sie hier ein­grei­fen. Ein Bei­spiel:


Abb. 1: Weg­punk­te des Gar­min etrex Vis­ta HCx

Der GPS Empfänger Gar­min etrex Vis­ta HCx spei­chert Da­tum und Uhr­zeit ei­nes Weg­punkts in den At­tri­bu­ten Cmt und De­sc der all­ge­mein an­er­kann­ten Weg­punkt­be­schrei­bung. Das Da­ten­for­mat, das sich als Stan­dard eta­bliert hat, sieht für Weg­punk­te kein Zei­tat­tri­but vor. GNavigia kann so ein At­tri­but ver­wal­ten, al­ler­dings ist das am An­fang na­tur­gemäß leer. Sie wer­den im Lau­fe die­ser Do­ku­men­ta­ti­on ler­nen, wie Sie das Pro­blem mit­hil­fe ei­nes Cli­ents lösen können. Da­zu wer­den wir die Auf­nah­me­num­mer, die un­ter Na­me ein­ge­tra­gen ist, nach Cmt übert­ra­gen und das At­tri­but Da­teAn­dTi­me füllen. De­sc wer­den wir löschen.

Voraus­set­zun­gen

Um GNavigia selbst zu pro­gram­mie­ren benöti­gen Sie
  • Ein Ver­zeich­nis, in dem das Pro­gramm GNavigia.exe und sei­ne DLLs ent­hal­ten sind. Für die Do­ku­men­ta­ti­on hier neh­me ich C:\Pro­gram­me\GNavigia\GNavigia.exe an.
  • Ei­ne Ent­wick­lungs­um­ge­bung (C#, VB oder C++) für Mi­cro­soft .NET 2.0 (3.5 tut es auch), be­vor­zugt Mi­cro­soft Vi­su­al Stu­dio 2005 oder 2008. Als Pri­vat­mann be­nut­zen Sie zweckmäßig die kos­ten­lo­se Ex­press-Edi­ti­on. Hier­auf be­zieht sich die Do­ku­men­ta­ti­on.
  • Kennt­nis­se über das In­ne­re von GNavigia, die Sie sich aus den Me­ta­da­ten an­eig­nen können. Ei­ne Do­ku­men­ti­on ist in Vor­be­rei­tung, wird aber den Rest die­ses Jah­res in An­spruch neh­men.
  • Grund­le­gen­de Kennt­nis­se der Pro­gram­mie­rung. Die fol­gen­den Ausführun­gen rich­ten sich de­fi­ni­tiv nicht an Anfänger der Soft­wa­re­ent­wick­lung!
Ich hat­te zunächst vor, das GpsDa­taE­di­tor-Ob­jekt nicht frei­zu­ge­ben, aber wenn man das im fol­gen­den be­schrie­be­ne In­ter­face auf GpsDa­taE­di­tor cas­tet, hat man das Ob­jekt «in der Hand». Da­mit können Sie pro­gram­mie­ren, als läge GNavigia im Quell­code vor. Auch den können Sie auf An­fra­ge be­kom­men, aber er ist nicht nötig, es sei denn, Sie wol­len mich tat­kräftig un­terstützen! Neh­men wir ein­mal an, dass Sie nur ihr ei­ge­nes Süpp­chen ko­chen wol­len, dann würden Sie je­de Quell­codeände­rung nach­voll­zie­hen müssen. Die In­ter­face-Metho­den, die Ih­nen GNavigia an­bie­tet, wer­den sich al­ler­dings nur noch bei gro­ben Feh­lern ändern. Dann können Sie ei­ne Pro­gram­mer­wei­te­rung schrei­ben, die auch in späte­ren Ver­sio­nen noch lau­fen wird. Set­zen Sie auf je­den Fall für den Ver­weis Spe­zi­fi­sche Ver­si­on auf fal­se.

Das Cli­ent-Pro­jekt

Le­gen Sie in der Ent­wick­lungs­um­ge­bung ein neu­es Pro­jekt an und zwar ei­ne Klas­sen­bi­blio­thek; hier ha­be ich den spre­chen­den Na­men GNavigiaGpsEle­ment­sC­li­ent ver­ge­ben. Neh­men Sie ei­nen an­de­ren, weil der sonst mit dem Na­men des Cli­ents bei der In­stal­la­ti­on von GNavigia kol­li­diert! Es ist gut, wenn der Na­me ein­deu­tig ist, wirk­lich ein­deu­tig sein muss am En­de aber der Na­me, mit dem sich der Cli­ent ge­genüber GNavigia iden­ti­fi­ziert. Zunächst genügt es, dem neu­en Pro­jekt die in Ab­bil­dung 2 an­ge­zeig­ten Ver­wei­se hin­zu­zufügen. Später brau­chen Sie ei­ni­ge mehr.
Ver­ge­ben Sie auch ei­nen star­ken Na­men, da­zu öffe­nen Sie die Ei­gen­schaf­ten des neu­en Pro­jekts, ge­hen nach Signie­rung und wählen in dem un­te­ren Feld als Schlüssel­da­tei <Neu...> aus. Im Dia­log ge­ben Sie ei­nen Da­tein­amen (oh­ne Pfad!) an, hier wie­der GNavigiaGpsEle­ment­sC­li­ent. Der Na­me wird dem Pro­jekt hin­zu­gefügt; ich ver­schie­be den Na­men bei of­fe­nen Fens­tern nach Pro­per­ties, wo­bei auch der Schlüssel­na­me links un­ten an­ge­passt wird.
Wenn Sie die Ex­press-Edi­ti­on ein­set­zen, können Sie un­ter dem Rei­ter De­bug­gen kei­ne Appli­ka­ti­on an­ge­ben, die ge­st­ar­tet wer­den soll, wenn Sie Ausführen! sa­gen. Da­mit Sie die Appli­ka­ti­on GNavigia den­noch so­fort star­ten können, können Sie un­ter Buil­der­g­eig­nis­se/Post­buil­der­eig­nis die Appli­ka­ti­on an­ge­ben: C:\Pro­gram­me\GNavigia\GNavigia.exe. Spei­chern Sie die Ein­stel­lun­gen in­dem Sie die Ent­wick­lungs­um­ge­bung ver­las­sen. Wa­rum erzähle ich das, ob­gleich Sie auf die­se Art den De­bug­ger trotz­dem nicht nut­zen können? Nun, es ent­steht ne­ben der Pro­jekt­da­tei ei­ne, die auf user en­det. Edi­tie­ren Sie die­se mit ei­nem Edi­tor, der Uni­code-Zei­chen ver­ar­bei­ten kann, und fügen Sie die fol­gen­den Zei­len ein:
<Star­tAc­ti­on>Pro­gram</Star­tAc­ti­on>
<Star­tPro­gram>C:\Pro­gram­me\GNavigia\GNavigia.exe</Star­tPro­gram>
Ent­fer­nen Sie nach dem Neu­start der Ent­wick­lungs­um­ge­bung zunächst das Post­buil­der­eig­nis und füllen Sie die Fel­der Ar­beits­ver­zeich­nis und Be­fehls­zei­len­ar­gu­men­te im Rei­ter De­bug­gen aus. Die Pa­ra­me­ter wer­den auf das zu star­ten­de Pro­gramm be­zo­gen, nicht auf die DLL. Wer die Ex­press Edi­ti­on von C++ be­nutzt, kann die Ein­träge übri­gens di­rekt im Dia­log vor­neh­men.

Abb. 2: C# Ent­wick­lungs­um­ge­bung mit den wich­tigs­ten Ein­stel­lun­gen

Die Kon­fi­gu­ra­ti­on

Ich ha­be mir zum Ziel ge­setzt, die Ent­wick­lung ei­ner Er­wei­te­rung oh­ne Ad­mi­nis­tra­tor­rech­te zu ermögli­chen. Das er­for­dert, dass die DLL nicht re­gis­triert wer­den muss, wie das bei den Ser­vern für die Hin­ter­grund­bil­der not­wen­dig ist. Um sie GNavigia be­kannt zu ma­chen, be­die­nen wir uns der Kon­fi­gu­ra­ti­ons­da­tei, die je­der .NET Kom­po­nen­te bei­ge­stellt wer­den kann. Die­se Da­tei liegt im sel­ben Ver­zeich­nis wie GNavigia.exe selbst. Sie ist ei­ne XML-Datei na­mens GNavigia.exe.con­fig. Die­se Da­tei können Sie in je­dem herkömm­li­chen XML-Edi­tor be­ar­bei­ten, brau­chen für die­sen Vor­gang ggf. aber ein­ma­lig Ad­mi­nis­tra­tor­rech­te. Ma­chen Sie ei­ne Si­che­rungs­ko­pie ih­rer Ände­run­gen, da die­se Da­tei bei der nächs­ten In­stal­la­ti­on von GNavigia über­schrie­ben wer­den wird!

Abb. 3: Die Da­tei GNavigia.exe.con­fig

Im ers­ten Schritt gibt es für Sie zwei in­ter­essan­te Ein­träge, lo­g4­net und GNavigia.GpsEle­mentHand­ler. Letz­te­rer lädt Ih­re DLL! Be­fas­sen Sie sich aber auch mit lo­g4­net. Der Grund: Wenn Sie die Pro­to­kol­l­aus­ga­be Ih­rer Soft­wa­re über die­sen Weg aus­ge­ben, ha­ben Sie ge­nau ei­ne Da­tei, die Sie be­ob­ach­ten müssen, nämlich die, die in der lo­g4­net sec­ti­on un­ter va­lue des Pa­ra­me­ters Fi­le an­ge­ge­ben ist. Zu­dem ist die Aus­ga­be syn­chron zu der von GNavigia, was die Be­ur­tei­lung der Abläufe ver­ein­facht und er­for­dert zu­gleich nur sehr we­nig Over­head. Sie wer­den lo­g4­net im Quell­code wie­der­be­geg­nen. An die­ser Stel­le soll­ten Sie zunächst nur den Da­tein­amen ändern, falls Ih­nen die Vor­ga­be nicht zu­sagt.
In die Kon­fi­gu­ra­ti­ons­da­tei wird un­ter ap­pSet­tings der Schlüssel GNavigia.GpsEle­mentHand­ler ein­ge­tra­gen mit dem Wert: Ih­re Cli­ent-DLL, vollständi­ger Pfad­na­me. Aus Dar­stel­lungs­gründen ha­be ich den Pfad stark ab­gekürzt. Wich­tig ist, dass die keys al­le ein­deu­tig sein müssen! Da­her müssen Sie für ei­ne zwei­te DLL de­ren key um be­lie­bi­ge (gülti­ge) Zei­chen er­wei­tern. GNavigia lädt hem­mungs­los al­les, was mit GNavigia.GpsEle­mentHand­ler be­ginnt.
Zerstören Sie bei Ände­run­gen nicht die Struk­tur der XML-Datei, da sonst auch GNavigia selbst nicht mehr rich­tig funk­tio­niert.
Um lo­g4­net nut­zen zu können, müssen Sie den Ver­weis auf die DLL hin­zufügen. Auch die­se fin­den Sie im In­stal­la­ti­ons­ver­zeich­nis 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.
Die Ausgangsdatei muss in mannigfacher Hinsicht ergänzt werden, so zunächst um die Referenzen auf die drei KorKarNet (ich brauchte einen eindeutigen Namen!) Komponenten. Dann folgen die Referenzen, die System.Collections, die wir brauchen, weil die Anfänge der Entwicklung nach .NET 1.1 zurück reichen, sowie System.Reflection, die wir für die auf Reflexion basierende Ausgabe der Methodennamen benötigen, sowie System.Windows.Forms für das Einhängen in das Applikationsmenü sowie den Aufruf von Dialogfeldern und die Ausgabe von Fehlermeldungen.
Die Klasse selbst muss public angelegt werden, was zu überprüfen wäre. Als Namespace nehmen Sie entweder den Projektnamen oder einen, den Sie auch für andere Entwicklungen benutzt haben. Für die Funktion unerlässlich ist die Ableitung der Klasse von IGpsElementClient. Schlagen Sie nach dem Eintrag das Kontextmenü über dem Wort auf und wählen Sie aus der Liste Schnittstelle explizit implementieren aus. Damit werden die Methoden, die mit IGpsElementClient members, explicit implementation geklammert sind, implementiert werden. Dazu später mehr. Ihre Klasse sollte aus folgenden Abschnitten (#region/#endregion) bestehen:
  • Logger
  • Variablen und Kontruktoren
  • Methoden der IGpsElementClient-Schnittstelle
  • Private Methoden

Wir werden diese Abschnitte nach und nach aufklappen und nachsehen, wie sie im Einzelnen implementiert sind.


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:

  • TreeView GpsObjectTree() - Wurzelelement des Objektbaums. Ein Thema für Fortgeschrittene.
  • List<GpsTrackpoint> GpsTrackpoints(GpsTrack gpsTrack) - Liste aller Trackpoints eines Tracks.
  • List<GpsTrack> GpsTracks() - Liste aller verfügbaren Tracks.
  • List<GpsWaypoint> GpsWaypoints() - Liste aller Wegpunkte.
  • IGpsDataEditor IGpsDataEditor() - Interface, das ausgewählte Methoden des GpsDataEditor bereitstellt, in Entwicklung. Wird zurzeit nur für den Refresh der Oberfläche benötigt. Durch einen cast auf GpsDataEditor erschließen Sie sich GNavigia als Ganzes! Aber das haben Sie nicht von mir!
  • void SetStatusBarText( string text) - Set den Haupttext der Statuszeile. Threadsicher.

Zwei weitere Variablen identifizieren den Client nach außen, cs_clientName ("cs_" steht für const static) und cs_clientID. Während cs_clientName auch eine beliebige, mit Leerzeichen versehenen Zeichenkette enthalten könnte, empfehle ich, cs_clientID mit einer GUID zu belegen. Sie können hier auch "Hugo" schreiben, aber dann könnte es Probleme geben, wenn ein zweiter Client ebenfalls diese außerordentlich originelle ID wählt. Die Zuordnung der gespeicherten Daten zum Client erfolgt über diesen Namen! Doppelte Einträge führen zu unvorhersehbaren Problemen. Also merke: Nicht der Name muss global eindeutig sein sondern die Client-ID. Die ID ist eine Zeichenkette, die eine GUID repräsentiert. Benutzen Sie zur Erzeugung eines solchen Namens GUIDGEN.EXE und entfernen Sie die geschweiften Klammern!


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

Der Quellcode - Teil II

Wir haben unser Ziel, die Bearbeitung der Wegpunkte, nicht aus dem Auge verloren, auch wenn wir uns bis hierher nur mit dem Rahmen beschäftigt haben, in den letztlich alle Erweiterungen von GNavigia eingebettet werden müssen. Wenn sie bis hier gekommen sind, die Applikation läuft und Sie das Logfile so vor sich sehen, wie zuvor beschrieben, stehen die Chancen gut für eine Implementierung der Wegpunktberichtigung.
Bevor wir uns aber auf die Implementierung stürzen, sei auf einen wichtigen Umstand hingewiesen, der leicht übersehen werden kann. Der Client ist immer Bestandteil der Applikation und nicht Teil eines bestimmten Dokumentfensters. Daher liefern Anfragen an den Server immer die Daten des aktiven Dokuments. Ist kein Fenster geöffnet oder enthält das aktive Dokument keine passenden Daten, so wird eine leere Menge zurückgegeben. Hierbei gilt: Liefert eine Schnittstellenmethode eine Liste zurück, so ist das leere Ergebnis niemals null sondern immer eine Liste mit Count == 0. Diesen Umstand haben wir uns in Abbildung 7 bereits zunutze gemacht.
Mit Abbildung 13 kommen wir nun zum Kern der Sache, der Methode OnMenuItemClicked, die ich Ihnen so lange vorenthalten habe. Das MenuItem mit Name WDK verzweigt zur Bearbeitung. Wir zählen die Anzahl der tatsächlich geänderten Wegpunkte in der Variablen count. Zugleich berücksichtigen wir, dass die Analyse des Datums, so wie der Empfänger es schreibt, nicht unbedingt von Standardmethoden erkannt und fehlerfrei interpretiert wird. Daher klammern wir die Methode DateTime.Parse in einen try/catch Block. Zuvor stellen wir durch die Methode Split sicher, die gegen date == null gesichert ist, dass die formalen Voraussetzungen für die Interpretation des Datums gegeben sind.
Fehler geben wir ins Logfile aus. Dort schauen wir nach, wenn die Anzahl der behandelten Wegpunkte von unseren Erwartungen abweicht. Man könnte auch noch die Anzahl der Fehler zählen und als dritte Zeile in der MessageBox ausgeben, aber das würde den Rahmen der Dokumentation sprengen. Zudem untersuchen wir vorab, ob nicht vielleicht schon zuvor Änderungen stattgefunden haben. Da wir bei Erfolg das Attribut Desc löschen, können wir Wegpunkte aus der Behandlung ausschließen, für die Desc ungleich Cmt ist. Der mehrfache Aufruf der Methode liefert dann das Ergebnis, dass die Anzahl der Änderungen 0 ist. Zuletzt weisen wir Cmt den Wert des Attributs Name zu, sofern dieser eine ganze Zahl ist, also noch nicht von Hand gesetzt wurde.


Abb. 13: Die Methode OnMenuItemClicked

Die Konstruktion m_gpsElementServer as IWin32Window liefert das Fensterhandle der Applikation, sodass ein Dialog oder eine MessageBox ein korrektes parent window mitgegeben bekommen. Schludern Sie hier nicht und benutzen Sie diese Möglichkeit!
Nun müssen wir nur noch schauen, ob unsere Änderungen tatsächlich vorgenommen worden sind. Dazu öffnen wir den Reiter Wegpunkte im Verwaltungsfenster der Applikation und klappen einen Wegpunkt auf. Das Ergebnis ist in Abbildung 14 festgehalten und entspricht unseren Erwartungen. Zugleich ist die Anzeige im Hauptfenster aktualisiert. Sollte das einmal nicht geschehen, rufen Sie m_gpsElementServer.IGpsDataEditor ab, prüfen den Wert auf null und rufen dann die Methode GpsRedraw an diesem Interface auf.
  Achtung: Speichern Sie keine vergänglichen Referenzen! Die einzige Referenz, die immer gilt, ist die IGpsElementServer Referenz, die im Konstruktor übergeben wird. Alle anderen Referenzen hängen davon ab, ob überhaupt ein Dokument existiert und welches das ist. Starten Sie keine eigenen Threads! Das wird nicht funktionieren, wenn Oberflächenelemente ins Spiel kommen. Das Setzen der Statuszeile ist die einzige Ausnahme. Dort werden fremde Threads korrekt behandelt.


Abb. 14: Das Ergebnis am Beispiel von Wegpunkt 003

Nun bleibt zum Schluss nur noch zu erwähnen, dass die Änderungen in den Tiefen von GNavigia stattgefunden haben und daher fester Bestandteil des Undo/Redo-Mechanismus sind. Um Darstellungsfläche zu sparen wurde die Liste in Abbildung 15, die erwartungsgemäß 9 Wegpunkte umfasst, zusammengeschoben. Die Liste liest sich etwas anders als andere Wiederherstellen-Listen: Es wird in jeder Zeile dargestellt, welche Aktion ein Undo auslöst. In der obersten Zeile erkennt man, dass das auf null gesetzte Attribut Desc mit dem Wert des ursprünglichen Datums belegt werden würde. Die Liste wird riesig, wenn Sie sehr viele Daten auf einmal ändern. Eine Zusammenfassung von Aktionen ist zurzeit noch nicht möglich.


Abb. 15:  Die Undo-Liste nach den Änderungen