Delphi/Lazarus |有没有办法简化全局 OnMouseEnter 事件处理?

Delphi/Lazarus | Is there a way to simplify global OnMouseEnter event handling?

问题

我想在 Windows 上的 Delphi XE6 中简化以下代码,随着我添加越来越多类型的组件,它变得无法维护.

可移植性说明:我想稍后在 Linux 上的 Lazarus 2.0.2 中使用相同的代码没有变化,所以 Windows 消息处理是不可能的。

问题是,我似乎找不到将 OnMouseEnter 事件处理程序分配给整个表单上的所有组件的方法。

我尽我所能,包括在他们的对象树中常见的各种 类。 OnMouseEnter 事件似乎没有共同点。

事件处理程序本身只包含一个命令(procedure = void function),以后不会再有,也许这可以简化整个问题?

正如您在下面看到的,此时我需要将每种类型的组件(目前只有 TLabel、TButton 和 TEdit)添加到 for 循环中。


procedure TFormMain.FormCreate(Sender: TObject);
var
    I: Integer;
begin
    for I := 0 to FormMain.ComponentCount - 1 do
    begin
        if FormMain.Components[I] is TLabel then
        begin
            (FormMain.Components[I] as TLabel).OnMouseEnter
                := @CustomGenericMouseEnter;
        end;
        if FormMain.Components[I] is TButton then
        begin
            (FormMain.Components[I] as TButton).OnMouseEnter
                := @CustomGenericMouseEnter;
        end;
        if FormMain.Components[I] is TEdit then
        begin
            (FormMain.Components[I] as TEdit).OnMouseEnter
                := @CustomGenericMouseEnter;
        end;
    end;
end;

procedure TFormMain.CustomGenericMouseEnter(Sender: TObject);
begin
    SingleCustomProcedure; // no arguments, nor return value
end;

动机

我正在编写 颜色选择器 应用程序,因此想向用户显示鼠标坐标。

我有一个轮询计时器,我不想添加不必要的代码,所以我希望这是不言自明的:

procedure TFormMain.TimerMousePollTimer(Sender: TObject);
begin
    if MousePosChanged then
    begin
        LabelEdit_MousePosX.Text := MousePosX.ToString;
        LabelEdit_MousePosY.Text := MousePosY.ToString;
    end;
end;

此外,我确实实现了 OnMouseLeave

The OnMouseEnter event seems to be nowhere in the common ground.

其实是的。 OnMouseEnterTControl 的成员,所有视觉控件都派生自它,但大多数控件不会将其提升为 published。但是,由于它被声明为 protected,您可以使用访问器 class 在任何控件上访问它,例如:

type
  TControlAccess = class(TControl)
  end;

procedure TFormMain.FormCreate(Sender: TObject);
var
  I: Integer;
  Comp: TComponent;
begin
  for I := 0 to ComponentCount - 1 do
  begin
    Comp := Components[I];
    if Comp is TControl then
      TControlAccess(Comp).OnMouseEnter := CustomGenericMouseEnter;
  end;
end;

这是可行的,因为 TControlAccess 可以访问所有 TControl 的受保护成员,并且声明 TControlAccess 的单位可以访问所有 TControlAccess 的成员受保护的成员。

另一方面,OnMouseEnter 最初是 protected,因此控件可以决定是否要公开对它的访问。如果你想尊重那个决定并且只为提升它的控件设置它,你可以为此使用 RTTI,例如:

uses
  ..., TypInfo;

procedure TFormMain.FormCreate(Sender: TObject);
var
  I: Integer;
  Comp: TComponent;
  Prop: PPropInfo;
  M: TMethod;
begin
  TNotifyEvent(M) := CustomGenericMouseEnter;
  for I := 0 to ComponentCount - 1 do
  begin
    Comp := Components[I];
    if not (Comp is TControl) then Continue;
    Prop := GetPropInfo(Comp, 'OnMouseEnter', [tkMethod]);
    if Prop <> nil then
      SetMethodProp(Comp, Prop, M);
  end;
end;

或者(Delphi 2010+):

uses
  ..., System.Rtti;

procedure TFormMain.FormCreate(Sender: TObject);
var
  I: Integer;
  Ctx: TRttiContext;
  Comp: TComponent;
  Prop: TRttiProperty;
  V: TValue;
begin
  V := TValue.From<TNotifyEvent>(CustomGenericMouseEnter);
  for I := 0 to ComponentCount - 1 do
  begin
    Comp := Components[I];
    if not (Comp is TControl) then Continue;
    Ctx.GetType(Comp.ClassType).GetProperty('OnMouseEnter');
    if (Prop <> nil) and (Prop.Visibility in [TMemberVisibility.mvPublic, TMemberVisibility.mvPublished]) then
      Prop.SetValue(Comp, V);
  end;
end;