7

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

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

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

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

我正在寻找如何检测焦点变化并发现这TScreen.OnActiveControlChange正是我所需要的。所以我的组件可以连接到这个事件。问题是可能存在不止一个TFocusObserver,或者在将来其他人可能想要使用OnActiveControlChange

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

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

  1. 以某种方式扩展TScreen,以便为我提供更多活动。
  2. 引入一个中间对象,该对象将连接OnActiveControlChange并为其他对象公开一个多播事件。

在简要查看了来源之后,我不清楚如何使用第一个想法来解决它,而第二个想法的缺点是有人可以简单地分配另一种方法,OnActiveControlChange而一切都分崩离析。

将不胜感激一些建议。

4

3 回答 3

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;

听起来怎么样?

于 2012-06-25T13:14:46.193 回答
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 事情可能会出错。

于 2012-06-25T12:03:51.437 回答
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 注入它。这是进一步的步骤。

This way, all your calls to Screen.OnActiveControlChanged should instead use TActiveControl.Instance.OnActiveControlChanged. Spring4d is awesome. There's lots of stuff in it, including multicast, IOC container, collections, etc. etc.

于 2018-08-03T22:25:00.620 回答