How do I determine the height of a line of text in a TMemo programmatically?
Asked Answered
P

4

4

I've got a TMemo, and I want to always make it exactly high enough to display the number of lines it contains. Unfortunately, I don't quite know how to calculate that. I can't base it off the .Font.Size property, because that will vary based on DPI. And I can't use TCanvas.TextHeight because TMemo doesn't seem to have a canvas.

Anyone know how to do this right?

Pinnacle answered 24/7, 2011 at 4:24 Comment(2)
Was this not of use #936359Slash
@Brian: Similar, but not quite the same problem.Pinnacle
M
4

You need to use a TCanvas for this. You can either create a TBitMap in the background and use its TCanvas (after assigning the Memo's Font property to the Bitmap.Canvas' one), or use a TCanvas from another component. Somthing like this:

BMP:=TBitMap.Create;
TRY
  BMP.Canvas.Font.Assign(Memo.Font);
  TotalHeight:=0;
  FOR LineNo:=1 TO Memo.Lines.Count DO INC(TotalHeight,BMP.Canvas.TextHeight(Memo.Lines[PRED(I)]))
FINALLY
  FreeAndNIL(BMP)
END;

Edit:

Or perhaps this one:

BMP:=TBitMap.Create;
TRY
  BMP.Canvas.Font.Assign(Memo.Font);
  LineHeight:=BMP.Canvas.TextHeight('Wq');
  TotalHeight:=Memo.Lines.Count*LineHeight
FINALLY
  FreeAndNIL(BMP)
END;
Mistranslate answered 24/7, 2011 at 5:8 Comment(3)
Instead of messing with bitmaps you could call CreateCompatibleDC(0) and use DrawTextEx with DT_CALCRECTIdealistic
Yes, but then it wouldn't be Delphi anymore, but plain old (cryptic - for some) Windows 32 API calls...Mistranslate
+1 for 1980 keyword style! TBitmap only look excessive here, @gordy. (Personaly, i'd create TCanvas and grabbed compatible DC)Joettejoey
O
11

I see a problem, i think all lines on a TMemo are equal on height, but some can be empty...

So getting Height of empty ones will give zero while they are not zero height on the TMemo.

So the solution maybe doing something like Memo.Lines.Count*LineHeight

Beware that the Lineheight may not be getted by Canvas.TextHeight since Canvas.TextHeight will give more or less exact height of minimal height for a text... i mean it will not give same height for text 'ABC' than for 'ABCp', etc...

I would recomend (if not want to call Windows API) to use Font.Height, but if it is negative the internal leading of each line is not measured...

So i would recomend to do the next steps (tested):

  • Asign a positive value for Memo.Font.Height on the OnCreate event or anywhere you want, with this the lineheight ot the TMemo will be such value you asigned
  • Total height now can be obtained directly by Memo.Lines.Count*LineHeight, since you have asigned a positive value to Memo.Font.Height (remember that would make Memo.Font.Size to be negative)

Personally i do this on the TForm OnCreate event (to ensure it is done only once), just to ensure visual font size is not changed and MyMemo.Font.Height includes internal leading of each line:

MyMemo.Font.Height:=Abs(MyMemo.Font.Size*MyMemo.Font.PixelsPerInch div Screen.PixelsPerInch);

Ensure the previous to be done only once, otherwise the text size will be visaully bigger and bigger, as much as times you run it... it is caused because not allways MyMemo.Font.PixelsPerInch is equal to Screen.PixelsPerInch... in my case they are 80 and 96 respectively.

Then, when i need to know line height i just use:

Abs(MyMemo.Font.Height*Screen.PixelsPerInch div 72)

That gives exact height of one TMemo line, since all lines have the same height, the total height would be:

MyMemo.Lines.Count*Abs(MyMemo.Font.Height*Screen.PixelsPerInch div 72)

So, to make TMemo height as big as its contained text i do this (read the comment of each line, they are very important):

MyMemo.Font.Height:=Abs(MyMemo.Font.Size*MyMemo.Font.PixelsPerInch div Screen.PixelsPerInch); // I do this on the Tform OnCreate event, to ensure only done once
MyMemo.Height:=1+MyMemo.Lines.Count*Abs(MyMemo.Font.Height*Screen.PixelsPerInch div 72); // I do this anywhere after adding the text and/or after editing it

I you do not want to play with Screen.PixelsPerInch you can just do this (read the comment of each line, they are very important):

MyMemo.Font.Height:=Abs(MyMemo.Font.Height); // This may make text size to visually change, that was why i use the corrector by using MyMemo.Font.PixelsPerInch and Screen.PixelsPerInch
MyMemo.Height:=1+MyMemo.Lines.Count*Abs(MyMemo.Font.Height*MyMemo.Font.PixelsPerInch div 72);

Hope this can help anyone.

Owensby answered 12/11, 2012 at 10:41 Comment(0)
M
4

You need to use a TCanvas for this. You can either create a TBitMap in the background and use its TCanvas (after assigning the Memo's Font property to the Bitmap.Canvas' one), or use a TCanvas from another component. Somthing like this:

BMP:=TBitMap.Create;
TRY
  BMP.Canvas.Font.Assign(Memo.Font);
  TotalHeight:=0;
  FOR LineNo:=1 TO Memo.Lines.Count DO INC(TotalHeight,BMP.Canvas.TextHeight(Memo.Lines[PRED(I)]))
FINALLY
  FreeAndNIL(BMP)
END;

Edit:

Or perhaps this one:

BMP:=TBitMap.Create;
TRY
  BMP.Canvas.Font.Assign(Memo.Font);
  LineHeight:=BMP.Canvas.TextHeight('Wq');
  TotalHeight:=Memo.Lines.Count*LineHeight
FINALLY
  FreeAndNIL(BMP)
END;
Mistranslate answered 24/7, 2011 at 5:8 Comment(3)
Instead of messing with bitmaps you could call CreateCompatibleDC(0) and use DrawTextEx with DT_CALCRECTIdealistic
Yes, but then it wouldn't be Delphi anymore, but plain old (cryptic - for some) Windows 32 API calls...Mistranslate
+1 for 1980 keyword style! TBitmap only look excessive here, @gordy. (Personaly, i'd create TCanvas and grabbed compatible DC)Joettejoey
P
4

You can write your own implementation of TCanvas.TextHeight for TMemo:

function CountMemoLineHeights(Memo: TMemo): Integer;
var
  DC: HDC;
  SaveFont: HFont;
  Size: TSize;
  I: Integer;

begin
  DC:= GetDC(Memo.Handle);
  SaveFont:= SelectObject(DC, Memo.Font.Handle);
  Size.cX := 0;
  Size.cY := 0;
//  I have not noticed difference in actual line heights for TMemo,
//    so the next line should work OK
  Windows.GetTextExtentPoint32(DC, 'W', 1, Size);
//  BTW next (commented) line returns Size.cY = 0 for empty line (Memo.Lines[I] = '') 
//    Windows.GetTextExtentPoint32(DC, Memo.Lines[I], Length(Memo.Lines[I]), Size);
  Result:= Memo.Lines.Count * Size.cY;
  SelectObject(DC, SaveFont);
  ReleaseDC(Memo.Handle, DC);
end;
Pyrrhic answered 24/7, 2011 at 5:41 Comment(3)
You should include a character with a descender as well, like Windows.GetTextEntentPoint32(DC, 'Wq', 1, Size). I'm not sure if GetTextExtentPoint32 takes this into consideration when calculating the height (neither do I know if TextHeight does - it'll probably end up calling GetTextExtentPoint anyway).Mistranslate
@Mistranslate - I can't see any difference. GetTextEntentPoint32(DC, '-', 1, Size) produce the same value for Size.cY as 'W' or 'Wq'.Pyrrhic
Okay - like I said, I hadn't tested it, but have always (to be safe) used Wq as string when calculating maximum height of a text line.Mistranslate
M
0

I originally suggested looing at the "Lines" TStrings list member in TMemo.

Instead, please look at the "Font" member in the parent class, TCustomEdit.

'Hope that helps .. PS

Miriam answered 24/7, 2011 at 4:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.