On runtime I created some buttons that are part of a menu (TCastleVerticalGroup).
and insert them in controls.
But how can I access their individual contents (depending on the caption text or controlindex / button number) in the NewButtonClickmenu?
procedure TStatePlay.Button_GoToLocationClick(Sender: TObject);
begin
PlayerMenu.Exists:= true;
NewButton := TCastleButton.Create(Application);
NewButton.Caption:= 'westbeach';
PlayerMenu.InsertControl(1, NewButton);
NewButton := TCastleButton.Create(Application);
NewButton.Caption:= 'passagebeach';
PlayerMenu.InsertControl(2, NewButton);
NewButton := TCastleButton.Create(Application);
NewButton.Caption:= 'eastbeach';
PlayerMenu.InsertControl(3, NewButton);
PlayerMenu.InsertFront(NewButton);
end;
procedure TStatePlay.Button_NewButtonClick(Sender: TObject);
begin
How can I access the clicked PlayerMenu index number?
end;
NewButton := TCastleButton.Create(Application);
NewButton.Name := 'ButtonGoWest';
NewButton.Caption:= 'westbeach';
NewButton.OnClick := @Button_NewButtonClick; // DON'T FORGET THIS
PlayerMenu.InsertControl(1, NewButton);
...
procedure TStatePlay.Button_NewButtonClick(Sender: TObject);
begin
If (Sender as TCastleButton).Name = 'ButtonGoWest' then
GoWest;
end;
However, depending on the situation (if the amount of buttons is fixed and they do some defined thing without much context needed then creating a separate OnClick events for different actions may be better. E.g.:
This inserts the NewButton twice to PlayerMenu (which is not allowed, you cannot insert the same UI 2x into the hierarchy, and you will get a warning about it). Delete one of the above lines.
As for the topic question, others have exhausted the answers To summarize, the approach can be:
Use different OnClick handlers for each button.
Use the same OnClick for every button, but assign different Tag to each button, and then in click handler look at (Sender as TCastleButton).Tag to read this tag.
Look at really anything else you want through the Sender. You can even look at Name, like (Sender as TCastleButton).Name.
There are more possibilities actually ā you could create your own TCastleButton descendant, that can ācarryā any additional information (fields and methods) you want.
To stick to the simplest advise: If the buttons do different things, go with AD 1. If the buttons do the same thing, Iād go with AD 2.
Thanks all!
I go for the AD2 because the only outcome of every click will be a sprite going to the button caption name location.
The idea is that every location that is āvisitedā before by the sprite will be added to a stringlist. So every new visited location will create a new button in the location list.
So the next thing is: how do I add to the stringlist and read back the contents to update a button list?
var
S: String;
I: Integer;
Locations: TStringList;
begin
Locations := TStringList.Create;
Locations.Add('firstlocation');
Locations.Add('secondlocation');
Locations.Add('thirdlocation');
for S in Locations do
WriteLnLog(S); // will output fist, second and third location names
Locations.Delete(1); // will delete 'secondlocation'
for I := 0 to Locations.Count - 1 do
WriteLnLog(Locations[I]); // will output first and third locations names.
Locations.Free;
end;
There are more possibilities actually ā you could create your own TCastleButton descendant
Didnāt suggest it because - while it the most correct approach from theoretical OOP - it would require some non-trivial for newcomers scafoflding to make those subclasses into GCE Editor GUI.
Though for a program which user interface is designed by code not by visual tools that could be the cleanest approach
Itās PtrInt and PtrInt = Int64; so should be good. And the crashes are not from buttons I just mentioned that Iām doing something stupidly unsafe.
Blockquote user interface is designed by code
Waitā¦ that button is generated by code. Why the heck did I need to use Tag hack for it when I can effortlessly make a descendant??? Thank you for opening my eyes (Yeah, I know how to rebuild Editor, but try to avoid custom components for as long as possible because it creates some issues, e.g. setting up paths correctly which Iām often too lazy to)
You can register your custom button class to be available even visually. See Editor | Manual | Castle Game Engine , Custom Components | Manual | Castle Game Engine . So the custom class is a working approach for both buttons created by code and designed visually. But in general, indeed I would not advise AD 4 (creating custom class) as a first choice ā itās probably more work than necessary for something trivial, when the TComponent.Tag seems āsimple enoughā.
Yeah, sure. Iām using it often Just āif itās possibleā then try to avoid custom components, because they create some additional hassle: need to be registered, registered in Manifest, rebuild Editor every time (and itās not always trivial, e.g. I failed to set up properly environment variables in XFCE, so I need to quit Editor and run it from terminal to have CGE at $path - again additional hassle :D), etc.
You need to include uses CastleLog for this function to work. It just writes a line into the game log (usually in AppData on Windows, into Terminal output on Linux) where it can be found and used for debugging.
Also note that if you donāt know where something is, you can just search the API reference. The first result for searching WritelnLog correctly shows it is part of CastleLog unit: Search Results
Fist it says : CastleLog.WriteLnLog - the first one is the ānamespaceā (in Pascal it means name of the Unit you have to put into the uses section). Also you can additionally click on the found link and on top of the page there you can see which unit provides the appropriate class or function:
Now I want to write the contents to a text file and read them back.
Locations := TStringList.Create;
Locations.Add('westbeach');
Locations.Add('passagebeach');
Locations.Add('eastbeach');
for S in Locations do
WriteLn (InitialDBFile, S);
Locations.Free;
This works, but reading them back?
Procedure LoadNPC_Database;
var
S, S1: String;
I: Integer;
begin
Text := TTextReader.Create(DBfile);
try
S := Text.ReadLn;
NPCinfo[I].ID:= StrToInt(S);
S := Text.Readln;
NPCinfo[I].Personalia.FirstName:= S;
S := Text.Readln;
NPCinfo[I].Personalia.LastName:= S;
for S1 in NPCinfo[I].Locations do
S := Text.Readln;
NPCinfo[I].Locations.Free;
```
It does not read the 3 location strings apparently as my game crashes while loading the text file.
First: You do not seem to have I initialized. This alone is enough to have a crash. Also make sure how many elements NPCInfo contains (and why you are reading only one of them).
Next: You call for S1 in NPCinfo[I].Locations do on, most likely, non-initialized instance of Locations. And even if it would be initialized, you wonāt be able to for inside of it, because itās empty. Iām not exactly sure what is the state and purpose of all the classes and variables here, but something like this should hopefully get you started:
try
I := 0;
S := Text.ReadLn;
NPCinfo[I].ID:= StrToInt(S);
S := Text.Readln;
NPCinfo[I].Personalia.FirstName:= S;
S := Text.Readln;
NPCinfo[I].Personalia.LastName:= S;
NPCinfo[I].Locations := TStringList.Create; // Create the instance
while not Text.Eof do // Read every other line until the end of the file
NPCinfo[I].Locations.Add(Text.Readln); // Add every line to the Locations class
NPCinfo[I].Locations.Free; // this will destroy everything we've just read, but for testing purposes...
Thanks!
The while not Text.Eof caused an error (" ") but maybe I should also save the location.count of strings in the textfile and then read it back on loading the textfile
NPCinfo[I].Locations := TStringList.Create;
S := Text.Readln;
NPCinfo[I].Locations.Count := StrToInt(S);
for I := 0 to Locations.Count - 1 do
NPCinfo[I].Locations.Add(Text.Readln);
something like this or is there a better way to read the number of strings back and then continue with other variables that are not part of the stringlist?