15. Implementieren eines Dokument-Interfaces
15.2 Die UI: Datei-Menü und Toolbar 15.4 Standard Dokument Operationen 15.4.1 Ein neues Dokument anlegen15.5 Erweiterte Dateioperationen 15.8 Verwendung von Muster-Dateien 15.9 Schnittstelle zu GEOS Dateisystem
^15.1 KonzeptionellesIm PC/GEOS SDK wird die Arbeit mit Dokumenten durch drei Objektklassen realisiert. Diese Klassen erzeugen die benötigte UI selbst und arbeiten eng zusammen. Dazu müssen bestimmte Messages in bestimmter Weise gehandelt werden. Das ist so in R-BASIC nicht realisierbar. Deswegen muss sowohl die UI als auch die Routinen mit BASIC-Mitteln nachgebildet werden.Dieses Kapitel beschreibt, wie man zur Realisierung eines Dokument-Interfaces unter R-BASIC vorgehen muss. Alle hier beschriebenen Routinen und UI-Objekte sind dem Beispiel "Dokument Interface" entnommen, das sich im Ordner "R-BASIC\Beispiel\Objekte\Dateiarbeit" befindet. Dort finden Sie auch die hier aus Platzgründen nicht aufgeführten Objekte.
Um ein eigenes Dokument-Interface zu erstellen, sollten Sie in R-BASIC im Menü "Extras" den Punkt "Code-Sequenz" -> "Dokument-Interface" verwenden. Dort werden die in diesem Kapitel beschriebenen Routinen bzw. UI-Objekte bereitgestellt und automatisch in Ihren Code eingefügt. Dabei können Sie auswählen, ob Sie das komplette hier beschriebene Interface implementieren wollen oder nur Teile davon. Wenn Sie einmal verstanden haben, wie das Prinzip geht, können Sie auch leicht weitere Features, die hier nicht besprochen sind, hinzufügen.
Unser Dokument-Interface soll folgendes leisten:Standard-Dateioperationen:
Grundstruktur des Dokument-InterfaceDas folgende Bild zeigt alle Routinen, die zur vollständigen Implementation der oben genannten Fähigkeiten notwendig sind.
Die unten links angeordneten Routinen werden an vielen Stellen aufgerufen. Die Zuordnung erfolgt deshalb nicht durch Pfeile, sondern durch die in den Kästchen vermerkten Buchstaben. Zum Beispiel rufen alle Routinen, die ein "R" im Kästchen haben die Routine DoReadDataFromDoc. Im Bild sind drei Gruppen von Routinen zu sehen:
Bei der Verwaltung von Dokumenten fallen eine Reihe von Aufgaben an, die unabhängig von der Art und der Struktur der eigentlichen Dokumentdatei sind. R-BASIC unterstützt das durch die Bereitstellung einer Objektklasse und einer Library. Beide arbeiten eng zusammen. Die Objektklasse DocumentGuardian erleichtert Ihnen den Umgang mit Dokumenten, indem Sie allgemeine Informationen, die bei der Arbeit mit Dokumenten anfallen, verwalten. Dazu zählen z.B. der Name und der Pfad zur Dokumentendatei sowie das FileHandle der offenen Datei. Außerdem können Objekte der Klasse DocumentGuardian vorhandenen Dokumente öffnen, neue Dokumente anlegen und offene Dokumente schließen. Dabei berücksichtigen sie z.B. den Dateityp und das Token, behandeln schreibgeschützte Dateien korrekt und vieles mehr. Auf diese Weise entlasten diese Objekte den BASIC-Programmierer von einer Vielzahl von Standardaufgaben.
Die Library DocumentTools stellt eine Reihe von Funktionen bereit, die Sie bei der Arbeit mit Dokumenten unterstützen. Dazu gehören zum Beispiel die typischen Dialogboxen zum Öffnen oder Speichern einer Datei. Um die DocumentTools Library nutzen zu können müssen Sie sie ihn Ihr Programm einbinden: |
Include "DocumentTools" |
|
Die DocumentTools Library muss separat heruntergeladen werden, sie ist nicht Teil des R-BASIC Standard-Pakets. Wenn Sie mit VM-Dateien als Dokumente arbeiten wollen müssen Sie außerdem die VMFiles Library herunterladen und includen.
Um die in diesem Kapitel beschriebenen Funktionen und Zusammenhänge zu verstehen, sollten Sie zumindest den einführenden Abschnitt zum DocumentGuardian-Objekt (Objekt Handbuch, Kapitel 4.13), sowie das Kapitel 1 (Konzeptionelles) aus dem Handbuch der DocumentTools Library gelesen haben.
15.2 Die UI: Datei-Menü und ToolbarDas Dateimenü und die Toolbar bestehen aus einer Reihe von Buttons. Die Grafiken für die Buttons finden wir im Ordner "Icon Tool Graphics\Document", einem Unterordner von "USERDATA\R-BASIC\IMAGES".
Jeder Button hat als ActionHandler den zentralen Handler DocumentAndToolButtonHandler gesetzt und der actionData-Wert bestimmt die Funktion des Buttons. Aus Platzgründen sind nur einige der Objekte gezeigt. |
Group DocumentMenuButtons
Children = MenuNewOpenButton, MenuOpenButton, MenuCloseButton, MenuSaveButton,
MenuSaveAsButton, MenuBackupGroup, MenuOtherGroup
End OBJECT
Button MenuNewOpenButton
Caption$ = "Neu/Öffnen ", 0
ActionHandler = DocumentAndToolButtonHandler
actionData = ID_NEW_OPEN_DIALOG
BringsUpWindow
End OBJECT
Button MenuOpenButton
Caption$ = "Öffnen " , 1
ActionHandler = DocumentAndToolButtonHandler
actionData = ID_OPEN_DOC
BringsUpWindow
End OBJECT
...
|
|
|
Group DocumentToolGroup
Children = DocumentToolButtons
ExpandWidth
End OBJECT
Group DocumentToolButtons
Children = ToolNewButton, ToolOpenButton, ToolTemplateButton,
ToolCloseButton, ToolSaveButton, ToolBackupButton
OrientChildren = ORIENT_HORIZONTALLY
MakeToolbox
ExpandHeight
End OBJECT
Button ToolNewButton
CaptionImage = "Icon Tool Graphics\\Document\\NEW.GIF"
ActionHandler = DocumentAndToolButtonHandler
actionData = ID_NEW_DOC
ExpandHeight
End OBJECT
Button ToolOpenButton
CaptionImage = "Icon Tool Graphics\\Document\\OPEN.GIF"
ActionHandler = DocumentAndToolButtonHandler
actionData = ID_OPEN_DOC
ExpandHeight
End OBJECT
...
|
^15.3 Kernroutinen und ToolsDieser Abschnitt behandelt grundlegende Routinen, die zur Implementation der in den folgenden Abschnitten besprochenen Funktionen benötigt werden. Im Einzelnen sind das
Der Handler ist im Folgenden in Teilen wiedergegeben. In der ON - SWICTH Anweisung werden die Anweisungen entsprechend dem actionData Wert des gedrückten Buttons ausgeführt. Die dort verwendeten Routinen haben häufig selbsterklärende Namen. Sie werden weiter unten ausführlich beschrieben. Hingewiesen werden soll auf drei Dinge:
|
BUTTONACTION DocumentAndToolButtonHandler
DIM err
ON ACTIONData SWITCH
CASE ID_NEW_OPEN_DIALOG:
DTShowNewOpenDialog(ConvertObjForSDK(DocumentObj), NOF_NEW_OPEN_TEMPLATE +
NOF_CONFIG + NOF_IMPORT, "")
END CASE
CASE ID_NEW_DOC:
BasicCreateNewDoc
END CASE
CASE ID_OPEN_DOC:
BasicOpenDoc
END CASE
CASE ID_CLOSE_DOC:
err = BasicCloseDoc(TRUE) ! Änderungen speichern
IF err THEN RETURN
DoReadDataFromDoc ' UI updaten
DoUpdateDocButtons
End CASE
CASE ID_SAVE_DOC:
BasicSaveDoc
END CASE
... usw. ...
END SWICTH
END ACTION
|
|
Die Routine DoSetDocModified ermöglicht die Verwaltung der Information, ob das aktuelle Dokument geändert wurde oder nicht. Das ermöglicht z.B. der Routine BasicCloseDoc den Nutzer zu fragen, ob er die Änderungen speichern möchte oder nicht. Für diesen Zweck verwaltet das DocumentGuardian-Objekt das Bit DOCS_MODIFIED in seiner Instancevariablen documentState. Der Parameter "modi" bestimmt, ob das Dokument als "modifiziert" (modi = TRUE) oder als "nicht modifiziert" (modi = FALSE) gekennzeichnet werden soll. Entsprechend setzt die Methode SetDocumentState dieses Bit oder setzt es zurück. Abschließend wird mit DoUpdateDocButtons die UI angepasst. Sie sollten die Routine DoSetDocModified jedes Mal rufen, wenn der Nutzer Daten des Dokuments ändert. Typische Situationen sind der OnModified-Handler von Text-Objekten, die Apply-Handler der Objektklassen Number, Memo, InputLine sowie der Listenobjekte oder der ColorChangedHandler von ColorSelector-Objekten. Die Routine fragt jeweils selbst ab, ob sich der "modifiziert"-Zustand geändert hat und kehrt sofort zurück, wenn dem nicht so ist. Wichtig: Nehmen wir an, Sie haben ein Text-Objekt (Memo, InputLine), und das Dokument soll "modifiziert" sein, wenn der Nutzer etwas eingibt. Text-Objekte senden ihre OnModified-Message aber nur aus, wenn der Nutzer "erstmalig" etwas eingibt, das heißt wenn sie vom Zustand "nicht modifiziert" in den Zustand "modifiziert" wechseln. Deswegen müssen Sie den "modified" Zustand der betroffenen Textobjekte zurücksetzen, wenn DoSetDocModified mit dem Parameter modi=FALSE gerufen wird. Der Apply-Handler von Listen- und Number-Objekten wird im Allgemeinen jedes Mal aufgerufen, wenn er Nutzer etwas ändert. Deswegen ist es normalerweise nicht nötig, den Modified-Zustand dieser Objekte hier zu ändern. |
SUB DoSetDocModified (modi as INTEGER)
IF modi THEN
' ist schon "modified"? => Return
IF DocumentObj.documentState AND DOCS_MODIFIED THEN RETURN
DocumentObj.SetDocumentState DOCS_MODIFIED, 0
ELSE
' ist schon "nicht modified"? => Return
IF (DocumentObj.documentState AND DOCS_MODIFIED) = 0 THEN RETURN
DocumentObj.SetDocumentState 0, DOCS_MODIFIED
End IF
' -X- Bei Bedarf: weitere Aktionen.
' -> den "Modified" Status von Memo und InputLine,
' manchmal auch von Number und Listen-Objekten anpassen
' Sie sollten hier NICHT DoReadDataFromDoc rufen
DoUpdateDocButtons
END SUB
|
|
Die Sub DoInitDocumentGuardian initialisiert das DocumentGuardian-Objekt mit den Daten, die Ihrem Programm entsprechen. Im Beispiel soll das DocumentGuardian-Objekt eine GEOS Datendatei (fileType = GFT_DATA) mit dem Token "PHO2", 5 und dem CreatorToken "PHON",5 verwalten. Es ist wichtig, dass das hier angegeben Token mit dem DocToken-Statement und das CreatorToken mit dem AppToken-Statement des Application-Objekts im UI-Code übereinstimmen. Mit der Anweisung "guardian.ConfigData = dc" werden die Einstellungen an das DocumentGuardian-Objekt übertragen. Der ButtonHandler wird von der DocumentTools-Library für den "Neuen/Öffnen" Dialog benötigt. |
SUB DoInitDocumentGuardian(guardian as object) DIM dc AS DocumentConfigStruct guardian.ButtonHandler = DocumentAndToolButtonHandler dc.noDocumentString$ = "kein Dokument" dc.templateFolder$ = "DemoTemplates" dc.nameForNew$ = "Unbenanntes Dokument " dc.fileType = GFT_DATA dc.creatorToken.tokenChars = "PHON" dc.creatorToken.manufid = 5 dc.token.manufid = 5 dc.token.tokenChars = "PHO2" dc.matchFlags = DOC_MATCH_TOKEN guardian.ConfigData = dc END SUB |
|
Eine der wichtigsten Routinen ist die Sub DoUpdateDocButtons. Sie hat die Aufgabe, die Dokument-Buttons aus dem Datei-Menü und aus der Dokument-Toolbar sowie ihre eigene UI entsprechend der aktuellen Situation zu enablen oder zu disablen. Dadurch sorgt diese Routine dafür, dass einzelne Funktionen nur dann aufgerufen werden können, wenn sie zulässig sind. Zum Beispiel wird der "Speichern" Button disabled, wenn keine Datei offen ist. Im Allgemeinen müssen Sie diese Routine ergänzen. Ein typischer Fall ist, dass Sie UI Objekte haben (z.B. ein Memo-Objekt), in die der Nutzer Daten für das aktuelle Dokument eingeben kann. Diese UI muss disabled werden, wenn kein Dokument offen ist. Die Entscheidung, ob ein Button enabled sein soll oder nicht kann komplex und unübersichtlich sein. Deswegen stellt die DocumentTools Library die Routine DTFindEnabled bereit, die genau diese Information liefert. Welcher Button gemeint ist wird über eine Konstante bestimmt, die in der Library definiert ist. Sie wird auch für den actionData-Wert des Buttons benutzt wird. Der folgende Code zeigt die Routine in Ausschnitten. DTFindEnabled erwartet eine Referenz auf das DocumentGuardian-Objekt, die mit der BASIC-Routine ConvertObjForSDK konvertiert wurde. Die Variable docObj speichert die konvertierte Referenz auf das Objekt. Die Anweisung "DemoPrimary.Caption2$ = DocumentObj.documentName$" bewirkt, dass der Name des aktuell offenen Dokuments in der Titelzeile des Primary-Objekts angezeigt wird. Hier müssen Sie den Namen ihres eigenen Primary-Objekts verwenden. |
SUB DoUpdateDocButtons () DIM docObj as Object DIM docState docObj = ConvertObjForSDK(DocumentObj) MenuNewOpenButton.enabled = DTFindEnabled(docObj, ID_NEW_OPEN_DIALOG) MenuOpenButton.enabled = DTFindEnabled(docObj, ID_OPEN_DOC) MenuSaveAsButton.enabled = DTFindEnabled(docObj, ID_SAVE_AS_DOC) MenuCloseButton.enabled = DTFindEnabled(docObj, ID_CLOSE_DOC) ToolNewButton.enabled = DTFindEnabled(docObj, ID_NEW_DOC) ToolOpenButton.enabled = DTFindEnabled(docObj, ID_OPEN_DOC) ToolCloseButton.enabled = DTFindEnabled(docObj, ID_CLOSE_DOC) ... usw ... DemoPrimary.Caption2$ = DocumentObj.documentName$ ... hier eigene UI enablen/disablen ... END SUB |
|
Die Sub DoReadDataFromDoc hat die Aufgabe die im Dokument gespeicherten Daten auszulesen und darzustellen. Weil das immer vom konkreten Programm abhängt erinnert uns eine MsgBox daran, dass wir hier noch etwas programmieren müssen. Wichtig ist, dass wir den Fall "Kein Dokument offen" ebenfalls berücksichtigen. Das können wir durch Abfrage der Instancevariablen documentState auf Null oder wie im Beispiel durch Abfrage der Instancevariablen documentHandle auf NullFile() tun. Häufig werden hier UI-Objekte "geleert", Listen mit Null Einträgen versehen, Texte gelöscht usw. |
SUB DoReadDataFromDoc ()
IF DocumentObj.documentHandle == NullFile() THEN
MsgBox "DoReadDataFromDoc: Keine Datei offen. UI anpassen."
ELSE
MsgBox "DoReadDataFromDoc: Hier Daten aus der Datei lesen und
zugehörige UI anpassen."
End IF
END SUB
|
|
Die Routine DoSaveDataToDoc wird jedes Mal gerufen, wenn Daten dauerhaft in der Datei gespeichert werden sollen. Für VM-Dateien heißt dass, das VMSave gerufen werden muss. In welche Datei die Daten geschrieben werden sollen wird durch den übergebenen Parameter fh bestimmt. Sie dürfen hier NICHT auf DocumentObj.documentHandle zurückgreifen, weil die Routine je nach Kontext (z.B. erstellen eines Backups) auch für andere Dateien gerufen wird. |
SUB DoSaveDataToDoc (fh as FILE) MsgBox "DoSaveDataToDoc: Hier Daten in der Datei fh speichern" ' bei Bedarf: FileCommit (fh) END SUB |
|
Beim Herunterfahren von GEOS müssen Dokument-Daten, die in globalen Variablen zwischengespeichert sind, an einem sicheren Platz abgelegt werden. Ein sicherer Speicherplatz für globalen Variablen sind Instancevariablen von Objekten. Diese werden vom System automatisch gesichert. Wenn man genau eine globale Struktur, hat bietet sich die Instancevariable privData des Document- Guardian-Objekts dafür geradezu an. Die Routine DoSaveCachedData wird vom OnExit handler gerufen und muss die globalen Variablen sichern. Das Gegenstück DoReadCachedData wird vom OnStartup Handler gerufen und muss die globalen Variablen wieder herstellen. Für den Fall, dass Sie keine Dokument-Daten in globalen Variablen zwischenspeichern, können Sie diese Routinen löschen. |
SUB DoSaveCachedData (docObj as Object) ' Globals Struktur in "privData" speichern ' -X- docObj.privData = globalData, sizeof(GlobalDataStruct) END SUB 'DoSaveCachedData |
SUB DoReadCachedData (docObj as Object) ' DoReadCachedData: Hier globale Variablen wiederherstellen. ' Die UI muss nicht upgedatet werden ' -X- globalData = docObj.privData END SUB 'DoReadCachedData |
|
|
SUB DoEnterDocumentPath (forNew as Integer)
IF forNew THEN
SetStandardPath SP_TOP
ELSE
SetStandardPath SP_DOCUMENT
CreateDir "Subfolder"
SetCurrentPath "Subfolder"
END IF
END SUB
|
^ |