Handling buttons that are created programmatically

Godot Version

4.1.3 (please let me know if upgrading to a more recent version will make a difference)

Question

Hi, I am pretty new to Godot, so I am unsure if the way I am thinking through this is even the right mindset or pattern. I am also aware this is probably not a new question, but after searching for a day, I was not able to find responses that made sense for my question. If you have links or even suggestions on what to search for, I would appreciate that. Since this is my first question here, please let me know if there is question etiquette that I should be aware of (such as formatting).

I am creating buttons programmatically (because there will be user made buttons) and I am trying to understand how to connect and/or handle the button presses of those new buttons.

The way I have my code currently is to create a new button, save it in a dictionary with the information it will need to handle, and then connect it to the function that will handle it. However, since my handler function is in the script for the scene containing all of the buttons, it doesn’t know which button is calling it.

I feel like one solution would be to create a script on the button itself instead of the general on in the main scene. If that is the case, would that be a child component to the button? How would that work? Then adding a new button is not as simple, right?

The other solution is to just pass a reference of the button being clicked to the button handler. That is what I have tried to do, but I can’t figure out how to get that reference in the connect code. My code so far:

func CreateButton(ButtonText:String, ButtonLocation:BoxContainer, StatChangeDictionary:Dictionary) -> Button:
	var newButton:= Button.new()
	newButton.text=ButtonText
	newButton.pressed.connect(HandleButtons)
	ButtonLocation.add_child(newButton)
	ButtonsDictionary[newButton]=StatChangeDictionary
	return newButton

func HandleButtons(ButtonReference:Button) ->void:
	var StatDeltas : Dictionary=ButtonsDictionary[ButtonReference]
	for StatKey in StatDeltas:
		StatKey.AdjustAmount(StatDeltas[StatKey])
	EnableButtons()

You can store the data to the button itself using metadata. Just use set_meta / has_meta / get_meta functions. No need to create script files for each button.

To your question:

When you .new() an object, the engine calls the objects _init() function. You can use that to setup the button with text and other data as you see fit. You can do this by adding arguments like .new(my_arg1, my_arg2)and matching _init(arg1, arg2)

My suggestion is to make your own custom button class using class_name UserButton extends Buttonand fill it with all the information you want.

Then you could connect the signal and put .bind(arg) at the end of the function to pass some identifying data to your HandleButton function.

I would provide links but I’m on my phone and my internet is slow. You can search the Objects page in the godot docs for explanations of .new()

Also it might be helpful to tell us if your script is GDscript or C#, because at first glance you are using C# conventions (func MyFunc instead of func my_func, var MyVar / var my_var)

I don’t understand how that would change my problem. I see how I could use that to store my data for each button instead of using the dictionary. However, wouldn’t I need to know which button is being pressed in order to use get_meta in the HandleButtons function?

Thank you! I have not looked into .bind yet, so that is a good lead for me. I will try it with a custom button class. I hadn’t done that yet, because I figured I didn’t know the “right” way to do it with out of the box nodes.

I was aiming for using GDscript. I am just accustomed to naming variables with camelcase instead of underscores. If that causes confusion, I can change it.

1 Like

Bind example:

func _ready() -> void:
	var new_button = Button.new()
	new_button.set_meta("foo", 42)
	new_button.text = "Foobar"
	new_button.pressed.connect(my_button_pressed.bind(new_button))
	add_child(new_button)

func my_button_pressed(button: Button) -> void:
	prints("button's foo:", button.get_meta("foo"))

You can just directly send all the data that you need to your signal handler function by binding it to the function that you pass to connect.

func CreateButton(ButtonText:String, ButtonLocation:BoxContainer, StatChangeDictionary:Dictionary) -> Button:
	# ...
	var some_value := 42
	newButton.pressed.connect(HandleButtons.bind(some_value))

func HandleButtons(ButtonReference:Button, some_value: int) ->void:
	prints("look at this:", some_value)
1 Like

Excellent! Thank you! The bind function in the connect was exactly the piece I was missing, thank you for the example code. I tried it on my code and it works perfectly.