51

是什么TApplication.Handle

  • 它从何而来?
  • 它为什么存在?
  • 最重要的是:为什么所有表单都将其作为父窗口句柄?

德尔福帮助说:

TApplication.Handle

提供对应用程序主窗体(窗口)的窗口句柄的访问。

property Handle: HWND;

描述

在调用需要父窗口句柄的 Windows API 函数时使用句柄。例如,显示其自己的顶级弹出窗口的 DLL 需要一个父窗口来在应用程序中显示其窗口。使用 Handle 属性使此类窗口成为应用程序的一部分,以便它们与应用程序一起最小化、恢复、启用和禁用。

如果我专注于“应用程序主窗体的窗口句柄”一词,并且我认为它是指应用程序主窗体的窗口句柄,那么我可以比较:

  • “应用程序主窗体的窗口句柄”,带有
  • MainForm的窗口句柄Application

但它们不一样:

Application.MainForm.Handle: 11473728
Application.Handle: 11079574

那么是什么Application.Handle

  • 它从何而来?
  • 它是什么 Windows® 窗口句柄?
  • 如果Application's的 Windows® 窗口句柄MainForm,那么它们为什么不匹配?
  • 如果不是Application's的窗口句柄MainForm,那是什么?
  • 更重要的是:为什么它是每个表单的最终所有者?
  • 最重要的是:如果我尝试让表单成为无父无主的表单(因此它可以出现在任务栏上),或者尝试使用IProgressDialog之类的东西,为什么一切都会变得混乱?

我真正要问的是:使Application.Handle存在的设计原理是什么?如果我能理解为什么,那么应该如何变得显而易见。


通过二十个问题的游戏更新理解:

在谈到通过设置其所有者使窗口出现在任务栏上的解决方案时null彼得·弗莱德在 2000 年说

这可能会导致从辅助形式显示的模态形式出现一些问题。

如果用户在模态表单打开时从应用程序切换,然后返回显示它的表单,则模态表单可能会隐藏在表单下方。可以通过确保模态表单是父级来解决这个问题[原文如此;他的意思是拥有] 以显示它的形式( params.WndParent如上使用)

Dialogs但这对于来自单元和异常的标准对话框是不可能的,这需要更多的努力才能让它们正常工作(基本上处理Application.OnActivate,寻找以Application via为父级的模态表单GetLastActivePopup 并将它们带到 Z-order via 的顶部SetWindowPos) .

  • 为什么模态表单最终会卡在其他表单后面?
  • 什么机制通常将模态形式带到前面,为什么它在这里不起作用?
  • Windows® 负责显示堆叠的窗口。Windows® 没有显示正确的窗口出了什么问题?

他还谈到了使用新的 Windows 扩展样式,该样式通过添加扩展样式来强制窗口出现在任务栏上(当使其不拥有的正常规则不足、不切实际或不受欢迎时)WS_EX_APPWINDOW

procedure TForm2.CreateParams(var Params: TCreateParams); 
begin 
   inherited CreateParams( params ); 

   Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW; 
end; 

但随后他警告说:

如果您在另一个应用程序处于活动状态时单击辅助表单任务栏按钮,这仍会将所有应用程序表单置于前面。如果你不希望有选项

当表单的所有者静止时,谁将所有表单带到最前面Application.Handle应用程序是这样做的吗?为什么要这样做?与其这样做,应该这样做吗?这样做有什么缺点?我看到了这样做的缺点(系统菜单无法正常工作,任务栏按钮缩略图不准确,Windows® shell 无法最小化窗口。


在另一篇处理 . 的帖子中ApplicationMike Edenfield 说父窗口向其他窗口发送它们的最小化、最大化和恢复消息

这将为您的表单添加任务栏按钮,但还有一些其他小细节需要处理。最明显的是,您的表单仍然会收到发送到父表单(应用程序的主表单)的最小化/最大化。为了避免这种情况,您可以通过添加如下行来为 WM_SYSCOMMAND 安装消息处理程序:

procedure WMSysCommand(var Msg: TMessage); WM_SYSCOMMAND; 

procedure TParentForm.WMSysCommand(var Msg: TMessage); 
begin 
   if Msg.wParam = SC_MINIMIZE then 
   begin 
      // Send child windows message, don't 
      // send to windows with a taskbar button. 
   end; 
end; 

请注意,此处理程序以您希望独立于应用程序其余部分的行为的PARENT形式出现,以避免传递最小化消息。您可以为 SC_MAXIMIZE、SC_RESTORE 等添加类似的 > 代码。

为什么我的 Windows® 窗口的最小化/最大化/恢复消息没有进入我的窗口?这是因为 Windows® 将发往窗口的消息发送给了窗口的所有者吗?在这种情况下,Delphi 应用程序中的所有表单都是“拥有”的Application?这是否意味着使所有者为空:

procedure TForm2.CreateParams(var Params: TCreateParams);
begin
   inherited;
   Params.WndParent := 0; //NULL
end;

将删除Application它的窗口句柄干扰我的表单,Windows 应该再次向我发送的最小化/最大化/恢复消息?


也许如果我们现在比较和对比一个“正常”的 Windows 应用程序做事,与 Borland 最初设计 Delphi 应用程序做事的方式 - 关于这个Application对象和它的主循环。

  • 对象解决的是什么解决方案Application
  • 更高版本的 Delphi 进行了哪些更改以使这些相同的问题不存在?
  • Delphi 后期版本中的更改是否没有引入其他问题,最初的应用程序设计如此努力地解决?
  • 那些较新的应用程序如何在没有应用程序干扰的情况下仍然运行?

显然,Borland 意识到了他们最初设计中的缺陷。他们最初的设计是什么,它解决了什么问题,缺陷是什么,重新设计是什么,它是如何解决问题的?

4

3 回答 3

54

应用程序窗口的原因有一段肮脏的历史。在开发 Delphi 1 时,我们知道我们想为 IDE 使用“SDI”(分散在桌面上的窗口)ui 模型。我们也知道 Windows 在该模型上很糟糕(现在仍然如此)。然而,我们也注意到当时的 Visual Basic 采用了该模型,并且似乎运行良好。经过进一步检查,我们发现 VB 使用了一个特殊的“隐藏”停车窗口,用作所有其他可见窗口的“所有者”(Windows 有时会模糊父和所有者的概念,但区别类似于 VCL) .

这就是我们如何解决包含主菜单的窗口很少聚焦的“问题”,因此处理文件菜单的 Alt-F 根本不起作用。通过使用这个中央停车窗口作为中介,我们可以更轻松地跟踪消息并将消息路由到适当的窗口。

这种安排还解决了通常多个顶层窗口完全独立的另一个问题。通过让应用程序处理所有这些窗口的“所有者”,它们都会表现得一致。例如,您可能已经注意到,当您选择任何一个应用程序窗口时,所有应用程序窗口都会移到前面并保持它们的相对于彼此的 z 顺序。这也将使应用程序最小化并恢复为功能组。

这是使用此模型的结果。我们本可以手动完成所有这些工作以保持正常,但设计理念不是重新发明 Windows,而是尽可能利用它。这也是为什么 TButton 或 TEdit实际上分别是 Windows“用户”BUTTON 和 EDIT 窗口类和样式的原因。

随着 Windows 的发展,这种“SDI”模型开始失宠。事实上,Windows 本身开始对这种应用程序风格产生“敌意”。从 Windows Vista 开始并继续到 7,用户 shell 似乎不适用于使用停车窗口的应用程序。因此,我们着手在 VCL 中进行洗牌,以消除停车窗口并将其功能转移到主窗体中。这带来了几个“鸡和蛋”的问题,我们需要在应用程序初始化时足够早地使停车窗口可用,以便其他窗口可以“附加”到它,但主窗体本身可能无法足够快地构建。TApplication 必须跳过几个环节才能使其正常工作,并且有一些微妙的边缘情况导致了问题,但是大部分问题都已经解决了。但是,对于您向前推进的任何应用程序,它将继续使用旧的停车窗口模型。

于 2010-02-05T17:55:37.113 回答
12

所有 VCL 应用程序都有一个名为 Application 的“隐藏”顶层窗口。这是在应用程序启动时自动创建的。除其他外,它是 VCL 的主要 Windows 消息处理程序 - 因此是 Application.ProcessMessages。

隐藏应用程序的顶级窗口确实会导致一些奇怪的事情,尤其是任务栏中显示的系统菜单不完整,以及 Vista 中的缩略图窗口不正确。之后的 Delphi 版本更正了这一点。

但是,并非所有窗口都必须将其作为父窗口,如果是,Windows 往往会更好地工作。但是,使用 Application.CreateForm 创建的任何表单都将它作为父表单,并且它也将归 Application 对象所有。由于它们是拥有的,一旦应用程序被释放,它们就会被释放。这发生在 Forms.DoneApplication 的幕后

于 2010-02-05T04:01:57.433 回答
8

通过查看 forms.pas (Delphi 2009) 中的源代码,他们似乎在 win32 gui 应用程序中创建了一个“主”窗口以允许调用

  • TApplication.最小化
  • TApplication.Restore
  • ETC

似乎传递给 的消息Application.Handle会适当地转发给MainForm(如果存在)。如果尚未创建主窗口,这将允许应用程序响应最小化等。通过修改项目源,您可以创建一个没有主窗口的 delphi 应用程序。

在这种情况下,TApplication即使您尚未创建主窗口,这些方法仍然有效。不确定我是否掌握了所有目的,但我没有时间浏览所有 TApplication 代码。

根据您的问题:

  • 它从何而来?它是在中创建的窗口的句柄TApplication.Create

  • 它是什么窗口句柄?每个 gui delphi 应用程序都需要作为 TApplication 抽象的一部分的假窗口

  • 是应用程序主窗体的windows句柄吗

  • 如果它不是应用程序主窗体的句柄,那它是什么?往上看

  • 更重要的是:为什么它是每种形式的最终父项?假设您是正确的,它是最终的父级,我认为它是正确的,因为它可以很容易地在您的应用程序中找到所有表单(枚举此“主”表单的子级)。

  • 最重要的是:如果我试图让一个表单成为非父表单,为什么一切都会变得混乱我认为是因为隐藏的“主”表单正在获取系统消息,它应该传递给它的子表单和/或主表单,但找不到无父形式。

无论如何,这是我的看法。您可能可以通过查看 TApplication 声明和forms.pas. 我所看到的底线是它是一个方便的抽象。

最好的祝福,

大学教师

于 2010-02-05T04:01:48.497 回答