Home » Tutorials » Datenspeicherung » Binäre Dateien

Binäre Dateien

Kopieren

In dem Rumpfcode gibt es bereits eine leer implementierte Methode für den Menüpunkte Kontakt/Kopieren. Diese werden wir jetzt mit Leben füllen.

procedure TFrmMain.ActKontaktCopyExecute(Sender: TObject);
var
  Stream: TMemoryStream;
  MemHandle: THandle;
  MemBlock: Pointer;
  Data: TRakBinaryStreamData;
begin
  if InputToPuffer(SelectedKontakt) then
  begin // Wenn alle Eingaben an SelectedKontakt übergeben werden
    // konnten
    Stream := nil;
    Data := nil;
    try
       // Einen Memory-Stream erzeugen, in den wir alle Daten des
      // Kontaktes ablegen
      Stream := TMemoryStream.Create;
      // Den Container für das Zwischenformat erzeugen
      Data := TRakBinaryStreamData.Create;

      // Einen Namen festlegen, an dem wir erkennen können, mit welchen
      // Daten wir es zu tun haben
      Data.ApplicationName := APPNAME_CLIPBOARD;

      // Die Daten des Kontaktes in das Zwischenformat kopieren
      SelectedKontakt.SaveToBinaryTag(Data);
      // Das Zwischenformat in den Stream schreiben
      Data.SaveToStream(Stream);

      // Speicher für die Zwischenablage anfordern
      MemHandle := GlobalAlloc(GMEM_DDESHARE, Stream.Size);
      // Den Speicher fixieren
      MemBlock := GlobalLock(MemHandle);
      try
         // Den Inhalt des Streams in den Speicher kopieren
        Stream.Seek(0, soFromBeginning);
        Stream.Read(MemBlock^, Stream.Size);
      finally
         // Die Fixierung wieder aufheben
        GlobalUnlock(MemHandle);
      end;
      // Den Speicher an die Zwischenablage übergeben und als das
      // richtigen Format kennzeichnen
      Clipboard.Open;
      Clipboard.SetAsHandle(TClipboardFormat, MemHandle);
      Clipboard.Close;
    finally
      Stream.Free;
      Data.Free;
    end;
  end;
end;

Zuerst müssen wir natürlich sicherstellen, dass die durch den Benutzer gemachten Eingaben keine Fehler enthalten und dass die Eingaben in das entsprechende TKontakt-Objekt übernommen wurden. Das erledigt die Methode InputToPuffer für uns.
Damit man Daten in die Zwischenablage bringen kann, müssen diese in einem zusammenhängenden Speicherbereich vorliegen. Dafür eignet sich ideal ein TMemoryStream.

Stream := TMemoryStream.Create;

Da unsere Daten im Speicher weit verstreut sind, müssen wir diese irgendwie in dem Stream reinbringen. Da eine Datei im Grunde auch nichts anderes ist, als eine spezielle Form eines Streams, können wir also nicht nur für Dateien, sondern auch für MemoryStreams die RakBinaryStreamData-Klassen verwenden.

Data := TRakBinaryStreamData.Create;

Für den unwahrscheinlichen Fall, dass tatsächlich ein anderes Programm, mit dem gleichen Clipboard-Format arbeitet, bauen wir noch eine Sicherheit ein.

Data.ApplicationName := APPNAME_CLIPBOARD;

Wir legen noch einen String in der Zwischenablage mit ab, über dem wir später erkennen können, ob die Daten tatsächlich für uns bestimmt sind. Hier verwenden wir die gleiche Konstante, die wir schon für das Registrieren des Clipboard-Formats genommen haben.
Als nächstes legen wir die Daten, die wir in die Zwischenablage bringen wollen, in das Zwischenformat.

SelectedKontakt.SaveToBinaryTag(Data);

SelectedKontakt ist ein Property, mit dem man das TKontakt-Objekt erhält, das die Daten des Kontaktes speichert, welches in der Liste selektiert ist.

property SelectedKontakt: TKontakt read GetSelectedKontakt;

Wir müssen hier nur dessen Methode SaveToBinaryTag aufrufen. Diese haben wir bereits für das Speichern in eine Datei implementiert. Wir müssen nichts an der Methode ändern und können diese eins zu eins auch für die Zwischenablage verwenden

Data.SaveToStream(Stream);

Das Speichern der Daten in den MemoryStream übernimmt Data für uns. Da wir hier in einem Stream und nicht in eine Datei speichern, rufen wir nicht dessen Methode SaveToFile, sondern SaveToStream auf.
Jetzt befinden sich die Daten im MemoryStream und können an die Zwischenablage übergeben werden.
Leider stellt uns hier die VCL keine einfache Kapselung zur Verfügung und wir müssen selbst die diversen API-Funktionen aufrufen.

MemHandle := GlobalAlloc(GMEM_DDESHARE, Stream.Size);

Zuerst fordern wir den Speichern an, der später von der Zwischenablage übernommen wird.

MemBlock := GlobalLock(MemHandle);

Mit MemHandle selbst können wir nichts anfangen. Deshalb lassen wir uns dafür einen Zeiger auf den entsprechenden Speicher geben.

Stream.Seek(0, soFromBeginning);

Der interne Zeiger (Position) des Streams zeigt noch auf das Ende des Streams. Wir müssen jetzt aber den kompletten Stream auslesen.

Stream.Read(MemBlock^, Stream.Size);

Damit kopieren wir den Inhalt des Streams in den Speicher der Zwischenablage.

GlobalUnlock(MemHandle);

Damit Windows den Speicher wieder je nach Bedarf intern verschieben kann, müssen wir die Fixierung wieder auflösen. Ab jetzt dürfen wir nicht mehr mit MemBlock arbeiten.
Ab hier unterstützt uns die VCL wieder.
Wir haben zwar inzwischen den Speicher für die Zwischenablage beschrieben. Die Zwischenablage weiß aber noch nichts von dem Speicher. Diese übergeben wir jetzt an die Zwischenablage.

 Clipboard.Open;
      Clipboard.SetAsHandle(TClipboardFormat, MemHandle);
      Clipboard.Close;

Mit SetAsHandle weiß die Zwischenablage, dass sie jetzt der Besitzer des Speichers MemHandle ist und sich auch um die Freigabe des Speichers kümmern muss.
Und hier kommt das erste mal unser eigenes Clipboard-Format TClipboardFormat ins Spiel. Die Zwischenablage muss ja auch wissen, mit welchen Format der Speicher zusammen hängt.
Rein theoretisch könnten wir hier den Kontakt noch in irgendeine Textform bringen und diese dann noch zusätzlich als CF_TEXT in die Zwischenablage bringen. Dann könnte man einzelne Kontakte mit Hilfe der Zwischenablage z.B. nach Word oder Excel kopieren. Da das eigentliche Thema des Tutorials aber nicht die Zwischenablage ist, sparen wir uns das hier.