Autor Beitrag
Logikmensch
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 390

Win XP
Delphi 2007 Prof., XE2, XE5
BeitragVerfasst: Di 19.06.07 08:34 
Liebe Delphi-Freunde.

Ich möchte einen Textblock innerhalb eines rechteckigen Bereiches ausgeben. Den Schriftgrad (Buchstabenhöhe) kann der Benutzer frei wählen, ebenso die Schriftart.

Der rechteckige Bereich, in den der Text mit Wortumbruch geschrieben werden soll, hat eine feste Breite (in Pixeln bzw. cm), die nicht geändert werden darf, weil es sich um den Zeichenbereich einer Druckvorschau handelt.

Lediglich die Höhe, die der umgebrochene, ggf. mehrzeilige Text in Pixeln einnimmt, ist zu berechnen, also unbekannt.
Ich habe bereits mit DrawText und der Option WortBreak+CalcRect versucht, das geeignete
Rechteck zu berechnen, aber dummerweise wird die Breite dabei auch mitverändert.
Mein zu zeichnender Text verfügt nämlich nicht über Zeilenvorschübe, sondern nur Leerfelder, daher versagt DrawText mit vorhergehender Calcrect-Option
leider seinen Dienst. Die Breite des Rechteckes darf wie gesagt nicht geändert werden, nur die Höhe soll ermittelt werden.

Muss ich, um das Problem zu lösen, wirklich Zeichen für Zeichen bzw. Wort für Wort manuell in den rechteckigen Bereich reinzeichnen?

_________________
Es gibt keine Probleme - nur Lösungen!
Stefan.Buchholtz
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 612

WIN 2000, WIN XP, Mac OS X
D7 Enterprise, XCode, Eclipse, Ruby On Rails
BeitragVerfasst: Di 19.06.07 08:47 
Nein, mit DrawText sollte es eigentlich funktionieren. Zeig mal deinen Code.

Stefan

_________________
Ein Computer ohne Windows ist wie eine Schokoladentorte ohne Senf.
Logikmensch Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 390

Win XP
Delphi 2007 Prof., XE2, XE5
BeitragVerfasst: Di 19.06.07 10:16 
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
procedure gettextblock(line:integer; canvas:TCanvas; var drawrect:TRect; fontname:string;
  textheight:integer);
var
  r:TRect;
begin
  if (line>=0) and (line<textlist.Count) then 
  begin
    r:=drawrect;
    canvas.font.name:=fontname;
    Canvas.font.size:=textheight;
    //Drawrect ist hier mit der Breite, sowie der Left/Top-Koordinate initialisiert.
    //Die Höhe liegt bei 0
    DrawText(Canvas.Handle,PChar(textlist[line]),length(textlist[line]),r,
      dt_wordwrap or dt_left or dt_calcrect);
    //Hier sollte jetzt R berechnet sein, die Breite gleichgeblieben sein,
    //aber da TEXTLIST[Line] keine Zeilenumbrüche enthält, wird
    //offenbar (laut Debugger) auch die Zeilenbreite von R geändert.
    //Z.B. vorher: Left:354, Top: 354, Width: 6601; height:0
    //    nachher: Left:354, Top: 354, Width: 14000; height:21100
    drawrect:=r;
  end;
end


Ist aus dem Gedächtnis hier eingefügt. Das Dumme ist, dass die ermittelten Koordinaten
für den berechneten rechteckigen Bereich zwar sinn machen (auch wenn die Breite ungewollt verändert wird), aber ein nachfolgendes
DrawText mit genau diesen Koordinaten, jedoch ohne dt_calcrect, den Text nicht zeichnen will (es erscheint gar nichts),
aber das ist noch ein anderes Problem, das ich lösen muss.
Es handelt sich übrigens um Druckerkoordinaten, die ich oben angebe.

Ich versuche das ganze nochmal in einem einfachen Formular mit einem einfachen,
einzeiligen Text. Da sehe ich dann hoffentlich auch mal, wie der Text denn nach
der Anpassung aussieht.

_________________
Es gibt keine Probleme - nur Lösungen!
Stefan.Buchholtz
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 612

WIN 2000, WIN XP, Mac OS X
D7 Enterprise, XCode, Eclipse, Ruby On Rails
BeitragVerfasst: Di 19.06.07 11:22 
Komisch - es sieht für mich richtig aus.
Überprüfe mal den Rückgabewert von DrawText - da sollte die texthöhe auch zurückgegeben werden, oder 0 wenn ein Fehler aufgetreten ist. Dann kannst du den Fehlercode mit GetLastError ermitteln.

Stefan

_________________
Ein Computer ohne Windows ist wie eine Schokoladentorte ohne Senf.
Logikmensch Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 390

Win XP
Delphi 2007 Prof., XE2, XE5
BeitragVerfasst: Di 19.06.07 12:42 
Ok, Stefan, das mach ich. Ich melde mich hier, sobald ich etwas Neues weiß.
Danke sehr!

_________________
Es gibt keine Probleme - nur Lösungen!
Lannes
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2352
Erhaltene Danke: 4

Win XP, 95, 3.11, IE6
D3 Prof, D4 Standard, D2005 PE, TurboDelphi, Lazarus, D2010
BeitragVerfasst: Di 19.06.07 12:48 
Hallo,

ich hab so den Verdacht das Du mit einer falschen Konstante arbeitest.
imho ist dt_wordwrap falsch, nimm mal DT_WORDBREAK = $16

_________________
MfG Lannes
(Nichts ist nicht Nichts) and ('' <> nil ) and (Pointer('') = nil ) and (@('') <> nil )
Logikmensch Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 390

Win XP
Delphi 2007 Prof., XE2, XE5
BeitragVerfasst: Di 19.06.07 14:11 
Ja, danke Lannes, ich hab's gemerkt, als ich nachfolgendes Demo eingetippselt habe. :)

Hier eine kleine Demonstration des Problems. Ich habe mal ein winziges Demoprogramm geschrieben, welches zeigt, dass bei Worten, die zu breit sind für das rote Rechteck, diese den ganzen Rechteckblock
verbreitern (das Ergebnis wird als hellblaues Rechteck dargestellt).

Hier ein Bild, wie es korrekt arbeitet (Schriftart ist klein genug):
user defined image

Hier ein Bild, wo die Schriftart zu groß wird und die gesetzte Rahmenbegrenzung (hier rot) überschreitet:
user defined image

Und weil es so interessant ist, gibt's das kleine Demoprogrämmchen hier noch mit dabei.

Ich habe dabei Regler für die Textbegrenzung eingerichtet, und einen für die prozentuale Texthöhe.
Leider wird auch nie ein Fehlercode erzeugt.
Was meint ihr dazu - ist das nicht blöd?

Ich meine, wenn Text in ein Memo eingegeben wird, dann verhält sich das ganze eigentlich so, wie ich es mir
hier auch wünschen würde. Aber dort wird ja nicht einfach nur dargestellt, da wird richtig zusammen-
und auseinandergebaut (Memo.Text --> Memo.Lines).

Tja ich habe nochmal in der API32-Referenz nachgelesen, es gibt keine weiteren Konstanten für Drawtext, die den Text vollständig, aber immer innerhalb der angegebenen Breite berechnen. Oder hat da einer/eine von Euch noch eine Idee.

:?: :?: :?: :?: :?:
Einloggen, um Attachments anzusehen!
_________________
Es gibt keine Probleme - nur Lösungen!
Lannes
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2352
Erhaltene Danke: 4

Win XP, 95, 3.11, IE6
D3 Prof, D4 Standard, D2005 PE, TurboDelphi, Lazarus, D2010
BeitragVerfasst: Di 19.06.07 15:40 
Hallo,

ist eigentlich alles klar :wink: :?
DrawText berechnet auf der Grundlage der vorgegebenen Breite aus der Rect-Variable, die benötigte Höhe. Benötigt ein Wort mehr Platz als die vorgegebene Breite ist die zurückgelieferte Breite entsprechend größer.


Die Funktionalität wie sie zum Beispiel ein Memo bietet, Umbruch im Wort, erfüllt DrawText nicht.

Da wirst Du selbst die Funktionalität des Memo nachbilden müssen und damit das Wort an der entsprechenden Stelle getrennt wird(ein Leerzeichen einfügen). Ein Ansatzpunkt bietet Dir schon DrawText, denn wenn die zurückgelieferte Breite größer als die vorgegebenen Breite ist, enthält der String ein zu langes Wort.

Ich kann mich da an ein Thema in der DP erinnern, finde es aber nicht wieder.

_________________
MfG Lannes
(Nichts ist nicht Nichts) and ('' <> nil ) and (Pointer('') = nil ) and (@('') <> nil )
Logikmensch Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 390

Win XP
Delphi 2007 Prof., XE2, XE5
BeitragVerfasst: Di 19.06.07 17:42 
Hallo!

ich weiß, ich bin manchmal ein bisschen genervt, wenn ich herausfinde, dass die GDI die Routinen nicht so umgesetzt hat, wie es (meiner Meinung nach) mehr Sinn machen würde. Aber hätte ja auch sein klnnen, dass es da eine einfache Variante (vielleicht eine GDI-Funktion) gegeben hätte.
Lannes: Du hast völlig recht, ich denke auch, dass ich die Methode, die ich dringend benötige, selber machen muss. Ich habe nochmal weiter geschaut und in der API32-Referenz von Charles Petzold auch eine entsprechende Stelle gefunden, die genau das Problem aufgreift. Sinngemäß steht da drin, dass DrawText eher für einfache Textumbrüche gedacht ist und nicht etwa solche Dinge tut, wie wir sie von kleinen Textverarbeitungen her kennen.

Dann werde ich diesen Thread erst einmal zu machen und meine Methode, wenn ich sie fertig habe, hier posten.

_________________
Es gibt keine Probleme - nur Lösungen!
Logikmensch Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 390

Win XP
Delphi 2007 Prof., XE2, XE5
BeitragVerfasst: Di 19.06.07 17:44 
... und danke für Eure Hilfen! Sie haben geholfen, keine Sorge!

_________________
Es gibt keine Probleme - nur Lösungen!
Logikmensch Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 390

Win XP
Delphi 2007 Prof., XE2, XE5
BeitragVerfasst: Mi 20.06.07 15:15 
Hallo Freunde,

zum Abschluss dieses Threads möchte ich nun meine Lösung vorstellen, die zwar momentan nur für linksbündigen Text und ohne die Möglichkeit, Leerräume zwischen den einzelnen Texten zu verbreitern (= Blocksatz) geschaffen ist. Aber das, was ich fordere, erfüllt sie souverän. Die Funktion CalcTextInRect schreibt Text in ein vorgegebenes Rechteck, ohne seine Breite zu verändern - KOMME WAS WOLLE. ;-)
Bei zu breiten Wörtern wird versucht, diese einzupassen. Bitte lest euch dafür meine Kommentare durch, die sprechen glaube ich für sich.
Zwei Fehler werden von der Funktion erfolgreich abgefangen (Result=False): Ein einzelnes Zeichen ist breiter als das Rechteck, oder der Versuch, durch Anhängen neuer Zeilen das Platzproblem zu lösen, schlägt fehl (was natürlich auch bedeutet, dass ein Zeichen breiter als das Rechteck ist).

Ich habe jetzt noch kein Flag Calcrec eingebaut, aber wer es mag, kann dies nachrüsten und im Falle von einer Größenberechnung ohne Textausgabe die ganzen TextOuts unterlassen.

Das rote Rechteck ist übrigens das Ausgangsrechteck, das blaue das Ergebnisrechteck,

ausblenden volle Höhe 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:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Mask, JvExMask, JvSpin, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    TextEdit: TEdit;
    widthedit: TJvSpinEdit;
    heightedit: TJvSpinEdit;
    Label1: TLabel;
    Label2: TLabel;
    textheightedit: TJvSpinEdit;
    Label3: TLabel;
    procedure CommonEditChange(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

var
  wordchars:set of char=['a'..'z','A'..'Z','0'..'9','$','_'];

function getwordlength(const t:string):integer;
var
  i:integer;
begin
  result:=0;
  for i := 1 to length(t) do
    if t[i] in wordchars then inc(result)
      else break;
end;

function getspacelength(const t:string):integer;
var
  i:integer;
begin
  result:=0;
  for i := 1 to length(t) do
    if t[i]=' ' then inc(result)
      else break;
end;

function CalculateTextinRect(canvas:TCanvas; var destrect:TRect; t:string):boolean;
var
  wordlen,wordwidth,i,x,y,w:integer;
  wd:string;
  textheight:integer;
begin
  result:=true;
  x:=destrect.left;
  y:=destrect.Top;
  w:=destrect.Right-destrect.Left;
  if length(t)>0 then begin
    textheight:=canvas.TextHeight(t[1]);
    while length(t)>0 do begin
      case t[1] of
        'a'..'z','A'..'Z','0'..'9','_':begin
          //Wort gefunden:
          wordlen:=getwordlength(t);
          wd:=copy(t,1,wordlen);
        end; {Zeichen eines Wortes}
        ' ':begin
          wordlen:=getspacelength(t);
          wd:=stringofchar(' ',wordlen);
        end; {Leerzeichen}
        else begin
          wordlen:=1;
          wd:=t[1];
        end; {else}
      end; {case}
      wordwidth:=canvas.TextWidth(wd);
      if x+wordwidth<=destrect.Right then begin
        //Word/Leerzeichen/Sonderzeichen passt vollständig hinein:
        canvas.TextOut(x,y,wd);
        inc(x,wordwidth);
        if x>=destrect.Right then begin
          inc(y,textheight);
          x:=destrect.Left;
        end; {if}
      end
        else begin
          //Wort passt nicht vollständig hinein:
          if wordwidth>w then begin
            if wordlen>1 then begin
              //Wenn das Wort selbst nicht hineinpasst, dann einen passenden
              //Teil vom Anfang abschneiden, aber in aktueller Zeile noch
              //damit beginnen:
              for i := wordlen-1 downto 1 do begin
                wordwidth:=canvas.TextWidth(copy(wd,1,i));
                if wordwidth+x<=destrect.right then begin
                  //Abschnitt passt jetzt rein:
                  wd:=copy(wd,1,i);
                  wordlen:=length(wd);
                  break;
                end; {if}
              end; {for}
              if i>0 then begin
                canvas.TextOut(x,y,wd);
                inc(x,wordwidth);
                if x>=destrect.right then begin
                  inc(y,textheight);
                  x:=destrect.Left;
                end; {if}
              end
                else begin
                  //Auch ein vorderer Abschnitt passt nicht an das Ende.
                  //Also nächste Zeile beginnen und es erneut versuchen:
                  if x>destrect.left then begin
                    inc(y,textheight);
                    x:=destrect.Left;
                    wordlen:=0;
                  end
                    else begin
                      result:=false;
                      exit; //Abfangen einer Endlosschleife, weil ein oder
                      //mehrere Zeichen zu Breit für den Bereich ist/sind
                    end;
                end; {else}
            end
              else begin
                result:=false;
                exit; //Ein Zeichen muss reinpassen, sonst Abbruch!
              end; {else}
          end
            else begin
              //Wort länger als der Platz in der aktuellen Zeile zulässt.
              //Daher in einer neuen Zeile beginnen:
              inc(y,textheight);
              x:=destrect.Left;
              canvas.TextOut(x,y,wd);
              inc(x,wordwidth);
              if x>=destrect.right then begin
                inc(y,textheight);
                x:=destrect.Left;
              end; {if}
            end; {else}
        end; {else}
      delete(t,1,wordlen);
    end; {while}
    //Wenn eine neu angefangene Zeile existiert, ohne dass was
    //reingeschrieben wurde, dann die letzte Zeile wieder löschen:
    if x=destrect.left then dec(y,textheight);
    destrect.Bottom:=y+textheight;
  end; {if}
end;

procedure TForm1.CommonEditChange(Sender: TObject);
var
  r:TRect;
  w,h,textheight:integer;
begin
  //Oberen Fensterinhalt leeren:
  canvas.Brush.Style:=bsSolid;
  canvas.Brush.Color:=clBtnFace;
  canvas.FillRect(clientrect);
  //Die %-Einstellungen der zwei SpinEdits auswerten und Rechteckbereich
  //bilden:
  w:=round(clientwidth*widthedit.value/100);
  h:=round((clientheight-Panel1.Height)*heightedit.Value/100);
  r:=bounds((clientwidth-w) div 2,(clientheight-Panel1.Height-h) div 2,w,h);
  //Rechteck (unten ist quasi durch die Texthöhe variabel) zeichnen:
  canvas.Pen.Color:=clRed;
  canvas.Brush.Style:=bsClear;
  canvas.Rectangle(r);
  //Texteinstellungen lesen:
  textheight:=round(textheightedit.value*h/100);
  canvas.Font.Color:=clMaroon;
  canvas.Font.Height:=textheight;
  //Textbegrenzungen ermitteln:
  (*
  if DrawText(canvas.Handle,PChar(TextEdit.Text),length(TextEdit.Text),r,
    dt_calcrect or dt_wordbreak or dt_left)>0 then
  begin
    canvas.Pen.Color:=clblack;
    canvas.DrawFocusRect(r);
    drawtext(canvas.Handle,pchar(Textedit.Text),length(textedit.Text),r,
      dt_wordbreak or dt_left);
  end
    else showmessage('Fehler: '+inttostr(getlasterror));
  *)

  CalculateTextInRect(canvas,r,textedit.Text);
  canvas.Pen.Color:=clblue;
  canvas.Rectangle(r);
end;

end.


Ich wünsche viel Spaß mit der Routine. Übrigens, weiter oben ist der Projektcode der ersten Version dieses Programmes (und das Formular). Ihr müsst nur den neuen Sourcecode in den IDE-Editor per drag & drop reinholen.

_________________
Es gibt keine Probleme - nur Lösungen!