[Gelöst]TMemo EIngabetext Länge begrenzen (in Bytes, nicht Zeichen!)

Für Fragen von Einsteigern und Programmieranfängern...
Nimral
Beiträge: 390
Registriert: Mi 10. Jun 2015, 11:33

[Gelöst]TMemo EIngabetext Länge begrenzen (in Bytes, nicht Zeichen!)

Beitrag von Nimral »

Hi allseits,

mal wieder brauche ich einen Denkanstoß. Ich muss die Länge eines Texts in einem TMemo kontrollieren, und zwar in Bytes, nicht in Characters, weil der Text später in eine Datei mit fester Blockgröße eingepasst werden muss. MaxLength im Memo zählt aber nur Zeichen, und die können 1 - 4 Bytes lang werden. Umlaute haben z.B. 2 Bytes. Siehe Beispielprojektchen: MaxLength steht auf 60, gebe ich 60 Umlaute ein, bekomme ich 120 Bytes zurück. Blöd.

Ich habe dann versucht, dagegen zu halten:

Code: Alles auswählen

procedure TForm1.Memo1UTF8KeyPress(Sender: TObject; var UTF8Key: TUTF8Char);

var
  x:integer;

begin
  if UTF8Key < #32 then exit;         // pass through control characters
  x := length(UTF8Key);
  If Memo1.GetTextLen + x > Memo1.MaxLength then UTF8Key := #0;  // ignore this character
end;
Und sieh da, das klappt. Length liefert tatsächlich die Zeichenlänge in Bytes. Ein schlechtes Gewissen hab ich natürlich schon, wer weiß ob wirklich alle Steuercodes im Bereich von #0..#31 enthalten sind, aber sollte da noch einer kommen kann ich das Problem einfach lösen.

Aber keine Lösung ohne neues Problem: das funktioniert leider nicht mit der Zwischenablage. Fülle ich die Zwischenablage z.B. mit einem japanischen Text (von hier: https://www.branah.com/japanese), kommen - da japanische Zeichen mit 3 Bytes codiert werden - 180 Bytes ins Memo geflogen. Die werden richtig angezeigt, aber das hilft mir nicht, sobald ich das Memo wegspeichern möchte bekomme ich ein Problem. Dann abhacken oder das Speichern verweigern ist natürlich möglich, begeistert mich aber natürlich nicht.

Wie kann ich das Problem lösen, die Länge schon bei der Eingabe zu beschränken?

HG, Armin.

P.S: Ich habe natürlich versucht, die Memo1.PasteFromClipboard Methode *irgendwie* durch eine eigene zu ersetzen, die Idee ist naheliegend: erst ins Clipboard schauen wie lang der Text dort ist, und dann entweder *irgendwie* abhacken, oder den Paste verweigern wenn es sich nicht mehr ausgeht. Hab ich leider nicht hinbekommen. Ich habe den Code mit dem ich es versucht habe mal im Projekt stehen lassen, mal sehen wie weit ich von der Lösung entfernt war :-)
Dateianhänge
MemoToFile.zip
(106.76 KiB) 47-mal heruntergeladen
Zuletzt geändert von Nimral am Mo 2. Aug 2021, 08:04, insgesamt 2-mal geändert.

Benutzeravatar
Winni
Beiträge: 1577
Registriert: Mo 2. Mär 2009, 16:45
OS, Lazarus, FPC: Laz2.2.2, fpc 3.2.2
CPU-Target: 64Bit
Wohnort: Fast Dänemark

Re: TMemo EIngabetext Länge begrenzen (in Bytes, nicht Zeichen!)

Beitrag von Winni »

Hi!

Ich verstehe Dein Problem nicht so recht.
Irgendwie machst Du Dir alles zu kompliziert.

Fang doch mal,so an:

Code: Alles auswählen

Clipboard.AsText:= 'äöüÄÖÜß';
showMessage (IntToStr(length(ClipBoard.asText))+' / ' +
               IntToStr(UTF8Length(ClipBoard.asText)) );   
Ergibt 14 byte und 7 UTF8chars.

Winni

wp_xyz
Beiträge: 4888
Registriert: Fr 8. Apr 2011, 09:01

Re: TMemo EIngabetext Länge begrenzen (in Bytes, nicht Zeichen!)

Beitrag von wp_xyz »

Die Stringlänge auf eine festen Größe abzuschneiden hat bei UTF8 zur Folge, dass mit einer gewissen Wahrscheinlichkeit das letzte Zeichen zerstört wird. Wenn der String zu lang ist, dann musst du schon bei einer UTF8-Grenze abschneiden und in Kauf nehmen, dass die erlaubte Buffergröße nicht voll ausgeschöpft wird.

Ansonsten kannst du, was winni schon angedeutet hat, mit Length(Memo1.Lines.Text) die Textgröße in Bytes prüfen.

Nimral
Beiträge: 390
Registriert: Mi 10. Jun 2015, 11:33

Re: TMemo EIngabetext Länge begrenzen (in Bytes, nicht Zeichen!)

Beitrag von Nimral »

Na ja, ich wollte das direkt bei der EIngabe nun, und nicht erst hinterher wenns ans Speichern geht. Beim Hinzufügen von Zeichen mit der Tastatur gibt es ja mit UTF8KeyPress einen geeigneten Einhängpunkt, der die Maximalgrenze tatsächlich so weit als möglich ausschöpft. Ich suche eine Möglichkeit, das auch beim EInfügen von Text aus dem Clipboard zu machen.

Armin.

Benutzeravatar
Winni
Beiträge: 1577
Registriert: Mo 2. Mär 2009, 16:45
OS, Lazarus, FPC: Laz2.2.2, fpc 3.2.2
CPU-Target: 64Bit
Wohnort: Fast Dänemark

Re: TMemo EIngabetext Länge begrenzen (in Bytes, nicht Zeichen!)

Beitrag von Winni »

wp_xyz hat geschrieben:
Do 29. Jul 2021, 19:42
Die Stringlänge auf eine festen Größe abzuschneiden hat bei UTF8 zur Folge, dass mit einer gewissen Wahrscheinlichkeit das letzte Zeichen zerstört wird. Wenn der String zu lang ist, dann musst du schon bei einer UTF8-Grenze abschneiden und in Kauf nehmen, dass die erlaubte Buffergröße nicht voll ausgeschöpft wird.
SElbst dafür hat UTF8 vorgesorgt:

Bei defekten UTF8 Zeichen sollte � erscheinen. Das ist aber U+FFFD und benötigt 3 byte ....
Keine Ahnung ob das im Memo vernünftig implementiert ist.

Also lieber der Idee von wp_xyz folgen.

Winni

Benutzeravatar
Winni
Beiträge: 1577
Registriert: Mo 2. Mär 2009, 16:45
OS, Lazarus, FPC: Laz2.2.2, fpc 3.2.2
CPU-Target: 64Bit
Wohnort: Fast Dänemark

Re: TMemo EIngabetext Länge begrenzen (in Bytes, nicht Zeichen!)

Beitrag von Winni »

Also hier mal das benötigte Code-Fragment.

allowed sind die noch freien Bytes
s ist ein temporärer string, den Du nach dieser Operation ins Memo einfügst.

Der Trick geht so:
kopiere alle noch freien bytes in einen String. Dabei wird evtl das letzte UTF8 Zeichen zerstückelt.
UTF8Length gibt Dir aber nur die Antahl der kompletten Zeichen zurück.
Also stelle die UTF8-Länge fest.
Nun kopiere diese korrekte Anzahl UTF8-Zeichen aus dem Clipboard.
Nun hast Du einen String der die maximal mögliche Anzahl von UTF8 Zeichen enthält. Ohne Schrott.

Code: Alles auswählen

var allowed,UTFlen : integer;
        s : string;
    ....
    if length (clipboard.asText) <= allowed then s := clipboard.asText else
      begin
      s := copy (ClipBoard.asText,1,allowed);
      UTFLen := UTF8Length (s);
      s := UTF8copy (ClipBoard.asText,1,UTF8Len);
      end;

    
Winni

Nimral
Beiträge: 390
Registriert: Mi 10. Jun 2015, 11:33

Re: TMemo EIngabetext Länge begrenzen (in Bytes, nicht Zeichen!)

Beitrag von Nimral »

Hi WInni,

danke für die Antwort, aber sie setzt da an wo ich (gedanklich) auch schon war, und wo es für mich aber handwerklich nicht weiter geht. Wie genau baust Du Deine eigene PasteFromClipboard in das bestehende TMemo ein? Würde ich das Memo aus dem Code erzeugen, wüsste ich wie, aber ich hab das TMemo als Ressource und bearbeite sie mit dem Formular-Editor, da fand ich bisher keine Möglichkeit, Memo1.PasteFromClipboard zu überschreiben. in meinem Sample siehst Du ganz unten den Code, wie ich es versucht habe, aber da spielt FPC nicht mit:

Code: Alles auswählen

procedure TForm1.FormCreate(Sender: TObject);
begin
  //  Self.Memo1.PasteFromClipboard := @TMyMemo.PasteFromClipboard;   // Argument cannot be assigned to
  MyMemo := TMyMemo.Create(Self);
  // Self.Memo1.PasteFromClipboard := @MyMemo.PasteFromClipboard;   // Argument cannot be assigned to
  doClearMemo;
  doDisplayStatistics;
end;
Diese Frage hatte ich schon öfters, gibt es da eine Möglichkeit - außer eben das ganze Control im Code zu erzeugen.

Im Moment nütze ich aus, dass das Memo offenbar nicht abstürzt, wenn man seine MaxLength überschreitet. Beim Hinzufügen eigener Tastendrucke hatte ich ja eine Lösung gefunden, und beim Paste aus dem Clipboard feuert onChange, da stutze ich das Memo wieder zurecht. Schön ist das aber natürlich nicht, und ich muss mich drauf verlassen, dass das Memo die temporäre Überschreitung seiner MaxLength ab kann.

Wenn es keine bessere Möglichkeit gibt, mach ich eben aus dem was ich bisher geschafft habe das Beste. Ich beschränke dann die MaxLength während der EIngabe gar nicht, (der Benutzer sieht ja an der Statistics-Ausgabe dass er zu lang wird, die kann ich ja auch gerne noch rot einfärben), und weigere mich dann zu speichern wenn er versucht, das Memo zu verlassen.

HG, Armin.

Benutzeravatar
Winni
Beiträge: 1577
Registriert: Mo 2. Mär 2009, 16:45
OS, Lazarus, FPC: Laz2.2.2, fpc 3.2.2
CPU-Target: 64Bit
Wohnort: Fast Dänemark

Re: TMemo EIngabetext Länge begrenzen (in Bytes, nicht Zeichen!)

Beitrag von Winni »

Hi!

Falls bereits Text im Memo vorhanden ist, dann holst Du Dir separat den Text aus dem Clipboard - sogar mit Beachtung der maximalen Größe, wie ich es oben gezeigt habe. Dann steht der Clipboard-Text im String s.

Dann machst Du:

Code: Alles auswählen

Memo1.lines.Text:= Memo1.lines.Text +s;
Das hängt den Clipboard-Text an den vorhandenen Text hinten dran.

Wenn Du den Clipboard-Inhalt am Cursor einfügen willst, dann wird's etwas komplizierter:

Code: Alles auswählen

var p: Integer;
tmp : string;
....
tmp := Memo1.lines.text;
p := memo1.Selstart+1;
UTF8Insert(s,tmp,p);
Memo1.Lines.Text := tmp;



Winni

Nimral
Beiträge: 390
Registriert: Mi 10. Jun 2015, 11:33

Re: TMemo EIngabetext Länge begrenzen (in Bytes, nicht Zeichen!)

Beitrag von Nimral »

Winni! Vielen, vielen Dank, aber das Problem das mich im Moment aufhält ist das Andere!
Nimral hat geschrieben:
Fr 30. Jul 2021, 13:29
Wie genau baust Du Deine eigene PasteFromClipboard in das bestehende TMemo ein?
:-) :-)

Armin.

Benutzeravatar
Winni
Beiträge: 1577
Registriert: Mo 2. Mär 2009, 16:45
OS, Lazarus, FPC: Laz2.2.2, fpc 3.2.2
CPU-Target: 64Bit
Wohnort: Fast Dänemark

Re: TMemo EIngabetext Länge begrenzen (in Bytes, nicht Zeichen!)

Beitrag von Winni »

Nimral hat geschrieben:
Fr 30. Jul 2021, 22:28
Winni! Vielen, vielen Dank, aber das Problem das mich im Moment aufhält ist das Andere!
Nimral hat geschrieben:
Fr 30. Jul 2021, 13:29
Wie genau baust Du Deine eigene PasteFromClipboard in das bestehende TMemo ein?
:-) :-)

Armin.
Was ich Dir in der letzten Mail gezeigt habe, ist Dein persönliches PasteFromClipboard!!

Lesen bevor schreiben hilft.

Winni

Nimral
Beiträge: 390
Registriert: Mi 10. Jun 2015, 11:33

Re: TMemo EIngabetext Länge begrenzen (in Bytes, nicht Zeichen!)

Beitrag von Nimral »

Jaaaa, schon ... aber ich möchte natürlich den Rest vom TMemo behalten, also z.B. das Keybinding für Ctrl-x,c,v, statt das nochmal zu codieren.

Das muss doch irgendwie gehen, ich komm nur nicht drauf wie. Ich würde es aber gerne lernen.

Natürlich kann ich ein eigenes Memo von TMemo ableiten, und dort die Methode PasteFromClipboard durch eine eigene ersetzen, aber soweit ich mich dunkel erinnern kann verliere ich dann den Formular-Editor, weil der nun mal kein von TMemo abgeleitetes Memo bearbeiten mag. Aber ichd acht, am Ende des Tages sind doch die Methodenzeiger ebenso zuweisbar wie jeder andere Zeiger, sofern man nur auf eine Routine zeigt welche die richtege Parameterliste hat.

Irre ich mich da?

Armin.

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1435
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: TMemo EIngabetext Länge begrenzen (in Bytes, nicht Zeichen!)

Beitrag von fliegermichl »

Nimral hat geschrieben:
Sa 31. Jul 2021, 01:04
Aber ichd acht, am Ende des Tages sind doch die Methodenzeiger ebenso zuweisbar wie jeder andere Zeiger, sofern man nur auf eine Routine zeigt welche die richtege Parameterliste hat.

Irre ich mich da?
Ich fürchte ja. Die Klasse TMemo hat eine VMT (Virtual Method Table) und alle Instanzen dieser Klasse zeigen auf diese Tabelle. Was du für jede Instanz zuweisen kannst sind Events.
Du kannst aber Deine eigene Klasse von TMemo ableiten und als eigenständige Komponente registrieren. Dann kannst du auch alles im Formulareditor/Objektinspektor bearbeiten.

Benutzeravatar
Winni
Beiträge: 1577
Registriert: Mo 2. Mär 2009, 16:45
OS, Lazarus, FPC: Laz2.2.2, fpc 3.2.2
CPU-Target: 64Bit
Wohnort: Fast Dänemark

Re: TMemo EIngabetext Länge begrenzen (in Bytes, nicht Zeichen!)

Beitrag von Winni »

Nimral hat geschrieben:
Sa 31. Jul 2021, 01:04
Jaaaa, schon ... aber ich möchte natürlich den Rest vom TMemo behalten, also z.B. das Keybinding für Ctrl-x,c,v, statt das nochmal zu codieren.

Das muss doch irgendwie gehen, ich komm nur nicht drauf wie. Ich würde es aber gerne lernen.

Natürlich kann ich ein eigenes Memo von TMemo ableiten, und dort die Methode PasteFromClipboard durch eine eigene ersetzen, aber soweit ich mich dunkel erinnern kann verliere ich dann den Formular-Editor, weil der nun mal kein von TMemo abgeleitetes Memo bearbeiten mag. Aber ichd acht, am Ende des Tages sind doch die Methodenzeiger ebenso zuweisbar wie jeder andere Zeiger, sofern man nur auf eine Routine zeigt welche die richtege Parameterliste hat.

Irre ich mich da?

Armin.
Anstatt hier im virtuellen Raum rumzurätseln, solltest Du einfach nur meinen Code nehmen, den in deiner App anwenden und staunen, wie einfach das funktioniert.

Langsam ärgerst Du mich.

Winni

wp_xyz
Beiträge: 4888
Registriert: Fr 8. Apr 2011, 09:01

Re: TMemo EIngabetext Länge begrenzen (in Bytes, nicht Zeichen!)

Beitrag von wp_xyz »

Ich verstehe nicht, warum du dich so auf die Zwischenablage versteifst. Das Problem, dass dein Text zu lang werden kann, hast du doch auch allgemein schon beim Tippen. Daher würde ich bei jedem OnChange des Memo die Textlänge prüfen und ggfs den ganzen Text auf UTF8-Grenzen kürzen, z.B. mit sowas:

Code: Alles auswählen

uses
  LazUnicode;

function TrimToMaxBytes(s: String; MaxBytes: Integer): String;
var
  ch: String;
begin
  Result := '';
  for ch in s do
  begin
    if Length(Result) + Length(ch) > MaxBytes then exit;
    Result := Result + ch;
  end;
end; 
Generell wäre mir aber solche Software, die einfach ohne zu murren bereits eingegebenen Text abschneidet, zu rabiat. Ich würde den Text unverändert einfügen, auch aus der Zwischenablage, eine Fehlermeldung anzeigen und spätestens beim Speichern die Aktion verweigern, wenn der Text immer noch zu lang ist. Dann liegt es in der Verantwortung des Users das zu entfernen, was entbehrlich ist..

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2639
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: TMemo EIngabetext Länge begrenzen (in Bytes, nicht Zeichen!)

Beitrag von m.fuchs »

wp_xyz hat geschrieben:
Sa 31. Jul 2021, 14:57
Ich würde den Text unverändert einfügen, auch aus der Zwischenablage, eine Fehlermeldung anzeigen und spätestens beim Speichern die Aktion verweigern, wenn der Text immer noch zu lang ist. Dann liegt es in der Verantwortung des Users das zu entfernen, was entbehrlich ist..
Genau. Eine Anzeige unterhalb des Memos, die ansagt wieviel Byte noch möglich sind. Wenn das überschritten wurde die überzähligen Bytes in Rot oder so. Speichern dann verweigern. Das dürfte am benutzerfreundlichsten sein.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

Antworten