Positioning Hints for Components in Delphi

不打扰是莪最后的温柔 提交于 2019-12-05 09:48:41

As have been established in the comments to the question, the hint does not stay on the screen indefinitely but it is actually continuously re-shown as soon as it is hidden.

The reason for that is, the VCL assumes the hint control to be a child window, that's because it's Parent property is not nil. In the case of the code in the question, although the month calendar floats by mutating it to be a popup window, its parent is still the form as far as the VCL knows it. This causes the calculation for the hint rectangle in ActivateHint procedure of the Application to go wrong. On the other hand, HintMouseMessage procedure of the Application does not care if the control is parented or not. What happens then is, although you don't move the mouse pointer on the control, VCL infers the mouse pointer continuously leaves the hint boundary and then re-enters.

Here is a simplified reproduction for the problem:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;

type
  TPanel = class(vcl.extctrls.TPanel)
  protected
    procedure CreateParams(var Params: TCreateParams); override;
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    Panel1: TPanel;
    procedure FormCreate(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TPanel }

procedure TPanel.CreateParams(var Params: TCreateParams);
begin
  inherited;
  Params.Style := WS_POPUPWINDOW or WS_THICKFRAME;
end;

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
  Button1.Hint := 'Button1';
  Panel1.Hint := 'Panel1';
  ShowHint := True;
  Application.HintHidePause := 1000;
  Left := 0;
  Top := 0;
  Panel1.ParentBackground := False;
  Panel1.Left := 0;
  Panel1.Height := 50;
  Panel1.Top := Top + Height;
end;

end.

In the above code, the button's hint will hide when it times out, on the other hand the panel's hint is re-shown after it is hidden. I located the windows deliberately to their positions so that you can observe the significance of the position of the pointer when the hint is activated. If you enter the mouse pointer to the panel from the below, the hint will show only once and then hide. If you enter the panel from above however, you'll see the problem.

The fix is simple, you can modify the hint rectangle in a CM_HINTSHOW message handler. Since the control is floating, no complex calculation is required. Accordingly modified reproduction case, which also fixes the calendar in the question:

type
  TPanel = class(vcl.extctrls.TPanel)
  protected
    procedure CreateParams(var Params: TCreateParams); override;
    procedure CMHintShow(var Message: TCMHintShow); message CM_HINTSHOW;
  end;

  TForm1 = class(TForm)
    ...

{ TPanel }

procedure TPanel.CMHintShow(var Message: TCMHintShow);
begin
  inherited;
  if (GetAncestor(Handle, GA_ROOT) = Handle) and Assigned(Parent) then
    Message.HintInfo.CursorRect := Rect(0, 0, Width, Height);
end;



As for question 2, a custom hint window unfortunately does not seem to be designed position-able. The hint window is created locally and there is no neat way to get a hold of it or to specify its position in any other way. The only way I could think of is to override one of the custom hint's paint methods which exposes the hint window as a parameter. So we can relocate the hint window as soon as it receives a paint message.

Here is a working example (for a normal (non-floating) control):

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

type
  TMyCustomHint = class(TCustomHint)
  private
    FControl: TControl;
  public
    procedure NCPaintHint(HintWindow: TCustomHintWindow; DC: HDC); override;
  end;

procedure TMyCustomHint.NCPaintHint(HintWindow: TCustomHintWindow; DC: HDC);
var
  Pt: TPoint;
begin
  Pt := FControl.ClientToScreen(Point(0, 0));
  SetWindowPos(HintWindow.Handle, 0, Pt.X, Pt.Y + FControl.Height, 0, 0,
      SWP_NOSIZE or SWP_NOZORDER or SWP_NOACTIVATE);
  inherited;
end;

//--------

procedure TForm1.FormCreate(Sender: TObject);
begin
  ShowHint := True;
  Button1.Hint := 'button1 hint';
  Button1.CustomHint := TMyCustomHint.Create(Self);
  TMyCustomHint(Button1.CustomHint).FControl := Button1;
end;

end.
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!