I have a component that I am building in Delphi XE that I want to be used in the following way:
User creates a new blank project.
User drops my component on the form.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With