控制“xxx”没有父窗口

发布于 2024-09-24 10:32:20 字数 220 浏览 5 评论 0原文

我正在尝试在 Delphi 中编写一个 dll 库,其中包含一个创建 TFrame 后代实例并返回它的函数。但是当我在应用程序中导入这个函数时,每次调用它时我都会得到一个异常,例如“'xxx'控件没有父窗口”。我不是 100% 确定,但是当访问任何 GUI 控件时,异常会出现在该类的构造函数中。

您能告诉我这种行为的原因是什么吗?我应该只使用 TForm 后代还是有更好的解决方案?

谢谢你!

I'm was trying to write a dll library in Delphi wih a function that creates an instance of a TFrame descendant and returns it. But when I imported this function in an application, every time I called it I would get an exception like "the 'xxx' control has no parent window". I'm not 100% sure, but the exception appeared in the constructor of that class when any of GUI controls was accessed.

Could you please tell me what the reason of that behaviour is? Should I just use TForm descendants instead or is there a better solution?

Thank you!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

那小子欠揍 2024-10-01 10:32:20

关于错误

该错误消息是从 Controls.pas 单元的 TWinControl.CreateWnd 方法引发的。本质上,该代码用于为您的 TWinControl 后代(TFrame、TButton、TEdit...如果它可以有键盘焦点,那么它就是 TWinControl 后代)创建窗口句柄,这实际上是一个非常明智的错误消息:您不能有没有 WindowParent 的窗口,并且由于我们在这里讨论的是 VCL,因此尝试从 TWinControl.Parent 获取父窗口句柄很有意义;这还没有分配。

这不是弹出错误消息的原因。您会看到该错误消息,因为您用于设置框架的某些代码需要窗口句柄来执行某些操作。它可以是任何东西,比如设置某些组件的标题(内部需要窗口句柄来进行某些计算)。我个人真的很讨厌这种情况发生。当我从代码创建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。

About the error

That error message is raised from the Controls.pas unit, from the TWinControl.CreateWnd method. Essentially that code is used to create the Window handle for your TWinControl descendant (TFrame, TButton, TEdit... if it can have keyboard focus it's an TWinControl descendant), and it's actually an very sensible error message: You can't have a Window without an WindowParent, and since we're talking about the VCL here, it makes a lot of sense to try and get the parent window handle from TWinControl.Parent; And that's not assigned.

That's not WHY the error message is popping up. You get to see that error message because some of the code you're using to set up the frame requires an Window handle for some operation. It could be anything, like setting the Caption of some component (that internally requires an window handle do to some calculation). I personally really hate it when that happens. When I create GUI's from code I try to delay the assignment of Parent as much as possible, in an attempt to delay the creation of the window, so I got bitten by this many times.

Specific to your DLL usage, possible fix

I'm going to put my psycho mind reader hat on. Since you need to return a FRAME from your DLL, and you can't return the actual Frame because that's an Delphi-specific object and you're not allowed to return Delphi-specific objects over DLL boundaries, my guess is you're returning an Window Handle, as all the nice API's do, using a function definition like this:

function GiveMeTheNiceFrame:HWND;

The trouble is, that routine requires the creation of the actual Window Handle, by a call to TWinControl.CreateWnd, and in turn that call requires an parent window handle to set up the call to Windows.CreateWindowEx, and the routine can't get an parent window handle, so it errors out.

Try replacing your function with something allong the lines of:

function GiveMeTheNiceFrame(OwnerWindow:HWND):HWND;
begin
  Result := TMyNiceFrame.CreateParanted(OwnerWindow).Handle;
end;

... ie: use the CreateParented(AParentWindow:HWND) constructor, not the usual Create(AOwner:TComponent) and pass an owner HWND to your DLL.

沙与沫 2024-10-01 10:32:20

有几件重要的事情需要记住:

  1. 当使用 DLL 时,您的 DLL 和 EXE 都有一个争夺控制权的应用程序实例。 DLL 中的控件将看到属于该 DLL 的应用程序实例; EXE 中的控件将看到属于该 EXE 的应用程序实例。使用包时就不存在这种困难,因为那时只会有一个应用程序实例。
  2. 框架是控件,但它们不是表单。
  3. 在应用程序中使用控件时,如果没有父控件(通常是窗体或具有窗体父层次结构的容器),它们就无法在视觉上存在。
  4. 某些控件无法公开其全部功能,除非它们可见并具有有效的父控件。

尝试在 EXE 中重现您的问题;如果您无法重现,它可能是上面列表中的第一件事。

——杰罗恩

There are a few important things to remember:

  1. When using DLLs, both your DLL and your EXE each have an Application instance that are struggling for control. The Controls in your DLL will see the Application instance that belongs to the DLL; the Controls in your EXE will see the Application instance that belongs to the EXE. That struggle is not there when using packages, as then there will only be one Application instance.
  2. Frames are Controls, but they are not Forms.
  3. When using Controls in an application, they cannot visually exist without a parent Control (usually a Form or a container that has a parent hierarchy towards a Form).
  4. Some Controls cannot expose their full functionality unless they exist visually and have a valid parent.

Try to reproduce your problem inside the EXE; if you cannot reproduce, it is probably the first thing in the above list.

--jeroen

爱人如己 2024-10-01 10:32:20

听起来您只需将保存框架的组件(表单或表单的一部分,如面板)分配给 theframe.parent 。

在分配 GUI 工作之前,您不能执行该工作。框架是表单的一部分,可以重用,通常需要为其分配一些父级。

将 GUI 代码移至 onshow 或您显式调用的过程,以便调用代码可以分配父级。

或者将父级作为函数中的参数。

Sounds like you simply need to assign the component (a form or part of a form, like a panel) that holds the frame to theframe.parent.

You cannot do GUI work before it is assigned. Frames are parts of forms for reuse, and normally need to assign some parent to them.

Move the GUI code to onshow or a procedure you call explicitely, so that the calling code can assign parent.

Or make the parent a parameter in the function.

镜花水月 2024-10-01 10:32:20

我发现这个(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;

I found this (CreateParams is called as part of CreateWnd):

procedure TCustomFrame.CreateParams(var Params: TCreateParams);
begin
  inherited;
  if Parent = nil then
    Params.WndParent := Application.Handle;
end;

And Application.Handle = 0 so it always throws the error later in CreateWnd.
After reading this
Delphi: How to call inherited inherited ancestor on a virtual method?

I have solved it by overriding CreateParams in my frame to miss out the tCustomFrame version:

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;

Now it's just throwing errors when trying to set the focus on subcontrols, which I think I will fix by intercepting WM_FOCUS but we'll how it goes from here.

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;
第七度阳光i 2024-10-01 10:32:20

您可以通过将 nil 分配给父 OnClose 事件来避免此消息,有时它会起作用:

SomeControl.Parent := nil;//Before free your TControl
SomeControl.Free;

You can avoid this message by assigning nil to the parent OnClose event, sometimes it works:

SomeControl.Parent := nil;//Before free your TControl
SomeControl.Free;
缱绻入梦 2024-10-01 10:32:20

我认为这是非常酷的解决方案。我认为以前没有尝试过:)
我正在使用虚拟父级(这是一个表单)。

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

I think this is very cool solution. I think it is not tried before :)
I'm using a Dummy Parent (which is a Form).

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文