How best to programmatically tell whether a TLabel's Caption is clipped (i.e. drawn using ellipsis)?
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 DrawThemeTextEx
with 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-versio开发者_开发问答n-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.
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.
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.
精彩评论