How best to programmatically tell whether a TLabel's Caption is clipped (i.e. drawn using ellipsis)?

StackOverflow https://stackoverflow.com/questions/4313607

Question

I have a TLabel with EllipsisPosition set to epEndEllipsis and I need to be able to tell whether the text is currently clipped or not. Besides calculating the area required to display the text myself and comparing it with the actual dimensions of the label, has anyone come up with an easier/more elegant way of doing this?

Actually, calculating the required area in a fail-safe manner also doesn't appear to be as straight forward as it sounds... For instance TCanvas.GetTextHeight does not take into account linebreaks.

TCustomLabel.DoDrawText internally uses either DrawTextW or DrawThemeTextExwith the DT_CALCRECT flag to determine whether it should use the ellipsis or not. There's quite a lot of code involved there, all of which is declared private. Simply duplicating all that code would not exactly qualify as "elegant" in my book...

Any ideas?

(I'm using Delphi 2010 in case anyone comes up with a Delphi-version-specific solution)

Update 1: I now figured out that I can simply call TCustomLabel.DoDrawText(lRect, DT_CALCRECT) directly (which is merely declared protected) to let the label perform the required size calculation without having to duplicate its code. I just have to make sure to either temporarily set EllipsisPosition to epNone or use a temporary label instance altogether. This is actually not that bad and I might just go with it if noone can think of an even simpler solution.

Update 2: I have now added my solution as a separate answer. It turned out to be rather more straight-forward than I anticipated so there probably is no easier/better way to do it but I'll leave this question open for a little while longer anyway just in case.

Was it helpful?

Solution

FWIW, here's what I came up with (this is a method of a custom TLabel-descendant):

function TMyLabel.IsTextClipped: Boolean;
const
  EllipsisStr = '...';
var
  lEllipBup: TEllipsisPosition;
  lRect: TRect;
begin
  lRect := ClientRect;
  Dec(lRect.Right, Canvas.TextWidth(EllipsisStr));

  lEllipBup := EllipsisPosition;
  EllipsisPosition := epNone;
  try
    DoDrawText(lRect, DT_CALCRECT or IfThen(WordWrap, DT_WORDBREAK));
  finally
    EllipsisPosition := lEllipBup;
  end;
  Result := ((lRect.Right - lRect.Left) > ClientWidth)
         or ((lRect.Bottom - lRect.Top) > ClientHeight);
end;

As this now uses exactly the same logic as TCustomLabel.DoDrawText (especially the artificial padding and the correct WordWrap setting) it also deals correctly with multi-line and word-wrapped input texts. Note that "correctly" in this case means "it always returns True when the TLabel is drawn with a clipped caption and False otherwise".

While the above code does what I originally asked for I will probably not use it this way - but this is more due to shortcomings of TLabel itself: Especially with multi-line text it often does not behave the way I would have wanted it to, e.g. when there is not enough space for multiple lines, the last word of the first line will always be truncated even if that entire line plus the ellipsis would have fitted.

OTHER TIPS

As a starting point, you could use

function DrawStringEllipsis(const DC: HDC; const ARect: TRect; const AStr: string): boolean;
var
  r: TRect;
  s: PChar;
begin
  r := ARect;
  GetMem(s, length(AStr)*sizeof(char) + 8);
  StrCopy(s, PChar(AStr));
  DrawText(DC, PChar(s), length(AStr), r, DT_LEFT or DT_END_ELLIPSIS or DT_MODIFYSTRING);
  result := not SameStr(AStr, s);
  FreeMem(s);
end;

Sample usage:

procedure TForm1.FormClick(Sender: TObject);
begin
  Caption := 'Clipped ' + BoolToStr(DrawStringEllipsis(Canvas.Handle, Rect(10, 100, 50, 50), 'This is a text.'), true);
end;

It wouldn't be hard to write a TExtLabel component that has a WasClipped property using this technique. Indeed, the TLabel component is one of the simplest components in the VCL -- it just draws a string.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top