Apply Windows Theme to Office Com add-in
Asked Answered
L

1

7

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?

Locke answered 27/2, 2011 at 10:33 Comment(3)
I don't know of any elegant solutions. Quick peek in WinSDK headers shows ActivateActCtx and DeactivateActCtx seem to be important - they wrap calls to LoadLibrary and GetProcAddress when loading CommCtrl API functions. The WinSDK headers contain all the code AFAICS, but take some deobfuscation as they seem to have been renamed to avoid conflicts (e.g. IsolationAwarePrivatezltRgCebPnQQeRff, CommctrlIsolationAwarePrivatetRgCebPnQQeRff_pbZPgYQP_QYY, etc.)Guyenne
@Barry: Thanks for exploring! Am I correct in assuming the Delphi RTL/VCL doesn't do anything with activation contexts, and relies completely on the generated manifest?Locke
I don't have any particular knowledge, I just looked into the SDK headers to help give clues.Guyenne
V
10

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.

Vickeyvicki answered 27/2, 2011 at 12:32 Comment(5)
Thanks David, for confirming activation contexts are a feasible solution. Will look into it further. I'm a bit puzzled by your remark about the total surface area, does that influence the amount of work involved? As in: more controls, more activation contexts that need to be fired up?Locke
You need to activate when you enter you DLL and deactivate when you leave. But you have very few entry points with a COM add in. Let me know if you'd like me to post sample code but I couldn't do it for 24 hours.Vickeyvicki
Ah silly me, I completely misread what you were saying there. With my mind still hung-up on theming I read interface == UI, things went downhill from there. Thanks for clearning that up! :)Locke
@Locke I've added some code now, I hope it helps, although you are probably already on top of it!Vickeyvicki
Awesome, thanks for sharing. Had to so many misc project management stuff today that I didn't get to touch any code (Monday... you know how it is :P), so it just in time to be a massive boost.Locke

© 2022 - 2024 — McMap. All rights reserved.