Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 287
Warren P Avatar asked Jul 19 '11 15:07

Warren P


2 Answers

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.
like image 135
Ondrej Kelle Avatar answered Oct 04 '22 22:10

Ondrej Kelle


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.

like image 23
David Heffernan Avatar answered Oct 04 '22 22:10

David Heffernan