How to create resize event for TLabel (TGraphicControl)

2.2k views Asked by At

I'm trying to create TLabel descendant, which will display hint with the whole caption when the text width exceed label width. I've set the EllipsisPosition property to epEndEllipsis and my caption is automatically shorted by an ellipsis at the end. That's fine.

However I need to be notified, that the text has been shortened to set up the hint. This in my case may happen only when the text is changed (message CM_TEXTCHANGED) and when the component is resized.

And that's my question - how can I be notified, that my TLabel has been resized ? I have the anchors there, so it's resized along with the form, but I would like to wrap it in the separate TLabel descendant.

This code works, but isn't there a better way ? Something like WM_EXITSIZEMOVE, but working for TGraphicControl ?

procedure TEllipsisLabel.WMWindowPosChanged(var Message: TWMWindowPosChanged);
begin
  inherited;

  if Assigned(Parent) then
    if Canvas.TextWidth(Caption) > Width then
      begin
        ShowHint := True;
        Hint := Caption;
      end
    else
      begin
        ShowHint := False;
        Hint := '';
      end;
end;

Thanks a lot :)

4

There are 4 answers

2
David On BEST ANSWER

I don't think you want to be notified that the TLabel descendant has been resized. Instead, you want to be notified that it has shortened the text. I know they seem the same, but they're not - the label might still be wider than the text, it might have WordWrap turned on, etc. In addition, TCustomLabel descendants can use a couple of different methods to draw text, based on theming / Vista / Aero glow stuff (they boil down to DrawThemeTextEx and DrawText), so you need to hook into that system to know exactly what the text it's drawing is doing, including what the size of the rendered text is.

If you're using a non-Starter edition of Delphi, have a look at the TCustomLabel source in stdctrls.pas. There are two methods of interest:

  • TCustomLabel.AdjustBounds - this is where the bounding rectangle is set, and it adjusts for word wrapping etc. It does this by calling (as does painting) the other method of interest:
  • TCustomLabel.DoDrawText - this paints the text and/or calculates the text bounding rectangle accounting for ellipses, wrapping, that kind of thing. Internally, it generates an altered string that is the text that is actually painted. In other words, this method's functionality is what tells you whether the text fits or not.

Both are dynamic, which is semantically equivalent to being virtual - ie, you can override them.

DoDrawText unfortunately doesn't return the final text string it's painting - if it did, you could override it, call the inherited version and compare the painted text with the real text. However, you can override and reimplement it, and do this yourself. Use the VCL code as a guide (you want equivalent functionality, although you should not copy it directly since it's owned by Embarcadero.) Being dynamic, your descendant class's version will be called by AdjustBounds. In your code, when you shorten the text, also set a flag it's been shortened or generate the hint immediately. Voila. You accurately know exactly when it's been shortened :)

5
David Heffernan On

I can't think of anything better than WM_WINDOWPOSCHANGED:

Sent to a window whose size, position, or place in the Z order has changed as a result of a call to the SetWindowPos function or another window-management function.

That looks ideal. What do you have against using it?

2
M Schenkel On

I think you need to override the AdjustBounds method. Try the following code (simply create a form with a TButton and TLabel on it and replace the .pas with this code). This example demonstrates detecting a label size change if the text changes. You will need to create your own event though.

unit unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TSizeNotifyLabel = class(TLabel)
  public
    procedure AdjustBounds; override;

end;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Label1: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }

    SizeNotifyLabel: TSizeNotifyLabel;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation


procedure TSizeNotifyLabel.AdjustBounds;
begin
  inherited;
  form1.label1.caption := 'Width of Label:'+inttostr(form1.SizeNotifyLabel.Width);
end;

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  SizeNotifyLabel.Caption := SizeNotifyLabel.Caption + ' Change My Size';
end;

procedure TForm1.FormCreate(Sender: TObject);

begin
  SizeNotifyLabel := TSizeNotifyLabel.Create(self);
  with SizeNotifyLabel do begin
    caption := 'Hello World';
    left := 10;
    top := 10;
    autosize := true;
    parent := self;
  end;

end;

end.
1
Ondrej Kelle On

You can simply override Resize method. However, be aware that your code

if Canvas.TextWidth(Caption) > Width then

is different from how TCustomLabel.DoDrawText determines when to draw ellipses so you may get unexpected results.

Also, the ellipses drawing may also be caused by a font change, a theme setting change, and maybe some other events.