检测聚焦控制变化的灵活方法

时间:2012-06-25 11:40:40

标签: delphi focus components delphi-2009

我需要编写一个组件,该组件将在其他组件中注册,并将检测其中一个注册组件是否获得焦点。

例如,对于我的组件TFocusObserver,我正在注册三个对象。

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

现在,如果其中一个组件获得焦点,那么FocusObserver会启动一些通知事件。

我正在寻找如何检测焦点变化,并发现TScreen.OnActiveControlChange正是我所需要的。所以我的组件可以连接到这个事件。问题在于,可能存在多个TFocusObserver,或者稍后将来可能需要使用OnActiveControlChange。{/ p>

这是我将从多播事件中受益的时间 - 它将立即解决我的问题。

我在想如何解决这个问题,目前我有两个想法:

  1. 以某种方式TScreen进行扩展,以便为我提供更多活动。
  2. 介绍一个中间对象,它将挂钩OnActiveControlChange并为其他对象公开一个多播事件。
  3. 在简要介绍一下这些来源之后,我不清楚如何通过使用第一个想法来解决它,而第二个想法的缺点是有人可以简单地将另一种方法分配给OnActiveControlChange并且所有内容都会崩溃。< / p>

    对一些建议表示感谢。

3 个答案:

答案 0 :(得分:9)

如果您的focusObserver类可以是TWinControl的后代,那么您可以这样做:

TFocusObserver = class( TWinControl )

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

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;

这里的主要想法是观看CM_FOCUSCHANGED

第二种方法:

注册控件时,请将其替换为WindowProc。这是一个小代码片段:

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;

正在实施中:

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;

而不仅仅是注册你想要观看的控件,例如:

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

听起来怎么样?

答案 1 :(得分:0)

你可以记住Screen.OnActiveControlChange的值 在组件替换之前。

FOnActiveControlChange := Screen.OnActiveControlChange;
Screen.OnActiveControlChange = MyOnActiveControlChange;

然后在xxx.MyOnActiveControlChange

begin
  // what you wanted to do here
  ...

  if Assigned( FOnActiveControlChange) then begin

    // Forward to previous subscriber.
    FOnActiveControlChange( Sender, ...);
end;

但这只适用于您控制应用程序,如果其他人使用您的组件并且他/她有其他组件也使用OnActiveControlChange可能会出错。

答案 2 :(得分:0)

我知道这是一篇很老的文章,但这是:

如果可以选择使用第三方库,则可以始终使用Spring4d

我过去实现了类似的功能。符合以下条件:

uses

Spring;

type

TActiveControl = class(TObject)
private
   FEvent : Event<TNotifyEvent>;
   class var FInstance : TActiveControl;
   function GetOnActiveControlChanged : IEvent<TNotifyEvent>;
   procedure DoOnActiveControlChanged(Sender : TObject);
public
   class property Instance : TActiveControl read FActiveControl;
   property OnActiveControlChanged : IEvent<TNotifyEvent> get GetOnActiveControlChanged;
   constructor Create;
   destructor Destroy; override;
end;

TSubscriber = class(TObject)
private
   procedure DoOnActiveControlChanged(Sender : TObject);
public
   constructor Create;
   destructor Destroy; override;
end;

...

function TActiveControl.GetOnActiveControlChanged : IEvent<TNotifyEvent>
begin
  Result := FEvent;
end;

constructor TActiveControl.Create
begin
  Screen.OnActiveControlChanged := DoOnActiveControlChanged;
end;

destructor TActiveControl.Destroy;
begin
   OnActiveControlChanged.Clear;
   FOnActiveControlChanged := nil;
   inherited;
end;

procedure TActiveControl.DoOnActiveControlChanged(Sender : TObject);
begin
  if OnActiveControlChanged.CanInvoke
     OnActiveControlChanged.Invoke(Sender);
end;

procedure TSubscriber .DoOnActiveControlChanged(Sender : TObject);
begin
   // OnActiveControl has been triggered
end;


constructor TSubscriber.Create;
begin
   TActiveControl.Instance.OnActiveControlChanged.Add(DoOnActiveControlChanged); 
end;


destructor TSubscriber .Destroy


initialization

   TActiveControl.FInstance := TActiveControl.Create;

finalization

   FreeAndNil(TActiveControl.FInstance);

在这种情况下,我将单例模式的简单版本用于TActiveControl。您也可以为其创建一个接口(IActiveControl),并在必要时使用IOC注入它。不过,这是进一步的步骤。

这样,您对Screen.OnActiveControlChanged的所有调用都应改用TActiveControl.Instance.OnActiveControlChanged。 Spring4d很棒。其中有很多东西,包括多播,IOC容器,集合等。