我试图用一个函数在 Delphi 中编写一个 dll 库,该函数创建一个 TFrame 后代的实例并返回它。但是当我在应用程序中导入这个函数时,每次调用它都会得到一个异常,比如“'xxx'控件没有父窗口”。我不是 100% 确定,但是当访问任何 GUI 控件时,该类的构造函数中会出现异常。
你能告诉我这种行为的原因是什么吗?我应该只使用 TForm 后代还是有更好的解决方案?
谢谢!
关于错误
该错误消息是从TWinControl.CreateWnd
方法中的 Controls.pas 单元引发的。本质上,该代码用于为您的 TWinControl 后代(TFrame、TButton、TEdit...没有 WindowParent 的窗口,因为我们在这里讨论的是 VCL,所以尝试从 TWinControl.Parent 获取父窗口句柄是很有意义的;这不是分配的。
这不是弹出错误消息的原因。您会看到该错误消息,因为您用于设置框架的某些代码需要 Window 句柄才能进行某些操作。它可以是任何东西,比如设置某个组件的标题(内部需要一个窗口句柄来进行某些计算)。当这种情况发生时,我个人真的很讨厌它。当我从代码创建 GUI 时,我尝试尽可能地延迟 Parent 的分配,试图延迟窗口的创建,所以我被这个咬了很多次。
特定于您的 DLL 使用,可能的修复
我要戴上我的心理读心器帽子。由于您需要从 DLL 返回一个 FRAME,并且您不能返回实际的 Frame,因为这是一个特定于 Delphi 的对象,并且不允许您在 DLL 边界上返回特定于 Delphi 的对象,我猜你正在返回一个窗口句柄,就像所有漂亮的 API 所做的那样,使用如下函数定义:
function GiveMeTheNiceFrame:HWND;
问题是,该例程需要通过调用来创建实际的窗口句柄,而该调用TWinControl.CreateWnd
又需要一个父窗口句柄来设置对 的调用Windows.CreateWindowEx
,并且该例程无法获得父窗口句柄,所以它出错了。
尝试用以下内容替换您的功能:
function GiveMeTheNiceFrame(OwnerWindow:HWND):HWND;
begin
Result := TMyNiceFrame.CreateParanted(OwnerWindow).Handle;
end;
...即:使用CreateParented(AParentWindow:HWND)
构造函数,而不是通常的构造函数,Create(AOwner:TComponent)
并将所有者 HWND 传递给您的 DLL。
有几件重要的事情要记住:
尝试在 EXE 中重现您的问题;如果你不能重现,它可能是上面列表中的第一件事。
——杰伦
我发现了这个(CreateParams 被称为 CreateWnd 的一部分):
procedure TCustomFrame.CreateParams(var Params: TCreateParams);
begin
inherited;
if Parent = nil then
Params.WndParent := Application.Handle;
end;
并且 Application.Handle = 0 所以它总是稍后在 CreateWnd 中抛出错误。
阅读此
Delphi 后:如何在虚拟方法上调用继承的继承祖先?
我已经通过在我的框架中覆盖 CreateParams 来解决它以错过 tCustomFrame 版本:
type
tCreateParamsMethod = procedure(var Params: TCreateParams) of object;
type
tMyScrollingWinControl = class(TScrollingWinControl);
procedure TDelphiFrame.CreateParams(var Params: TCreateParams);
var
Proc: tCreateParamsMethod;
begin
TMethod(Proc).Code := @TMyScrollingWinControl.CreateParams;
TMethod(Proc).Data := Self;
Proc(Params);
end;
现在它只是在尝试将焦点设置在子控件上时抛出错误,我想我会通过拦截 WM_FOCUS 来解决这个问题,但我们会从这里开始。
function CreateFrame(hwndParent: HWnd): HWnd; stdcall;
var
frame: tFrame;
begin
Result := 0;
try
frame := TDelphiFrame.CreateParented(hwndParent);
Result := frame.Handle;
except on e: Exception do
ShowMessage(e.Message);
end;
end;
您可以通过将 nil 分配给父 OnClose 事件来避免此消息,有时它会起作用:
SomeControl.Parent := nil;//Before free your TControl
SomeControl.Free;
听起来您只需将包含框架的组件(表单或表单的一部分,如面板)分配给 theframe.parent。
在分配之前,您不能进行 GUI 工作。框架是可重复使用的表单的一部分,通常需要为它们分配一些父级。
将 GUI 代码移动到 onshow 或您显式调用的过程,以便调用代码可以分配父级。
或者使父级成为函数中的参数。
我认为这是非常酷的解决方案。我认为以前没有尝试过:) 我正在使用一个虚拟父母(这是一个表格)。
function MyFrame_Create(hApplication, hwndParent:THandle; X, Y, W, H:Integer):Pointer; stdcall;
var Fr: TMyFrame;
F: TForm;
CurAppHandle: THandle;
begin
CurAppHandle:=Application.Handle;
Application.Handle:=hApplication;
//---
F:=TForm. Create(Application);//Create a dummy form
F.Position:=poDesigned;
F.Width:=0; F.Top:=0; F.Left:=-400; F.Top:=-400;//Hide Form
F.Visible:=True;
//---
Fr:=TMyFrame.Create(Application);
Fr.Parent:=F;//Set Frame's parent
//Fr.ParentWindow:=hwndParent;
Windows.SetParent(Fr.Handle, hwndParent);//Set Frame's parent window
if CurAppHandle>0 then Application.Handle:=CurAppHandle;
//---
Fr.Left:=X;
Fr.Top:=Y;
Fr.Width:=W;
Fr.Height:=H;
Result:=Fr;
end;//MyFrame_Create
procedure MyFrame_Destroy(_Fr:Pointer); stdcall;
var Fr: TMyFrame;
F: TObject;
begin
Fr:=_Fr;
F:=Fr.Parent;
Fr.Parent:=Nil;
if (F is TForm) then F.Free;
//SetParent(Fr.Handle, 0);
//Fr.ParentWindow:=0;
Fr.Free;
end;//MyFrame_Destroy