Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flexible method of detecting focused control change

I need to write a component which will register in self other components and will detect if one of the registered components receive focus.

For example for my component TFocusObserver I am registering three objects.

FocusObserver.Register(MyMemo);
FocusObserver.Register(MyButton);
FocusObserver.Register(MyEdit);

And now if one of this components receives focus then FocusObserver is firing up some notification event.

I was looking how to detect a focus change and have found that TScreen.OnActiveControlChange is exactly what I need. So my component could hook up to this event. The problem is that more than one TFocusObserver might exists or later in a future somoene else might want to use OnActiveControlChange.

This is the time in which I would benefit from multicast event - it would solve my problem right away.

I was thinking how to solve this and I have currently two ideas:

  1. Extending somehow TScreen so it would provide one more event for me.
  2. Introduce an intermediate object which will hook up to OnActiveControlChange and expose one multicast event for other objects.

After a brief look at the sources I have no clear idea how to solve it by using first idea and the second idea has the drawback that someone can simply assign another method to OnActiveControlChange and everything fill fall apart.

Will be grateful for some suggestions.

like image 635
Wodzu Avatar asked Jun 25 '12 11:06

Wodzu


1 Answers

If your focusObserver class can be a descendant of TWinControl, than you can do this:

TFocusObserver = class( TWinControl )

  procedure CMFocusChanged(var Message: TCMFocusChanged); message CM_FOCUSCHANGED;
end;

and

procedure TFocusObserver.CMFocusChanged(var Message: TCMFocusChanged);
var
  LControl: TWinControl;

begin
      LControl := TWinControl(Message.Sender);

      if LControl <> nil then
      begin
        form1.Caption := lControl.Name;
      end;
end;

Here the main idea is to watch CM_FOCUSCHANGED.

Second approach:

When registering the Control you replace it's WindowProc. Here's a little code snippet:

TRegisteredComp = class
  private
    fControl: TControl;
    fowndproc: TWndMethod;
    procedure HookWndProc(var Message: TMessage);
  public
    constructor Create( c: TControl );
    destructor Destroy; override;
  end;

  TFocusObserver = class
  private
    l: TList;
   public
    constructor Create;
    destructor Destroy; override;
    procedure reg( c: TControl );

  end;

and under implementation:

constructor TFocusObserver.Create;
begin
  l := TList.Create;
end;

destructor TFocusObserver.Destroy;
var i: integer;
begin
  for i := 0 to l.Count - 1 do
    TRegisteredComp(l[i]).Free;
  l.Free;
  inherited;
end;

procedure TFocusObserver.reg( c: TControl );
var
  rc: TRegisteredComp;
begin
  rc := TRegisteredComp.Create( c );
  l.Add( rc );
end;

constructor TRegisteredComp.Create(c: TControl);
begin
  fControl := c;
  fowndproc := c.WindowProc;
  c.WindowProc := HookWndProc;
end;

destructor TRegisteredComp.Destroy;
begin
  fControl.WindowProc := fowndproc;
  inherited;
end;

procedure TRegisteredComp.HookWndProc(var Message: TMessage);
begin
  if ( Message.Msg = CM_FOCUSCHANGED ) and
    ( TControl(Message.LParam) = fControl ) then
    form1.ListBox1.Items.Add( 'focused: ' + fControl.Name );

  fowndproc( Message );
end;

than just register the control you want to watch, example:

procedure TForm1.FormCreate(Sender: TObject);
var
  i: Integer;
begin
  fo := TFocusObserver.Create;
  for i := 0 to ControlCount - 1 do
    fo.reg( Controls[i] );
end;

How does it sound?

like image 165
balazs Avatar answered Oct 23 '22 00:10

balazs