【问题标题】:How to know that a form was created?如何知道表单已创建?
【发布时间】:2013-06-11 11:00:22
【问题描述】:

我想找到一种方法来知道表单是在运行时创建(或销毁)的。 这适用于 Delphi 或 fpc。 非常感谢

PS:有没有办法检索所有对象的信息?

【问题讨论】:

  • "有没有办法检索所有对象的信息?"这是什么意思?
  • 我想要一个事件告诉我一个新对象刚刚在运行时创建(或销毁)。
  • 您能解释一下为什么需要这样的活动吗?一点背景知识可能会为您的问题提供其他可能的解决方案。

标签: delphi runtime oncreate lazarus


【解决方案1】:

我想要一个事件告诉我一个新对象刚刚在运行时创建(或销毁)。

没有在创建或销毁对象时触发的内置事件。

因为我喜欢写代码钩子,所以我提供以下单元。这会在System 单元中挂钩_AfterConstruction 方法。理想情况下,它应该使用蹦床,但我从未学会如何实现这些。如果您使用真正的挂钩库,您将能够做得更好。无论如何,这里是:

unit AfterConstructionEvent;

interface

var
  OnAfterConstruction: procedure(Instance: TObject);

implementation

uses
  Windows;

procedure PatchCode(Address: Pointer; const NewCode; Size: Integer);
var
  OldProtect: DWORD;
begin
  if VirtualProtect(Address, Size, PAGE_EXECUTE_READWRITE, OldProtect) then
  begin
    Move(NewCode, Address^, Size);
    FlushInstructionCache(GetCurrentProcess, Address, Size);
    VirtualProtect(Address, Size, OldProtect, @OldProtect);
  end;
end;

type
  PInstruction = ^TInstruction;
  TInstruction = packed record
    Opcode: Byte;
    Offset: Integer;
  end;

procedure RedirectProcedure(OldAddress, NewAddress: Pointer);
var
  NewCode: TInstruction;
begin
  NewCode.Opcode := $E9;//jump relative
  NewCode.Offset := NativeInt(NewAddress)-NativeInt(OldAddress)-SizeOf(NewCode);
  PatchCode(OldAddress, NewCode, SizeOf(NewCode));
end;

function System_AfterConstruction: Pointer;
asm
  MOV     EAX, offset System.@AfterConstruction
end;

function System_BeforeDestruction: Pointer;
asm
  MOV     EAX, offset System.@BeforeDestruction
end;

var
  _BeforeDestruction: procedure(const Instance: TObject; OuterMost: ShortInt);

function _AfterConstruction(const Instance: TObject): TObject;
begin
  try
    Instance.AfterConstruction;
    Result := Instance;
    if Assigned(OnAfterConstruction) then
      OnAfterConstruction(Instance);
  except
    _BeforeDestruction(Instance, 1);
    raise;
  end;
end;

initialization
  @_BeforeDestruction := System_BeforeDestruction;
  RedirectProcedure(System_AfterConstruction, @_AfterConstruction);

end.

将处理程序分配给OnAfterConstruction,并且每当创建对象时都会调用该处理程序。

我将其作为练习留给读者添加OnBeforeDestruction 事件处理程序。

请注意,我并不是说这种方法是一件好事。我只是回答你问的直接问题。您可以自己决定是否要使用它。我知道我不会这样做!

【讨论】:

  • +1 但在我看来,在大多数情况下,他可以简单地继承一个共同的祖先形式。管理起来会更简单方便
  • @MatheusFreitas 确实如此。阅读我的最后一段。我不提倡使用此代码。
  • @MatheusFreitas 是的,这不是一个很好的做法,但无论如何它都非常优雅......
  • @David Heffernan : 你很棒很慷慨,我会研究你的代码,非常感谢。
  • 你应该多研究他的cmets,上面写着“不要使用这个”。
【解决方案2】:

使用TFormOnCreate 事件以任何你想要的方式通知你想要的人。

【讨论】:

  • 好吧,这就是我的想法,但是你如何为所有创建的新表单分配一个一般事件,例如一个对话框?
  • 更清楚一点,是否可以为所有将要创建的新表单分配一个新的 OnCreate 事件?
  • @user2418856 是的 - 只需分配一个。当你做NewForm := TNewForm.Create;时,跟着NewForm.OnCreate := MyOnCreateHandler;
  • 我认为最好以祖先形式编写事件并为所有其他形式继承它。
  • 一般来说,要创建一个继承的表单,您可以在菜单:文件/新建/其他//选择您的表单,确保已标记继承,然后使用它。其他版本不知道,我还在用Delphi 7。
【解决方案3】:

在 MS Windows 中,您可以使用这个小模板挂钩您的进程事件:

{$mode objfpc}{$H+}
uses
    Windows, JwaWinUser;

function ShellProc(nCode: longint; wParam: WPARAM; lParam: LPARAM): longint; stdcall;
var
    wnd: HWND;
begin
    Result := 0;
    case nCode of
        HSHELL_WINDOWCREATED:
        begin
            wnd := wParam;
            // Check window
            // Get task handle
            // Get window icon
            // Add task to the list
            // Call event
        end;
        HSHELL_WINDOWDESTROYED:
        begin
            wnd := wParam;
            // Check window
            // Get task handle
            // Get window icon
            // Remove task to the list
            // Call event
        end;
        HSHELL_LANGUAGE:
        begin
            // Get language
            // Call event
        end;
        HSHELL_REDRAW:
        begin
            // Call event
        end;
        HSHELL_WINDOWACTIVATED:
        begin
            // Get language
            // Call event
        end;
        //HSHELL_APPCOMMAND:
        //begin
        //    { TODO 1 -ond -csys : Specify return value for this code }
        //    Result := -1;
        //end;
    end;

    // Call next hook in the chain
    Result := CallNextHookEx(
        0,
        nCode,
        wParam,
        lParam);
end;

var
    FCallbackProc: HOOKPROC;

function InitShellHook(AProc: HOOKPROC): HHOOK; stdcall; export;
begin
    FCallbackProc := AProc;
    Result := SetWindowsHookEx(WH_SHELL, @ShellProc, 0, 0);
end;

procedure DoneShellHook(AHook: HHOOK); stdcall; export;
begin
    UnhookWindowsHookEx(AHook);
end;

HSHELL_WINDOWCREATED 将通知您您的进程正在创建新窗口。

使用您的过程地址调用InitShellHook(参见HOOCPROC 声明)。

【讨论】:

  • 很难将其与 Delphi 对象联系起来。尤其是 VCL 中的窗口重建技术。
  • @DavidHeffernan 它适用于任何窗口(我记得问题也是关于 ShowMessage 的)。对于常规的 Delphi 表单,我将尝试查看 Application 对象或尝试编写 TForm 助手。
  • 不是任何窗口。只有顶层无主窗口。我的观点更多的是关于再创造。您将在表单的整个生命周期内多次收到这些通知,而不是在最初创建表单时。
  • @DavidHeffernan 这个钩子适用于调用进程的任何事件:新窗口、nwe keybd 布局等等(还记得我关于 win 任务栏的问题吗?这是其中的一部分)。
  • 我知道。我自己也用同样的钩子。我只是指出,只有在创建 Delphi 表单时,它才不会触发一次。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-22
相关资源
最近更新 更多