Как лучше всего программно определить, обрезана ли подпись TLabel (т.нарисовано с использованием многоточия)?
Вопрос
у меня есть TLabel
с EllipsisPosition
установлен в epEndEllipsis
и мне нужно знать, обрезан ли текст в данный момент или нет.Помимо расчета площади, необходимой для отображения текста, и сравнения ее с фактическими размерами этикетки, придумал ли кто-нибудь более простой/более элегантный способ сделать это?
На самом деле, расчет необходимой площади безопасным способом также не так прост, как кажется...Например TCanvas.GetTextHeight
не учитывает разрывы строк.
TCustomLabel.DoDrawText
внутренне использует либо DrawTextW
или DrawThemeTextEx
с DT_CALCRECT
флаг, чтобы определить, следует ли использовать многоточие или нет.Там задействовано довольно много кода, весь из которого объявлен private
.Простое дублирование всего этого кода в моей книге не будет считаться «элегантным»…
Есть идеи?
(Я использую Delphi 2010 на случай, если кто-нибудь предложит решение для конкретной версии Delphi)
Обновление 1: Теперь я понял, что могу просто позвонить TCustomLabel.DoDrawText(lRect, DT_CALCRECT)
напрямую (что просто объявляется protected
), чтобы позволить этикетке выполнить требуемый расчет размера без необходимости дублировать свой код.Мне просто нужно либо временно установить EllipsisPosition
к epNone
или вообще используйте экземпляр временной метки.На самом деле это не так уж и плохо, и я мог бы просто пойти на это, если никто не сможет придумать еще более простое решение.
Обновление 2: Теперь я добавил свое решение как отдельный ответ.Это оказалось более простым, чем я ожидал, поэтому, вероятно, не существует более простого/лучшего способа сделать это, но на всякий случай я все равно оставлю этот вопрос открытым еще немного.
Решение
Кстати, вот что у меня получилось (это метод кастомного TLabel
-потомок):
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;
Поскольку теперь здесь используется та же логика, что и TCustomLabel.DoDrawText
(особенно искусственное дополнение и правильная настройка WordWrap), он также правильно обрабатывает многострочные и переносимые по словам входные тексты.Обратите внимание, что «правильно» в данном случае означает «всегда возвращает True
когда TLabel
рисуется с обрезанной подписью и False
в противном случае".
Хотя приведенный выше код делает то, что я изначально просил, я, вероятно, не буду использовать его таким образом, но это больше связано с недостатками TLabel
сам:Особенно с многострочным текстом он часто ведет себя не так, как мне хотелось бы, например.когда недостаточно места для нескольких строк, последнее слово первой строки всегда будет обрезано, даже если бы поместилась вся эта строка плюс многоточие.
Другие советы
В качестве отправной точки вы можете использовать
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;
Пример использования:
procedure TForm1.FormClick(Sender: TObject);
begin
Caption := 'Clipped ' + BoolToStr(DrawStringEllipsis(Canvas.Handle, Rect(10, 100, 50, 50), 'This is a text.'), true);
end;
Было бы не сложно написать TExtLabel
компонент, который имеет WasClipped
свойство с использованием этой техники. Действительно, TLabel
Компонент является одним из самых простых компонентов в VCL - он просто рисует строку.