Entwickler-Ecke

Sonstiges (FPC / Lazarus) / Linux API - TRichMemo - Grafik einfügen


galagher - Mo 04.05.20 07:12
Titel: TRichMemo - Grafik einfügen
Hallo!

Weiss jemand, wie ich per Code in ein TRichMemo eine Grafik einfügen kann, ohne den Weg über ClipBoard zu nehmen?
Alternativ: Wie realisiere ich eine "eigene" Zwischenablage, die ich dazu verwenden kann? Der Hintergrund dabei ist, dass ich dabei den Inhalt der systemweiten Zwischenablage nicht überschreiben will.


jaenicke - Mo 04.05.20 08:54

Du musst soweit ich weiß ein TRichMemoInlinePicture erstellen, dem Konstruktor dein Bild mitgeben und das dann mit InDelInline in das TRichMemo einfügen.


Th69 - Mo 04.05.20 09:13

In der Wiki steht auch noch RichMemo: InsertImageFromFile [https://wiki.freepascal.org/RichMemo#InsertImageFromFile] (sowie InsertImageFromFileNoResize).


galagher - Mo 04.05.20 09:14

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Du musst soweit ich weiß ein TRichMemoInlinePicture erstellen, dem Konstruktor dein Bild mitgeben und das dann mit InDelInline in das TRichMemo einfügen.
Sagt mir jetzt erstmal gar nichts, aber ich seh's mir an! :mrgreen:


galagher - Mo 04.05.20 16:43

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
In der Wiki steht auch noch RichMemo: InsertImageFromFile [https://wiki.freepascal.org/RichMemo#InsertImageFromFile] (sowie InsertImageFromFileNoResize).
Das fügt Grafiken als eingebettetes Objekte ein, ich möchte aber lediglich eine Grafik so einfügen, wie man sie auch per ClipBoard einfügen kann.
Ich sehe mir jetzt erstmal den Vorschlag von user profile iconjaenicke an:
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Du musst soweit ich weiß ein TRichMemoInlinePicture erstellen, dem Konstruktor dein Bild mitgeben und das dann mit InDelInline in das TRichMemo einfügen.

Am einfachsten wäre es aber per ClipBoard. Kann man denn davon nicht eine eigene Instanz erstellen? - Ja, kann man, aber dabei wird trotzdem der Inhalt der "echten" Zwischenablage ersetzt. Wie kann ich das verhindern?


jaenicke - Mo 04.05.20 17:36

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
Am einfachsten wäre es aber per ClipBoard.
Nein, der korrekte Weg ist genauso einfach.

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
Kann man denn davon nicht eine eigene Instanz erstellen? - Ja, kann man, aber dabei wird trotzdem der Inhalt der "echten" Zwischenablage ersetzt. Wie kann ich das verhindern?
Gar nicht, die Zwischenablagenfunktionalität macht ja Windows und nicht die Ansteuerung in der eigenen Anwendung.

Das ist quasi als ob du dir ein eigenes Lenkrad im 3D-Drucker kopierst und dann fragst wie du damit nun ein Auto lenken kannst ohne das echte Auto zu lenken. ;-)


galagher - Mo 04.05.20 18:31

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Nein, der korrekte Weg ist genauso einfach.
Ist er nicht! In RichMemo.pas gibt es zwar die erfoderlichen Prozeduren, die sind aber leer.

Also habe ich das Ganze zuerst mal in Form1 erstellt und dort funktioniert es auch. Dann habe ich alles in RichMemo.pas kopiert, den Code natürlich angepasst, aber ich bekomme dort, sobald ich an imageList Width und Height zuweise, die Fehlermeldung:
Zitat:
Projekt project1 hat Exception-Klasse »External: SIGSEGV« ausgelöst.


Wozu brauche ich denn da überhaupt eine ImageList?

Ich blicke da nicht durch, und das Sample-Projekt bringt mich auch nicht weiter (von dort habe ich ja die Vorgehensweise mit der ImageList)...

//Edit: Wenn ich die ImageList weglasse, wird ein "leerer Bereich" eingefügt, aber kein sichtbares Bitmap.


galagher - Mo 04.05.20 19:38

Es funktioniert dank: https://forum.lazarus.freepascal.org/index.php?topic=33563.0
Ein einziges Beispiel ist Goldes Wert!

Ich habe den Code angepasst, sodass nun keine Datei mehr geöffnet werden muss, sondern Picture übergeben wird. Nach dem Einfügen gebe ich piclist frei (aber natürlich nicht dessen Elemente).

Warum richmemo.pas das nicht schon OnBoard hat (genauso wie so maches andere), weiss der Kuckuck... Aber ich arbeite dran!


galagher - Mi 06.05.20 17:51

Die Grafik wird nicht mitgespeichert. Da macht sich also jemand die Arbeit, eine gut funktionierende Funktion zum Einfügen von Grafiken zu schreiben. Aber natürlich kommt der niemals auf die Idee, dass man das Ganze dann auch speichern können will! :mrgreen: :evil: Nein, wie denn auch? Das ist ja abwegig! Wer will schon Text UND Grafiken als Datei speichern? :autsch:

Nein, im Ernst: Das ärgert mich. Eine halbe Sache, an sich gut, aber doch letzlich so, wie sie ist, unbrauchbar. Leider kann ich das nicht fertig schreiben, weil ich keine Idee habe, wie man die Daten der Grafik in den RTF-Text hinein bekommt.
Wenn man sich die rtf-Datei ansieht, findet sich darin der String {\pict{\*\picprop}\wmetafile0 , es fehlen aber die Daten der Grafik. Wie erhalte ich diese als Text? Ich habe zwar eine Funktion, die das zumindest mit Bitmaps erledigt, aber es funktioniert nicht, wenn man die da einfach dran hängt.

Ich habe das vorläufig so gelöst, dass ich den Text oder das Bitmap der Zwischenablage sichere, dann die Grafik über ClipBoard einfüge und danach den ursprünglichen Inhalt der Zwischenablage wiederherstelle. Das klappt, ist aber natürlich unelegant...

Weiss jemand wie ich beliebigen Inhalt der Zwischenablage sichern und wieder herstellen kann? :eyecrazy:


galagher - Mi 06.05.20 19:44

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
Weiss jemand wie ich beliebigen Inhalt der Zwischenablage sichern und wieder herstellen kann? :eyecrazy:

ich habe das jetzt erstmal so gelöst:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
procedure TCustomRichMemo.InsertGraphic(const aPicture: TPicture);
var
  Stream: TMemoryStream;
  i, n: Integer;
  aArray: array of TClipBoardFormat;
begin
  Stream := TMemoryStream.Create;
  SetLength(aArray, ClipBoard.FormatCount);

  n := ClipBoard.FormatCount;

  {ClipBoard sichern}
  for i := 0 to ClipBoard.FormatCount-1 do
  begin
    aArray[i] := ClipBoard.Formats[i];
    ClipBoard.GetFormat(ClipBoard.Formats[i], Stream);
  end;

  ClipBoard.Assign(aPicture.Bitmap);
  PasteFromClipboard;

  ClipBoard.Clear;
  
  {ClipBoard wiederherstellen}
  for i := 0 to n-1 do
    ClipBoard.AddFormat(aArray[i], Stream);
end;

Scheint zu funktionieren, aber geht das auch besser?

//Edit: Funktioniert aber nicht immer, teilweise enthält ClipBoard danach irgendwelche Zeichen. Was stimmt da denn nicht?


jaenicke - Mi 06.05.20 22:39

Du packst alles in einen Stream? Wie soll das denn funktionieren? Woher soll denn beim Laden erkannt werden wo welches Format endet usw.?


galagher - Do 07.05.20 06:47

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Du packst alles in einen Stream? Wie soll das denn funktionieren?
Nun, AddFormat oder auch SetFormat verlangt eben einen Stream. Ich dachte, das Array erledigt das, aber da wird nun einmal ein Stream verlangt. Und das Array enthält die korrekten Daten ja (denke ich...).
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Woher soll denn beim Laden erkannt werden wo welches Format endet usw.?
Ja, stimmt. Das ist dann wohl auch der Grund, warum die Zwischenablage manchmal nur "Unsinn" enthält, einfach nur irgendwelche Zeichen. Das passiert genau dann, wenn zB. rtf-formatierter Text in der Zwischenblage ist. Dann ist dort ja nicht nur Text, sondern eben auch der rtf-Code.

Wie kann ich das also verbessern? Muss ja prinzipiell funktionieren: 1. Sichere ClipBoard, 2. schreibe da was rein, 3. stelle ClipBoard wieder her. Im Grunde nichts Aufregendes. Aber wie?


jaenicke - Do 07.05.20 10:50

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Du packst alles in einen Stream? Wie soll das denn funktionieren?
Nun, AddFormat oder auch SetFormat verlangt eben einen Stream.
Die Betonung liegt auf einem Stream. Da packst du ja quasi einfach nur Daten hintereinander rein.

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
Wie kann ich das also verbessern? Muss ja prinzipiell funktionieren: 1. Sichere ClipBoard, 2. schreibe da was rein, 3. stelle ClipBoard wieder her. Im Grunde nichts Aufregendes. Aber wie?
Einfach einen Stream pro Typ verwenden. ;-)


galagher - Do 07.05.20 15:38

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Einfach einen Stream pro Typ verwenden. ;-)
Woher weiss ich vorher, wie viele Typen es aktuell gibt bzw. wie genau erstelle ich einen Stream pro Typ?


Th69 - Do 07.05.20 16:23

Du benutzt doch schon die Anzahl ClipBoard.FormatCount? Und davon erstellst du dynamisch entsprechend viele Streams und packst jedes Format in den zugehörigen Stream (und beim Wiederherstellen entsprechend) - so wie du es ja bisher für das array of TClipBoardFormat auch schon gemacht hast.


galagher - Do 07.05.20 16:36

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Du benutzt doch schon die Anzahl ClipBoard.FormatCount? Und davon erstellst du dynamisch entsprechend viele Streams und packst jedes Format in den zugehörigen Stream (und beim Wiederherstellen entsprechend) - so wie du es ja bisher für das array of TClipBoardFormat auch schon gemacht hast.
Ich steh wohl auf der Leitung. Die können doch nicht alle den selben Namen haben? Und wie packe ich die einzelnen Formate da hinein?
Das Prinzip ist mir schon klar, aber die Umsetzung nicht...

//Edit:
Ok, die einzelnen Formate schreibe ich mit GetFormat hinein. Gut. aber wie erstelle ich n Streams? n ist eine Zahl, die ich vorher nicht kenne.
//Edit: Doch, n kenne ich. n = ClipBoard.FormatCount.
Bleibt die Frage: Wie erstelle ich die einzelnen Streams, die ja nicht alle einfach Stream heissen können?


Th69 - Do 07.05.20 17:00

Genau so wie du es für aArray auch gemacht hast:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
var
  // ...
  aStreams: array of TMemoryStream;
begin
  n := ClipBoard.FormatCount;

  SetLength(aStreams, n);

  // todo: Schleife, um alle n aStreams[i] per TMemoryStream.Create zu erzeugen
  // ...

  for i := 0 to n-1 do
  begin
    aArray[i] := ClipBoard.Formats[i];
    ClipBoard.GetFormat(aArray[i], aStreams[i]);
  end;
  
   // ...
end


PS: aArray solltest du besser aFormats nennen!


galagher - Do 07.05.20 17:24

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:


Delphi-Quelltext
1:
2:
3:
var
  // ...
  aStreams: array of TMemoryStream;
Das ist es, was mir nicht klar war! Jetzt scheint es mir ja ganz einleuchtend, das so zu machen, aber an ein Array von Streams dachte ich nicht!

Hier die ganze Prozedur:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
{Grafik in den Text von TRichMemo einfügen}
procedure TCustomRichMemo.InsertGraphic(const aPicture: TPicture);
var
  i, n: Integer;
  aFormats: array of TClipBoardFormat;
  aStreams: array of TMemoryStream;
begin
  try
    n := ClipBoard.FormatCount;
    SetLength(aFormats, n);
    SetLength(aStreams, n);

    for i := 0 to n-1 do
      aStreams[i] := TMemoryStream.Create;

    for i := 0 to n-1 do
    begin
      aFormats[i] := ClipBoard.Formats[i];
      ClipBoard.GetFormat(aFormats[i], aStreams[i]);
    end;

    ClipBoard.Assign(aPicture.Bitmap);
    PasteFromClipboard;

    for i := Low(aFormats) to High(aFormats) do
      ClipBoard.SetFormat(aFormats[i], aStreams[i]);

  finally
    for i := High(aStreams) downto Low(aStreams) do
      aStreams[i].Free;
  end;
end;


galagher - Mo 11.05.20 23:56

Nach merfachem Testen kommt es öfter dann zu dem SIGSEGV-Fehler, wenn ich RTF-Daten in der Zwischenablage habe. Wenn ich jedoch ein Sleep(450) einfüge, tritt dieser Fehler nicht auf. :eyecrazy: Mittlerweile habe ich den Code so modifiziert, dass er die RTF-Formatierung in der Zwischenablage erkennt und einfügt. Aber es funktioniert immer nur zusammen mit Sleep!

Draufgekommen bin ich, als ich den Code mit Haltepunkten versehen habe: Eine Pause genügt, und es klappt, und auch Sleep bewirkt dies. Ausserdem tritt der Fehler nur dann auf, wenn die RTF-Daten "frisch" aus der Zwischenablage kommen. Wenn sie mit der Prozedur wieder dorthin zurück geschrieben wurden, funktioniert alles auch ohen Sleep.

Habt ihr eine Erklärung für dieses Phänomen?

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
{Grafik in den Text von TRichMemo einfügen}
procedure TCustomRichMemo.InsertGraphic(const aPicture: TPicture);
var
  i, n: Integer;
  aFormats: array of TClipBoardFormat;
  aStreams: array of TMemoryStream;
begin
  try
    n := ClipBoard.FormatCount;
    SetLength(aFormats, n);
    SetLength(aStreams, n);

Sleep(450//<- Dies behebt den SIGSEGV-Fehler, Wert kann auch höher sein, 400 klappt nicht

    for i := 0 to n-1 do
      aStreams[i] := TMemoryStream.Create;

    for i := 0 to n-1 do
    begin
      aFormats[i] := ClipBoard.Formats[i];
      ClipBoard.GetFormat(aFormats[i], aStreams[i]);
    end;

    ClipBoard.Assign(aPicture.Bitmap);
    PasteFromClipboard;

    for i := Low(aFormats) to High(aFormats) do
      ClipBoard.SetFormat(aFormats[i], aStreams[i]);

  finally
    for i := High(aStreams) downto Low(aStreams) do
      aStreams[i].Free;
  end;
end;


galagher - So 17.05.20 18:59

Weiss denn keiner weiter? Der Fehler tritt jetzt, obwohl ich die unten stehende Prozedur seit Tagen nicht verändert habe, nur noch sehr selten auf, und wenn, dann immer dann, wenn die RTF-Daten schon vor dem Programmstart in der Zwischenablage waren. Da hilf auch das try-except nichts, es knallt beim Abfragen des Formats der Zwischenablage: if Windows.IsClipboardFormatAvailable(CF_RICHTEXT). Mit if ClipBoard.HasFormat(CF_RICHTEXT) ebenso.

Weiters: Muss CF_RICHTEXT als Word oder als UINT (= LongWord) dekalriert werden? In Lazarus' VirtualTree.pas sind ClipBoard-Formate als Word's deklariert, irgendwo im Internet habe ich aber UINT aufgeschnappt. Teste nun mit Word...


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
implementation
//...
var
//...
  CF_RICHTEXT: Word;//UINT;

procedure TCustomRichMemo.InsertGraphic(const aPicture: TPicture);
var
  i, n: Integer;
  aFormats: array of TClipBoardFormat;
  aStreams: array of TMemoryStream;
  cpFormat: TClipBoardFormat;
begin
  try
    try
      n := ClipBoard.FormatCount;
      SetLength(aFormats, n);
      SetLength(aStreams, n);

      for i := 0 to n-1 do
        aStreams[i] := TMemoryStream.Create;

      for i := 0 to n-1 do
      begin
        aFormats[i] := ClipBoard.Formats[i];
        ClipBoard.GetFormat(aFormats[i], aStreams[i]);
      end;

      if Windows.IsClipboardFormatAvailable(CF_RICHTEXT) then
        cpFormat := CF_RICHTEXT;

      ClipBoard.Assign(aPicture.Bitmap);
      PasteFromClipboard;

      if cpFormat = CF_RICHTEXT then
        for i := Low(aFormats) to High(aFormats) do
        begin
          if aFormats[i] = cpFormat then
            Clipboard.SetFormat(cpFormat, aStreams[i])
        end
      else
        for i := Low(aFormats) to High(aFormats) do
          ClipBoard.SetFormat(aFormats[i], aStreams[i]);  {Andere Formate: Text, Grafiken usw.}

    finally
      for i := High(aStreams) downto Low(aStreams) do
        aStreams[i].Free;
    end;

  except
    ;  {Dzt. nichts}
  end;
end;

//...

initialization
  CF_RICHTEXT := RegisterClipboardFormat(RichEdit.CF_RTF);


jaenicke - So 17.05.20 19:58

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
Weiters: Muss CF_RICHTEXT als Word oder als UINT (= LongWord) dekalriert werden?
RegisterClipboardFormat liefert einen UINT zurück, von daher ist das auch der passende Typ.


galagher - So 17.05.20 20:36

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
Weiters: Muss CF_RICHTEXT als Word oder als UINT (= LongWord) dekalriert werden?
RegisterClipboardFormat liefert einen UINT zurück, von daher ist das auch der passende Typ.
Ok, danke!

Ich muss mich korrigieren: Der Fehler tritt, wenn, dann schon an dieser Stelle auf:

Delphi-Quelltext
1:
2:
3:
4:
5:
for i := 0 to n-1 do
begin
  aFormats[i] := ClipBoard.Formats[i];  //<- Hier knallts!
  ClipBoard.GetFormat(aFormats[i], aStreams[i]);
end;

Die Fehlermeldung lautet:
Zitat:
Projekt project1 hat Exception-Klasse "External SIGSEV" ausgelöst.

Bei Adresse: 7FF833245544

Wie gesagt, zig Mal funktioniert es, dann 1x nicht. Und auch nicht nur dann, wenn der RTF-formatierte Text schon in der Zwischenablage ist, bevor mein Programm startet, auch das muss ich korrigieren.
Auch ist es egal, wo ich CF_RICHTEXT deklariere, ob im CreateWnd oder im initialization-Abschnitt oder auch gar nicht. Es klappt meistens, aber eben nicht immer.

Das Beste: Wenn ich im Debug-Modus das Programm einfach weiter ausführe, ist die Grafik eingefügt und der Text ist samt Formatierung wieder in der Zwischenablage, so, als sei nichts gewesen!

Wenn ich das Programm alleine, ohne Lazarus, ausführe, kommt die Fehlermeldung:
Zitat:
project1.exe funktioniert nicht mehr. [...]


galagher - So 17.05.20 21:46

Ich denke, ich habe die Ursache des Fehlers gefunden: Es geht (manchmal) schief, wenn versucht wird, auf ein ungültiges ClipBoard-Format zuzugreifen. Das passiert zB., wenn die Zwischenablage RTF-formatierten Text einer alten Microsoft-Word-Version enthält.
Leider kann ich das nicht abfangen, denn eine Funktion ClipBoard.isValidFormat(...) gibt es nicht.
Verwende ich als Quelle hingegen ein aktuelles LibreOffice, tritt der Fehler nicht mehr auf.

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
Das Beste: Wenn ich im Debug-Modus das Programm einfach weiter ausführe, ist die Grafik eingefügt und der Text ist samt Formatierung wieder in der Zwischenablage, so, als sei nichts gewesen!
Ja, das ist das Beste daran. Ich habe das jetzt so behoben, dass ich daraus eine Funktion gemacht habe, die False zurück gibt, wenn dieser Fehler auftritt, und dabei dennoch weiterarbeitet, denn es klappt ja trotzdem:

Delphi-Quelltext
1:
2:
if not ClipBoard.GetFormat(aFormats[i], aStreams[i]) then
  Result := False;
Ob das ratsam ist, kann ich nicht beurteilen, aber, wie gesagt, alles funktioniert dabei ganz normal.


jaenicke - Mo 18.05.20 00:11

Interessant wäre bei dem Fehler der Stacktrace. Die Speicheradresse hilft da nicht viel...


galagher - Mo 18.05.20 09:16

Ich muss den Code noch verbessern: So, wie er jetzt ist, sind danach alle ClipBoard-Formate im RTF-Format, normaler Text steht dann anderen Anwendungen nicht mehr zur Verfügung.

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Interessant wäre bei dem Fehler der Stacktrace. Die Speicheradresse hilft da nicht viel...
Meinst du die Zeile, bei der der Debugger (in einem eigenen Fenster) anhält und diese markiert?


Th69 - Mo 18.05.20 11:05

Ja, und dann das Fenster Call Stack [https://wiki.freepascal.org/IDE_Window:_Call_Stack] (auch Stacktrace genannt) anschauen, um die genaue Ursache (hoffentlich) zu finden.

Aber was willst du überhaupt mit der Sonderbehandlung von CF_RICHTEXT (beim Wiederherstellen der Zwischenablage)?


galagher - Mo 18.05.20 14:03

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Aber was willst du überhaupt mit der Sonderbehandlung von CF_RICHTEXT (beim Wiederherstellen der Zwischenablage)?
Ohne CF_RICHTEXT ist sonst nur der reine Text, ohne RTF-Formatierung, in der Zwischenablage.


Th69 - Mo 18.05.20 15:22

Ich meine

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
if cpFormat = CF_RICHTEXT then
    for i := Low(aFormats) to High(aFormats) do
    begin
        if aFormats[i] = cpFormat then
           Clipboard.SetFormat(cpFormat, aStreams[i])
    end
else
    ...

Warum du also nur im else-Zweig alle Formate wieder zurückschreibst?


galagher - Mo 18.05.20 17:13

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Warum du also nur im else-Zweig alle Formate wieder zurückschreibst?
Ja, das meinte ich damit, dass ich dann nur noch den RTF-formatierten Text in der Zwischenablage habe. Ich weiss im Moment aber nicht, wie ich herausfinde, wo ich den RTF-formatierten Text zurückschreiben muss.


galagher - Mo 18.05.20 18:00

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Ja, und dann das Fenster Call Stack [https://wiki.freepascal.org/IDE_Window:_Call_Stack] (auch Stacktrace genannt) anschauen, um die genaue Ursache (hoffentlich) zu finden.


Nun, das Assembler-Fenster markiert die Zeile:
00007FF833245544 807a0f05 cmpb $0x5,0xf(%rdx)

Wo finde ich das Fenster Call Stack bzw. Stacktrace?

Was die Wiederherstellung der verschiedenen Formate anlangt, komme ich nicht weiter:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
for i := 0 to n-1 do
begin
  aFormats[i] := ClipBoard.Formats[i];
    if not ClipBoard.GetFormat(aFormats[i], aStreams[i]) then
      Result := False;
//Merke hier, ob das Format CF_RICHEDIT ist (den Code habe ich verworfen, weil es nicht klappte)
end;

// füge die Grafik ein

for i := Low(aFormats) to High(aFormats) do
begin
  if aFormats[i] = CF_RICHEDIT then
    Clipboard.SetFormat(CF_RICHEDIT, aStreams[i])
  else
    ClipBoard.SetFormat(aFormats[i], aStreams[i]);
end;


Funktioniert aber nicht. Es ist dann nur reiner Text in der Zwischenablage.


Th69 - Mo 18.05.20 18:42

Müsste im Menü "View -> Debug Windows -> Call stack" sein (laut Internet, da ich Lazarus nicht kenne).

Ich verstehe immer noch nicht, was du genau erreichen möchtest.
Fehlen dir noch Grundkenntnisse beim Programmieren? Die Sonderabfrage auf CF_RICHTEXT bei deiner unteren Schleife ist doch komplett überflüssig (da die Schleife doch sowieso über alle gespeicherten Formate iteriert).

Ich kann dir das Tool Free Clipboard Viewer [https://www.freeclipboardviewer.com/windowsclipboard.html] empfehlen, das zeigt alle zurzeit enthaltenen Formate an (habe ich gestern auch für ein Programm bei mir bzgl. HTML-Format benötigt).
Es liegt ja an dem jeweiligen Programm, welches der aktuell enthaltenen Formate es benutzt (ein reiner Texteditor nutzt nur das Text-Format, wenn es denn enthalten ist und ignoriert alle anderen Formate - und andere Programme überprüfen dann die Formate in jeweiliger Priorität und das Text-Format dann meistens als letztes).


galagher - Mo 18.05.20 19:25

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Müsste im Menü "View -> Debug Windows -> Call stack" sein (laut Internet, da ich Lazarus nicht kenne).
Ok, das ist dann also "Aufrufstack". Dort steht:
Zitat:
Index Position Zeile Funktion
0 :7FF833245544 - ??

Ich muss das jedesmal abtippen, da kopieren nicht funktioniert, weil die Zwischenablage in diesen Momenten gesperrt ist.

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Ich verstehe immer noch nicht, was du genau erreichen möchtest.
Ich möchte ine Grafik in ein TRichMemo einfügen. Die Methode, die TRichMemo mitbringt, ist unbrauchbar, weil man damit zwar Grafiken einfügen kann, diese aber nicht gespeichert werden, weil die entsprechenden Daten nicht in die Datei geschrieben werden. Aber per ClipBoard funktioniert es. Also dachte ich, sichere den Inhalt der Zwischenablage, kopiere die Grafik hinein, füge die Grafik ins RichMemo ein, stelle den Inhalt der Zwischenablage wieder her.
Funktioniert prinzipiell. Ich kann aber nicht erreichen, dass bei RTF-Daten die beiden Formate CF_RICHEDIT und CF_TEXT erhalten bleiben. Entweder habe ich ausschliesslich CF_RICHEDIT oder ausschliesslich CF_TEXT, je nachdem, wie ich den Code abändere (s. den Code-Schnipsel).

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Fehlen dir noch Grundkenntnisse beim Programmieren? Die Sonderabfrage auf CF_RICHTEXT bei deiner unteren Schleife ist doch komplett überflüssig (da die Schleife doch sowieso über alle gespeicherten Formate iteriert).
Ja, das sollte man meinen. Es ist aber nicht so. Meine Grundkenntnisse beim Programmieren sind durchaus ausreichend, zu erkennen, dass das überflüssig ist - normalerweise. Hier aber nicht, denn wenn ich den Code so abändere: ...

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
(*      if cpFormat = CF_RICHTEXT then
        for i := Low(aFormats) to High(aFormats) do
        begin
          if aFormats[i] = cpFormat then
            Clipboard.SetFormat(cpFormat, aStreams[i]);
        end
      else
*)
        for i := Low(aFormats) to High(aFormats) do
          ClipBoard.SetFormat(aFormats[i], aStreams[i]);  {Andere Formate: Text, Grafiken usw.}

... schreibe ich natürlich alles in die Zwischenablage zurück, aber es ist danach nur reiner Text in der Zwischenablage, die RTF-Formatierung geht verloren.

Leider kann man das alles mit Delphi nicht nachstellen, da gibt es zB. kein ClipBoard.GetFormat.


jaenicke - Mo 18.05.20 20:56

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Müsste im Menü "View -> Debug Windows -> Call stack" sein (laut Internet, da ich Lazarus nicht kenne).
Ok, das ist dann also "Aufrufstack". Dort steht:
Zitat:
Index Position Zeile Funktion
0 :7FF833245544 - ??

Ich muss das jedesmal abtippen, da kopieren nicht funktioniert, weil die Zwischenablage in diesen Momenten gesperrt ist.
Ein Screenshot ist auch möglich. ;-)
Aber es gibt auch einen Knopf für das Kopieren:

LazarusStacktrace


galagher - Di 19.05.20 08:25

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Ein Screenshot ist auch möglich. ;-)
Aber es gibt auch einen Knopf für das Kopieren:
Da gibt es einen Button "Alles kopieren" (oder so, in diesem Sinne, hab ich jetzt nicht vor mir). Das funktionierte nicht.

Es bleibt aber die Frage stehen, warum ich das RTF-Format nicht richtig hin bekomme! Es wird jedenfalls nicht automatisch erkannt, automatisch wird nur CF_TEXT, CF_BITMAP usw. erkannt.
Da gibt es noch eine weitere Möglichkeit: Ein temporäres TRichMemo. Unelegant, zugegeben, wird aber vermutlich funktionieren. Kann ich ja mal versuchen.


Th69 - Di 19.05.20 14:25

Wie überprüfst du denn, welche Formate alles in der Zwischenablage sind?

Wenn du also zweimal deinen Code aufrufst (mit Auslesen und zurückschreiben aller Formate), dann fehlt plötzlich das RTF-Format?

Was mich auch noch wundert, ist, daß du extra noch RichEdit.CF_RTF registrieren mußt, denn das ist ja ein Standard-Format (s.a. "Standard Clipboard Formats"-Liste im oben schon verlinkten FreeClipboardViewer [https://www.freeclipboardviewer.com/windowsclipboard/]).
Edit: OK, in Windows Clipboard Formats [https://www.codeproject.com/Reference/1091137/Windows-Clipboard-Formats] steht, daß es nur ein "Common Application Clipboard Format" ist und kein "Standard Clipboard Format".

Aber entspricht denn RichEdit.CF_RTF = 'Rich Text Format' (bzw. 'text/richtext')?
Edit: Auch das konnte ich jetzt in How to reliably detect RICHTEXT format on clipboard? [https://stackoverflow.com/questions/45837425/how-to-reliably-detect-richtext-format-on-clipboard] nachlesen.

Du kannst auch mal mittels Clipboard.SupportedFormats(list) dir eine Liste aller vom System unterstützten Formate zurückliefern lassen (ohne vorher die extra Registrierung durchzuführen), s.a. View the clipboard contents [https://wiki.lazarus.freepascal.org/Clipboard#View_the_clipboard_contents].

Und bzgl.
galagher hat folgendes geschrieben:
Leider kann man das alles mit Delphi nicht nachstellen, da gibt es zB. kein ClipBoard.GetFormat.

In der VCL gibt es die Methoden TPicture.LoadFromClipboardFormat [http://docwiki.embarcadero.com/Libraries/Rio/de/Vcl.Graphics.TPicture.LoadFromClipboardFormat] und TPicture.SaveToClipboardFormat [http://docwiki.embarcadero.com/Libraries/Rio/de/Vcl.Graphics.TPicture.SaveToClipboardFormat], da diese hauptsächlich für die verschiedenen Bildformate benutzt werden. Andererseits sind dies auch nur Wrapper für die WinAPI-Funktionen, s. Using the Clipboard [https://docs.microsoft.com/de-de/windows/win32/dataxchg/using-the-clipboard]:
- Copying Information to the Clipboard [https://docs.microsoft.com/de-de/windows/win32/dataxchg/using-the-clipboard#copying-information-to-the-clipboard]
- Pasting Information from the Clipboard [https://docs.microsoft.com/de-de/windows/win32/dataxchg/using-the-clipboard#pasting-information-from-the-clipboard]


galagher - Di 19.05.20 17:08

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Wie überprüfst du denn, welche Formate alles in der Zwischenablage sind?
Gar nicht, ich sichere zuerst alle Formate und stelle sie danach wieder her. Das funktioniert aber mit dem RTF-Format nicht. Da bleibt nur Plain-Text übrig. Ansosnten funktioniert das sehr wohl, mit normalem Text, mit Icons und mit Bitmaps.

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Wenn du also zweimal deinen Code aufrufst (mit Auslesen und zurückschreiben aller Formate), dann fehlt plötzlich das RTF-Format?
Nein, nur wenn ich alle Formate zurückschreibe, ohne das RTF-Format extra zu berücksichtigen. Es wird offenbar nicht erkannt.
Auch, wenn ich mir die Position im Array merke und das RTF-Format (den formatierten Text) explizit an diese Position zurückschreibe, klappt es nicht. Da passt offenbar der Wert von "i" bei aStream[i] nicht. Oder was weiss ich. :nixweiss: Es geht eben nicht.

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Was mich auch noch wundert, ist, daß du extra noch RichEdit.CF_RTF registrieren mußt, denn das ist ja ein Standard-Format
Wie sonst? CF_RTF ist ja ein String, kein Format. Ich muss es also registrieren.

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Aber entspricht denn RichEdit.CF_RTF = 'Rich Text Format' (bzw. 'text/richtext')?
Edit: Auch das konnte ich jetzt in How to reliably detect RICHTEXT format on clipboard? [https://stackoverflow.com/questions/45837425/how-to-reliably-detect-richtext-format-on-clipboard] nachlesen.
Ja, es entspricht! Das habe ich mir von eben dieser Seite abgeschaut.

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Du kannst auch mal mittels Clipboard.SupportedFormats(list) dir eine Liste aller vom System unterstützten Formate zurückliefern lassen (ohne vorher die extra Registrierung durchzuführen),
Da ist 'Rich Text Format' bzw. 'text/richtext' nicht dabei.

Wird vor dem Einfügen der Grafik, also beim Sichern der Formate der Zwischanablage, das RTF-Format noch erkannt (Clipboard.HasFormat(CF_RICHTEXT liefert True zurück), kann ich es nach dem Einfügen der Grafik, also bei Wiederherstellen der Formate der Zwischanablage, nicht mehr wiederherstellen. Der reine Text ist ja da, aber jede RTF-Formatierung ist verloren gegangen.

Wie erwartet, funktioniert es aber mit einem temporären TRichMemo, in das ich den RTF-formatierten Inhalt einfüge und hinterher von dort wieder in die Zwischanablage zurück kopiere. Aber irgendwie ist das doch nicht besonders schön. Klappt und kann man zur Not auch belassen, aber der direkte Weg über die Formate wäre eleganter.


Th69 - Di 19.05.20 18:22

Ich frage noch mal: Wie überprüfst du denn, welche Formate alles in der Zwischenablage sind?
Damit meine ich nicht codetechnisch, sondern verwendest du ein externes Programm oder woher weißt du, daß das Richtext-Format nicht mehr zurückgeschrieben wird?

Darum habe ich dir ja auch den Link zu dem FreeClipboardViewer gegeben.

Und mit welchem Programm erzeugst du denn überhaupt den Richtext in der Zwischenablage?


galagher - Di 19.05.20 18:44

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Ich frage noch mal: Wie überprüfst du denn, welche Formate alles in der Zwischenablage sind?
Damit meine ich nicht codetechnisch, sondern verwendest du ein externes Programm oder woher weißt du, daß das Richtext-Format nicht mehr zurückgeschrieben wird?
Ganz einfach: Indem ich den Text, der ja das RTF-Format sein müsste, wenn man alles korrekt wieder zurückschreibt, ins RichMemo einfüge. Da wird aber eben nur Text ohne Formatierung eingefügt.

Ich habe dabei jetzt den FreeClipboardViewer eingesetzt: das RTF-Format ist hinterher tatsächlich nicht mehr vorhanden, sondern (u. a.) nur noch der reine Text. Also entweder ist die Wiederherstellung nicht korrekt, aber was kann bei dieser for-Schleife falsch sein? Oder das RTF-Format wird in aStreams[i] nicht mit gesichert. Das wird's sein, denke ich.

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Und mit welchem Programm erzeugst du denn überhaupt den Richtext in der Zwischenablage?
Einerseits mit dem RichMemo selbst, andererseits mit Microsoft Word und mit LibreOffice, den RTF-Text kopiere ich dann in die Zwischenablage. Immer ist beim Zurückschreiben das RTF-Format weg, wie man mit FreeClipboardViewer sieht, obwohl es vorher vorhanden war.


Th69 - Di 19.05.20 19:07

Wenn ich den Kommentar in Clipboard.pp [https://github.com/alrieckert/lazarus/blob/master/lcl/clipbrd.pp]
Zitat:
SetFormat
Clears the clipboard and adds the data.

richtig verstehe, so kann mit dieser Funktion immer nur genau 1 Format gespeichert werden...

Probiere mal stattdessen AddFormat!
Zitat:
AddFormat:
Use these functions to add data to the supported formats.

(und vor der Schleife noch Clear aufrufen)


galagher - Di 19.05.20 20:54

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Probiere mal stattdessen AddFormat!
Es funktioniert! :dance: Jetzt passt alles!

Nun tritt aber der SIGSEGV-Fehler wieder auf, wenn ich RTF-Text aus dem alten Word verwende. Er tritt auf beim Sichern der Formate in der Schleife:

Delphi-Quelltext
1:
2:
3:
4:
5:
for i := 0 to n-1 do
begin
  aFormats[i] := ClipBoard.Formats[i];  //<- Hier!
  //...
end;

Nicht schon beim 1.Durchlauf, sondern beim 8. Den Fehler kann ich aber nicht abfangen.

Klar, jetzt ist der Code besser, übersichlicher und sauberer als mit dem leidigen temporären TRichEdit. Aber nun habe ich das Problem wieder, dass der mit Daten alter Programme (ca. 20 Jahre) nicht kann. Ok, sollte man ja ohnehin nicht mehr verwenden, aber es geht ums Prinzip: Warum?

Vielleicht findest du ja die Ursache, oder man kann noch etwas verbessern. Hier die ganze Funktion:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
function TCustomRichMemo.InsertGraphic(const aPicture: TPicture): Boolean;
var
  i, n: Integer;
  aFormats: array of TClipBoardFormat;
  aStreams: array of TMemoryStream;
begin
  Result := True;

  try  {except}
{--------------------------- Zwischenablage sichern ---------------------------}
    n := ClipBoard.FormatCount;
    SetLength(aFormats, n);
    SetLength(aStreams, n);

    {Stream-Array anlegen für die ClipBoard-Formate}
    for i := 0 to n-1 do
      aStreams[i] := TMemoryStream.Create;

    try  {finally}
      {ClipBoard-Formate sichern}
      {Array aFormats (=die ClipBoard-Formate) befüllen und Daten in das Array aStreams schreiben}
      for i := 0 to n-1 do
      begin
        aFormats[i] := ClipBoard.Formats[i];
        Result := ClipBoard.GetFormat(aFormats[i], aStreams[i]);
      end;


{------------------------------ Grafik einfügen -------------------------------}
      {Grafik zuweisen und ...}
      ClipBoard.Assign(aPicture.Bitmap);
      PasteFromClipboard;  {... einfügen}


{---------------------- Zwischenablage wiederherstellen -----------------------}
      {ClipBoard-Formate wiederherstellen}
      ClipBoard.Clear;
      for i := Low(aFormats) to High(aFormats) do
        ClipBoard.AddFormat(aFormats[i], aStreams[i]);

    finally
      for i := High(aStreams) downto Low(aStreams) do
        aStreams[i].Free;
    end;

  except
    Result := False;
    ClipBoard.Clear;
  end;
end;


galagher - Di 19.05.20 21:11

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
Nun tritt aber der SIGSEGV-Fehler wieder auf, wenn ich RTF-Text aus dem alten Word verwende. Er tritt auf beim Sichern der Formate in der Schleife:

Delphi-Quelltext
1:
2:
3:
4:
5:
for i := 0 to n-1 do
begin
  aFormats[i] := ClipBoard.Formats[i];  //<- Hier!
  //...
end;
Ok, der Fehler tritt so oder so auf, mit oder ohne temporärem RichMemo. Denn er tritt ja an einer Stelle auf, wo ein RichMemo noch gar nicht vorkommt.

Aber er tritt nur manchmal auf. Ich möchte gerene darauf reagieren, aber das Programm rumpelt drüber und dann knallts... Wie gesagt, immer nur bei ClipBoard-Daten alter Programme.


jaenicke - Di 19.05.20 21:33

Den kompletten Stacktrace von dem Fehler haben wir bisher aber noch nicht gesehen. ;-)
Und da wir das ohne die passenden Daten nicht reproduzieren können, können wir auch nicht überlegen wo der Fehler sein könnte.


galagher - Di 19.05.20 22:17

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Den kompletten Stacktrace von dem Fehler haben wir bisher aber noch nicht gesehen. ;-)
Und da wir das ohne die passenden Daten nicht reproduzieren können, können wir auch nicht überlegen wo der Fehler sein könnte.
Wenn das weiterhilft, gerne!


jaenicke - Di 19.05.20 23:37

Hmm, dann ist da schon richtig etwas kaputt, wenn es nicht einmal mehr einen richtigen Stacktrace gibt. Dann bleibt nur manuell durch debuggen...

Nach meinen bisherigen Erfahrungen mit Lazarus ist es durchaus nicht unwahrscheinlich, dass du dabei einen Bug findest.


galagher - Mi 20.05.20 06:30

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Hmm, dann ist da schon richtig etwas kaputt, wenn es nicht einmal mehr einen richtigen Stacktrace gibt. Dann bleibt nur manuell durch debuggen...
Das Komische ist ja, dass es unter Lazarus, wenn man es im Debug-Modus ausführt, und dann, nach dem Auftreten des Fehlers, weiter ausführt, einfach ganz normal weiterläuft. Alles funktioniert, als sei nicht passiert. Nur ohne Lazarus, also "alleine", beendet sich das Programm einfach. Und ich kann das wie gesagt nicht abfangen. Zumindest weiss ich nicht, wie.


jaenicke - Mi 20.05.20 06:44

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
Und ich kann das wie gesagt nicht abfangen. Zumindest weiss ich nicht, wie.
try..except klappt nicht?

Aber wie gesagt, interessant wäre ja an welcher Stelle genau der Fehler auftritt, wenn du durch den Quelltext debuggst, also durch TClipboard.GetFormats usw. (ich sehe dort z.B. eine Unterscheidung nach CanReadFromCache).


galagher - Mi 20.05.20 07:27

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
Und ich kann das wie gesagt nicht abfangen. Zumindest weiss ich nicht, wie.
try..except klappt nicht?
Nein. Ich habe es zwar eingebaut, aber es ist in diesem Fall nutzlos.

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Aber wie gesagt, interessant wäre ja an welcher Stelle genau der Fehler auftritt, wenn du durch den Quelltext debuggst, also durch TClipboard.GetFormats usw. (ich sehe dort z.B. eine Unterscheidung nach CanReadFromCache).
Das werde ich mir jedenfalls ansehen!


Th69 - Mi 20.05.20 09:02

Tja, manchmal hilft Doku lesen. ;-)

Und auch bzgl. des SIGSEGV-Fehlers: Exceptions: Exception Classes [https://wiki.freepascal.org/Exceptions#Exception_classes]
Füge die Unit SysUtils mal hinzu.

Dann solltest du aber zusätzlich einen try..except-Block um die fehlerhafte Funktion packen.

PS: Das Erzeugen der Stream-Objekte kannst du auch in die 2. Schleife packen (wie für das Formats-Array).
Und wenn du den Code wiederverwendbar machen möchtest, dann solltest du die Funktionalität in eine eigene Klasse packen (z.B. ClipboardSaver).


galagher - Mi 20.05.20 12:47

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Aber wie gesagt, interessant wäre ja an welcher Stelle genau der Fehler auftritt, wenn du durch den Quelltext debuggst, also durch TClipboard.GetFormats usw. (ich sehe dort z.B. eine Unterscheidung nach CanReadFromCache).
In clipbrd.pas ist GetFormats zwar deklariert, aber im implementation-Teil nicht vorhanden. Wo also finde ich CanReadFromCache?

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Und auch bzgl. des SIGSEGV-Fehlers: Exceptions: Exception Classes [https://wiki.freepascal.org/Exceptions#Exception_classes]
Füge die Unit SysUtils mal hinzu.

Dann solltest du aber zusätzlich einen try..except-Block um die fehlerhafte Funktion packen.
Seh ich mir an!

Danke für eure Tipps!


jaenicke - Mi 20.05.20 13:29

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
In clipbrd.pas ist GetFormats zwar deklariert, aber im implementation-Teil nicht vorhanden. Wo also finde ich CanReadFromCache?
Debugge doch einfach von der Zeile in deinem Quelltext aus durch. ;-)


galagher - Mi 20.05.20 15:59

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Füge die Unit SysUtils mal hinzu.
SysUtils ist eingebunden.

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Dann solltest du aber zusätzlich einen try..except-Block um die fehlerhafte Funktion packen.
Nützt leider nichts.

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
PS: Das Erzeugen der Stream-Objekte kannst du auch in die 2. Schleife packen (wie für das Formats-Array).
Und wenn du den Code wiederverwendbar machen möchtest, dann solltest du die Funktionalität in eine eigene Klasse packen (z.B. ClipboardSaver).
Habe ich vor! Erstmal herausfinden, was da los ist.

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Debugge doch einfach von der Zeile in deinem Quelltext aus durch. ;-)
Zwei Möglichkeiten:
1. Das Programm wird mit einer SIGSEGV-Meldung abgebrochen, mit oder ohne try/except. Debuggen von der Zeile im Quelltext? Ich setzt einen Haltepunkt bei aFormats[i] := ClipBoard.Formats[i];, der Debugger hält mehrfach dort an, dann kracht es. Das except wird erst gar nicht angesprungen. (ClipBoard.Formats; verwendet ja ClipBoard.GetFormats, da ist ein Haltepunkt also prinzipiell sinnvoll).
2. Es klappt alles wie gewünscht.
Mal so, mal so, aber nie mit RTF-Text oder Grafiken aktueller Programme. Wenn ich den RTF-Text mit LibreOffice in die Zwischenablage kopiere, befindet sich dort ein Format namens "Richtext Format". Wenn ich den RTF-Text mit Word in die Zwischenablage kopiere, gibt es dieses Format nicht. Ob das die Ursache ist, weiss ich nicht.

Ich kann leider nirgendwo CanReadFromCache finden, geschweige denn den Code von ClipBoard.GetFormats.

Welche Möglichkeiten gibt es noch, wenn try/except nichts nützen?


jaenicke - Mi 20.05.20 16:50

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Debugge doch einfach von der Zeile in deinem Quelltext aus durch. ;-)
Zwei Möglichkeiten:
1. Das Programm wird mit einer SIGSEGV-Meldung abgebrochen, mit oder ohne try/except. Debuggen von der Zeile im Quelltext? Ich setzt einen Haltepunkt bei aFormats[i] := ClipBoard.Formats[i];, der Debugger hält mehrfach dort an, dann kracht es. Das except wird erst gar nicht angesprungen.
Ja, aber was passiert dort?
Hast du noch nie schrittweise debuggt? Sprich F7 um in eine Methode hinein zu debuggen, F8 um darüber hinweg zu debuggen?

Beispiel:
LazarusDebugging


Th69 - Mi 20.05.20 16:52

Läuft das Programm denn außerhalb der IDE (mit aktiviertem try..except) durch?
Hast du evtl. in der IDE "Halt on exception" in den Optionen aktiviert?

Und hast du nur einen leeren except-Block? Dann versuche mal

Delphi-Quelltext
1:
2:
3:
4:
5:
try
  { ... }
except 
  on E : Exception do ShowMessage(E.Message);
end


galagher - Do 21.05.20 20:53

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Hast du noch nie schrittweise debuggt? Sprich F7 um in eine Methode hinein zu debuggen, F8 um darüber hinweg zu debuggen?
F7 nicht so oft, aber F8 doch, natürlich, aber es war noch nie so mühsam wie diesmal, weil es ja nicht jedesmal zu diesem SIGSEGV kommt. Aber ich denke, ich habe die Stelle gefunden in win32winapi.inc in Prozedur TWin32WidgetSet.ClipboardGetData bei Size := Windows.GlobalSize(DataHandle);:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
function TWin32WidgetSet.ClipboardGetData(ClipboardType: TClipboardType;
  FormatID: TClipboardFormat; Stream: TStream): Boolean;
var
  DataHandle: HGLOBAL;
  Data: pointer;
  Size: integer;
  {$IFDEF VerboseWin32Clipbrd}
  DbgFormatID: integer;
  {$ENDIF}
  Bitmap: TBitmap;
  BufferStream: TMemoryStream;
  BufferWideString: widestring;
  BufferString: ansistring;

  function ReadClipboardToStream(DestStream: TStream): Boolean;
  begin
    Result := false;

    DataHandle := Windows.GetClipboardData(FormatID);
    if DataHandle<>HWND(0then
    begin
      Size := Windows.GlobalSize(DataHandle);  //Nach F7 hier kommt dann die SIGSEGV-Meldung!
//...

Warum klappt es manchmal, dann wieder nicht? Und: Wenn ich das Programm einfach trotzdem weiter laufen lasse, wird die Grafik eingefügt und der ursprüngliche Inhalt der Zwischanablage ist wieder vorhanden!

Vor allem aber: Wie kann ich das nun beheben?


galagher - Do 21.05.20 21:27

Mittlerweile bin ich mir nicht mehr sicher, dass das die einzige Stelle ist, bei der der Fehler auftritt. Denn wenn ich dort einen Haltepunkt setze und dann F8 drücke, läuft der Code weiter und dann erst kommt SIGSEGV.

Auch das Ummanteln der Codestelle mit try/except nützt nichts.

Weil der Fehler ja nicht jedesmal auftriff, ist das Debuggen wirklich mühselig.


galagher - Do 21.05.20 21:35

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Läuft das Programm denn außerhalb der IDE (mit aktiviertem try..except) durch?
Nein. Es bricht mit der Meldung "... funktioniert nicht mehr ... muss beendet werden" ab.

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Hast du evtl. in der IDE "Halt on exception" in den Optionen aktiviert?
Ich finde diese Einstellung nicht, ich weiss es nicht

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Und hast du nur einen leeren except-Block?
Nein, egal ob leer oder nicht, es geht nie ins except.


Th69 - Fr 22.05.20 16:36

Versuche mal einen eigenen "Runtime error" wie DivideByZero (x/0) auszulösen und zu fangen - klappt das denn?


galagher - Fr 22.05.20 17:00

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Versuche mal einen eigenen "Runtime error" wie DivideByZero (x/0) auszulösen und zu fangen - klappt das denn?

Ja, das klappt mit dem try/except. Ich habe mir eine kleine Prozedur gebastelt:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
var
  x, y: Double;
begin
  try
    x := 1;
    y := 0;
    Caption := IntToStr(Round(x / y));

  except
    ShowMessage('Fehler.');
  end;
end;

Mit der IDE kommt zunächst eine SIGFPE-Fehlermeldung und die IDE markiert die Zeile Caption := IntToStr. Wenn ich das Programm weiter ausführe, springt es ins except und ShowMessage erscheint.
Ohne IDE springt es gleich ins except und ShowMessage erscheint.


Th69 - Fr 22.05.20 18:07

Das, was mir dann noch einfällt, wäre die Source-Unit von TClipboard zu duplizieren (und einen anderen Klassennamen bzw. Namensbereich geben), dort dann uses SysUtils hinzufügen und mit dieser neuen Klasse noch mal auszuprobieren.

Edit: Oh, sehe gerade, daß der SIGSEGV-Fehler ja noch tiefer in der Klasse TWin32WidgetSet auftritt: Ist das eine Basisklasse von TClipboard?
Trotzdem probiere mal (zuerst) das oben beschriebene.


galagher - Fr 22.05.20 18:20

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Das, was mir dann noch einfällt, wäre die Source-Unit von TClipboard zu duplizieren (und einen anderen Klassennamen bzw. Namensbereich geben), dort dann uses SysUtils hinzufügen und mit dieser neuen Klasse noch mal auszuprobieren.
Aber in clipbrd.pp ist SysUtils bereits angegeben. Was meinst du?


Th69 - Fr 22.05.20 18:47

Dann ist das ja noch eigenartiger.
Ist das denn in TWin32WidgetSet auch schon eingebunden?

PS: Linkst du die Bibliotheken dynamisch oder statisch (d.h. verwendest du die Runtime-Packages oder nicht)?


galagher - Fr 22.05.20 19:05

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Ist das denn in TWin32WidgetSet auch schon eingebunden?
Nun, TWin32WidgetSet ist in Win32.int deklariert, und ja, SysUtils ist auch dort eingebunden.

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
PS: Linkst du die Bibliotheken dynamisch oder statisch (d.h. verwendest du die Runtime-Packages oder nicht)?
Die Packages sind fix in Lazarus, das heisst, ich habe die IDE kompiliert, um die verschiedenen Packages nutzen zu können.


Th69 - Fr 22.05.20 20:15

Kommst du denn auch an den Sourcecode von SysUtils ran, um zu schauen, wie dort die Exceptions für die Runtime-Fehler eingeschaltet werden?

Irgendwie muß es ja einen Unterschied geben, denn bei deinem Code funktioniert es ja.

Edit: Es wird wohl ErrorProc [https://www.freepascal.org/docs-html/rtl/system/errorproc.html] auf eine eigene Prozedur gesetzt (evtl. kannst du in dieser mal debuggen, d.h. Haltepunkt setzen).


galagher - Fr 22.05.20 20:19

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Kommst du denn auch an den Sourcecode von SysUtils ran, um zu schauen, wie dort die Exceptions für die Runtime-Fehler eingeschaltet werden?
Ja, den Sourcecode habe ich. Wonach genau suchen wir denn?


Th69 - Fr 22.05.20 20:28

Hatte gerade noch meinen Beitrag editiert: schau mal nach der Prozedur, welcher ErrorProc zugewiesen wird.

Evtl. liegt es ja auch an diesem speziellen SIGSEV-Fehler. Dieser dürfte wohl "216 General Protection fault" aus Appendix D: Run-time errors [https://www.freepascal.org/docs-html/user/userap4.html] entsprechen.
Kannst du diesen mal mit deinem Testprogramm auslösen (d.h. einen ungültigen Speicherzugriff, z.B. auf eine feste Adresse bzw. Dereferenzierung von nil)?


galagher - Fr 22.05.20 20:28

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Edit: Es wird wohl ErrorProc [https://www.freepascal.org/docs-html/rtl/system/errorproc.html] auf eine eigene Prozedur gesetzt (evtl. kannst du in dieser mal debuggen, d.h. Haltepunkt setzen).
In sysutils.pp kommt ErrorProc nicht vor.

Aber ich habe eine interessante Seite gefunden: https://www.delphipraxis.net/149513-clipboard-temporaer-speichern.html
Leider kann ich den Code aber nicht nutzen, weil Lazarus keine Prozedur ClipBoard.GetAsHandle hat. Lieber würde ich aber natürlich den momentanen Weg mit den Arrays beibehalten.

Edit: Und auch ClipBoard.SetAsHandle gibt es nicht.


galagher - Fr 22.05.20 20:41

In sysutils.inc habe ich gefunden:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
Procedure InitExceptions;
begin
//...
  ErrorProc:=@RunErrorToExcept;
//...
end;


//...


Edit: Ich denke ja doch, dass es - namentlich bei Lazarus - erlaubt ist, fremden Sourcecode zur Gänze zu posten.


galagher - Fr 22.05.20 21:04

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Evtl. liegt es ja auch an diesem speziellen SIGSEV-Fehler. Dieser dürfte wohl "216 General Protection fault" aus Appendix D: Run-time errors [https://www.freepascal.org/docs-html/user/userap4.html] entsprechen.
Kannst du diesen mal mit deinem Testprogramm auslösen (d.h. einen ungültigen Speicherzugriff, z.B. auf eine feste Adresse bzw. Dereferenzierung von nil)?
Was und wie genau zB.?


Th69 - Fr 22.05.20 21:10

Ich habe es jetzt auch direkt auf dem freepascal-git repository gefunden: sysutils.inc [https://github.com/graemeg/freepascal/blob/master/rtl/objpas/sysutils/sysutils.inc]

Mein Delphi ist schon etwas eingerostet, aber laut Pointers and references: Delphi [https://rosettacode.org/wiki/Pointers_and_references#Delphi] so ähnlich:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
var
  pIntPointer : ^Integer;
  IntVar : Integer;
begin
  pIntPointer := nil;
  IntVar := pIntPointer^; { <- und das dann in einem try..except Block }
end


Und dann debugge mal die RunErrorToExcept-Prozedur (Step by Step).
Überprüfe auch mal, ob CatchUnhandledException angesprungen wird (was ja bei aktiviertem try..except-Block eigentlich nicht passieren sollte).


galagher - Fr 22.05.20 21:16

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Und dann debugge mal die RunErrorToExcept-Prozedur (Step by Step).
Ich soll den Code also in eine Prozedur meines Programms testweise einbauen, try/except drum rum, und das dann testen?


Th69 - Fr 22.05.20 21:27

Ja, so wie dein vorheriges Testprogramm mit der DivideByZero-Exception und dann Haltepunkt auf die erste Zeile der RunErrorToExcept-Prozedur setzen und schrittweise debuggen, was da passiert (warum dein except-Block nicht angesprungen wird).

Edit:
Du sollst nicht den Code der RunErrorToExcept-Prozedur kopieren, sondern diesen nur debuggen (d.h. die Sourcedatei im Editor öffnen und Haltepunkt dort setzen - es kann sein, daß du erst dein Programm starten mußt und einen eigenen Haltepunkt anspringen mußt, ehe du den Haltepunkt in der Systemdatei setzen kannst).

PS: Falls du explizit 216 abfangen willst, dies sollte dann eine EAccessViolation-Exception sein.


galagher - Fr 22.05.20 21:56

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Edit:
Du sollst nicht den Code der RunErrorToExcept-Prozedur kopieren, sondern diesen nur debuggen (d.h. die Sourcedatei im Editor öffnen und Haltepunkt dort setzen - es kann sein, daß du erst dein Programm starten mußt und einen eigenen Haltepunkt anspringen mußt, ehe du den Haltepunkt in der Systemdatei setzen kannst).
Lazarus akzeptiert in sysutils.inc keine Haltepunkte...

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Ja, so wie dein vorheriges Testprogramm mit der DivideByZero-Exception und dann Haltepunkt auf die erste Zeile der RunErrorToExcept-Prozedur setzen und schrittweise debuggen, was da passiert (warum dein except-Block nicht angesprungen wird).
Im Testcode wird except sehr wohl angesprungen, und zwar mit und ohne IDE!

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Edit:
Du sollst nicht den Code der RunErrorToExcept-Prozedur kopieren, sondern diesen nur debuggen (d.h. die Sourcedatei im Editor öffnen und Haltepunkt dort setzen - es kann sein, daß du erst dein Programm starten mußt und einen eigenen Haltepunkt anspringen mußt, ehe du den Haltepunkt in der Systemdatei setzen kannst).
Ok, dann entferne ich das aus meinem Beitrag. Aber wie gesagt: Haltepunkt klappt in sysutils.inc nicht. Liegt's an der Dateinamenerweiterung oder an der Tatsache, das die Methoden dort nur im implementation-Teil und nicht auch im interfacce stehen?

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
PS: Falls du explizit 216 abfangen willst, dies sollte dann eine EAccessViolation-Exception sein.
Hm... Schritt für Schritt erstmal...

Edit:
Für heute erstmal vielen Dank!


Th69 - Fr 22.05.20 22:05

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
Lazarus akzeptiert in sysutils.inc keine Haltepunkte...

Das ist blöd. ;-(

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
Im Testcode wird except sehr wohl angesprungen, und zwar mit und ohne IDE!

Auch bei einem SIGSEGV-Fehler?
Dann verstehe ich nicht, warum es nicht bei deinem Clipboard-Programm funktioniert?

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
Für heute erstmal vielen Dank!

Dann gute Nacht!


galagher - Sa 23.05.20 05:21

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Dann verstehe ich nicht, warum es nicht bei deinem Clipboard-Programm funktioniert?
Ich auch nicht. Und was ich noch nicht verstehe: Der SIGSEGV-Fehler tritt nur so etwa jedes 10. Mal auf, ansonsten funktioniert es.

Mit der IDE kam diesmal danach noch folgende Meldung:
Zitat:
Projekt project1 hat Exception-Klasse "Exception" ausgelöst mit der Meldung:
IndexOfCachedFormatID: Internal Error: invalid FormatID 0 for clipboard

In Datei 'include\clipboard.inc' in Zeile 58


Und Zeile 58 dieser Datei ist eben genau dies:

Delphi-Quelltext
1:
2:
3:
raise Exception.Create(
  'IndexOfCachedFormatID: Internal Error: invalid FormatID 0 for '+
     ClipboardTypeName[ClipboardType]);

Vielleicht hilf das ja weiter.

Zu den SIGSEGV-Fehlermeldungen selbst: Die linke Grafik zeigt den SIGSEGV-Fehler bei dem "pIntPointer := nil;"-Test, die rechte den SIGSEGV-Fehler beim Einfügen der Grafik.

Edit:
Und was die Sache noch seltsamer macht: Ich kopiere also irgendeinen RTF-Text von Word in die Zwischanablage, füge danach meine Grafik ein, es knallt und das Programm beendet sich. Danach starte ich das Programm erneut, füge wieder die Grafik ein und diesmal funktioniert es. Und wenn ich dann die Grafik noch so oft einfüge - es klappt.
Wenn es 1x klappt, dann bleibt das auch so. Wenn nicht, dann bleibt auch das so. Bis ich exakt den selben Text erneut kopiere. Dann sind die Karten neu gemischt und es wird klappen oder eben nicht, meist jedoch schon... :eyecrazy:


Th69 - Sa 23.05.20 09:26

Guten Morgen,

sind das dann beides Fehlermeldungen vom System oder sind das deine eigenen (per ShowMessage(E.Message))?

Und bei dem DivideByZero-Testprogramm verhält es sich anders, d.h. da wird direkt der except-Block angesprungen?

Du könntest auch einfach mal selber ErrorProc auf eine eigene Prozedur umleiten und dann nur folgenden Code dort verwenden:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
var
  E : Exception;
begin
  E := Exception.Create('Test-Exception');
  Raise E at Address,Frame;
end;

Und wenn der dann gefangen wird (ohne Dialog), dann temporär vor dem Clipboard-Aufruf diese ErrorProc-Prozedur verwenden und nachher wieder auf die ursprüngliche zurücksetzen.


galagher - Sa 23.05.20 09:38

Guten Morgen!

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
sind das dann beides Fehlermeldungen vom System oder sind das deine eigenen (per ShowMessage(E.Message))?
Beides sind Fehlermeldungen vom System.

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Und bei dem DivideByZero-Testprogramm verhält es sich anders, d.h. da wird direkt der except-Block angesprungen?
Ja, ausserhalb der IDE schon. Mit der IDE kommt zuerst eine SIGFPE-Meldung und dann das except. Bei dem IntVar := pIntPointer^-Test verhält es sich ebenso, nur dass da mit der IDE eben eine SIGSEGV-Meldung kommt.

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Du könntest auch einfach mal selber ErrorProc auf eine eigene Prozedur umleiten und dann nur folgenden Code dort verwenden:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
var
  E : Exception;
begin
  E := Exception.Create('Test-Exception');
  Raise E at Address,Frame;
end;

Und wenn der dann gefangen wird (ohne Dialog), dann temporär vor dem Clipboard-Aufruf diese ErrorProc-Prozedur verwenden und nachher wieder auf die ursprüngliche zurücksetzen.
Das verstehe ich einfach nicht!


galagher - Sa 23.05.20 12:36

Ok, ich habe ErrorProc nach dem Vorbild von sysutils.inc in meinen Code eingebaut, die Deklaration ErrorProc:=@RunErrorToExcept; habe ich in den initialization-Abschnitt von RichMemo.pas gesetzt. Deinen Code habe ich dann in Prozedur RunErrorToExcept eingefügt.

Ergebnis: Unverändert, alles ist wie vorher. :? RunErrorToExcept wird nicht erreicht.


Th69 - Sa 23.05.20 13:37

Vllt. ist die Zuweisung zu früh (und sie wird von der anderen Initialisierung überschrieben).
Setze es mal direkt vor dem try.


galagher - So 24.05.20 19:59

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Vllt. ist die Zuweisung zu früh (und sie wird von der anderen Initialisierung überschrieben).
Setze es mal direkt vor dem try.
Welches try und welche Zuweisung genau meinst du:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
    try  {finally}  //<- dieses try?
      {Array aFormats (=die ClipBoard-Formate) befüllen und Daten in das Array aStreams schreiben}
      for i := 0 to n-1 do
      begin
        {Stream-Array anlegen für die ClipBoard-Formate}
        aStreams[i] := TMemoryStream.Create;
        aFormats[i] := ClipBoard.Formats[i];  //<- diese Zuweisung?
        {ClipBoard-Formate sichern}
        ClipBoard.GetFormat(aFormats[i], aStreams[i]);


galagher - Mo 25.05.20 22:03

Der SIGSEGV-Fehler tritt nur bei Zwischenablagendaten von meinen beiden alten Programmen auf, bei Zwischenablagendaten anderer aktueller Programme funktioniert es ja problemlos, also akzeptiere ich das erstmal so.

Wie ich heute festgestellt habe, funktioniert ClipBoard.Clear nicht. TClipBoard ist offenbar ein wenig buggy...