父句柄未到达的消息传递
Message passing with parent handle not reaching
在一个新项目中,我创建了一个带有 2 个面板的 MainForm 和一个带有按钮的 Form。
我在 MainForm 上添加了这段代码:
interface
type
TForm1 = class(TForm)
Panel1: TPanel;
Panel2: TPanel;
procedure FormCreate(Sender: TObject);
private
procedure OnMyMessage(var Msg: TMessage); message WM_FILEREADY;
public
{ Public declarations }
end;
implementation
uses
PannelForm;
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
with TForm2.Create(self) do
try
parent := panel2;
borderstyle := bsNone;
InnerHandle := self.Handle;
Show;
finally
end;
end;
procedure TForm1.OnMyMessage(var Msg: TMessage);
begin
showmessage('got event');
end;
在带有按钮的表单上的这段代码:
type
TForm2 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
InnerHandle:HWND;
end;
procedure TForm2.Button1Click(Sender: TObject);
begin
// PostMessage(Application.Mainform.Handle, WM_FILEREADY, 0, 0); // works
// PostMessage(Application.Handle, WM_FILEREADY, 0, 0); // not working
// PostMessage(parent.Handle, WM_FILEREADY, 0, 0); // not working
PostMessage(InnerHandle, WM_FILEREADY, 0, 0); // works
end;
我的问题是:调用第一版和第四版时,一切正常。
第三个版本中缺少什么不起作用?
为什么 Parent 不包含右句柄?不就是传递一个Parent的(部分)点吗?
您已在 TForm1
中实现了消息处理,但 Form2.Parent.Handle
不是 Form1.Handle
,而是您已将 Panel2.Handle
分配给它。
每个窗口控件都有自己的句柄。因此,您的面板具有与表单不同的句柄,它们无法处理在表单 class.
中实现的消息
一切正常,即使它不是您所期望的。
您将 Parent
设置为 Form1.Panel2
,而不是 Form1
本身。您的消息处理程序将只接收直接 posted 到 Form1
的消息。您的其他电话正在 post 打到 Form1.Handle
,这就是它们起作用的原因。
如果在 Parent
不是 Form1
时要 post 向 Parent.Handle
发送消息,则必须将要分配的面板子类化为 Parent
:
type
TForm1 = class(TForm)
Panel1: TPanel;
Panel2: TPanel;
procedure FormCreate(Sender: TObject);
private
DefPanelWndProc: TWndMethod;
procedure PanelWndProc(var Msg: TMessage);
public
{ Public declarations }
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
DefPanelWndProc := Panel2.WindowProc;
Panel2.WindowProc := PanelWndProc;
Form2 := TForm2.Create(Self);
Form2.Parent := Panel2;
Form2.BorderStyle := bsNone;
Form2.Show;
end;
procedure TForm1.PanelWndProc(var Msg: TMessage);
begin
if Msg.Msg = WM_FILEREADY then
ShowMessage('got event')
else
DefPanelWndProc(Msg);
end;
否则,post 将消息改为 Form1
。
如果你post每次都使用Form1.Handle
属性,一切都会好起来的(我不算多线程代码,因为TWinControl.Handle
不是线程-安全的)。但是,如果您将 Form1.Handle
的值缓存到一个变量,然后 post 使用该变量,那么如果 Form1.Handle
被重新创建(这可能而且确实会发生),您的代码将停止工作。在这种情况下,您需要检测重新创建并相应地更新变量:
type
TForm1 = class(TForm)
Panel1: TPanel;
Panel2: TPanel;
procedure FormCreate(Sender: TObject);
protected
procedure CreateWnd; override;
procedure DestroyWnd; override;
private
procedure OnMyMessage(var Msg: TMessage); message WM_FILEREADY;
public
{ Public declarations }
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Form2 := TForm2.Create(Self);
Form2.Parent := Panel2;
Form2.BorderStyle := bsNone;
Form2.InnerHandle := Self.Handle;
Form2.Show;
end;
procedure TForm1.CreateWnd;
begin
inherited;
if Form2 <> nil then
Form2.InnerHandle := Self.Handle;
end;
procedure TForm1.DestroyWnd;
begin
if Form2 <> nil then
Form2.InnerHandle := 0;
inherited;
end;
procedure TForm1.OnMyMessage(var Msg: TMessage);
begin
ShowMessage('got event');
end;
否则,根本不要使用 Form1.Handle
。使用永远不会重新创建的不同 window。
您可以使用 AllocateHWnd()
创建专用 window:
type
TForm1 = class(TForm)
Panel1: TPanel;
Panel2: TPanel;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
MsgWnd: HWND;
procedure MsgWndProc(var Msg: TMessage);
public
{ Public declarations }
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
MsgWnd := AllocateHWnd(MsgWndProc);
Form2 := TForm2.Create(Self);
Form2.Parent := Panel2;
Form2.Borderstyle := bsNone;
Form2.InnerHandle := MsgWnd;
Form2.Show;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if MsgWnd <> 0 then
DeallocateHWnd(MsgWnd);
end;
procedure TForm1.MsgWndProc(var Msg: TMessage);
begin
if Msg.Msg = WM_FILEREADY then
ShowMessage('got event')
else
Message.Result := DefWindowProc(MsgWnd, Msg.Msg, Msg.WParam, Msg.LParam);
end;
或者您可以使用 Application.Handle
window:
type
TForm1 = class(TForm)
Panel1: TPanel;
Panel2: TPanel;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
function AppWndProc(var Msg: TMessage): Boolean;
public
{ Public declarations }
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.HookMainWindow(AppWndProc);
Form2 := TForm2.Create(Self);
Form2.Parent := Panel2;
Form2.Borderstyle := bsNone;
Form2.InnerHandle := Application.Handle;
Form2.Show;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
Application.UnhookMainWindow(AppWndProc);
结束;
function TForm1.AppWndProc(var Msg: TMessage): Boolean:
begin
if Msg.Msg = WM_FILEREADY then
begin
ShowMessage('got event');
Result := True;
end else
Result := False;
end;
在一个新项目中,我创建了一个带有 2 个面板的 MainForm 和一个带有按钮的 Form。
我在 MainForm 上添加了这段代码:
interface
type
TForm1 = class(TForm)
Panel1: TPanel;
Panel2: TPanel;
procedure FormCreate(Sender: TObject);
private
procedure OnMyMessage(var Msg: TMessage); message WM_FILEREADY;
public
{ Public declarations }
end;
implementation
uses
PannelForm;
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
with TForm2.Create(self) do
try
parent := panel2;
borderstyle := bsNone;
InnerHandle := self.Handle;
Show;
finally
end;
end;
procedure TForm1.OnMyMessage(var Msg: TMessage);
begin
showmessage('got event');
end;
在带有按钮的表单上的这段代码:
type
TForm2 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
InnerHandle:HWND;
end;
procedure TForm2.Button1Click(Sender: TObject);
begin
// PostMessage(Application.Mainform.Handle, WM_FILEREADY, 0, 0); // works
// PostMessage(Application.Handle, WM_FILEREADY, 0, 0); // not working
// PostMessage(parent.Handle, WM_FILEREADY, 0, 0); // not working
PostMessage(InnerHandle, WM_FILEREADY, 0, 0); // works
end;
我的问题是:调用第一版和第四版时,一切正常。
第三个版本中缺少什么不起作用?
为什么 Parent 不包含右句柄?不就是传递一个Parent的(部分)点吗?
您已在 TForm1
中实现了消息处理,但 Form2.Parent.Handle
不是 Form1.Handle
,而是您已将 Panel2.Handle
分配给它。
每个窗口控件都有自己的句柄。因此,您的面板具有与表单不同的句柄,它们无法处理在表单 class.
中实现的消息一切正常,即使它不是您所期望的。
您将 Parent
设置为 Form1.Panel2
,而不是 Form1
本身。您的消息处理程序将只接收直接 posted 到 Form1
的消息。您的其他电话正在 post 打到 Form1.Handle
,这就是它们起作用的原因。
如果在 Parent
不是 Form1
时要 post 向 Parent.Handle
发送消息,则必须将要分配的面板子类化为 Parent
:
type
TForm1 = class(TForm)
Panel1: TPanel;
Panel2: TPanel;
procedure FormCreate(Sender: TObject);
private
DefPanelWndProc: TWndMethod;
procedure PanelWndProc(var Msg: TMessage);
public
{ Public declarations }
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
DefPanelWndProc := Panel2.WindowProc;
Panel2.WindowProc := PanelWndProc;
Form2 := TForm2.Create(Self);
Form2.Parent := Panel2;
Form2.BorderStyle := bsNone;
Form2.Show;
end;
procedure TForm1.PanelWndProc(var Msg: TMessage);
begin
if Msg.Msg = WM_FILEREADY then
ShowMessage('got event')
else
DefPanelWndProc(Msg);
end;
否则,post 将消息改为 Form1
。
如果你post每次都使用Form1.Handle
属性,一切都会好起来的(我不算多线程代码,因为TWinControl.Handle
不是线程-安全的)。但是,如果您将 Form1.Handle
的值缓存到一个变量,然后 post 使用该变量,那么如果 Form1.Handle
被重新创建(这可能而且确实会发生),您的代码将停止工作。在这种情况下,您需要检测重新创建并相应地更新变量:
type
TForm1 = class(TForm)
Panel1: TPanel;
Panel2: TPanel;
procedure FormCreate(Sender: TObject);
protected
procedure CreateWnd; override;
procedure DestroyWnd; override;
private
procedure OnMyMessage(var Msg: TMessage); message WM_FILEREADY;
public
{ Public declarations }
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Form2 := TForm2.Create(Self);
Form2.Parent := Panel2;
Form2.BorderStyle := bsNone;
Form2.InnerHandle := Self.Handle;
Form2.Show;
end;
procedure TForm1.CreateWnd;
begin
inherited;
if Form2 <> nil then
Form2.InnerHandle := Self.Handle;
end;
procedure TForm1.DestroyWnd;
begin
if Form2 <> nil then
Form2.InnerHandle := 0;
inherited;
end;
procedure TForm1.OnMyMessage(var Msg: TMessage);
begin
ShowMessage('got event');
end;
否则,根本不要使用 Form1.Handle
。使用永远不会重新创建的不同 window。
您可以使用 AllocateHWnd()
创建专用 window:
type
TForm1 = class(TForm)
Panel1: TPanel;
Panel2: TPanel;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
MsgWnd: HWND;
procedure MsgWndProc(var Msg: TMessage);
public
{ Public declarations }
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
MsgWnd := AllocateHWnd(MsgWndProc);
Form2 := TForm2.Create(Self);
Form2.Parent := Panel2;
Form2.Borderstyle := bsNone;
Form2.InnerHandle := MsgWnd;
Form2.Show;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if MsgWnd <> 0 then
DeallocateHWnd(MsgWnd);
end;
procedure TForm1.MsgWndProc(var Msg: TMessage);
begin
if Msg.Msg = WM_FILEREADY then
ShowMessage('got event')
else
Message.Result := DefWindowProc(MsgWnd, Msg.Msg, Msg.WParam, Msg.LParam);
end;
或者您可以使用 Application.Handle
window:
type
TForm1 = class(TForm)
Panel1: TPanel;
Panel2: TPanel;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
function AppWndProc(var Msg: TMessage): Boolean;
public
{ Public declarations }
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.HookMainWindow(AppWndProc);
Form2 := TForm2.Create(Self);
Form2.Parent := Panel2;
Form2.Borderstyle := bsNone;
Form2.InnerHandle := Application.Handle;
Form2.Show;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
Application.UnhookMainWindow(AppWndProc); 结束;
function TForm1.AppWndProc(var Msg: TMessage): Boolean:
begin
if Msg.Msg = WM_FILEREADY then
begin
ShowMessage('got event');
Result := True;
end else
Result := False;
end;