11

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

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

谢谢!

4

6 回答 6

8

关于错误

该错误消息是从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。

于 2010-09-23T08:08:05.820 回答
1

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

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

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

——杰伦

于 2010-09-23T07:46:08.187 回答
0

我发现了这个(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;
于 2011-03-18T18:44:18.597 回答
0

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

SomeControl.Parent := nil;//Before free your TControl
SomeControl.Free;
于 2011-11-17T22:09:35.287 回答
0

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

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

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

或者使父级成为函数中的参数。

于 2010-09-23T07:37:45.820 回答
0

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

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
于 2014-12-30T05:09:46.110 回答