Entwickler-Ecke

Grafische Benutzeroberflächen (VCL & FireMonkey) - Länge einer formatierten Zeile in RichEdit


Daniel L. - Mi 27.10.10 17:29
Titel: Länge einer formatierten Zeile in RichEdit
Hallo!

Kann man die Länge in Pixel einer formatierten Textzeile in einer RichEdit bestimmen?


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
with RichEdit do
begin
    SelAttributes.Color := clred;
    SelAttributes.Size := 20;
    Paragraph.Alignment := taCenter;
    Lines.Add('Wie lang ist eigentlich diese Zeile in Pixeln');
end;


Schon mal Danke + Gruss: Daniel


elundril - Mi 27.10.10 18:23

Mittels Canvas.TextWidth;

hier ein beispiel:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
  var bmp := TBitmap;
begin
  bmp := TBitmap.Create();
  bmp.canvas.Font := Richedit1.Font;
  laenge := bmp.canvas.textwidth(deine_Zeile);
  bmp.free
end;


das noch mit entsprechenden Ressourcenschutzblöcken ausstatten und voilá!

lg elundril


jaenicke - Mi 27.10.10 19:40

In einem RichEdit kann der Text aber beliebig formatiert sein.

Ich vermute daher, dass du da manuell durchgehen musst und die Zeichen entsprechend ihrer Formatierung behandeln musst. Eine fertige Routine gibt es dafür AFAIK nicht.


Gerd Kayser - Mi 27.10.10 23:50

user profile iconDaniel L. hat folgendes geschrieben Zum zitierten Posting springen:
Kann man die Länge in Pixel einer formatierten Textzeile in einer RichEdit bestimmen?
Beispiel:

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 TMainform.Button1Click(Sender: TObject);
var
  PunktZeile  : TPoint;
  PunktAnfang : TPoint;
  CharPos     : integer;
begin
  CharPos := 0;
  SendMessage(RichEdit1.Handle, EM_PosFromChar, wParam(@PunktAnfang), lParam(CharPos));
  CharPos := SendMessage(RichEdit1.Handle, EM_LineLength, 00);
  SendMessage(RichEdit1.Handle, EM_PosFromChar, wParam(@PunktZeile), lParam(CharPos));
  Label1.Caption := 'Anzahl Zeichen: ' + IntToStr(CharPos);
  Label2.Caption := 'Breite der Zeile (Space + Text): ' + IntToStr(PunktZeile.X);
  Label3.Caption := 'Breite der Zeile (nur Text): '
    + IntToStr(PunktZeile.X - PunktAnfang.X + 1);
end;

procedure TMainform.Button2Click(Sender: TObject);
begin
  with RichEdit1 do
    begin
      Clear;
      SelAttributes.Color := clred;
      SelAttributes.Size := 20;
      Paragraph.Alignment := taCenter;   // ggf. zum Testen auskommentieren
      Lines.Add('Wie lang ist eigentlich diese Zeile in Pixeln?');
    end;
end;

Im Beispiel gehe ich von nur einer Zeile aus, also ggf. anpassen.

Edit1: Ob die Zeichenbreite des letzten Zeichens in den Werten eingerechnet ist, habe ich noch nicht geprüft.
@elundril: Deine Prozedur scheint falsche Werte zu liefern (203). Ich ermittele hingegen 509, was wohl eher hinkommen dürfte.

Edit2: Das letzte Zeichen ist mit eingerechnet, was man leicht überprüfen kann, wenn der Text aus nur einem Zeichen besteht.


jaenicke - Do 28.10.10 05:51

Stimmt, auf die Idee bin ich gar nicht gekommen. Über die Position des ersten und letzten Zeichens lässt sich die Breite natürlich berechnen. :zustimm:


Daniel L. - Do 28.10.10 09:12

@Gerd: Vielen Dank für die kompetente Antwort!
Verrätst du mir noch, was ich anpassen muss, um z.B. die 3. Zeile zu 'messen'?

Danke + Gruss: Daniel


jaenicke - Do 28.10.10 11:11

CharPos musst du (bezogen auf das Beispiel) entsprechend setzen.


Gerd Kayser - Do 28.10.10 13:14

user profile iconDaniel L. hat folgendes geschrieben Zum zitierten Posting springen:
Verrätst du mir noch, was ich anpassen muss, um z.B. die 3. Zeile zu 'messen'?
Ich komme erst heute Nacht dazu, weiter zu programmieren. Jetzt stehen erst einmal zwei Stunden Gassigänge mit meiner verspielten Schäferhündin und ein längerer Zahnarztbesuch an.


Daniel L. - Do 28.10.10 19:16

hm, intern scheint TRichEdit für jede Textzeile 2 weitere Charakters dazuzuzählen (vielleicht #10 und #13 ?)

Folgende Funkion funzt bei mir:


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:
function TForm1.GetZeilenBreite (Zeile : integer) : integer;
var i, CharCount : integer;
    ZeileAnfang, ZeileEnde : TPoint;
begin

  if (zeile > (RichEdit1.Lines.Count - 1)) or (zeile < 0then
     begin
       result := -1;
       exit;
     end;

  CharCount := 0;

  for I := 0 to zeile - 1 do inc (CharCount, length (RichEdit1.Lines [i]) + 2);
      // erstmal die Zeichen der Zeilen davor zählen


  SendMessage (RichEdit1.Handle, EM_PosFromChar, wparam (@ZeileAnfang), CharCount);
  CharCount := CharCount  + SendMessage (RichEdit1.Handle, EM_LineLength, CharCount, 0);
  SendMessage(RichEdit1.Handle, EM_PosFromChar, wParam (@ZeileEnde), CharCount);
  result := ZeileEnde.X - ZeileAnfang.X;
end;

//-----------

procedure TForm1.Button1Click(Sender: TObject);
begin
   showmessage ('Zeilenbreite  = ' + Inttostr (GetZeilenBreite (1)));
end;


Ich hoffe, dass das auch programmiertechnisch in Ordnung ist und dass Gerd kein Zahnschmerzen hat :)

Gruss: Daniel


jaenicke - Do 28.10.10 19:29

user profile iconDaniel L. hat folgendes geschrieben Zum zitierten Posting springen:
Ich hoffe, dass das auch programmiertechnisch in Ordnung ist und dass Gerd kein Zahnschmerzen hat :)
Ich bekomm da immer eher Bauchschmerzen. :P

Zur Sache:
Du brauchst keine Schleife. Mit EM_LINEINDEX bekommst du das erste Zeichen einer Zeile:
http://msdn.microsoft.com/en-us/library/bb761611(v=VS.85).aspx
Steht alles in der Doku. ;-)

// EDIT:
Ach ja: Die beiden Zeichen pro Zeile mehr sind in der Tat die Zeilenumbruchszeichen.


Gerd Kayser - Do 28.10.10 20:17

user profile iconDaniel L. hat folgendes geschrieben Zum zitierten Posting springen:
Ich hoffe, dass das auch programmiertechnisch in Ordnung ist und dass Gerd kein Zahnschmerzen hat :)
Nö, keine Zahnschmerzen. Bin nur um ca. 3 Gramm Edelmetall wertvoller geworden (neue Zahnkrone). ;-)
Ich bin zur Zeit dabei, eine komfortable Funktion für die Aufgabe zu schreiben. Dazu brauche ich aber noch etwas Zeit (debuggen, testen usw.). Aber jetzt muß ich erst einmal wieder mit meiner Madame die Bäumchen im Park zählen und kontrollieren gehen. ;-)


Gerd Kayser - Do 28.10.10 23:51

So, hier ist meine Lösung, die auch Zeilenumbrüche durch WordWrap richtig behandelt. Bitte beachten, daß die Funktion verschieden aufgerufen werden kann:
1. mit Zeile = - 1: liefert die Zeilenanzahl zurück
2. mit Zeilennummer, aber NurText = false: Breite der Zeichenkette plus dem Leerraum vor dem 1. Zeichen
3. mit Zeilennummer, aber NurText = true: Breite der Zeichenkette
NurText kann beim Aufruf weggelassen werden. Dann arbeitet die Funktion mit NurText = true


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:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
unit Main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls;

type
  TMainform = class(TForm)
    RichEdit1: TRichEdit;
    Button1: TButton;
    Label1: TLabel;
    Label2: TLabel;
    Button2: TButton;
    Label3: TLabel;
    Edit1: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
    function ErmittelnTextBreite(Handle : THandle; Zeile : integer;
             NurText : boolean = true): integer;
  end;

var
  Mainform: TMainform;

implementation

{$R *.dfm}

// Zeilenindex ist 0-basierend ---> 0 = 1. Zeile
function TMainform.ErmittelnTextBreite(Handle : THandle; Zeile : integer;
         NurText : boolean = true): integer;
var
  AnzZeilen      : integer;
  ErstesZeichen  : integer;
  LetztesZeichen : integer;
  PunktStart     : TPoint;
  PunktEnde      : TPoint;
begin
  Result := - 1;
  AnzZeilen := SendMessage(Handle, EM_GetLineCount, 00);

  // Anzahl der Zeilen zurückgeben, wenn Zeile = - 1,
  // sonst die Textbreite der Zeile ermitteln.

  if Zeile = - 1 then
    Result := AnzZeilen
  else
    begin
      ErstesZeichen := SendMessage(Handle, EM_LineIndex, wParam(Zeile), 0);
      LetztesZeichen := SendMessage(Handle, EM_LineLength, wParam(ErstesZeichen), 0);
      SendMessage(Handle, EM_PosFromChar, wParam(@PunktStart), lParam(ErstesZeichen));
      SendMessage(Handle, EM_PosFromChar, wParam(@PunktEnde), lParam(ErstesZeichen + LetztesZeichen));

      // Zeilenumbruch durch WordWrap abfangen

      if PunktStart.Y <> PunktEnde.Y then     // Zeilenumbruch?
        SendMessage(Handle, EM_PosFromChar, wParam(@PunktEnde),
          lParam(ErstesZeichen + LetztesZeichen - 1));

      // Ergebnis zurückgeben

      if NurText then
        Result := PunktEnde.X - PunktStart.X + 1
      else
        Result := PunktEnde.X;
    end;
end;

procedure TMainform.Button1Click(Sender: TObject);
var
  Zeile     : integer;
  AnzZeilen : integer;
  Breite    : integer;
begin
  if not TryStrToInt(Trim(Edit1.Text), Zeile) then
    ShowMessage('Fehler: Keine Ziffer im Edit-Feld!')
  else
    begin
      AnzZeilen := ErmittelnTextBreite(RichEdit1.Handle, - 1);
      Label1.Caption := 'Anzahl Zeilen: ' + IntToStr(AnzZeilen);
      if Zeile > (AnzZeilen - 1then
        ShowMessage('Fehler: RichEdit enthält nicht so viele Zeilen!')
      else
        begin
          Breite := ErmittelnTextBreite(RichEdit1.Handle, Zeile, false);
          Label2.Caption := 'Breite (Space am Angfang + Text): ' + IntToStr(Breite);
          Breite := ErmittelnTextBreite(RichEdit1.Handle, Zeile);
          // alternativ:
          //   Breite := ErmittelnTextBreite(RichEdit1.Handle, Zeile, true);
          Label3.Caption := 'Breite (nur Text): ' + IntToStr(Breite);
        end;
    end;

end;

procedure TMainform.Button2Click(Sender: TObject);
var
  Text1 : string;
  Text2 : string;
begin
  Text1 := 'Wie lang ist eigentlich diese Zeile in Pixeln?';
  Text2 := Text1 + Text1 + Text1;
  with RichEdit1 do
    begin
      Clear;
      SelAttributes.Color := clred;
      SelAttributes.Size := 20;
      Paragraph.Alignment := taCenter;   
      Lines.Add(Text1);
      SelAttributes.Color := clred;
      SelAttributes.Size := 20;
      Paragraph.Alignment := taCenter;
      Lines.Add(Text2);
      SelAttributes.Color := clred;
      SelAttributes.Size := 20;
      Paragraph.Alignment := taLeftJustify;
      Lines.Add(Text1);
      SelAttributes.Color := clred;
      SelAttributes.Size := 20;
      Paragraph.Alignment := taRightJustify;
      Lines.Add(Text1);
    end;
end;

end.

Edit1: Fehler in Zeile 89 korrigiert


Daniel L. - Fr 29.10.10 09:39

Oh ja, stimmt, ohne die WordWrap Beachtung kämen ja ggf. falsche Werte raus.

EM_GetLineCount scheint immer eine um 1 grössere Zahl zu liefern, man muss also wieder 1 subtrahieren, oder man nimmt Richedit.lines.count

user profile iconGerd Kayser hat folgendes geschrieben Zum zitierten Posting springen:


Delphi-Quelltext
1:
2:
    if NurText then
        Result := PunktEnde.X - PunktStart.X + 1



Bist du sicher, dass das stimmt?
Für eine leere Textzeile kommt dann 1 raus!


Gruss: Daniel


Gerd Kayser - Fr 29.10.10 11:47

user profile iconDaniel L. hat folgendes geschrieben Zum zitierten Posting springen:
EM_GetLineCount scheint immer eine um 1 grössere Zahl zu liefern, man muss also wieder 1 subtrahieren, oder man nimmt Richedit.lines.count

Das ist so nicht richtig. Es sind zwar z. B. 6 Zeilen mit Text vorhanden, aber es gibt noch eine 7. (leere) Zeile, in die Du mit dem Cursor springen kannst. Wenn Du in dieser Leerzeile die Backspace-Taste drückst, so daß der Cursor in der vorhergehenden Zeile direkt hinter dem Text steht, erst dann stimmen die Werte von Richedit.lines.count und EM_GetLineCount überein.
Zitat:

Delphi-Quelltext
1:
2:
    if NurText then
        Result := PunktEnde.X - PunktStart.X + 1


Bist du sicher, dass das stimmt?
Für eine leere Textzeile kommt dann 1 raus!
Folgender Fall:
Eine Zeile ist linksbündig (taLeftJustify) ausgerichtet. Ohne "+ 1" ergäben sich dann folgende Werte für den String:
Nur Text: 508
aber Text plus Leerraum davor: 509
Das ist jetzt Auslegungssache, ob man das Pixel vor dem Text zur Textbreite hinzuzählt oder nicht. Du kannst das "+ 1" weglassen oder alternativ die Funktion am Ende um folgende Anweisung ergänzen:

Delphi-Quelltext
1:
2:
if Result = 1 then
  Result := 0;
Das überlasse ich Dir. Ist wie gesagt eine reine Interpretationssache.