This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Can I make a TMemo size itself to the text it contains?
Need to do autosize memo: height and width.
I autosize the height as follows:
function TForm1.AutoSizeMemoY(Memo: TMemo): word;
begin
Canvas.Font := Memo.Font;
Result := Canvas.TextExtent(Memo.Lines.Strings[0]).cy * Memo.Lines.Count +
Canvas.TextExtent(Memo.Lines.Strings[0]).cy;
end;
But I do not know how to do autosize the width. I have an idea: if the scrollbar is activated, then increase the width until it becomes inactive, but I do not know how to implement that.
Not the best solution but it works:
function GetTextWidth(F: TFont; s: string): integer;
var
l: TLabel;
begin
l := TLabel.Create(nil);
try
l.Font.Assign(F);
l.Caption := s;
l.AutoSize := True;
result := l.Width + 8;
finally
l.Free;
end;
end;
And add following code to the end of Memo1.Onchange event in this answer
LineInd := Memo1.Perform(EM_LINEFROMCHAR, Memo1.SelStart, 0);//focused Memo1 line Index
Wd := GetTextWidth(Memo1.Font, Memo1.Lines[LineInd]);
//MaxWidthLineInd = index of the line which has the largest width.
//Init value of MaxWidthLineInd = 0
if MaxWidthLineInd = LineInd then
Memo1.Width := Wd
else begin
if Wd > Memo1.Width then
begin
Memo1.Width := Wd;
MaxWidthLineInd := LineInd;
end;
end;
Related
I need to get the number of displayed lines in TMemo (this include the lines that was wrapped because WordWrap is set to true). I need this to auto adjust the height of the Tmemo to it's content.
lines.count of course don't care about wrapped lines so i can't use it. strangely TextPosToPos also don't care about wrapped lines so i can't use it too ...
I m under firemonkey and delphi Berlin
Edit after I learned about ContentsBounds. My original (and obsolete) answer is still visible in the revisions.
Why do you need the number of displayed lines to adjust the height of the TMemo? This resizes the TMemo to its content:
Memo1.Height := Memo1.ContentBounds.Height + 5; // Small const to allow for rendering margins
It also takes word-wrap into consideration.
I don't know why using ContentBounds is "not really ideal". Here's how I do it:
uses
FMX.TextLayout, FMX.Graphics;
function MeasureTextHeight(const AFont: TFont; const AText: string): Single;
var
LLayout: TTextLayout;
begin
LLayout := TTextLayoutManager.DefaultTextLayout.Create;
try
LLayout.BeginUpdate;
try
LLayout.WordWrap := False;
LLayout.Font.Assign(AFont);
LLayout.Text := AText;
finally
LLayout.EndUpdate;
end;
Result := LLayout.TextHeight;
finally
LLayout.Free;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
LTextHeight: Single;
LLines: Integer;
begin
LTextHeight := MeasureTextHeight(Memo1.TextSettings.Font, Memo1.Text);
LLines := Round(Memo1.ContentBounds.Height / LTextHeight);
end;
This is the rough way i use to calculate the number of lines an TMemo (styled) under firemonkey :
type
_TStyledMemoProtectedAccess = class(TStyledMemo);
procedure TALStyledMemo.OnApplyStyleLookupImpl(sender: Tobject);
Var I, j, k, l: integer;
begin
// TALStyledMemo
// TStyledMemo
// TLayout
// TActiveStyleObject
// TLayout
// TScrollBar
// TScrollBar
// TLayout
// TSmallScrollBar
// TSmallScrollBar
// TScrollContent
for I := 0 to controls.Count - 1 do begin
if (controls[i] is TStyledMemo) then begin // << TStyledMemo
fStyledMemo := TStyledMemo(controls[i]);
end;
end;
end;
function TALStyledMemo.getLineHeight: Single;
begin
if fStyledMemo <> nil then result := _TStyledMemoProtectedAccess(fStyledMemo).GetLineHeight
else result := 0;
end;
function TALStyledMemo.getLineCount: integer;
var aLineHeight: Single;
begin
aLineHeight := getLineHeight;
if compareValue(aLineHeight, 0, Tepsilon.Position) > 0 then result := round(ContentBounds.Height / aLineHeight)
else result := 0;
end;
I have a label with fixed length and word wrap property set to true. At run time that label has two lines e.g.:
test := 'quick brown fox jumps over the lazy dog';
On Label this text displayed as two lines
quick brown fox jumps
over the lazy dog
I want to know number of lines at run time:
#13#10 does not work.
The DrawText function can be used for this purpose.
The rest of the procedure doesn't differ so much from what David Heffernan proposes in his comment.
The key here is to adopt the flags DT_WORDBREAK to automatically break the lines and the DT_EDITCONTROL to mimic the caption's text behaviour.
function TForm1.getNumberOfLinesInCaption(ALabel: TLabel): Integer;
var
r: TRect;
h: Integer;
begin
h := ALabel.Canvas.TextHeight(ALabel.Caption);
if h = 0 then
Exit(0);//empty caption
if not ALabel.WordWrap then
Exit(1);//WordWrap = False
FillChar(r, SizeOf(TRect), 0);
r.Width := ALabel.Width;
r.Height := ALabel.Height;
if 0 = DrawText(ALabel.Canvas.Handle, ALabel.Caption, Length(ALabel.Caption), r, DT_EDITCONTROL or DT_WORDBREAK or DT_CALCRECT) then
Exit(-1);//function call has failed
Result := r.Height div h;
//Assert(r.Height mod h = 0);
end;
Function NumberOfLines(MyLabel: TLabel): Integer;
var
TempLabel: TLabel;
Pint1: Integer;
Begin
TempLabel := TLabel.Create(Self);
TempLabel.Caption := MyLabel.Caption;
TempLabel.WordWrap := True;
TempLabel.AutoSize := True;
TempLabel.Width := MyLabel.Width;
TempLabel.Font := MyLabel.Font;
PInt1 := TempLabel.Height;
TempLabel.Caption := '';
TempLabel.WordWrap := False;
TempLabel.AutoSize := True;
Result := PInt1 div TempLabel.Height;
TempLabel.Free;
End;
I'm tyring calculate the maximum fontsize in order for at Text to fit into the ClientRect of a TCxLabel. But I cant get it to work probably. (See picture)
The fontsize is to big and the thxt is not drawn the corrent place.
Here how to reproduce:
Place a tcxLabel on an empty Form, and allign the label to client
Add a FormCreate and a FormResize event :
procedure TForm48.FormCreate(Sender: TObject);
begin
CalculateNewFontSize;
end;
procedure TForm48.FormResize(Sender: TObject);
begin
CalculateNewFontSize;
end;
and Finally implement CalculateNewFontSize :
uses
Math;
procedure TForm48.CalculateNewFontSize;
var
ClientSize, TextSize: TSize;
begin
ClientSize.cx := cxLabel1.Width;
ClientSize.cy := cxLabel1.Height;
cxLabel1.Style.Font.Size := 10;
TextSize := cxLabel1.Canvas.TextExtent(Text);
if TextSize.cx * TextSize.cx = 0 then
exit;
cxLabel1.Style.Font.Size := cxLabel1.Style.Font.Size * Trunc(Min(ClientSize.cx / TextSize.cx, ClientSize.cy / TextSize.cy) + 0.5);
end;
Does any one know how to calculate the font size and ho to place the text correctly?
I'd use something along these lines:
function LargestFontSizeToFitWidth(Canvas: TCanvas; Text: string;
Width: Integer): Integer;
var
Font: TFont;
FontRecall: TFontRecall;
InitialTextWidth: Integer;
begin
Font := Canvas.Font;
FontRecall := TFontRecall.Create(Font);
try
InitialTextWidth := Canvas.TextWidth(Text);
Font.Size := MulDiv(Font.Size, Width, InitialTextWidth);
if InitialTextWidth < Width then
begin
while True do
begin
Font.Size := Font.Size + 1;
if Canvas.TextWidth(Text) > Width then
begin
Result := Font.Size - 1;
exit;
end;
end;
end;
if InitialTextWidth > Width then
begin
while True do
begin
Font.Size := Font.Size - 1;
if Canvas.TextWidth(Text) <= Width then
begin
Result := Font.Size;
exit;
end;
end;
end;
finally
FontRecall.Free;
end;
end;
Make an initial estimate, and then fine tune by modifying the size by increments of one at a time. This is easy to understand and verify for correctness, and also quite efficient. In typical use the code will call TextWidth only a handful of times.
Text size doesn't depend linearly on font size. So you would better to increment or decrement font size by one and calculate text sizes, or find needed size with binary search (preferable, if size differs significantly)
I am a newbie to this Delphi. I have been given an assignment that to create buttons dynamically. But the problem is that all buttons have to be aligned in a manner that it should fit inside the whole screen. i.e, if 10 buttons created the whole screen should be filled. Or if 9 is given 9 should be present and filled in the screen. Is it possible to do that? I tried and searched everywhere. But was helpless.
Please help me if its possible. A good example is also appreciated since I mentioned earlier I am really new to this. The code I did follows here.
procedure TfrmMovieList.PnlMovieClick(Sender: TObject);
begin
for i := 0 to 9 do
begin
B := TButton.Create(Self);
B.Caption := Format('Button %d', [i]);
B.Parent := Panel1;
B.Height := 23;
B.Width := 100;
B.Left := 10;
B.Top := 10 + i * 25;
end;
end;
This looks workable to me:
procedure TForm1.CreateButtons(aButtonsCount, aColCount: Integer; aDestParent: TWinControl);
var
rowCount, row, col, itemWidth, itemHeight: Integer;
item: TButton;
begin
if aColCount>aButtonsCount then
aColCount := aButtonsCount;
rowCount := Ceil(aButtonsCount / aColCount);
itemHeight := aDestParent.Height div rowCount;
itemWidth := aDestParent.Width div aColCount;
for row := 0 to rowCount-1 do begin
for col := 0 to aColCount-1 do begin
item := TButton.Create(Self);
item.Caption := Format('Button %d', [(row*aColCount)+col+1]);
item.Left := itemWidth*col;
item.Top := itemHeight*row;
item.Width := itemWidth;
item.Height := itemHeight;
item.Parent := aDestParent;
Dec(aButtonsCount);
if aButtonsCount=0 then
Break;
end; // for cols
end; // for rows
end;
An example of usage is:
procedure TForm1.Button1Click(Sender: TObject);
begin
CreateButtons(10, 4, Panel1);
end;
The function Ceil requires the uses of unit Math.
The method receives the count of buttons and the numbers of columns to calculate the number of rows. It also receives the destination parent where the buttons will be located.
In Delphi I'm having trouble preserving the SelStart and SelLength in a Memo that updates it text every 5 seconds when the selection is negative/reverse.
With negative/reverse selection I mean that I have started the selection somewhere and while holding shift pressed the left arrow key some times.
Code:
var
caret: TPoint;
sel_start, sel_length: Integer;
begin
sel_start := Memo1.SelStart; // = 5
sel_length := Memo1.SelLength; // = 10
caret := Memo1.CaretPos; // caret.x = 15
//'adi and bl' selected
caret.x := sel_start;
Memo1.Lines.Clear;
Memo1.Lines.Add('laditadi and blah blah');
Memo1.SelStart := sel_start;
Memo1.SelLength := sel_length;
Memo1.CaretPos := caret;
end;
The thing is that setting the SelLength seems to actually move the caret. And setting the caret after setting SelLength makes SelLength := 0;. Since the text keeps changing I can't use TMemo.SelText / TMemo.SetSelText before and after.
I can't find a way to preserve the caret pos...any clues?
If sel_start has the same value as characterposition of the Caret, selection will be reversed by setting selstart to selstart+sellength and setting sellength to -sellength.
procedure TForm1.Button1Click(Sender: TObject);
var
caret: TPoint;
sel_start, sel_length,CharFromPos: Integer;
begin
Memo1.SetFocus;
GetCaretPos(Caret);
CharFromPos := SendMessage(Memo1.Handle, EM_CHARFROMPOS, 0 ,Caret.Y*$FFFF + Caret.X) AND $FFFF;
sel_start := Memo1.SelStart; // = 5
sel_length := Memo1.SelLength; // = 10
Memo1.Lines.Clear;
Memo1.Lines.Add('laditadi and blah blah'#13#10'laditadi and blah blah');
if sel_start<>CharFromPos then
begin
Memo1.SelStart := sel_start;
Memo1.SelLength := sel_length;
end
else
begin
Memo1.SelStart := sel_start + sel_length;
Memo1.SelLength := - sel_length;
end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
p:Tpoint;
b:Boolean;
CharFromPos:Integer;
begin
b := GetCaretPos(p);
CharFromPos := SendMessage(Memo1.Handle, EM_CHARFROMPOS, 0 ,p.Y*$FFFF + p.X) AND $FFFF;
Caption := Format('SelStart %d CharFromPos %d',[Memo1.SelStart,CharFromPos])
end;