4

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

它继承了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);

当单元完成时,我单例也同样被删除:

initialization
   ...
finalization
    InternalDwmServices.Free;
end.

现在我们来解决这个问题。我不能保证某人可能选择访问ThemeServices或的顺序DWM,每个都应用他们的子类。我也不知道德尔福最终确定单位的顺序。

子类以错误的顺序被删除,并且应用程序关闭时发生崩溃。

怎么修?我如何确保我的子类化方法保持足够长的时间,直到其他在我完成后完成?(毕竟我不想泄漏内存)

也可以看看


更新:我看到 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;

呸呸呸

换句话说:尝试继承 TApplication 是一个错误,Borland 在采用 Mike 的TThemeManager.

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

4

4 回答 4

4

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

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

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

于 2011-01-06T16:51:18.650 回答
2

与其继承 TApplication 窗口,不如使用 AllocateHWnd() 来分别接收相同的广播,因为它是自己的顶级窗口。

于 2011-01-06T21:04:07.867 回答
1

我想我会做以下事情:

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

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

于 2011-01-06T15:36:50.297 回答
0

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

干杯

于 2011-01-06T16:44:28.793 回答