【问题标题】:How do I catch certain events of a form from outside the form?如何从表单外部捕获表单的某些事件?
【发布时间】:2012-01-05 14:10:51
【问题描述】:

我正在做一些需要监控许多表单的事情。从表单外部,并且不将任何代码放入表单内,我需要以某种方式从这些表单中捕获事件,最有可能以 Windows 消息的形式。但是,您将如何从与其相关的类之外捕获 Windows 消息?

我的项目有一个对象,它包装了它正在监视的每个表单,我认为这个处理将在这个对象中进行。本质上,当我创建一个我想要监控的表单时,我创建了一个相应的对象,该对象又被添加到所有已创建表单的列表中。最重要的是,当该表单关闭时,我必须知道以便从列表中删除该表单的包装对象。

这些事件包括:

  • 最小化
  • 最大化
  • 恢复
  • 关闭
  • 对焦/对焦

我不想要的:

  • 用于此处理的任何表单或表单单元内的任何代码
  • 从任何自定义基本表单继承表单
  • 使用表单的事件,例如OnClose,因为它们将用于其他目的

我想要什么:

  • 为这些事件处理 Windows 消息
  • 关于如何从课堂外获取 windows 消息的任何提示
  • 我需要监听哪些 Windows 消息

用相同的信息但不同的方法重写了问题

【问题讨论】:

  • 我不太确定,但我认为您也可以考虑代码注入,因为它是由某些 AOP 框架完成的。
  • 您知道您可以用自己的处理程序替换表单事件,但保留旧值,然后从您的替换处理程序调用旧处理程序,对吗?这比真正的“代码注入”或真正的“挂钩”要简单。这与大多数操作系统中“中断处理程序”的工作方式非常相似。我们称之为“向量替换”。
  • @WarrenP 我知道这一点,如果大卫没有提到更清洁的方法,我可能会这样做。但是这个策略(至少在我看来)可能有 90-95% 的效率(我可以预见到一些问题会搞砸这种情况)。 David 的解决方案是 100% 有效的。

标签: delphi delphi-7 windows-messages tform


【解决方案1】:

您需要侦听传送到表单的特定 Windows 消息。最简单的方法是分配表单的WindowProc 属性。请记住保留之前的 WindowProc 值并从您的替代者处调用它。

在你的包装对象中声明一个像这样的字段:

FOriginalWindowProc: TWndMethod;

然后在包装器的构造函数中这样做:

FOriginalWindowProc := Form.WindowProc;
Form.WindowProc := NewWindowProc;

最后,实现替换窗口过程:

procedure TFormWrapper.NewWindowProc(var Message: TMessage);
begin
  //test for and respond to the messages of interest
  FOriginalWindowProc(Message);
end;

【讨论】:

  • 这看起来很有希望,但在我回家试一试之前还不能确定。
  • 我正要问你如何处理NewWindowProc 中的消息,但迈克打败了我 - 我现在必须接受他的回答:( 仍然 +1 原始答案
  • 哦,我认为这是简单的部分,所以我只做了困难的部分。我只处理了消息拦截,因为问题和 cmets 的全部焦点就是那一部分。
  • FWIW 你没有问鼠标进入/离开。您确实询问了焦点,为此您可以收听WM_ACTIVATE
  • 我从来不知道有一种方法可以捕获来自同一个处理程序的任何和所有消息。
【解决方案2】:

这是大卫提供的解决方案的更完整示例:

private
  { Private declarations }
  SaveProc : TWndMethod;
  procedure CommonWindowProc(var Message: TMessage);

...

procedure TForm1.Button1Click(Sender: TObject);
var
  f : tForm2;
begin
  f := tForm2.Create(nil);
  SaveProc := f.WindowProc;
  f.WindowProc := CommonWindowProc;
  f.Show;
end;

procedure TForm1.CommonWindowProc(var Message: TMessage);
begin
  case Message.Msg of
    WM_SIZE : Memo1.Lines.Add('Resizing');
    WM_CLOSE : Memo1.Lines.Add('Closing');
    CM_MOUSEENTER : Memo1.Lines.Add('Mouse enter form');
    CM_MOUSELEAVE : Memo1.Lines.Add('Mouse leaving form');
    // all other messages will be available as needed
  end;
  SaveProc(Message); // Call the original handler for the other form
end;

【讨论】:

    【解决方案3】:

    比尝试在表单之外工作更好的解决方案是让每个表单都从实现该功能的通用基本表单派生。表单事件处理程序正是添加此代码的正确位置,但您将其全部编写在祖先表单中。任何后代表单仍然可以使用表单事件,只要它们总是在事件处理程序中的某处调用继承,祖先代码仍然会执行。

    【讨论】:

    • 我同意这一点,但有时您会在项目中包含第 3 方代码,这会使这难以实现
    • 这是我最初的计划,但这行不通的主要原因是——就像我的问题中提到的那样——我不能为此将任何代码放入任何形式中。制作基本表单与将代码放入表单没有什么不同。
    • @Jerry - 理解,但您也表示您最终会向单个表单事件处理程序添加代码,这也将代码放入表单中。
    • 这是一个不同的故事。该代码与我的问题无关,显然每个表单中都会包含某种代码。我的意思是,由于我将表单的事件处理程序用于其他目的,我需要找到其他东西来捕获这些事件。
    • 我的意思是你可以两者都做。祖先表单可以在 OnClose/OnShow/OnResize 等中包含代码。并且后代表单可以为这些处理程序拥有自己的代码。
    【解决方案4】:

    另一个选项是创建 TApplicationEvents 并将处理程序分配给 OnMessage 事件。一旦它被触发,使用 FindControl 函数和 Msg.hWnd 来检查它是否是 tform 类型,并在没有钩子的情况下做任何你想做的事情

    【讨论】:

    • 但请注意,TApplication.OnMessage 事件(这里的 TApplicationEvents 只是事件处理程序乘数)不会处理 WM_CLOSE 命令,该命令用于由于用户的操作而隐藏/释放窗口。您可以使用TComponent.Notification 机制拦截表单的Release,但对于caHide 类型的表单,它将不起作用! WM_CLOSE 是在内部生成的,并绕过Application 的主队列(SendMessagePostMessage)被泵入Window 的消息处理程序。实际可行的似乎是使用OnMessage 来拦截前兆消息
    • WM_SYSCOMMANDwParam=SC_CLOSEWM_NCLBUTTONDOWNwParam=HTCLOSE。但是,如果未来的 Windows 版本会添加更多关闭表单的方法,那么该列表将不得不扩展!!!此外,理想情况下,必须以某种方式考虑监控形式偶尔的句柄更改(AKA RecreateWndstackoverflow.com/questions/27400973
    • 哦,还有一个“利基案例”是使用OnCloseOnCloseQuery 事件拒绝关闭,这也是一个困难的案例。也许只是给PostMessageTThread.Queue 一个懒惰的完成后处理程序,它会检查受监控表单的.Showing - 如果那个时候TObject 的表单还没有被销毁。
    【解决方案5】:

    使用 Windows 消息确实可以达到 fine granularity(是的,这是您的要求的一部分!)但在某些仅依赖 VCL Event Framework 就足够的用户案例中,可以建议类似的解决方案:

    unit Host;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls;
    
    type
      THostForm = class(TForm)
        Memo1: TMemo;
        Button1: TButton;
        procedure Button1Click(Sender: TObject);
      private
        FFormResize: TNotifyEvent;
        FFormActivate: TNotifyEvent;
        FFormDeactivate: TNotifyEvent;
        FFormDestroy: TNotifyEvent;
    
        procedure _FormResize(Sender: TObject);
        procedure _FormActivate(Sender: TObject);
        procedure _FormDeactivate(Sender: TObject);
    
        procedure InternalEventHandlerInit(const AForm:TForm);
      public
        procedure Log(const Msg:string);
        procedure Logln(const Msg:string);
      end;
    
    var
      HostForm: THostForm;
    
    implementation
    
    {$R *.dfm}
    
    procedure THostForm.Button1Click(Sender: TObject);
    var
      frm: TForm;
    begin
      frm := TForm.Create(nil);
      frm.Name := 'EmbeddedForm';
      frm.Caption := 'Embedded Form';
      //
      InternalEventHandlerInit(frm);
      //
      Logln('<'+frm.Caption+'> created.');
      //
      frm.Show;
    end;
    
    
    procedure THostForm.InternalEventHandlerInit(const AForm: TForm);
    begin
      FFormResize := AForm.OnResize;
      AForm.OnResize := _FormResize;
      //
      FFormActivate :=  AForm.OnActivate;
      AForm.OnActivate := _FormActivate;
      //
      FFormDeactivate :=  AForm.OnDeactivate;
      AForm.OnDeactivate := _FormDeactivate;
    end;
    
    procedure THostForm.Log(const Msg: string);
    begin
      Memo1.Lines.Add(Msg);
    end;
    
    procedure THostForm.Logln(const Msg: string);
    begin
      Memo1.Lines.Add(Msg);
      Memo1.Lines.Add('');
    end;
    
    procedure THostForm._FormActivate(Sender: TObject);
    begin
      Log('Before OnActivate <'+(Sender as TCustomForm).Caption+'>');
      //
      if Assigned(FFormActivate) then
        FFormActivate(Sender) // <<<
      else
        Log('No OnActivate Event Handler attached in <'+(Sender as TCustomForm).Caption+'>');
      //
      Logln('After OnActivate <'+(Sender as TCustomForm).Caption+'>');
    end;
    
    procedure THostForm._FormDeactivate(Sender: TObject);
    begin
      Log('Before OnDeactivate <'+(Sender as TCustomForm).Caption+'>');
      //
      if Assigned(FFormDeactivate) then
        FFormDeactivate(Sender)
      else
        Log('No OnDeActivate Event Handler attached in <'+(Sender as TCustomForm).Caption+'>');
      //
      Logln('After OnDeactivate <'+(Sender as TCustomForm).Caption+'>');
    end;
    
    procedure THostForm._FormResize(Sender: TObject);
    begin
      Log('Before OnResize <'+(Sender as TCustomForm).Caption+'>');
      //
      if Assigned(FFormResize) then
        FFormResize(Sender)
      else
        Log('No OnResize Event Handler attached in <'+(Sender as TCustomForm).Caption+'>');
      //
      Logln('After OnResize <'+(Sender as TCustomForm).Caption+'>');
    end;
    
    end.
    

    【讨论】:

    • 这很好,直到受害者表单决定修改自己的事件处理程序,然后游戏结束。
    • @David Heffernan:你是对的。现在我看到他们根本没有受到保护。在这种情况下,恐怕唯一的办法就是对“受害者”表单进行一些代码注入(推测)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多