Search Unity

[UI Builder] How to make buttons interactable?

Discussion in 'UI Toolkit' started by alexanderameye, Dec 2, 2019.

  1. alexanderameye

    alexanderameye

    Joined:
    Nov 27, 2013
    Posts:
    1,383
    Hello, I am using UI Builder.

    I would like the functionality where the buttons Tab1, Tab2, Tab3 can be selected (only 1 of them at a time) and depending on which one is selected, the content belows it changes. The tabs are button elements.

    upload_2019-12-2_15-33-52.png

    Thank you in advance.

    Alex
     
  2. SevenSolaris

    SevenSolaris

    Joined:
    Jan 14, 2019
    Posts:
    20
    I would store a variable for the tab index (0 for Tab 1, 1 for Tab 2, 2 for Tab 3), then for the clickable.clicked for each element, I would set the tab index variable to the index of the tab. So you could, if you wanted to, set the userData for each button to the index you want it to be.

    Then you would want to update your style for the button. So before you change the tab index, you could revert the style of the old tab index element, then change the tab index, set the style for the new tab element, and then hide the old tab's container element, and show the new tab's container element.
     
  3. fffgh123000

    fffgh123000

    Joined:
    Mar 14, 2018
    Posts:
    1
    Maybe changing the picking mode to ignore and dim the color of button by yourself?
     
  4. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    A couple of notes:
    1. UI Builder can only really build your UI layout/hierarchy and styles. You still have the use C# to control behavior.
    2. Use a different UXML file for each "pane" or tab "container", then you can work on the layouts individually and at runtime load them all up under your tabs.
    3. Once you load all containers (via
      visualTreeAsset.CloneTree()
      ), you should use
      style.display = DisplayStyle.None
      to hide the containers for the tabs that not active.
    4. When you register for the Tab buttons clickables, use
      myTabButton.clickable.clickedWithEventInfo
      so you can get the
      EventBase
      in your callback.
    5. You can use
      EventBase.target
      to get the Tab button that was clicked (so you can use a single callback for all 3 buttons)
    6. In your Tab button callback, just set
      myElement.style.display = DisplayStyle.None
      on all containers and then set
      myElement.style.display = DisplayStyle.Flex
      on the newly active container.
     
  5. rahuxx

    rahuxx

    Joined:
    May 8, 2009
    Posts:
    537
    how to create editor window with
    [UI Builder]
     
  6. alexanderameye

    alexanderameye

    Joined:
    Nov 27, 2013
    Posts:
    1,383
    Hey! I only just got back to working on this. Thank you very much for the help.

    I got up to the point where I registered to the clickedWithEventInfo callback and the EventBase is properly passed but how would I differentiate between the tabs?

    Using EventBase.target.ToString() I could maybe get the button's text and differentiate based on that but is there a better way to get the button's text in order to differentiate? Or should I not be looking at the button's text at all but rather some ID? Button position? Something else?

    Thank you in advance,

    Alex

    Edit: EventBase.target.ToString().Contains() is working perfectly but I imagine there must be a better way to differentiate between my tabs.

    Edit2: I figured I could do the following: "Button b = tab.target as button" and like that I can get the text which is a cleaner solution. Thank you!
     
    Last edited: Jan 14, 2020
    jensschmidt likes this.
  7. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    Thanks for updating the post after you solved your problem. Always nice to see this. And yes, Edit 2 is one of the recommended approaches.
     
    alexanderameye likes this.
  8. funcookergames

    funcookergames

    Joined:
    Dec 10, 2018
    Posts:
    9
    Hello! New developer here. Getting better at navigating UIElements, but by chance is there anyway you can write this out in code so I can better see how this is structured (or should I create a new thread)?

    I have been hammering away at google and in code trying to figure out how to get a UI Elements Button/Toggle on a Component Inspector to open up an Editor Window and haven't been able to piece it together. This thread seems very close to what I am hoping for.
     
  9. alexanderameye

    alexanderameye

    Joined:
    Nov 27, 2013
    Posts:
    1,383
    I can show what I have. So in my initialize method I have this.

    Code (CSharp):
    1. root.Query<Button>().ForEach((button) =>
    2. {
    3.     button.clickable.clickedWithEventInfo += SwitchTab;
    4. });
    Then in the 'SwitchTab' method I do this.

    upload_2020-1-26_11-57-54.png

    Does that help?
     
    redeyetw-n2 and Eddierocksuk like this.
  10. Stardog

    Stardog

    Joined:
    Jun 28, 2010
    Posts:
    1,913
    You could even just add a unique class or name to each button and query for them one by one.

    Code (csharp):
    1. rootVisualElement.Q<Button>(className: "tab-button-one").clicked += () =>
    2. {
    3.     SwitchTab(1);
    4. };
    Or when looping through multiple buttons you could also use
    button.ClassListContains("tab-button-one")
    instead of
    button.name
    .
     
    mossibat and Selfmoon like this.
  11. funcookergames

    funcookergames

    Joined:
    Dec 10, 2018
    Posts:
    9
    I am gonna feel stupid for admitting this, but it doesn't help, but maybe I should be a bit more specific too.

    Ideally I'd like to have 10 toggles or set of buttons on a custom component inspector that each open up a new, but similar unique instance of an editor window.

    I am looking for any documentation on how to call an Editor Window from a Monobehavior script.
     
  12. funcookergames

    funcookergames

    Joined:
    Dec 10, 2018
    Posts:
    9
  13. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    @funcookergames Something like this?
    Code (CSharp):
    1.             rootVisualElement.Q<Button>(className: "my-tool-button").clicked += () =>
    2.             {
    3.                 var window = EditorWindow.GetWindow<MyWindowType>();
    4.                 // Display window
    5.                 window.Show();
    6.                 window.Focus();
    7.             };
    8.  
     
  14. FLDSMDFR2

    FLDSMDFR2

    Joined:
    May 14, 2019
    Posts:
    3
    Hello,

    So i have tried implementing every way possible to get a click event from my button... so far nothing is working.

    Any help would be great thanks.

    unity version 2020.1.0a

    upload_2020-3-2_19-53-21.png


    upload_2020-3-2_19-48-53.png
     

    Attached Files:

    smallstep likes this.
  15. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    I'm not sure why your clickable is not triggered. Your code looks ok, unless the many registrations conflict each other in some way. This is all you should need. I couldn't get it not to work for me.

    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3. using UnityEngine.UIElements;
    4.  
    5. public class SimpleBuggyWindow : EditorWindow
    6. {
    7.     [MenuItem("bla/SimpleBuggyWindow")]
    8.     public static void ShowExample()
    9.     {
    10.         SimpleBuggyWindow wnd = GetWindow<SimpleBuggyWindow>();
    11.         wnd.titleContent = new GUIContent("SimpleBuggyWindow");
    12.     }
    13.  
    14.     public void OnEnable()
    15.     {
    16.         // Each editor window contains a root VisualElement object
    17.         var root = rootVisualElement;
    18.  
    19.         var tree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Button.uxml");
    20.         var treeElement = tree.CloneTree();
    21.  
    22.         // Do not use .Query() for a single element.
    23.         //Button button = treeElement.Query<Button>();
    24.         // Use .Q():
    25.         var button = treeElement.Q<Button>();
    26.  
    27.         button.clickable.clicked += () => Debug.Log("Clicked");
    28.  
    29.         root.Add(button);
    30.     }
    31. }
    32.  
    UXML:
    Code (CSharp):
    1. <ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements">
    2.     <ui:Button text="Button" style="height: 98px;&#10;width: 110px;&#10;" />
    3. </ui:UXML>
    4.  
    Please see if the exact C# and UXML I have above works or doesn't work for you. Also try restarting Unity in between. Come back if there's still an issue.

    PS: Please use a code block next time instead of a screenshot. It's a bit easier for someone to test your code. :)
     
    FLDSMDFR2 likes this.
  16. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    @FLDSMDFR2 If you're using the runtime preview, make sure you have both a PanelRenderer and a EventSystem next to it, otherwise, no mouse or keyboard Events will be fired to your panel's content, I got bit by that once :)
     
  17. FLDSMDFR2

    FLDSMDFR2

    Joined:
    May 14, 2019
    Posts:
    3
    Thank you for the response,

    I have updated to use your exact code, but i am still not able to get a click event from within the Game Window. :(

    But i do get the click event when clicking the button within the SimpleBuggyWindow EditorWindow


    And i do have a UIElementsEventSystem attached
    upload_2020-4-2_21-57-20.png
     
  18. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    FLDSMDFR2 likes this.
  19. FLDSMDFR2

    FLDSMDFR2

    Joined:
    May 14, 2019
    Posts:
    3
    Thanks again for the response,

    I am using the RuntimeDemo Assets and within the Demo Scene the buttons work correctly.... So i update my original code to to follow the Demos implementation and it works for me.:)

    Basically adding a reference to the in game PanelRenderer and called the postUxmlReload.

    However if i am not referencing the in game PanelRenderer and try and grab the VisualTreeAsset from the AssetDatabase like so
    _assetTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Button.uxml");

    it does not work.

    Working Code
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UIElements;
    3. using UnityEditor;
    4. using System.Collections.Generic;
    5. using Unity.UIElements.Runtime;
    6.  
    7. public class ShopManagerUIBuilder : MonoBehaviour
    8. {
    9.     //private VisualElement _visualTree;
    10.     //private VisualTreeAsset _assetTree;
    11.  
    12.     public PanelRenderer GameScreen;
    13.  
    14.     public void OnEnable()
    15.     {
    16.         GameScreen.postUxmlReload = BindGameScreen;
    17.     }
    18.  
    19.     private IEnumerable<Object> BindGameScreen()
    20.     {
    21.         var root = GameScreen.visualTree;
    22.         Button b = root.Q<Button>("Button");
    23.         b.clickable.clicked += ButtonClicked;
    24.  
    25.  
    26.        //_assetTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Button.uxml");
    27.  
    28.         //_visualTree = _assetTree.CloneTree();
    29.  
    30.         //Button b = _visualTree.Q<Button>("Button");
    31.  
    32.         //b.clickable.clicked += ButtonClicked;
    33.  
    34.         return null;
    35.     }
    36.  
    37.     private void ButtonClicked()
    38.     {
    39.         TraceManager.WriteTrace(TraceChannel.Main, TraceType.info, "Button Clicked");
    40.     }
    41. }
    42.  
    UXML
    Code (CSharp):
    1. <ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements">
    2.     <ui:Button text="Button" name="Button" style="height: 98px;&#10;width: 110px;&#10;">
    3.         <Style src="Button.uss" />
    4.     </ui:Button>
    5. </ui:UXML>
    6.  
     
  20. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    This is an Editor-only API which means that it "might" work at Runtime when running the game in Playmode inside the Editor but it will not work when you build the Player. You can either use the Resources.Load() API or try to reference all the UXML and USS assets you need in a MonoBehaviour at edit time.
     
  21. GoodGabriel

    GoodGabriel

    Joined:
    Jul 7, 2019
    Posts:
    1
    When It will work?
     
  22. zylo01

    zylo01

    Joined:
    Jun 28, 2013
    Posts:
    2
    Hi,

    I stumbled across this problem while trying out the new UI Builder Package (Version 1.0.0-preview.9 - October 15, 2020).

    As far as i can tell a few things have changed since previous versions so here is my working version of registering a button callback.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class StartMenuManager : MonoBehaviour
    6. {
    7.     public UnityEngine.UIElements.UIDocument uIDocument;
    8.  
    9.     public UnityEngine.UIElements.TextField textField_serverAddress;
    10.     public UnityEngine.UIElements.Button button_connectToServer;
    11.     public UnityEngine.UIElements.Button button_options;
    12.     public UnityEngine.UIElements.Button button_quit;
    13.  
    14.     // Start is called before the first frame update
    15.     void Start()
    16.     {
    17.  
    18.         var allElementsInHirarchy = new List<UnityEngine.UIElements.VisualElement>();
    19.         this.getAllContainedElements(uIDocument.rootVisualElement, ref allElementsInHirarchy);
    20.  
    21.         for (int i=0; i< allElementsInHirarchy.Count; i++) {
    22.             Debug.Log("startMenuTreeClone["+i+"].name == "+ allElementsInHirarchy[i].name);
    23.             if (allElementsInHirarchy[i].name == "ServerAddressTextfield") {
    24.                 textField_serverAddress = (UnityEngine.UIElements.TextField)allElementsInHirarchy[i];
    25.             }
    26.             else if (allElementsInHirarchy[i].name == "ConnectToServerButton") {
    27.                 button_connectToServer = (UnityEngine.UIElements.Button)allElementsInHirarchy[i];
    28.             }
    29.             else if (allElementsInHirarchy[i].name == "OpenOptions")
    30.             {
    31.                 button_options = (UnityEngine.UIElements.Button)allElementsInHirarchy[i];
    32.             }
    33.             else if (allElementsInHirarchy[i].name == "QuitButton")
    34.             {
    35.                 button_quit = (UnityEngine.UIElements.Button)allElementsInHirarchy[i];
    36.             }
    37.         }
    38.      
    39.         button_connectToServer.clicked += clickEvent_connectToServerButton;
    40.         button_options.clicked += clickEvent_openOptions;
    41.         button_quit.clicked += clickEvent_quitGame;
    42.  
    43.     }
    44.  
    45.     // Update is called once per frame
    46.     void Update()
    47.     {
    48.  
    49.     }
    50.  
    51.     public void getAllContainedElements(UnityEngine.UIElements.VisualElement parentElement, ref List<UnityEngine.UIElements.VisualElement> allElements) {
    52.         allElements.Add(parentElement);
    53.  
    54.         for (int i = 0; i < parentElement.childCount; i++) {
    55.             getAllContainedElements(parentElement[i], ref allElements);
    56.         }
    57.     }
    58.  
    59.     public void clickEvent_connectToServerButton() {
    60.         Debug.Log("Connect to IP: "+ textField_serverAddress.text);
    61.     }
    62.  
    63.     public void clickEvent_openOptions()
    64.     {
    65.         Debug.Log("Open Options");
    66.     }
    67.  
    68.     public void clickEvent_quitGame()
    69.     {
    70.         Debug.Log("Quit");
    71.     }
    72. }
     
    a4ism likes this.
  23. Measter4

    Measter4

    Joined:
    Aug 31, 2021
    Posts:
    2

    This saved my night and my sanity. Thank you. I was able to create an infinite number of buttons and reference them uniquely after they were clicked by naming them in a loop with
    String btnName = "baseName" + i.ToString
    then comparing them using the EventBase data to get the name and running another loop through the number of possible names.
     
    alexanderameye likes this.
  24. redeyetw-n2

    redeyetw-n2

    Joined:
    Mar 11, 2020
    Posts:
    1
    thank you