Uncheck "Enable Runtime Themes" or remove the internal manifest in Delphi XE?
Asked Answered
S

2

9

I have a component that I am building in Delphi XE that I want to be used in the following way:

  1. User creates a new blank project.

  2. User drops my component on the form.

  3. Some special Designtime code in my component is executed, that will change Project Options to uncheck the "Enable runtime themes" checkbox in the project options. I am not sure this is even possible, so I'm asking if it's possible.

If #3 is not possible, then I need another solution to my "usability" problem with this component; The problem I have is that if users do not disable the statically linked manifest file by unchecking Enable Runtime Themes, then that statically generated manifest that is linked into the EXE seems to override the external manifest files that I want to have outside the EXE, on disk. I also need to modify these manifests at runtime, thus the need for external manifests. I can of course, enable the Runtime Theme functionality using these manifests, when it is desirable to do so. A secondary question is about the priority of external and internal manifests; Can an external manifest somehow take priority over the internal manifest resource that is linked into Delphi apps when you check "Enable Runtime Themes"?

Acceptable solutions other than #3:

A. Somehow cause Delphi to not generate a manifest. B. Somehow at runtime, have Windows recognize and prioritize external .manifest files even when an internal one is found.

C. Least good solution; At runtime, after CoCreateInstance in my component fails, I can enumerate resources, report that an external manifest is present and is messing us up, and rely on developers who use my component reading the runtime error messages my component spits out, telling them to disable runtime themes checkbox and rebuild their app. Extracting and reading a manifest is already covered in another stackoverflow question here, with C++ code that could easily be converted to Delphi.

Update The accepted answer does exactly what I asked, but is considered a hack, and David's answer about Activation Contexts, is much more sane, and is the Recommended Approach.

Update2 The built-in manifest is normally overridden in later versions of Delphi (XE5 and later) by specifying explicitly which manifest you want to link, via the project settings.

Subsidy answered 19/7, 2011 at 15:28 Comment(2)
#3 is definitely possible, project options are exposed through IOTAProjectOptions interface.Bacolod
Interesting read. It seems that on XP, the external manifest has higher priority than the embedded one, while on Vista+ it's the other way round.Bacolod
B
11

I think I've found a working solution for what you asked for, ie. to disable the runtime themes from project options when an instance of your component is created (dropped on a form, or a form/module containing an instance of it is opened in the IDE). This doesn't prevent the user from re-enabling the runtime themes manually later but maybe it's still useful to you.

BTW, IOTAProjectOptions doesn't seem to help in this case; it looks like IOTAProjectResource is needed.

TestComponentU.pas (part of the runtime package):

unit TestComponentU;

interface

uses
  Windows, Classes;

type
  ITestComponentDesign = interface
    function DisableRuntimeThemes: Boolean;
  end;

  TTestComponent = class(TComponent)
  public
    constructor Create(AOwner: TComponent); override;
  end;

var
  TestComponentDesign: ITestComponentDesign = nil;

implementation

uses
  Dialogs;

constructor TTestComponent.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  if (csDesigning in ComponentState) and Assigned(TestComponentDesign) and
    TestComponentDesign.DisableRuntimeThemes then
    ShowMessage('Project runtime themes disabled');
end;

end.

TestComponentRegU.pas (part of the design package installed in the IDE):

unit TestComponentRegU;

interface

procedure Register;

implementation

uses
  Windows, Classes, SysUtils, TestComponentU, ToolsAPI;

type
  TTestComponentDesign = class(TInterfacedObject, ITestComponentDesign)
  public
    function DisableRuntimeThemes: Boolean;
  end;

procedure Register;
begin
  RegisterComponents('Test', [TTestComponent]);
end;

function GetProjectResource(const Project: IOTAProject): IOTAProjectResource;
var
  I: Integer;
begin
  Result := nil;
  if not Assigned(Project) then
    Exit;

  for I := 0 to Project.ModuleFileCount - 1 do
    if Supports(Project.ModuleFileEditors[I], IOTAProjectResource, Result) then
      Break;
end;

function GetProjectResourceHandle(const ProjectResource: IOTAProjectResource; ResType, ResName: PChar): TOTAHandle;
var
  I: Integer;
  ResEntry: IOTAResourceEntry;
begin
  Result := nil;
  if not Assigned(ProjectResource) then
    Exit;

  for I := 0 to ProjectResource.GetEntryCount - 1 do
  begin
    ResEntry := ProjectResource.GetEntry(I);
    if Assigned(ResEntry) and (ResEntry.GetResourceType = ResType) and (ResEntry.GetResourceName = ResName) then
    begin
      Result := ResEntry.GetEntryHandle;
      Break;
    end;
  end;
end;

function DisableProjectRuntimeThemes(const Project: IOTAProject): Boolean;
var
  ProjectResource: IOTAProjectResource;
  ResHandle: TOTAHandle;
begin
  Result := False;
  ProjectResource := GetProjectResource(Project);
  if not Assigned(ProjectResource) then
    Exit;

  ResHandle := GetProjectResourceHandle(ProjectResource, RT_MANIFEST, CREATEPROCESS_MANIFEST_RESOURCE_ID);
  if Assigned(ResHandle) then
  begin
    ProjectResource.DeleteEntry(ResHandle);
    Result := True;
  end;
end;

function TTestComponentDesign.DisableRuntimeThemes: Boolean;
var
  Project: IOTAProject;
begin
  Project := GetActiveProject;
  Result := Assigned(Project) and DisableProjectRuntimeThemes(Project);
end;

initialization
  TestComponentDesign := TTestComponentDesign.Create;

finalization
  TestComponentDesign := nil;

end.
Bacolod answered 19/7, 2011 at 17:38 Comment(1)
Accepted, because this does what I ask. I should note that I think David is right, that I am asking about doing a hack (by using external manifest text files at all), when my component should probably try to use Activation Contexts, but if Windows appears to be too broken to work with Activation contexts in some way, this is the workaround. I think this question and answer are valuable as they are as a sample of using IOTAProjectResource, and this is merely one simple case where getting at that would be handy.Subsidy
A
6

I suspect that best solution is to let the users of the component do whatever they want with manifests for their applications. To do otherwise would place a serious constraint on any users of this component.

Instead use the activation context API to activate the manifests that your component needs, as and when it needs.

Your current idea to write manifest files in the executable directory sounds extremely brittle and liable to fail whenever that directory cannot be written to. On the other hand the activation context API does exactly what you really need with none of the drawbacks.

Away answered 19/7, 2011 at 19:51 Comment(5)
This is the mechanism I use to enable run time themes in my excel com add inAway
Do you know if Win7 UAC would block access to these APIs regular or restricted users?Subsidy
No issue with UAC. This is how you are meant to solve your problem.Away
I would have marked this one as accepted but for the fact that this does the Right Thing, whereas the other one does What I Asked for. (I 'm always tetchy when other people ask to do dumb things, so please continue to point out in future whenever people have ideas that would be problematic or evil hacks, for some reason.)Subsidy
Yeah, your accept is correct I agree. I'd never realised I had so many activation context answers here on SO. They are everywhere.Away

© 2022 - 2024 — McMap. All rights reserved.