Can I make a TMemo size itself to the text it contains?
Asked Answered
A

5

11

When you edit a TLabel's caption in the form designer, it resizes the TLabel for you. Is there any way I can get a TMemo to do that, at runtime?

I'd like to be able to take a TMemo, assign something to its .lines.text property, and then tell it to resize itself and not exceed a certain width, though it can get as tall as it wants to. Anyone know how to do that?

Armbrecht answered 1/6, 2009 at 17:38 Comment(0)
H
6

Set the WordWrap property of the TMemo to true, dump your text into it, count the lines, and set the height to the product of the line count and the line height, but you need to know the line height.

The TMemo does not expose a line height property, but if you're not changing the font or font size at runtime, you can determine the line height experimentally at design time.

Here's the code I used to set the height of the TMemo that had a line height of 13 pixels. I also found that I needed a small constant to account for the TMemo's top and bottom borders. I limited the height to 30 lines (396 pixels) to keep it on the form.

// Memo.WordWrap = True (at design time)
Memo.Text := <ANY AMOUNT OF TEXT>;
Memo.Height := Min(19 + Memo.Lines.Count * 13, 396); 

If you absolutely must extract the line height from the object at runtime, then you might use Someone's answer. Or, you can use TRichEdit, which has the SelAttributes property containing a Height property giving the line height.

-Al.

Hellbender answered 1/6, 2009 at 18:44 Comment(2)
I should have thought of that. I'm too used to working with TStringLists, which do linebreaks at CRLF, that it never occurred to me that the WordWrap property would actually put wrapped lines on different .Lines strings. Thanks!Armbrecht
Font.Height holds a negative number counting the number of pixels in a text line. Also you could call Canvas.TextExtent to have the text height calculated.Casement
H
8

This works just fine for me. The constant added (8) might vary on whether you are using a border and/or bevel, experiment with it.

procedure TForm1.Memo1Change(Sender: TObject);
var
  LineHeight: Integer;
  DC: HDC;
  SaveFont : HFont;
  Metrics : TTextMetric;
  Increase: Integer;
  LC: Integer;
begin
  DC := GetDC(Memo1.Handle);
  SaveFont := SelectObject(DC, Memo1.Font.Handle);
  GetTextMetrics(DC, Metrics);
  SelectObject(DC, SaveFont);
  ReleaseDC(Memo1.Handle, DC);
  LineHeight := Metrics.tmHeight;
  Increase := Memo1.Height;
  LC := Memo1.Lines.Count;
  if LC < 1 then
    LC := 1;
  Memo1.Height := LC * LineHeight + 8;
  Increase := Memo1.Height - Increase;
  Memo1.Parent.Height := Memo1.Parent.Height + Increase;
end;
Heathendom answered 1/6, 2009 at 18:28 Comment(2)
Nice answer. I picked the other one because it was simpler, but this works pretty well. BTW you don't need to worry about the +8; you can just assign ClientHeight and let the system take care of the border.Armbrecht
I know this is old, but I tried @MasonWheeler suggestion and it never worked right. I believe because ClientHeight includes the internal padding that the Memo has. So even setting ClientHeight you need to make a call to EM_GETRECT to really calculate the size adjustment.Primarily
H
6

Set the WordWrap property of the TMemo to true, dump your text into it, count the lines, and set the height to the product of the line count and the line height, but you need to know the line height.

The TMemo does not expose a line height property, but if you're not changing the font or font size at runtime, you can determine the line height experimentally at design time.

Here's the code I used to set the height of the TMemo that had a line height of 13 pixels. I also found that I needed a small constant to account for the TMemo's top and bottom borders. I limited the height to 30 lines (396 pixels) to keep it on the form.

// Memo.WordWrap = True (at design time)
Memo.Text := <ANY AMOUNT OF TEXT>;
Memo.Height := Min(19 + Memo.Lines.Count * 13, 396); 

If you absolutely must extract the line height from the object at runtime, then you might use Someone's answer. Or, you can use TRichEdit, which has the SelAttributes property containing a Height property giving the line height.

-Al.

Hellbender answered 1/6, 2009 at 18:44 Comment(2)
I should have thought of that. I'm too used to working with TStringLists, which do linebreaks at CRLF, that it never occurred to me that the WordWrap property would actually put wrapped lines on different .Lines strings. Thanks!Armbrecht
Font.Height holds a negative number counting the number of pixels in a text line. Also you could call Canvas.TextExtent to have the text height calculated.Casement
P
3

I've implemented a self-growing TMemo as a nice example of LiveBindings (one of the few useful examples I could come up with for LiveBindings in VCL).

A quote From my Delphi XE2 Development Essentials courseware manual:

"To build this example, place a TMemo component on a VCL form, open the LiveBindings property, and select the “New LiveBinding” option. Pick the TBindExpression choice. Open BindExpressionMemo11 in the Object Inspector and set SourceComponent to Memo1 and SourceExpression to Lines.Count * 22. To get a better result at runtime, set SourceExpression to the more exact expression

Font.Size - 4 + (Lines.Count + 1) * -1 * (Font.Height - 3)

Finally, in the OnChange event handler of the TMemo, write one line of code:

BindingsList1.Notify(Sender, '');

That’s it. Compile and run to see the growing memo in action.

[screenshot]

Initially, the TMemo control will be two lines high (the line with the contents, and a next line), and whenever we hit enter or word wrapping advances us to the next line, the TMemo control will grow in height (growing down actually, so make sure to leave enough space on the form for the TMemo to expand itself)."

Groetjes, Bob Swart

People answered 4/11, 2011 at 14:49 Comment(1)
I was really excited about this answer but it doesn't appear to be an option anymore as of XE10 Berlin.Primarily
L
1
procedure TTmpMessage.edMsgChange (Sender: TObject);
var
    LineHeight : Integer;
    DC         : HDC;
    SaveFont   : HFont;
    Metrics    : TTextMetric;
begin
    DC := GetDC ( TRxRichEdit (Sender).Handle );
    SaveFont := SelectObject ( DC, TRxRichEdit (Sender).Font.Handle );
    GetTextMetrics (DC, Metrics);
    SelectObject (DC, SaveFont);
    ReleaseDC ( TRxRichEdit (Sender).Handle, DC );
    LineHeight := Metrics.tmHeight;
    Height := TRxRichEdit (Sender).Lines.Count * LineHeight + 32;
end;
Lithium answered 4/11, 2011 at 13:51 Comment(0)
S
1

And why not just:

Memo1.Height := Memo1.ContentBounds.Height + 5;
Seismoscope answered 16/4, 2014 at 8:51 Comment(1)
ContentBounds presents only in FMX and not in VCL,Paddle

© 2022 - 2024 — McMap. All rights reserved.