【问题标题】:Delphi: How to remove subclasses in reverse order?Delphi:如何以相反的顺序删除子类?
【发布时间】:2011-01-06 15:27:30
【问题描述】:

Mike Lischke 的 TThemeServices 子类 Application.Handle,以便在主题更改时可以接收来自 Windows(即 WM_THEMECHANGED)的广播通知。

它继承了Application 对象的窗口:

FWindowHandle := Application.Handle;
if FWindowHandle <> 0 then
begin
 // If a window handle is given then subclass the window to get notified about theme changes.
 {$ifdef COMPILER_6_UP}
    FObjectInstance := Classes.MakeObjectInstance(WindowProc);
 {$else}
    FObjectInstance := MakeObjectInstance(WindowProc);
 {$endif COMPILER_6_UP}
 FDefWindowProc := Pointer(GetWindowLong(FWindowHandle, GWL_WNDPROC));
 SetWindowLong(FWindowHandle, GWL_WNDPROC, Integer(FObjectInstance));
end;

然后,子类化的窗口过程会按照应有的方式处理WM_DESTROY 消息,删除它的子类,然后传递WM_DESTROY 消息:

procedure TThemeServices.WindowProc(var Message: TMessage);
begin
  case Message.Msg of
     WM_THEMECHANGED:
        begin
               [...snip...]
        end;
     WM_DESTROY:
        begin
          // If we are connected to a window then we have to listen to its destruction.
          SetWindowLong(FWindowHandle, GWL_WNDPROC, Integer(FDefWindowProc));
          {$ifdef COMPILER_6_UP}
             Classes.FreeObjectInstance(FObjectInstance);
          {$else}
             FreeObjectInstance(FObjectInstance);
          {$endif COMPILER_6_UP}
          FObjectInstance := nil;
        end;
  end;

  with Message do
     Result := CallWindowProc(FDefWindowProc, FWindowHandle, Msg, WParam, LParam);
end;

TThemeServices 对象是一个单例,在单元完成过程中被销毁:

initialization
finalization
  InternalThemeServices.Free;
end.

这一切都很好——只要 TThemeServices 是唯一一个将应用程序的句柄子类化的人。

我有一个类似的单例库,它也想挂钩Application.Handle,以便我可以接收广播:

procedure TDesktopWindowManager.WindowProc(var Message: TMessage);
begin
case Message.Msg of
WM_DWMCOLORIZATIONCOLORCHANGED: ...
WM_DWMCOMPOSITIONCHANGED: ...
WM_DWMNCRENDERINGCHANGED: ...
WM_DESTROY:
    begin
        // If we are connected to a window then we have to listen to its destruction.
        SetWindowLong(FWindowHandle, GWL_WNDPROC, Integer(FDefWindowProc));
        {$ifdef COMPILER_6_UP}
        Classes.FreeObjectInstance(FObjectInstance);
        {$else}
        FreeObjectInstance(FObjectInstance);
        {$endif COMPILER_6_UP}
        FObjectInstance := nil;
    end;
end;

with Message do
    Result := CallWindowProc(FDefWindowProc, FWindowHandle, Msg, WParam, LParam);

当单元完成时,my 单例也同样被移除:

initialization
   ...
finalization
    InternalDwmServices.Free;
end.

现在我们来解决问题。我不能保证某人可能选择访问ThemeServicesDWM 的顺序,每个都应用它们的子类。我也不知道 Delphi 最终确定单元的顺序。

子类被删除顺序错误,应用程序关闭时崩溃。

如何解决?完成后我如何ensure that i keep my subclassing method around long enough until the other guy is done? (毕竟我不想泄露内存)

另见


更新:我看到 Delphi 7 通过重写 TApplication 解决了这个错误。 >

procedure TApplication.WndProc(var Message: TMessage);
...
begin
   ...
   with Message do
      case Msg of
      ...
      WM_THEMECHANGED:
          if ThemeServices.ThemesEnabled then
              ThemeServices.ApplyThemeChange;
      ...
   end;
   ...
end;

Grrrr

换句话说:尝试继承 TApplication 是一个错误,Borland 在采用 Mike 的 TThemeManager 时修复了该错误。

这很可能意味着无法以相反的顺序删除TApplication 上的子类。有人以答案的形式提出,我会接受。

【问题讨论】:

  • Delphi 7 的主题代码是基于 Mike Lischke 的代码。但是当然,既然他们有源代码,那么他们就没有必要进行子类化。您是否有充分的理由不能使用现代 Delphi?
  • 我完全不同意将 TApplication 子类化,就像 Mike 在他的 XP 主题管理器中所做的那样,是一个错误。他还能做什么?更重要的是,我认为该代码是我遇到过的最伟大的编码作品之一。事实上,这仍然是主题绘画的主要参考资料之一。尽管有一些小错误,但考虑到他正在尝试的复杂性,它们很少而且相差甚远,这不足为奇。所以,我支持迈克,回应你的“Grrrr”!!
  • 我并不是要暗示子类化TApplication 是一个错误——只是他的做法是错误的(假设在他之前没有其他人继承了 TApplication,并且他之后的其他人也不会)
  • 为什么是delphi-t标签?什么是delphi-t?
  • @Jørn。哎呀。 “T”是“5”的不足一键。修复了标签。谢谢。

标签: delphi themes subclassing delphi-5 dwm


【解决方案1】:

将您的代码更改为调用SetWindowSubclass,正如您链接到的文章所建议的那样。但这只有在每个人都使用相同的 API 时才有效,因此修补主题管理器以使用相同的技术。该 API 是在 Windows XP 中引入的,因此不存在在需要它的系统上不可用的危险。

修补主题管理器应该没有问题。它旨在支持 Microsoft 不再支持的 Windows XP,并支持 Borland 不再支持的 Delphi 4 到 6。由于所有相关产品的开发都已停止,因此您可以安全地分叉主题管理器项目,而不会因未来的更新而落后。

您并没有真正引入依赖关系。相反,您正在修复一个仅在同时使用两个窗口外观库时才出现的错误。您库的用户不需要拥有 Theme Manager 即可工作,但如果他们希望同时使用这两者,则需要使用 Theme Manager 并应用您的补丁。应该没有什么反对意见,因为他们已经有了基本版本,所以他们不会安装一个全新的库。他们只是在应用补丁并重新编译。

【讨论】:

  • 即使在使用 SetWindowSubClass 时,我仍然无法弄清楚的问题是,当我想取消注册时该怎么做。文档没有提到它(文档没有提到很多东西 - 甚至它存在的原因) - 但我仍然必须保留我的子类对象 - 正如 Raymond 指出的那样。
  • 还有一些客户还在使用Windows 2000;所以我也会介绍崩溃
  • 哦该死的...我意识到 SetWindowSubclass 在CommCtrl32.dll 版本 6 中。我获得版本 6 的唯一方法是显示我的应用程序以选择版本 6。如果我这样做,我会得到通用控件的版本 6。我正在使用 Delphi 5,它不正确地使用了常用控件,这会导致崩溃。 Mike 尝试写TThemeManager 来修补所有Borland 的包装器——但仍然存在无法修复的错误。所以我不能用那个:(
  • @Ian:如果您仍然关心,SetWindowSubclass 类函数仍然存在于旧版本的 CommCtrl32.dll 中,它们只是按序号导出。您必须进行一些挖掘,但如果您真的愿意,您可以使用它们。 See here for details,尽管是 VB 6 语言。
  • 文档说 SetWindowSubclass 在 Comctl32 5.8 版中。
【解决方案2】:

也许您可以使用 AllocateHWnd() 代替子类化 TApplication 窗口来分别接收相同的广播,因为它是自己的顶级窗口。

【讨论】:

  • 嗯。我从来没有想到这一点。实际上我有,但我认为我设置的一些随机消息循环不会计数。
  • 如果您在主线程的上下文中调用 AllocateHWND(),它将使用主消息循环,就像 TApplication 的窗口一样。一个线程内容可以运行多个顶级窗口。
  • 等等,它会使用主消息循环代码,还是直接使用主消息循环?因为前者利用代码,后者意味着我将收到发布到应用程序任何窗口的任何消息的消息。我只想将消息发送到 my AllocateHwnd 窗口。
  • HWND 与创建它的线程上下文相关联。该线程需要一个消息队列和消息处理循环。如果您在主线程上下文中调用 AllocateHWnd(),它的 HWND 将使用主线程的现有消息队列和消息循环(如果您在工作线程中调用 AllocateHWnd() - 这在技术上不安全 - 那么您必须实现自己的该线程中的消息队列/循环)。在这种情况下,HWND 将只接收明确发布/发送给它的消息,以及顶级广播。 HWND 不接收属于其他 HWND 的消息。
【解决方案3】:

我想我会做以下事情:

  • 在 ThemeSrv.pas 的初始化部分中添加对 ThemeServices 的引用。
  • 在 DwmSrv.pas 的初始化部分中添加对 DwmServices 的引用(我猜是您单位的名称)。

由于单元的最终确定顺序与初始化顺序相反,因此您的问题将得到解决。

【讨论】:

  • 不能真正改变别人的单位;或者想在它们之间建立依赖关系。
  • @Ian 我无法想象迈克会介意你这样做。我知道我已经修改了他的代码以修复其中的错误。最后,我不知道您对其他单位的依赖是什么意思。您的整个问题是依赖关系之一。
  • @Ian 关于你的评论,“不能真正改变其他人的单位”,你似乎对从他们那里复制代码没有任何疑虑。
【解决方案4】:

您为什么不直接使用 ApplicationEvents 并完成它。无需搞砸子类化。 另一种方法是只创建一个子类并创建多通知事件并根据需要订阅任意数量。

干杯

【讨论】:

  • 主题管理器支持 Delphi 版本 4 到 6。TApplicationEvents 并非在所有这些版本中都可用。此外,OnMessage 事件会为发布到应用程序中 any 窗口的 all 消息调用,而希望只拦截针对单个应用程序窗口的消息。此外,OnMessage 看不到 sent 消息,只看到 posted 消息。
猜你喜欢
  • 2013-05-29
  • 1970-01-01
  • 1970-01-01
  • 2014-09-22
  • 2018-10-13
  • 2011-02-17
  • 1970-01-01
  • 1970-01-01
  • 2023-03-31
相关资源
最近更新 更多