Apply Windows Theme to Office Com add-in

2019-01-24 13:47发布

For ages, Delphi has supported the Enable runtime themes switch on the Application Settings tab. However, this only works for executables. DLLs are assumed to take over the theming (and other) setings from their parent application.

Unfortunately, Microsoft Office doesn't play nice there. Their 'themed' look is achieved using custom controls, not through Windows' own Common Controls.

In the MSDN article 830033 - How to apply Windows XP themes to Office COM add-ins Microsoft explains how to apply a manifest to a DLL, making it Isolation Aware such that settings from the parent process are ignored.

Basically, it comes down to two steps:

  1. Include the default manifest resource in your process, using an int-resource id of 2 (as opposed to the 1 you'd normally use).
  2. Compile with the ISOLATION_AWARE_ENABLED define. **Which isn't available in Delphi.**

I think I've got (1) nailed down, although I'm never quite sure whether brcc32 picks up resource IDs as integers or as literal strings. The real problem lies with (2). Supposedly, this define changes several DLL function bindings.

Has anyone solved this problem in Delphi? Should I further investigate this route, should I try and manually creating activation contexts, or are there other elegant solutions to this problem?

1条回答
小情绪 Triste *
2楼-- · 2019-01-24 14:36

I've done this for my COM add-in. I used activation contexts. It's pretty easy for a COM add-in because the surface area of the add-in interface is so small. I could post code but I won't be at a machine with it on until tomorrow. Hope this helps!


UPDATE

As promised, here is the code that I use:

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;

When you want to use this, you simply write code like so:

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

You need each entry point that does GUI work to be wrapped in such code.

Note that in my COM add-in DLL I have taken special measures to avoid running code during DLLMain, and so my calls to InitialiseActivationContext and FinaliseActivationContext are not in unit initialization/finalization sections. However, I see no reason why this code would not be safe to place there.

查看更多
登录 后发表回答