将Windows主题应用于Office Com插件

8
长期以来,Delphi 在应用程序设置选项卡中支持“启用运行时主题”开关。然而,这仅适用于可执行文件。DLL 假定从其父应用程序继承主题(和其他)设置。
不幸的是,Microsoft Office 在这方面表现不佳。他们的“主题”外观是通过使用自定义控件实现的,而不是通过 Windows 自己的公共控件实现的。
在 MSDN 文章 830033 - 如何将 Windows XP 主题应用于 Office COM 加载项 中,Microsoft 解释了如何向 DLL 应用清单,使其成为“隔离感知”,从而忽略来自父进程的设置。
基本上,它归结为两个步骤:
1. 在您的进程中包含默认清单资源,使用 int-resource id 2(而不是通常使用的 1)。 2. 使用 ISOLATION_AWARE_ENABLED 定义进行编译。 **这在 Delphi 中不可用。**
我认为我已经解决了(1),尽管我从来不确定 brcc32 是否将资源 ID 作为整数或作为文字字符串。真正的问题在于(2)。据说,此定义会更改多个 DLL 函数绑定。
有人在 Delphi 中解决了这个问题吗?我应该进一步研究这条路吗,还是应该尝试手动创建激活上下文,或者是否有其他优雅的解决方案来解决这个问题?

我不知道有什么优雅的解决方案。WinSDK头文件中快速查看显示ActivateActCtx和DeactivateActCtx似乎很重要-它们在加载CommCtrl API函数时包装了对LoadLibrary和GetProcAddress的调用。 WinSDK头文件包含所有代码,但需要进行一些反混淆处理,因为它们似乎已更名以避免冲突(例如IsolationAwarePrivatezltRgCebPnQQeRff,CommctrlIsolationAwarePrivatetRgCebPnQQeRff_pbZPgYQP_QYY等)。 - Barry Kelly
@Barry:感谢您的探索!我是否正确地假设Delphi RTL/VCL在激活上下文方面没有任何作用,并完全依赖于生成的清单文件? - Paul-Jan
我没有特别的知识,只是查看了SDK头文件以帮助提供线索。 - Barry Kelly
1个回答

11

我已经用过这个方法来创建我的COM插件。我使用了激活上下文(activation contexts)。对于一个COM插件,这是相当容易的,因为插件接口的表面积非常小。我可以发布代码,但我要等到明天才能用机器找到它。希望这可以帮到你!


更新

如承诺所述,这是我使用的代码:

type
  (* TActivationContext is a loose wrapper around the Windows Activation Context API and can be used
     to ensure that comctl32 v6 and visual styles are available for UI elements created from a DLL .*)
  TActivationContext = class
  private
    FCookie: LongWord;
    FSucceeded: Boolean;
  public
    constructor Create;
    destructor Destroy; override;
  end;

var
  ActCtxHandle: THandle=INVALID_HANDLE_VALUE;
  CreateActCtx: function(var pActCtx: TActCtx): THandle; stdcall;
  ActivateActCtx: function(hActCtx: THandle; var lpCookie: LongWord): BOOL; stdcall;
  DeactivateActCtx: function(dwFlags: DWORD; ulCookie: LongWord): BOOL; stdcall;
  ReleaseActCtx: procedure(hActCtx: THandle); stdcall;

constructor TActivationContext.Create;
begin
  inherited;
  FSucceeded := (ActCtxHandle<>INVALID_HANDLE_VALUE) and ActivateActCtx(ActCtxHandle, FCookie);
end;

destructor TActivationContext.Destroy;
begin
  if FSucceeded then begin
    DeactivateActCtx(0, FCookie);
  end;
  inherited;
end;

procedure InitialiseActivationContext;
var
  ActCtx: TActCtx;
  hKernel32: HMODULE;
begin
  if IsLibrary then begin
    hKernel32 := GetModuleHandle(kernel32);
    CreateActCtx := GetProcAddress(hKernel32, 'CreateActCtxW');
    if Assigned(CreateActCtx) then begin
      ReleaseActCtx := GetProcAddress(hKernel32, 'ReleaseActCtx');
      ActivateActCtx := GetProcAddress(hKernel32, 'ActivateActCtx');
      DeactivateActCtx := GetProcAddress(hKernel32, 'DeactivateActCtx');
      ZeroMemory(@ActCtx, SizeOf(ActCtx));
      ActCtx.cbSize := SizeOf(ActCtx);
      ActCtx.dwFlags := ACTCTX_FLAG_RESOURCE_NAME_VALID or ACTCTX_FLAG_HMODULE_VALID;
      ActCtx.lpResourceName := MakeIntResource(2);//ID of manifest resource in isolation aware DLL
      ActCtx.hModule := HInstance;
      ActCtxHandle := CreateActCtx(ActCtx);
    end;
  end;
end;

procedure FinaliseActivationContext;
begin
  if ActCtxHandle<>INVALID_HANDLE_VALUE then begin
    ReleaseActCtx(ActCtxHandle);
  end;
end;

initialization
  InitialiseActivationContext;

finalization
  FinaliseActivationContext;

当你想使用它时,只需像这样编写代码:

var
  ActivationContext: TActivationContext;
....
ActivationContext := TActivationContext.Create;
try
  //GUI code in here will support XP themes
finally
  ActivationContext.Free;
end;

你需要将执行GUI操作的每个入口点都包装在这样的代码中。

请注意,在我的COM插件DLL中,我采取了特殊措施,避免在DLLMain期间运行代码,因此我的InitialiseActivationContextFinaliseActivationContext调用不在单元初始化/终止部分中。 但是,我认为将这段代码放置在那里也没有问题。


谢谢David确认激活上下文是可行的解决方案。我会进一步研究它。我有点困惑于你关于总表面积的评论,这是否影响所涉及的工作量?就像:更多的控件,需要启动更多的激活上下文吗? - Paul-Jan
当您进入DLL时需要激活,离开时需要停用。但是对于COM插件,您只有很少的入口点。如果您希望我发布示例代码,请告诉我,但我需要等待24小时才能完成。 - David Heffernan
啊,我真是太傻了,完全误读了你的意思。我的脑海里还在想着主题,我把接口等同于用户界面,然后事情就变得一塌糊涂了。谢谢你澄清这个问题! :) - Paul-Jan
@Paul-Jan,我已经添加了一些代码,希望能有所帮助,尽管你可能已经掌握了它的核心! - David Heffernan
太棒了,谢谢分享。今天我有很多杂项项目管理的事情要做,没时间碰代码(周一嘛...你知道的:P),所以这个时候来正好能给我巨大的帮助。 - Paul-Jan

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