user form

DanielCDanielC Posts: 48Member
edited June 27 in GUI

I am trying to make the following system. But I got stuck.
What it needs to become is a form. Each horizontal line will contain one instance of a scene, containing 2 LineEdit nodes. An unlimited number of lines may be created by the user. At the end all the contents of the LineEdit will be stored in a new dictionary file.

What happens is that the instance of the Scene B (see below) is not visible. If I instance it into a VBoxContainer, the container below overlaps the content of the Scene B.
And I have no clue how to start coding this. For example, how to instance a node to a specific place in the tree?

This is the structure:

  • VBoxContainer
    • VSplitContainer
    • HBoxContainer
      • Label
    • VSplitContainer
    • ScrollContainer
      • VBoxContainer
      • Control (this is an instance of a scene B. See below)
    • VSplitContainer
    • HBoxContainer
      • Label
      • HSplitContainer
      • TextureButton (click it to create a new instance of the control)
    • HBoxContainer
    • TextureButton (click it to save the line edit content of all the created instances of scene B )

Now in the scene B that I instance, I have:

  • VBoxContainer
    • HboxContainer
      • Label
      • LineEditA
      • Label
      • LineEditB

Comments

  • TwistedTwiglegTwistedTwigleg Posts: 1,620Admin

    For scene B, is the min size property set? I think the vertical and horizontal container boxes resize child elements within to the largest minimum size of all the children, but if no minimum size is set on any of the children then they are all sized to zero. I would check and see if that is causing the problem, as I know that has tripped me up several times.

    For programming the instancing of new scenes, you just need to know which scene you are instancing, and have a reference to the node you want to add the scene to. For example, if the script was attached to VBoxContainer, then you could do something like this (pseudo, untested gdscript code):

    extends VBoxContainer
    var scene_to_instance = preload("path_to_scene_here.tscn")
    var node_to_spawn_at = null
    func _ready():
        node_to_spawn_at = get_node("ScrollContainer/VBoxContainer")
        get_node("TextureButton").connect("pressed", self, "on_texture_button_pressed")
    func on_texture_button_pressed():
        var clone = scene_to_instance.instance()
        node_to_spawn_at.add_child(clone)
    

    Or something like that.

    The key element is spawning the scene in code using the instance function and then adding the newly instanced node to whatever node you want it to be a child of using the add_child function.
    If you need to setup the newly instanced scene, you can call functions on the newly created scene like normal, either before or after you add it as a child in the scene.

    Hopefully this helps!

    (Side note: I edited your post a bit so the node structure is a little easier to read, as before it was structured in a bunch of quote elements)

  • DanielCDanielC Posts: 48Member
    edited June 29

    Thank you TwistedTwigleg! Indeed the min size for the ScrollContainer did work to show the instances.
    I applied the code and managed to create instances of the scene inside the node.
    I also added this code in _ready to change the minimal size of the instance, so it does not overlap the previous ones:

    clone.rect_min_size = Vector2(600,180)

    But yet, again I got stuck. This time with connecting the button signals. In the v_a.tscn scene I have two buttons called TextureButton_plus and TextureButton_minus. Both have a signal to the Controll node, called v_a_controll.

    This is the structure:

    And this is how it looks like:

    The v_a_controll has a script:

    extends Control
    
    signal minus_ingedrukt
    signal plus_ingedrukt
    
    func _ready():
        pass # Replace with function body.
    
    
    func _on_TextureButton_plus_pressed():
        emit_signal("plus_ingedrukt")
        pass # Replace with function body.
    
    func _on_TextureButton_minus_pressed():
        emit_signal("minus_ingedrukt")
        pass # Replace with function body.
    

    And in the main scene I have this code.

    extends Control
    
    var v_a_scene = preload("res://v_a.tscn")
    var node_to_spawn_at = null
    
    func _ready():
        pass
        get_node("TextureButton_plus").connect("plus_ingedrukt", self, "plus_ingedrukt_ontvangen")
        get_node("TextureButton_minus").connect("minus_ingedrukt", self, "minus_ingedrukt_ontvangen")
    
    
    func _on_TextureButton_naam_opslaan_pressed():
        var clone = v_a_scene.instance()
        node_to_spawn_at=get_node("VBoxContainer_general/HBoxContainer/ScrollContainer/VBoxContainer2/HBoxContainer/VBoxContainer_v_a_containers")
        node_to_spawn_at.add_child(clone) #see _ready() node_to_spawn_at for the node
        clone.rect_min_size = Vector2(600,180)
        pass # Replace with function body.
    
    func _on_TextureButton_opslaan_sluiten_pressed():
        pass # Replace with function body.
    
    func minus_ingedrukt_ontvangen():
        print("minus ingedrukt")
        pass
    
    func plus_ingedrukt_ontvangen():
        print("plus ingedrukt")
        pass
    

    Yet I get the error:

    Attempt to call function 'connect' in base 'null instance' on a null instance.

    At line:

    get_node("TextureButton_plus").connect("plus_ingedrukt", self, "plus_ingedrukt_ontvangen")

  • DanielCDanielC Posts: 48Member

    And the second problem I have got is how to do the processing of the information.
    The v_a scene (the one I instance) contains this:

    The number will be the number of the instance

    The plus button saves the content of the two LineEdits as a string in key antwoorden and a string in key vragen in the new dictionary. It hides itself and shows the minus. Then it also creates a new instance in the main scene.

    The minus button deletes the instance, deletes the strings written in the dictionary and sets the previous instance to show the plus and hide the minus button.

    So I guess I need to rename all the new instances. Then I need to rename all the signals from all the buttons from all the instances and write them in a new file containing a dictionary.
    I know I can use this code to rename an instance:

    s=node.instance()
    s.set_name("name_of_instance" + str(i))
    

    And I know how to write a file. This code can be used to add a key string combination to the dictionary:

    dictionary_name["new_key"]="new_string"

    But I have no clue on how to fit everything together. Because the number of instances will get big.

  • TwistedTwiglegTwistedTwigleg Posts: 1,620Admin

    @DanielC said:
    Thank you TwistedTwigleg! Indeed the min size for the ScrollContainer did work to show the instances.
    I applied the code and managed to create instances of the scene inside the node.

    Great! I'm happy that I was able to be of some help.

    But yet, again I got stuck. This time with connecting the button signals. In the v_a.tscn scene I have two buttons called TextureButton_plus and TextureButton_minus. Both have a signal to the Controll node, called v_a_controll.

    Looking at the code, and the node structure, the issue is probably the NodePath you are using. For instance, I believe line 8 should use the following:

    get_node("VBoxContainer/HBoxContainer2/TextureButton_plus").connect("plus_ingedrukt", self, "plus_ingedrukt_ontvangen")
    

    All I did was change the NodePath so that it points to the correct node in the node tree. The get_node function needs the full path to the node you want to get, relative to the node the script is attached to. Since the script is attached to Main, you'll need to add all of the nodes in between Main and TextureButton_plus to the NodePath.

    And the second problem I have got is how to do the processing of the information.
    ...
    But I have no clue on how to fit everything together. Because the number of instances will get big.

    Renaming the instances is one way to differentiate between instances. Personally, I would store all of the instances into a single list/array and use the index to differentiate between them, but there is nothing wrong with using the name instead. Do whatever works best for you and your project :+1:

    As for storing the data, in theory you just need to do something like this:

    var instance = node_to_save_date_from
    dictionary_name[instance .name] = instance .variable_to_store
    

    Or if you need to store more than a single variable, you can either use a dictionary or a list. For example, using a dictionary:

    var instance = node_to_save_date_from
    var sub_dictionary = {}
    # Example data:
    sub_dictionary["name"] = instance.name;
    sub_dictionary["color"] = instance.color;
    sub_dictionary["shape"] = instance.shape; #etc.
    dictionary_name[instance .name] = sub_dictionary
    

    Of course, it the best way to store the data depends on what data you are trying to store and your personal programming preferences. The key is just to use a consistent method for storing and retrieving the data to/from the dictionary you are using.

    Hopefully this helps!

  • DanielCDanielC Posts: 48Member

    TwistedTwigleg unfortunatly I already tried that path too. I also tried v_a_controll/VBoxContainer/HBoxContainer2/TextureButton_plus
    I also tried var v_a_scene = preload("res://v_a.tscn").instance() to make an instance, instead of just preloading
    All with no luck Still the same error.

  • TwistedTwiglegTwistedTwigleg Posts: 1,620Admin

    Hmm, strange. The get_node path should be correct, at least as far as I can tell. I don't know why it would still have that error.

    As far as instancing goes, it makes sense that var v_a_scene = preload("res://v_a.tscn").instance() doesn't work. Last I knew you cannot instance directly after a preload statement. You'd have to do something like this, for example:

    extends Control
    # other stuff here...
    var scene_to_instance = preload("res://v_a.tscn")
    func _ready():
        # Simple test:
        add_child(spawn_child_at_node(self))
    func spawn_child_at_node(node_to_spawn_at):
        var clone = scene_to_instance.instance()
        node_to_spawn_at.add_child(clone)
    

    Do you mind sending the project to me? That way I can debug the issue and (hopefully) quickly find a solution. You can send it through a private message if you want to keep the download link private.

    Unfortunately my schedule has been rather busy, but I can at least take a look at the project and do some basic debugging work to try and find a solution. I cannot promise I'll be able to get it working, though working with the project directly will probably be easier and faster than trying to figure out a solution over the forums.

  • DanielCDanielC Posts: 48Member

    OK, just wanted to share with you the fix to the problem.

    The v_a scene had signals to the v_a_controll. Those were removed.

    This is the working script attached to v_a_controll

    extends Control
    
    signal minus_ingedrukt
    signal plus_ingedrukt
    
    
    func _ready():
        # Connect the signals here!
        # This will make it where when the buttons are pressed, the correct function is called.
        get_node("VBoxContainer/HBoxContainer2/TextureButton_plus").connect("pressed", self, "_on_TextureButton_plus_pressed");
        get_node("VBoxContainer/HBoxContainer2/TextureButton_minus").connect("pressed", self, "_on_TextureButton_minus_pressed");
    
    
    func _on_TextureButton_plus_pressed():
        # When the button is pressed, emit the signal!
        emit_signal("plus_ingedrukt")
    
    
    func _on_TextureButton_minus_pressed():
        # When the button is pressed, emit the signal!
        emit_signal("minus_ingedrukt")
    

    And this is the script for the zelf_maken scene

    extends Control
    
    onready var v_a_scene = preload("res://v_a.tscn")
    
    var node_to_spawn_at = null
    
    func _ready():
        pass;
    
        # For increased performance, initialize any variables you can here, as the code will only be called once.
        node_to_spawn_at = get_node("VBoxContainer_general/HBoxContainer/ScrollContainer/VBoxContainer2/HBoxContainer/VBoxContainer_v_a_containers")
    
        # Nothing else needed here. We will need to connect the signals in the _on_TextureButton_naam_opslaan_pressed function.
    
    
    func _on_TextureButton_naam_opslaan_pressed():
        var clone = v_a_scene.instance()
        node_to_spawn_at.add_child(clone)
        clone.rect_min_size = Vector2(600,180)
    
        # Connect the signals from the newly instanced scene to the correct functions:
        clone.connect("plus_ingedrukt", self, "plus_ingedrukt_ontvangen");
        clone.connect("minus_ingedrukt", self, "minus_ingedrukt_ontvangen");
    
    
    
    func _on_TextureButton_opslaan_sluiten_pressed():
        pass # Replace with function body.
    
    
    func minus_ingedrukt_ontvangen():
        print("minus ingedrukt")
        pass
    
    
    func plus_ingedrukt_ontvangen():
        print("plus ingedrukt")
        pass
    

    With big thanks to TwistedTwigleg. Have fun coding!

Leave a Comment

BoldItalicStrikethroughOrdered listUnordered list
Emoji
Image
Align leftAlign centerAlign rightToggle HTML viewToggle full pageToggle lights
Drop image/file