Delphi:什么是Application.Handle?

52

TApplication.Handle 是什么?

  • 它从哪里来?
  • 它为什么存在?
  • 最重要的是:为什么所有的窗体都将其作为父窗口句柄?

Delphi 帮助文件中说:

TApplication.Handle

Provides access to the window handle of the main form (window) of the application.

property Handle: HWND;

Description

Use Handle when calling Windows API functions that require a parent window handle. For example, a DLL that displays its own top-level pop-up windows needs a parent window to display its windows in the application. Using the Handle property makes such windows part of the application, so that they are minimized, restored, enabled and disabled with the application.

如果我专注于“应用程序主窗体的窗口句柄”这个词组,我理解为“应用程序主窗体的窗口句柄”,那么我可以进行比较:
- “应用程序主窗体的窗口句柄”,与 - ApplicationMainForm 的窗口句柄
但是它们并不相同。
Application.MainForm.Handle: 11473728
Application.Handle: 11079574

那么Application.Handle是什么?

  • 它来自哪里?
  • 它是哪个Windows®窗口句柄?
  • 如果它确实是ApplicationMainForm的Windows®窗口句柄,则为什么它们不匹配?
  • 如果它不是ApplicationMainForm的窗口句柄,则它是什么?
  • 更重要的是:为什么它是每个窗体的最终父级所有者?
  • 最重要的问题:如果我尝试让一个窗体成为未拥有未拥有(以便它可以出现在任务栏上),或者尝试使用类似IProgressDialog的东西,为什么一切都会出错?

我真正想问的是:是什么设计理念使得Application.Handle存在?如果我能理解为什么,那么如何做应该就变得很明显了。


通过二十个问题来理解:

当谈到通过将其所有者设置为null来使窗口出现在任务栏上的解决方案时,Peter Below在2000年说:

这可能会导致从次要窗体显示的模态窗体出现问题。

如果用户在模态窗体弹出时切换到其他应用程序,然后再切回来,模态窗体可能会隐藏在主窗体下面。可以通过确保模态窗体是父窗体(sic;他想说的是所有者)(使用params.WndParent如上所述)来处理此问题。

但这对于来自Dialogs单元的标准对话框和异常不可行,需要更多的努力来使它们正常工作(基本上是处理Application.OnActivate,查找通过GetLastActivePopupApplication拥有关系的模态窗体,并通过SetWindowPos将其置于Z轴的顶部)。

  • 为什么模态窗体会被卡在其他窗体后面?
  • 通常是什么机制将模态窗体带到最前面,为什么这里不起作用?
  • 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时,是谁将所有窗体置于前台。 是Application在这样做吗? 为什么它要这样做?与其这样做,难道它不应该这样做吗? 不这样做的缺点是什么? 我看到这样做的缺点(系统菜单无法正常工作,任务栏按钮缩略图不准确,Windows® shell无法最小化窗口)。


在另一篇涉及Application的文章中,Mike 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; 

Note that this handler goes in the PARENT form of the one you want to behave independently of > the rest of the application, so as to avoid passing on the minimize message. You can add similar > code for SC_MAXIMIZE, SC_RESTORE, etc.

我的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版本的更改是否带来了其他问题,而最初的Application设计则试图解决这些问题?
  • 那些更新的应用程序如何在没有Application干扰的情况下正常运行?

显然,Borland意识到了他们最初设计的缺陷。他们最初的设计是什么,解决了什么问题,有什么缺陷,重新设计后又如何解决这个问题呢?


我认为你会对这两个技巧感兴趣:http://yoy.be/item.asp?i89 http://yoy.be/item.asp?i87 - Stijn Sanders
2
@Stinh Sanders:我看过那些,它们并没有解决问题。而且,绝对不要将GetDesktopWindow作为窗口的所有者传递,就像那些帖子和其他帖子所建议的那样。这样做曾经会导致Windows冻结。这是一个如此严重的问题,以至于Microsoft修补了CreateWindow,因此任何将GetDesktopWindow作为所有者的人都被更改为使用NULL。如果我可以在yoy.com上编辑那篇文章,我会这么做。 - Ian Boyd
在东京,Application.Handle 的值为零! - Gabriel
一个窗体的“Owner”与其“Parent”是独立的(但可以相同)。Owner 与 Delphi 将基于 TComponent 的对象链接在一起以便在“Owner”(参见 Create(AOwner: TComponent) 被释放时自动释放它们有关。而“Parent”(或“WndParent”)则涉及到可视控件之间的父子关系。那么为什么每个窗体都有 Application 作为 Owner 呢?因为 Application.CreateForm(TMyForm, MyForm) 使用自身作为 owner 创建窗体。至于父句柄为什么是 'Application.Handle',请参见 TCustomForm.CreateParams - R. Hoek
3个回答

60
应用程序窗口的历史有些肮脏。在开发Delphi 1时,我们知道我们想要使用“SDI”(窗口分散在桌面上)UI模型来设计IDE。我们也知道Windows在这个模型上很糟糕(现在仍然如此)。但是我们还注意到,当时的Visual Basic采用了这种模型,并且似乎很好用。经过进一步的调查,我们发现VB使用了一个特殊的“隐藏”停车窗口,该窗口被用作所有其他可见窗口的“所有者”(Windows有时会模糊父窗口和所有者的概念,但区别类似于VCL)。
这就是我们解决菜单窗口很少被聚焦,因此Alt-F键来打开文件菜单栏几乎无效的“问题”的方式。通过使用这个中心停车窗口作为中间人,我们可以更轻松地跟踪和路由消息到适当的窗口。
这种安排还解决了另一个问题,即通常情况下多个顶级窗口是完全独立的。通过使应用程序处理所有这些窗口的“所有者”,它们将以协调的方式工作。例如,您可能已经注意到,当您选择任何应用程序窗口时,所有应用程序窗口都会移到前面,并保持它们相对于彼此的Z顺序。这也使应用程序作为一个功能组最小化和恢复。
这是使用这种模型的结果。我们本来可以手动完成所有这些工作以保持事情井然有序,但设计理念是不重新发明Windows,而是在可以利用的地方利用它。这也是为什么TButton或TEdit实际上是Windows“用户”BUTTON和EDIT窗口类和样式。随着Windows的不断发展,那个“SDI”模型逐渐不受欢迎。事实上,Windows本身开始对这种应用程序样式变得“敌对”。从Windows Vista开始一直到7,用户界面似乎无法很好地与使用停车窗口的应用程序配合使用。因此,我们着手重新排列VCL中的内容,以消除停车窗口并将其功能移入主窗体。这产生了几个“鸡和蛋”的问题,即我们需要在应用程序初始化的早期就可用停车窗口,以便其他窗口可以“连接”到它,但是主窗体本身可能不会很快构建。TApplication必须通过一些技巧来使其正常工作,并且有一些微妙的边缘情况会导致问题,但是大多数问题已经被解决了。然而,对于您推进的任何应用程序,它仍将使用较旧的停车窗口模型。

1
+1 是为了承认这是肮脏的事情。而在 Windows XP 中添加到 Microsoft 窗口管理层的黑客攻击则成为该系统的丧钟的一部分,因为当你在 XP 上运行 Delphi 7 应用程序时,可怕的 Z-Order Bug 开始出现。 - Warren P
你的解释并不是100%清晰。我理解最新版本的Delphi中不再创建隐藏的窗体。我猜这就是为什么Application.Handle现在为零的原因。对吗? - Gabriel

12

所有的VCL应用程序都有一个名为“Application”的“隐藏”顶层窗口。在应用程序启动时,它会自动创建。它是VCL的主窗口消息处理器之一,因此有了"Application.ProcessMessages"。

将应用程序的顶层窗口设为隐藏会导致某些奇怪的事情发生,尤其是任务栏中显示的不完整的系统菜单和Vista中不正确的缩略图窗口。Delphi的后续版本已经解决了这个问题。

然而,并不是所有的窗口都必须将其作为父窗口,但如果将其作为父窗口,Windows就能更好地工作。

使用"Application.CreateForm"创建的任何窗体都将其作为父窗口,并且还将被Application对象拥有。由于它们被拥有,一旦Application被释放,它们也会被释放。这是在Forms.DoneApplication中自动完成的过程。


3
你的应用程序顶层窗体没有将其“Parent”属性设置为应用程序窗口!只有“Owner”属性设置为应用程序对象。仅供澄清:Application.ProcessMessages处理主线程中所有窗口(所有VCL窗口)的消息,它实际上是在所有Windows GUI应用程序中找到的正常消息处理循环中的一步。 - Ritsaert Hornstra
@Ritsaert Hornstra:我的应用程序的顶层窗体有什么句柄作为它们的父级?还请注意,我创建的任何窗体都将具有Application.Handle作为其父级。 - Ian Boyd
所以这可能就是为什么http://www.saphua.com/minime/minime.aspx不能与我的Delphi(7)应用程序正常工作的原因。对于这个信息点点赞。 - cmw
我不是指“默认表单”,而是指“默认表单所有者”,即不受覆盖的 CreateParam 方法更改。我相信在之前的 Delphi 版本中(现在无法检查),我可以自由更改主窗体和其他窗体的 Z 顺序,现在我无法这样做 - 主窗体始终保持在后面。 - kludg
@Ian - D5现在11岁了 - 我在1999年有一个版本(修复了D3的一些Y2k问题) - Gerry Coll
显示剩余4条评论

9
从forms.pas(Delphi 2009)的源代码来看,它们在Win32 GUI应用程序中创建一个“主”窗口以允许调用以下内容:
- TApplication.Minimize - TApplication.Restore - 等等
如果存在,则似乎将传递给Application.Handle的消息适当地转发到MainForm。这将允许应用程序在未创建主窗口的情况下响应最小化等操作。通过修改项目源代码,可以创建一个没有主窗口的Delphi应用程序。
在这种情况下,即使您没有创建主窗口,TApplication方法仍将起作用。我不确定是否理解了所有目的,但我没有时间浏览所有TApplication代码。
针对您的问题:
- 它是在TApplication.Create中创建的窗口的句柄。 - 它是每个GUI Delphi应用程序都需要的虚拟窗口的句柄,作为TApplication抽象的一部分。 - 不是应用程序主窗体的窗口句柄。 - 见上文。 - 假设您正确,那么最重要的原因是因为它使查找应用程序中所有窗体变得容易(枚举此“主”窗体的子级)。 - 我认为最重要的原因是因为隐藏的“主”窗体正在接收系统消息,它应该将其传递给其子级和/或主窗体,但找不到未有父级的窗体。
总之,这是我的看法。您可以通过查看forms.pas中的TApplication声明和代码来了解更多信息。从我所看到的情况来看,它是一个方便的抽象。

2
在 Delphi 2007 中,VCL 默认不再有隐藏窗口,但如果需要,您仍然可以选择旧的方式。隐藏窗口会导致 Windows 7 预览功能无法正常工作。 - mj2008
@mj2008: 你有更多关于这个的信息链接吗?我正在将一个项目从C++ Builder 2006更新到C++ Builder 2009,我发现我的Application->Handle指针是空的。这在Builder 2009中是否是这种情况?如果是,那么使用MainForm->Handle是一个好的替代方案吗? - Rob Thomas
@Rob 这个 Delphi 控件是 Application.MainFormOnTaskbar,但我不知道它是否适用于您。通常升级的应用程序不会更改这个。 - mj2008

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接