Sorry - Beginner fumbling in the dark questions:
I want to use CGE within an existing ‘normal’ Delphi application that reads and modifies spreadsheet-like datasets; mainly to create 3D meshes to visualise the shape of the data. I have created a simple test app from an almost empty world in the CGE editor, just a camera, light and cone for basic reference and can display that but then I hit a wall. I can’t work out something simple like how to programitically select elements of the world. I though something like:
procedure TForm1.chkConeVisibleClick(Sender: TObject);
var
Cone : TCastleCone;
begin
Cone := CastleControl.FindComponent('Cone1') as TCastleCone;
Cone.Visible := chkConeVisible.Checked;
end;
Would be simple start but obviously does not work, Cone is always nil.
How can I iterate over the controls (components? Transforms?) of a world? (view?, viewport?) Neither CastleControl.FindComponent or CastleControl.FindChildControl can find ‘Cone1’.
and
for i := 0 to CastleControl.ComponentCount -1 do
listbox1.Items.Add(CastleControl.Components[i].Name);
for i := 0 to CastleControl.Controlcount-1 do
listbox1.Items.Add(CastleControl.Controls[i].Name);
just returns ‘Container’.
I realise and appreciate that ‘CGE inside Delphi’ is not the main intent of CGE but I think it’s supposed to be possible - I just want to know how!
Hello! First of all, don’t worry about asking, it’s a good question And we want to support CGE inside a Delphi form perfectly. So it’s all a valid thing to want!
Up to the question:
The important context of this question is – how did you load the design? I assume you have set the DesignUrl property of CastleControl.Container to castle-data:/gameviewmain.castle-user-interface.
If yes: The answer is that TCastleControl instance is not a direct owner of the components from the design. You should use the CastleControl.Container.DesignedComponent method, like
Cone := CastleControl.Container.DesignedComponent('Cone1') as TCastleCone;
This is documented – ah, but I see it’s not part of API docs of Fmx.CastleControl unit , because the relevant class TCastleContainerEasy is in the internal unit. I added a TODO to fix it, for now: just look at declaration of TCastleContainerEasy, the important bits:
{ When the DesignUrl is set you can use this method to find
loaded components. Like this:
@longCode(#
MyButton := MyCastleControl.Container.DesignedComponent('MyButton') as TCastleButton;
#)
When the name is not found, raises exception (unless Required is @false,
then it returns @nil).
@seealso DesignUrl }
function DesignedComponent(const ComponentName: String;
const Required: Boolean = true): TComponent;
....
{ Load and show the design (.castle-user-interface file).
You can reference the loaded components by name using @link(DesignedComponent).
If you have more complicated control flow,
we recommend to leave this property empty, and split your management
into a number of states (TCastleView) instead.
In this case, load design using TCastleView.DesignUrl.
This property makes it however easy to use .castle-user-interface
in simple cases, e.g. when TCastleControl just shows one UI.
The design loaded here is visible also at design-time,
when editing the form in Lazarus/Delphi.
Though we have to way to edit it now in Lazarus/Delphi (you have to use CGE editor
to edit the design), so it is just a preview in this case.
See https://castle-engine.io/control_on_form for documentation how to use TCastleControl. }
property DesignUrl: String read FDesignUrl write SetDesignUrl;
{ Initialize references to components designed using CGE editor,
saved in data/main.castle-user-interface. }
MainScene := CastleControl1.Container.DesignedComponent('MainScene') as TCastleScene;
MainViewport := CastleControl1.Container.DesignedComponent('MainViewport') as TCastleViewport;
LabelFps := CastleControl1.Container.DesignedComponent('LabelFps') as TCastleLabel;
WOW, Now that is a reply!! I will have to digest and play with all that but I think it should get me going again.
As you guessed, I used:
procedure TForm1.FormCreate(Sender: TObject);
begin
CastleControl.Container.DesignUrl := '../../gameviewmain.castle-user-interface';
end;
But does that mean I have to treat components that are already in the design when I load it differently to any components I might create in code? The cone was just there as marker - to prove the design loaded, but the background (for example) might be in the default design but all meshes I create from the user data would have to be found using other commands?
Also, this does not cover interating over all components (both predesigned and added in code)
The components you create from code – when you create them yourself, you set any owner for them (at creation time) as you need.
Our approach here is to not “mess” with the default TComponent ownership idea, when you create e.g. MyNewCone := TCastleCone.Create(SomeOwner) then you’re executing TComponent constructor with SomeOwner parameter. So SomeOwner will eventually free MyNewCone, thanks to TComponent code.
So indeed you don’t use DesignedComponent to find the components you create yourself.
Usually there’s no need to find them at all – you create them, you probably already have a reference to them Like that MyNewCone.
But if needed, you can also find them in whatever owner you used (using SomeOwner.FindComponent or SomeOwner.FindRequiredComponent or iterating over SomeOwner.Components[...] as you shown).
You can also store them in a dedicated list for this. Like MyConesList.Add(MyNewCone) and later using this list.
My personal order of preference would be to use AD 1 or AD 3, that is: do not rely on SomeOwner.Components[...] for much, leave it mostly for memory management use-case of TComponent. But that’s my personal preference, your use-case / preference can surely differ
To be clear, in general, all components created by code are exactly the same as the ones loaded by DesignUrl. It’s the same code underneath, same instances are created, regular properties are set Just for DesignUrl, we use an internal owner (not exposed), to avoid renaming components in some cases.
To iterate following ownership (determined by the owner, provided as parameter to constructor of TComponent.Create):
For iterating over components created from code: see above, i.e. the answer is: you can iterate over them SomeOwner.Components[...], SomeOwner.ComponentsCount. Which SomeOwner? Depends on how you create them.
For iterating over components loaded from DesignUrl and get using DesignedComponent: we don’t expose such option now. I.e. we don’t have a list like DesignedComponents[...] or DesignedComponentsCount. Admittedly I never found it useful – but you can iterate using visual hierarchy, see below.
To iterate following visual hierarchy (determined by what is visually inserted into what, using e.g. TCastleUserInterface.InsertFront, visible also in CGE editor sidebar):
You can iterate over MyControl.Container.Controls, then for each TCastleUserInterface visit everything in TCastleUserInterface.Controls, TCastleUserInterface.ControlsCount .
You can then enter also TCastleViewport.Items if you want to iterate also over Items in a viewport (3D or 2D stuff), and there iterate over TCastleTransform.Items/Count.
In all above, you can iterate recursively or not – depending on what you want to achieve, i.e. why do you need to iterate over the list.
You can also iterate over TCastleTransform.Behaviors/BehaviorsCount.
The above iteration is mostly what CGE editor is doing e.g. for displaying the components hierarchy in a sidebar. So you can find everything using this iteration, if you do it recursively.