【问题标题】:The control 'xxx' has no parent window控件“xxx”没有父窗口
【发布时间】:2011-04-16 02:49:11
【问题描述】:

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

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

谢谢!

【问题讨论】:

标签: delphi dll tframe


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 这是意外编程。
    • Kachwahed:你不应该靠猜测来编程。它只会在你的代码中乱扔不必要的垃圾,这些垃圾通常会成为微妙和意外错误的催化剂。是的,这可能很棘手;但是您需要将精力放在根本原因分析上,以便正确理解问题。在这种特殊情况下,您的答案与 OP 的问题完全无关。
    • 第二点,您不应该试图“避免错误消息”。错误消息不是问题 ...它们只是关于问题的消息。所以不要只射击信使。找出实际问题是什么,然后修复真正问题
    【解决方案3】:

    我发现了这个(CreateParams 被称为 CreateWnd 的一部分):

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

    并且 Application.Handle = 0 所以它总是稍后在 CreateWnd 中抛出错误。
    看完这篇 Delphi: How to call inherited inherited ancestor on a virtual method?

    我已经通过在我的框架中覆盖 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;
    

    【讨论】:

      【解决方案4】:

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

      在分配之前您不能进行 GUI 工作。框架是可重复使用的表单的一部分,通常需要为它们分配一些父级。

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

      或者在函数中将父级设为参数。

      【讨论】:

      • 在第二次阅读您的答案(我有点词盲)后,您的答案似乎在“通常需要”之后丢失了一点......
      • 已修复。匆忙写作对我来说一直是个问题。一次处理所有句子:)
      【解决方案5】:

      关于错误

      该错误消息来自 Controls.pas 单元,来自 TWinControl.CreateWnd 方法。本质上,该代码用于为您的 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。

      【讨论】:

      • 感谢您的回答。我认为它应该有所帮助;我稍后会试试。 BTW,让我问你一个问题。为什么不允许通过 DLL 返回 Delphi 特定对象?我将只在 Delphi 应用程序中使用这个库。该函数返回 TFrame 类型,但创建的实际对象是后代类的实例。不好吗?谢谢!
      • 由于版本控制的原因不可以:使用返回的对象需要了解它的内存布局,并且可能会发生变化(它可以随着 Delphi 的版本而变化)。当你测试它时它会工作,如果 Exe 和 Dll 是使用相同的编译器和相同的编译器设置构建的,它会工作,但如果你要施加这样的限制,你最好使用包。
      • 谢谢,很高兴知道这一点。我按照您的建议做了,但仍然出现异常:-(。导出的函数执行此操作:Result := TFrame1.CreateParented(ParentWindowHandle); ParentWindowHandle 是调用该方法的应用程序主窗口的句柄( Self.Handle,当从主窗口类调用时)。
      • 进一步调试。在编辑器中使用您的 DLL,转到运行 -> 加载进程并加载使用您的 DLL 的 EXE。用 EXE 做任何你需要的事情,让它加载你的 DLL 并调用你的函数。 IDE 将在错误时停止,查看调用堆栈,找到有问题的代码并确保已分配 Parent。
      • 调试DLL的正确方法是在Project Options中指定Host Application(exe)然后运行。主机应用程序将启动,当发生错误时,您可以像往常一样进行调试。
      【解决方案6】:

      有一些重要的事情要记住:

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

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

      --杰罗恩

      【讨论】:

        猜你喜欢
        • 2016-12-20
        • 2014-06-12
        • 2019-06-12
        • 2013-08-27
        • 1970-01-01
        • 2012-06-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多