GNavigia selbst programmieren
Erweiterungen mittels C# hinzufügen - Schritt für Schritt
Auf dieser Seite werden wir die Frage betrachen, was Sie machen können, wenn GNavigia trotz seiner zahlreichen Funktionen partout nicht anbietet, was Sie sich wünschen. Nehmen wir einmal an, dass Sie einen exotischen GPS-Empfänger besitzen, der Dinge tut, die ich nicht vorhersagen konnte und die gewiss nicht zum Standard zählen. Dann können Sie hier eingreifen. Ein Beispiel:
Der GPS Empfänger Garmin etrex Vista HCx speichert Datum und Uhrzeit eines Wegpunkts in den Attributen Cmt und Desc der allgemein anerkannten Wegpunktbeschreibung. Das Datenformat, das sich als Standard etabliert hat, sieht für Wegpunkte kein Zeitattribut vor. GNavigia kann so ein Attribut verwalten, allerdings ist das am Anfang naturgemäß leer. Sie werden im Laufe dieser Dokumentation lernen, wie Sie das Problem mithilfe eines Clients lösen können. Dazu werden wir die Aufnahmenummer, die unter Name eingetragen ist, nach Cmt übertragen und das Attribut DateAndTime füllen. Desc werden wir löschen.
Voraussetzungen
Um GNavigia selbst zu programmieren benötigen Sie
- Ein Verzeichnis, in dem das Programm GNavigia.exe und seine DLLs enthalten sind. Für die Dokumentation hier nehme ich C:\Programme\GNavigia\GNavigia.exe an.
- Eine Entwicklungsumgebung (C#, VB oder C++) für Microsoft .NET 2.0 (3.5 tut es auch), bevorzugt Microsoft Visual Studio 2005 oder 2008. Als Privatmann benutzen Sie zweckmäßig die kostenlose Express-Edition. Hierauf bezieht sich die Dokumentation.
- Kenntnisse über das Innere von GNavigia, die Sie sich aus den Metadaten aneignen können. Eine Dokumention ist in Vorbereitung, wird aber den Rest dieses Jahres in Anspruch nehmen.
- Grundlegende Kenntnisse der Programmierung. Die folgenden Ausführungen richten sich definitiv nicht an Anfänger der Softwareentwicklung!
Ich hatte zunächst vor, das GpsDataEditor-Objekt nicht freizugeben, aber wenn man das im folgenden beschriebene Interface auf GpsDataEditor castet, hat man das Objekt «in der Hand». Damit können Sie programmieren, als läge GNavigia im Quellcode vor. Auch den können Sie auf Anfrage bekommen, aber er ist nicht nötig, es sei denn, Sie wollen mich tatkräftig unterstützen! Nehmen wir einmal an, dass Sie nur ihr eigenes Süppchen kochen wollen, dann würden Sie jede Quellcodeänderung nachvollziehen müssen. Die Interface-Methoden, die Ihnen GNavigia anbietet, werden sich allerdings nur noch bei groben Fehlern ändern. Dann können Sie eine Programmerweiterung schreiben, die auch in späteren Versionen noch laufen wird. Setzen Sie auf jeden Fall für den Verweis Spezifische Version auf false.
Das Client-Projekt
Legen Sie in der Entwicklungsumgebung ein neues Projekt an und zwar eine Klassenbibliothek; hier habe ich den sprechenden Namen GNavigiaGpsElementsClient vergeben. Nehmen Sie einen anderen, weil der sonst mit dem Namen des Clients bei der Installation von GNavigia kollidiert! Es ist gut, wenn der Name eindeutig ist, wirklich eindeutig sein muss am Ende aber der Name, mit dem sich der Client gegenüber GNavigia identifiziert. Zunächst genügt es, dem neuen Projekt die in Abbildung 2 angezeigten Verweise hinzuzufügen. Später brauchen Sie einige mehr.
Vergeben Sie auch einen starken Namen, dazu öffenen Sie die Eigenschaften des neuen Projekts, gehen nach Signierung und wählen in dem unteren Feld als Schlüsseldatei <Neu...> aus. Im Dialog geben Sie einen Dateinamen (ohne Pfad!) an, hier wieder GNavigiaGpsElementsClient. Der Name wird dem Projekt hinzugefügt; ich verschiebe den Namen bei offenen Fenstern nach Properties, wobei auch der Schlüsselname links unten angepasst wird.
Wenn Sie die Express-Edition einsetzen, können Sie unter dem Reiter Debuggen keine Applikation angeben, die gestartet werden soll, wenn Sie Ausführen! sagen. Damit Sie die Applikation GNavigia dennoch sofort starten können, können Sie unter Buildergeignisse/Postbuildereignis die Applikation angeben: C:\Programme\GNavigia\GNavigia.exe. Speichern Sie die Einstellungen indem Sie die Entwicklungsumgebung verlassen. Warum erzähle ich das, obgleich Sie auf diese Art den Debugger trotzdem nicht nutzen können? Nun, es entsteht neben der Projektdatei eine, die auf user endet. Editieren Sie diese mit einem Editor, der Unicode-Zeichen verarbeiten kann, und fügen Sie die folgenden Zeilen ein:
<StartAction>Program</StartAction>
<StartProgram>C:\Programme\GNavigia\GNavigia.exe</StartProgram>
Entfernen Sie nach dem Neustart der Entwicklungsumgebung zunächst das Postbuildereignis und füllen Sie die Felder Arbeitsverzeichnis und Befehlszeilenargumente im Reiter Debuggen aus. Die Parameter werden auf das zu startende Programm bezogen, nicht auf die DLL. Wer die Express Edition von C++ benutzt, kann die Einträge übrigens direkt im Dialog vornehmen.
Die Konfiguration
Ich 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!
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.
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.
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.
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 eigenen 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:
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