^

15.5 Erweiterte Dateioperationen

In diesem Abschnitt wird beschrieben, wie die Operationen "Kopieren nach", "Verschieben nach", "Umbenennen" und das Ändern der Benutzernotizen implementiert werden. Die entsprechenden Routinen BasicCopyToDoc, BasicMoveToDoc, BasicRenameDoc und BasicChangeUsernotes werden vom DocumentAndToolButtonHandler aufgerufen, wenn der Nutzer den entsprechenden Menüpunkt anklickt.

Kopieren nach ...

Die Funktion "Kopieren nach" erstellt eine Kopie des aktuellen Standes unseres Dokuments unter einem neuen Namen. Die von BasicCopyToDoc aufgerufenen Routinen haben im Folgenden dargestellten Aufgaben. Sollte ein Fehler oder ein Nutzerabbruch möglich sein, wird dieser jeweils abgefragt und die Routine BasicCopyToDoc wird verlassen.
  • DoEnterDocumentPath wechselt in den Pfad, in dem Dokumente normalerweise abgelegt werden.

  • DTMoveCopyDialog( ... FALSE) zeigt den "Kopieren nach..."-Dialog an. Die Strukturvariable ret enthält danach alle nötigen Informationen.

  • DTConfirmAndDelete(ret.fileName$) prüft, ob schon eine Datei des gewünschten Namens vorhanden ist und löscht diese nach entsprechender Nachfrage beim Nutzer.

  • DTCloneAndOpenFile legt eine 1:1-Kopie des aktuell geöffneten Dokuments an und öffnet diese. NewFile enthält das FILE Handle der Kopie und die globale Variable fileError enthält im (sehr unwahrscheinlichen) Falle eines Fehlers den Fehlercode.

  • DoSaveDataToDoc(newFile) bringt die Kopie auf den neuesten Stand und DTCloseClone(newFile) schließt die Kopie.
Da wir an der Originaldatei nichts geändert haben, brauchen wir die UI nicht upzudaten.
SUB BasicCopyToDoc ()
DIM ret as DialogReturnStruct
DIM err
DIM newFile AS FILE

  DoEnterDocumentPath(FALSE)

  ret = DTMoveCopyDialog(ConvertObjForSDK(DocumentObj), "", FALSE)
  IF ret.retInfo = DRI_CANCEL THEN RETURN

  err = DTConfirmAndDelete(ret.fileName$)
  IF err THEN RETURN

  newFile = DTCloneAndOpenFile(ConvertObjForSDK(DocumentObj), ret.fileName$)
  IF fileError THEN RETURN
  DoSaveDataToDoc(newFile)
  DTCloseClone(newFile)
  
END SUB

Verschieben nach ...

Die Funktion "Verschieben nach" verschiebt das aktuelle Dokument an einen neuen Ort. Dazu muss das aktuelle Dokument zunächst kopiert werden. War das erfolgreich wird es geschlossen und gelöscht. Danach wird die Kopie geöffnet. Beachten Sie, dass sich durch dieses Vorgehen das FILE Handle des Dokuments ändert.
  • Die Sequenz aus DoEnterDocumentPath und DTMoveCopyDialog( ... FALSE) lässt den Nutzer den neuen Namen und den neuen Pfad des Dokuments eingeben und wechselt in den neuen Pfad. DTConfirmAndDelete(ret.fileName$) stellt sicher, dass keine Datei mit dem neuen Namen am Zielort existiert.

  • DTCloneFile legt die erforderliche 1:1-Kopie an. Schlägt das fehl brechen wir den Vorgang ab.
Eigentlich kann ab jetzt nichts mehr schief gehen. Trotzdem programmieren wir etwas auf Sicherheit.
  • Wir merken uns den vollständigen Pfad zur aktuell geöffneten Datei sowie ihren "modified" Zustand. Beachten Sie, dass oldFile$ als String(230) deklariert ist, damit wirklich der komplette Pfad abgelegt werden kann.

  • Die Methode CloseDocument schießt das aktuelle Dokument, OpenDocument (ret.FileName$) öffnet die 1:1-Kopie.

  • War das Öffnen erfolgreich enthält die Instancevariable documentState einen Wert ungleich Null. In diesem Fall können wir die Originaldatei beruhigt löschen. Außerdem passen wird den Documentstatus an.

  • Abschließend rufen wir DoUpdateDocButtons. Das bewirkt insbesondere, dass der Name der neuen Datei in der Titelzeile des Primary-Objekts angezeigt wird.
SUB BasicMoveToDoc ()
DIM ret as DialogReturnStruct
DIM err, oldState
DIM oldFile$ as STRING(230)

  DoEnterDocumentPath(FALSE)
  ret = DTMoveCopyDialog(ConvertObjForSDK(DocumentObj), "", TRUE)
  IF ret.retInfo = DRI_CANCEL THEN RETURN
  err = DTConfirmAndDelete(ret.fileName$)
  IF err THEN RETURN

  DTCloneFile(ConvertObjForSDK(DocumentObj), ret.fileName$)
  IF fileError THEN RETURN

  oldFile$ = DocumentObj.documentPath$ + "\\" + \
  DocumentObj.documentname$
  oldState = DocumentObj.documentState AND \
 (DOCS_MODIFIED OR DOCS_EDIT_TEMPLATE)
  DocumentObj.CloseDocument
  DocumentObj.OpenDocument(ret.FileName$)
  if ( DocumentObj.documentState ) THEN
    DocumentObj.SetDocumentState oldState, 0
    FileDelete oldFile$
  END IF
  DoUpdateDocButtons
  
END SUB

Umbenennen

Um eine Datei umbenennen zu können müssen wir sie zunächst schließen. Dann können wir sie umbenennen und mit neuem Namen wieder öffnen.
  • SetCurrentPath(DocumentObj.documentPath$) wechselt in den Pfad, in dem sich das aktuell geöffnete Dokument befindet.

  • Die Sequenz aus DTRenameDialog und DTConfirmAndDelete(ret.fileName$) ermöglicht dem Nutzer einen neuen Namen einzugeben und stellt sicher, dass keine Datei mit diesem Namen im aktuellen Ordner existiert.

  • Wir merken uns den Namen (oldFile$) und den Zustand (oldState) des aktuell geöffneten Dokuments und schließen es dann mit CloseDocument.

  • FileRename oldFile$, ret.fileName$, "m" erledigt das Umbenennen. Der Parameter "m" bewirkt, dass im (extrem unwahrscheinlichen Fall) eines Problems eine entsprechende Fehlermeldung ausgegeben wird.

  • Im Fehlerfall enthält die globale Variable fileError einen Fehlerwert (ungleich Null). In diesem Fall öffnen wird die originale Datei wieder, ansonsten die umbenannte.

  • In jedem Fall updaten wir den Dokumentstatus (SetDocumentState) und die UI (DoUpdateDocButtons).
SUB BasicRenameDoc ()
DIM ret as DialogReturnStruct
DIM err, oldState
DIM oldFile$ as STRING(32)

  SetCurrentPath(DocumentObj.documentPath$)
  ret = DTRenameDialog(ConvertObjForSDK(DocumentObj), "")
  IF ret.retInfo = DRI_CANCEL THEN RETURN
  err = DTConfirmAndDelete(ret.fileName$)
  IF err THEN RETURN

  oldFile$ = DocumentObj.documentName$
  oldState = DocumentObj.documentState AND (DOCS_MODIFIED OR DOCS_EDIT_TEMPLATE)
  DocumentObj.CloseDocument
  FileRename oldFile$, ret.fileName$, "m"

  IF fileError THEN
    DocumentObj.OpenDocument(oldFile$)
  ELSE
    DocumentObj.OpenDocument(ret.FileName$)
  END IF

  DocumentObj.SetDocumentState oldState, 0
  DoUpdateDocButtons

END SUB

Benutzernotizen ändern

Das Ändern der Benutzernotizen übernimmt die Routine DTChangeUsernotes aus der DocumentTools Library. Sie erledigt alle notwendigen Schritte, einschließlich der Anzeige der entsprechenden Dialogbox sowie der Prüfung, ob das aktuell geöffnete Dokument überhaupt Benutzernotizen unterstützt.
SUB BasicChangeUsernotes ()
  DTChangeUsernotes(ConvertObjForSDK(DocumentObj))
END SUB

^

15.6 Letzter Stand

Klickt der Nutzer im Menü auf den entsprechenden Button so wird im DocumentAndToolButtonHandler die Routine BasicRevertDoc gestartet. Diese Routine prüft zur Sicherheit, ob das Dokument überhaupt geändert wurde und fragt dann den User, ob er sicher ist. Sodann ruft es DoRevertDoc, dass die eigentliche Arbeit erledigt und updatet dann mit DoReadDataFromDoc und DoUpdateDocButtons die UI. Wichtig ist, dass mit DoSetDocModified (FALSE) der "modified" Zustand des Dokuments zurückgesetzt wird.
SUB BasicRevertDoc ()
DIM ans, docState

  docState = DocumentObj.documentState
  IF (docState AND DOCS_MODIFIED) = 0 THEN RETURN

  ans = QuestionBox ("Sind Sie sicher, dass Sie alle Änderungen
                     seit dem letzten Speichern verwerfen wollen?")
  IF ans <> YES THEN RETURN

  DoRevertDoc

  DoSetDocModified (FALSE)
  DoReadDataFromDoc
  DoUpdateDocButtons

END SUB
Nur VM-Dateien unterstützen ein echtes "Zurück zum letzten gespeicherten Stand". Wenn Sie VM-Dateien als Dokumente benutzen, können Sie beliebig Daten in die Datei schreiben und trotzdem VMRevert benutzen, um den letzten gespeicherten Stand wieder herzustellen. Deshalb werden VM-Dateien von allen großen Applikationen wie GeoWrite und auch R-BASIC selbst als Dokumente benutzt.

Wenn Sie sich, wie in unserem Beispiel, gegen VM-Dateien entscheiden, ist der einfachste Weg, eine "Revert"-Funktion zu unterstützen, alle Änderungen der Dokumentdaten in Instancevariablen oder globalen Variablen zu speichern und nicht in die Datei zu schreiben, bis der Nutzer explizit "Speichern" wählt. In diesem Fall besteht das Wiederherstellen des letzten gespeicherten Standes einfach darin, die (ungeänderten) Daten aus dem Dokument wieder auszulesen. DoRevertDoc hat dann nichts zu tun, weil das Auslesen der Daten von BasicRevertDoc erledigt wird, nachdem es DoRevertDoc aufgerufen hat.

Dieses Konzept hat einen großen Nachteil. Im Falle eines Fehlers gehen alle geänderten Daten verloren, weil sie nirgends in einer Datei gespeichert wurden.

Auch wenn DoRevertDoc nichts tut, wird es von allen Routinen, die ein Zurücksetzen der Datei auf den letzten gespeicherten Stand erwarten, gerufen. Deshalb, wenn Sie ein Konzept haben, nicht-VM-Dateien auf ihren letzten gespeicherten Stand zurückzusetzen, so können Sie es hier implementieren.

Wenn Sie VM-Dateien benutzen braucht DoRevertDoc nur VMRevert aufzurufen.

SUB DoRevertDoc ()

  MsgBox "DoRevertDoc: Datei auf den letzten gespeicherten Stand bringen - 
          falls  es dazu etwas zu tun gibt."
  ' Im aktuellen Konzept hat DoRevertDoc nichts zu tun
  ' Sie können die MsgBox einfach entfernen
  ' Für VM-Dateien: VMRevert(DocumentObj.documentHandle) rufen
  
END SUB

^

15.7 Quick Backup

Klickt der Nutzer im Menü auf den entsprechenden Button so wird im DocumentAndToolButtonHandler die Routine BasicQuickBackup bzw. BasicRestoreFromBackup gerufen.

BasicQuickBackup legt eine Kopie der Datei im Backup-Ordner (SP_BACKUP) an. Dort werden üblicherweise keine Unterordner verwaltet. Zunächst wird ein eventuell vorhandenes älteres Backup gelöscht. DTCloneAndOpenFile legt eine 1:1 Kopie der aktuell offenen Datei an und öffnet diese. Im Fehlerfall gibt es eine Fehlermeldung, ansonsten wird die Backupkopie mit DoSaveDataToDoc auf den neuesten Stand gebracht und danach geschlossen. In jedem Fall rufen wir DoUpdateDocButtons um den "Aus Backup wiederherstellen"-Button zu updaten.

SUB BasicQuickBackup ()
DIM fileName$ ', docState, ro
DIM docPath$ as string(200)
DIM backupFile as FILE

  fileName$ = DocumentObj.documentName$
  SetStandardPath SP_BACKUP

  FileDelete fileName$
  backupFile = DTCloneAndOpenFile(ConvertObjForSDK(DocumentObj), fileName$)
  IF fileError THEN
    MsgBox "Konnte Backup-Datei nicht anlegen. Fehlercode: "
            + ErrorText$(fileError)
  ELSE
    DoSaveDataToDoc(backupFile)
    DTCloseClone(backupFile)
  End IF

  DoUpdateDocButtons

END SUB
BasicRestoreFromBackup stellt eine Datei aus einer Backupkopie wieder her. Dazu gehen wir folgendermaßen vor:
  • Wir merken uns Name (fileName$) und Pfad (docPath$) der aktuell offenen Datei.

  • Nach dem Wechsel in den Backup-Ordner prüfen wir mit der Routine DTCheckFileType aus der DocumentTools Library ob eine Backupdatei existiert und ob diese kompatibel zur aktuell offenen Datei ist. Das umfasst den Dateityp, den Dateinamen und für GEOS bzw. VM-Dateien auch das Token bzw. das CreatorToken. Im Fehlerfall brechen wir den Prozess ab.

  • Das "Wiederherstellen" der Datei besteht aus vier Schritten:
  1. Schließen des aktuellen Dokuments.

  2. Kopieren der Backupkopie an die Stelle des aktuellen Dokuments. Das alte Dokument wird dabei automatisch überschrieben.

  3. Öffnen der herkopierten Backupkopie.

  4. Update der UI mit DoReadDataFromDoc und DoUpdateDocButtons.
SUB BasicRestoreFromBackup ()
DIM docPath$ as String(200)
DIM fileName$, err

  fileName$ = DocumentObj.documentname$
  docPath$ = DocumentObj.documentPath$

  SetStandardPath SP_BACKUP
  err = DTCheckFileType(ConvertObjForSDK(DocumentObj), fileName$, "*")
  IF err THEN
    MsgBox "Kann Backup nicht wieder herstellen. Fehlercode: " + ErrorText$(err)
    RETURN
  End IF

  DocumentObj.CloseDocument
  FileCopy fileName$, docPath$ + "\\" + fileName$
  SetCurrentPath docPath$
  DocumentObj.OpenDocument fileName$

  DoReadDataFromDoc
  DoUpdateDocButtons

END SUB

^

15.8 Verwendung von Muster-Dateien

Muster-Dateien werden immer in einem Unterordner des Ordners "USERDATA\TEMPLATE" gespeichert. Klickt der Nutzer im Menü auf einen der zugehörigen Buttons so wird im DocumentAndToolButtonHandler eine der Routinen BasicOpenTemplate oder BasicSaveAsTemplate gerufen.

Die Routine BasicOpenTemplate öffnet eine Musterdatei indem entweder eine neue Datei angelegt wird oder das Muster zum Bearbeiten geöffnet wird.

  • DTOpenTemplateDialog erlaubt es dem Nutzer eine Musterdatei auszuwählen. Standardmäßig ist die Option "Zum Bearbeiten" deaktiviert und ret.retInfo enthält den Wert DRI_READ_ONLY. Aktiviert der Nutzer die genannte Option enthält ret.retInfo den Wert DRI_OK.

    srcFile$ speichert den kompletten Pfad zu ausgewählten Musterdatei.

  • BasicCloseDoc(TRUE) schließt die aktuell geöffnete Datei und fragt den Nutzer gegebenenfalls ob er seine Änderungen speichern will usw. Wählt der Nutzer "Abbrechen" wird die Datei nicht geschlossen und wir verlassen die Routine.

  • Für den Fall, dass der Nutzer die Musterdatei bearbeiten will (ret.retInfo = DRI_OK) öffnen wir die Musterdatei selbst (ret.fileName$) und teilen dem DocumentGuardian-Objekt mit, dass wir eine Musterdatei bearbeiten (DocumentObj.SetDocumentState DOCS_EDIT_TEMPLATE, 0). Dieses Flag bewirkt nur, dass der nächste "Öffnen"-Dialog den Dokument-Ordner anzeigt, und nicht dem der aktuell offenen Datei (den Template-Ordner).

  • Wenn der Nutzer das Muster verwenden will um eine neue Datei anzulegen gehen wir folgendermaßen vor:
    1. DoEnterDocumentPath(TRUE) wechselt in den Pfad, wo neue Dokumente abgelegt werden.

    2. DTFindNameForNew findet einen passenden (und noch unbenutzten) Namen für das neue Dokument.

    3. FileCopy srcFile$, fileName$, "m" kopiert das Musterdokument unter dem neuen Namen in den aktuellen Ordner (siehe Punkt 1.)

    4. Für den Fall dass alles gut gegangen ist öffnen wir das neue Dokument und markieren es als "unbenannt".
  • Abschließend passen wir die UI mit der Standardsequenz aus DoReadDataFromDoc und DoUpdateDocButtons an.
SUB BasicOpenTemplate ()
DIM fileName$ as string(32)
DIM srcFile$ as String(240)
DIM err
DIM ret as DialogReturnStruct

  ret = DTOpenTemplateDialog(ConvertObjForSDK(DocumentObj), "")
  IF ret.retInfo = DRI_CANCEL THEN RETURN
  srcFile$ = currentPath$ + "\\" + ret.fileName$

  err = BasicCloseDoc(TRUE)
  IF err THEN RETURN

  IF ret.retInfo = DRI_OK THEN      ' => zum Bearbeiten
    DocumentObj.OpenDocument ret.fileName$
    DocumentObj.SetDocumentState DOCS_EDIT_TEMPLATE, 0
  ELSE
    DoEnterDocumentPath(TRUE)
    fileName$ = DTFindNameForNew(ConvertObjForSDK(DocumentObj))
    FileCopy srcFile$, fileName$, "m"
    IF fileError = 0 THEN
      DocumentObj.OpenDocument fileName$
      DocumentObj.SetDocumentState DOCS_UNTITLED, 0
    End IF
  End IF

  DoReadDataFromDoc
  DoUpdateDocButtons

END SUB


BasicSaveAsTemplate speichert eine Datei als Muster im Template-Ordner. Dazu ruft es zuerst DTSaveAsTemplateDialog. Diese Routine erlaubt es dem Nutzer einen Namen und ggf. einen anderen Pfad für die Musterdatei einzugeben. Die nächsten Schritte sind identisch mit dem "normalen" Speichern einer Datei mit einem neuen Namen. Deswegen können wir diese Aufgabe an InternalSaveAs delegieren. Diese Routine ist im Abschnitt 15.4.4 (Speichern unter neuem Namen) beschrieben. Sie speichert die aktuell offene Datei mit einem neuen Namen und öffnet diese zum Bearbeiten.

Nun informieren wir das DocumentGuardian-Objekt dass eine Musterdatei in Bearbeitung ist und wir haben die Möglichkeit "Muster-typische" Änderungen an der Datei vorzunehmen. Ob es da etwas gibt und was das ist hängt wieder vom Ihrem Programm ab. Abschließend geben wir noch eine Erfolgsmeldung an den Nutzer aus.

SUB BasicSaveAsTemplate ()
DIM err
DIM ret as DialogReturnStruct

  ret = DTSaveAsTemplateDialog(ConvertObjForSDK(DocumentObj), "", FALSE)
  IF ret.retInfo = DRI_CANCEL THEN RETURN

  err = InternalSaveAs(ret)
  IF err THEN RETURN

  DocumentObj.SetDocumentState DOCS_EDIT_TEMPLATE, 0
  MsgBox("BasicSaveAsTemplate: Datei gespeichert. Hier eventuell
          Sonderaufgaben für \"Muster\" erledigen")
  MsgBox "Die Datei wurde als Muster gespeichert
          und zum Bearbeiten geöffnet."
  RETURN

END SUB
Das GEOS-System hat für "Muster" Dateien ein spezielles Attribut, das dem Dokument-Interface mitteilt, dass es sich nicht um ein normales Dokument handelt. R-BASIC unterstützt dieses Attribut nicht. Wenn Sie möchten, können Sie an dieser Stelle einen bestimmten Wert in Ihrem Muster-Dokument setzen, der das Dokument als "Muster" kennzeichnet. Diesen können Sie beim Öffnen des Dokuments auslesen und entsprechend (analog zu BasicOpenTemplate) reagieren.

^

Weiter...