1
votes

Problem

I would like to simplify the following code in Delphi XE6 on Windows, which becomes unmaintainable as I am adding more and more types of components.

Portability note: I would like to use the same code later in Lazarus 2.0.2 on Linux with little to no change, so the Windows message handling is out of the question.

The problem is, I can't seem to find a way to assign the OnMouseEnter event handler to all components on the whole Form.

I tried all I could including various classes common in their object tree. The OnMouseEnter event seems to be nowhere in the common ground.

The event handler itself contains just one command (procedure = void function), and it will have no more, maybe this could simplify the whole problem?

As you can see below, at this moment I need to add each type of component (currently only TLabel, TButton, and TEdit) to the for-loop.


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;

Motivation

I am programming a color picker application and thus want to show the user the mouse coordinates.

I have a Polling timer in there, I don't want to add more code, than necessary, so I hope this is self-explanatory:

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

Further, I do have OnMouseLeave implemented.

1
Why not simply assign a handler to TApplication(Events).OnMessage and handle WM_MOUSEMOVE messages? You can retrieve the TControl under the mouse by using the GetMessagePos() and FindDragTarget() functions.Remy Lebeau
@RemyLebeau For future readers I have clarified why the Windows message handlig is out of the question, see Portability note, please.LinuxSecurityFreak

1 Answers

6
votes

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

Actually, it is. OnMouseEnter is a member of TControl, which all visual controls derive from, but most controls do not promote it to published. However, since it is declared as protected, you can use an accessor class to reach it on any control, eg:

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;

This works because TControlAccess gains access to all of TControl's protected members, and the unit that declares TControlAccess has access to all of TControlAccess's protected members.

On the other hand, OnMouseEnter is initially protected so controls can decide whether they want to expose access to it. If you want to respect that decision and only set it for controls that have promoted it, you can use RTTI for that, eg:

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;

Alternatively (Delphi 2010+ only):

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;