Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Have a look at our Games Focus blog post series which will show what Unity is doing for all game developers – now, next year, and in the future.
    Dismiss Notice

UI builder and custom elements

Discussion in 'UI Toolkit' started by RealMTG, Nov 28, 2019.

  1. RealMTG

    RealMTG

    Joined:
    Jul 27, 2013
    Posts:
    402
    Hi

    I want to make a custom UI element just to make my workflow a bit easier. A collection of UI elements with some variables to change, just like any button, progress bar, vector3 field, etc. Is this something we can do and it will work with the UI builder? I know you can place in other UXML files into your current one but I don't see a way to use custom code along with that custom UXML.

    I basically want to have some exposed variables like this but for my own custom elements:
     
  2. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,170
    Yep, you can do that by inheriting from VisualElement. You can then define a UxmlFactory inside your element class to expose it to UXML (so you can create it with a tag with same name as you C# type). And you can define a UxmlTraits class to add custom attributes that you can then set in UXML. These custom attributes will then be exposed in the UI Builder inspector, like Text, Binding Path, Name, etc. (in your screenshot). Here's a full example of a custom element and some UXML attribute declarations:


    Code (CSharp):
    1. class BuilderAttributesTestElement : VisualElement
    2. {
    3.     public enum Existance
    4.     {
    5.         None,
    6.         Good,
    7.         Bad
    8.     }
    9.  
    10.     public new class UxmlFactory : UxmlFactory<BuilderAttributesTestElement, UxmlTraits> { }
    11.  
    12.     public new class UxmlTraits : VisualElement.UxmlTraits
    13.     {
    14.         UxmlStringAttributeDescription m_String = new UxmlStringAttributeDescription { name = "string-attr", defaultValue = "default_value" };
    15.         UxmlFloatAttributeDescription m_Float = new UxmlFloatAttributeDescription { name = "float-attr", defaultValue = 0.1f };
    16.         UxmlDoubleAttributeDescription m_Double = new UxmlDoubleAttributeDescription { name = "double-attr", defaultValue = 0.1 };
    17.         UxmlIntAttributeDescription m_Int = new UxmlIntAttributeDescription { name = "int-attr", defaultValue = 2 };
    18.         UxmlLongAttributeDescription m_Long = new UxmlLongAttributeDescription { name = "long-attr", defaultValue = 3 };
    19.         UxmlBoolAttributeDescription m_Bool = new UxmlBoolAttributeDescription { name = "bool-attr", defaultValue = false };
    20.         UxmlColorAttributeDescription m_Color = new UxmlColorAttributeDescription { name = "color-attr", defaultValue = Color.red };
    21.         UxmlEnumAttributeDescription<Existance> m_Enum = new UxmlEnumAttributeDescription<Existance> { name = "enum-attr", defaultValue = Existance.Bad };
    22.  
    23.         public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription
    24.         {
    25.             get { yield break; }
    26.         }
    27.  
    28.         public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
    29.         {
    30.             base.Init(ve, bag, cc);
    31.             var ate = ve as BuilderAttributesTestElement;
    32.  
    33.             ate.Clear();
    34.  
    35.             ate.stringAttr = m_String.GetValueFromBag(bag, cc);
    36.             ate.Add(new TextField("String") { value = ate.stringAttr });
    37.  
    38.             ate.floatAttr = m_Float.GetValueFromBag(bag, cc);
    39.             ate.Add(new FloatField("Float") { value = ate.floatAttr });
    40.  
    41.             ate.doubleAttr = m_Double.GetValueFromBag(bag, cc);
    42.             ate.Add(new DoubleField("Double") { value = ate.doubleAttr });
    43.  
    44.             ate.intAttr = m_Int.GetValueFromBag(bag, cc);
    45.             ate.Add(new IntegerField("Integer") { value = ate.intAttr });
    46.  
    47.             ate.longAttr = m_Long.GetValueFromBag(bag, cc);
    48.             ate.Add(new LongField("Long") { value = ate.longAttr });
    49.  
    50.             ate.boolAttr = m_Bool.GetValueFromBag(bag, cc);
    51.             ate.Add(new Toggle("Toggle") { value = ate.boolAttr });
    52.  
    53.             ate.colorAttr = m_Color.GetValueFromBag(bag, cc);
    54.             ate.Add(new ColorField("Color") { value = ate.colorAttr });
    55.  
    56.             ate.enumAttr = m_Enum.GetValueFromBag(bag, cc);
    57.             var en = new EnumField("Enum");
    58.             en.Init(m_Enum.defaultValue);
    59.             en.value = ate.enumAttr;
    60.             ate.Add(en);
    61.         }
    62.     }
    63.  
    64.     public string stringAttr { get; set; }
    65.     public float floatAttr { get; set; }
    66.     public double doubleAttr { get; set; }
    67.     public int intAttr { get; set; }
    68.     public long longAttr { get; set; }
    69.     public bool boolAttr { get; set; }
    70.     public Color colorAttr { get; set; }
    71.     public Existance enumAttr { get; set; }
    72. }
    UXML usage:

    Code (CSharp):
    1. <BuilderAttributesTestElement
    2.     string-attr="my-string"
    3.     float-attr="4.2"
    4.     double-attr="4.3"
    5.     int-attr="4"
    6.     long-attr="423"
    7.     bool-attr="true"
    8.     color-attr="#CA7C03FF"
    9.     enum-attr="Good" />
    And this is how its UI Builder inspector looks like:
    upload_2019-11-28_16-55-39.png
     
    dasparadoxon, NotaNaN, DrViJ and 21 others like this.
  3. RealMTG

    RealMTG

    Joined:
    Jul 27, 2013
    Posts:
    402
    Oh wow, thanks for the detailed and quick response! Very much appreciated! I just got one more question. In the UI builder my element appears in an empty group (as it has no name). Is there a way to fix that so I can easily see it in the builder?
     
  4. RealMTG

    RealMTG

    Joined:
    Jul 27, 2013
    Posts:
    402
    I found another thing that I need help with. How do I make so the UI elements actually update when I use them? Right now the elements just stay the way they are when updating them through a script, in my case, at runtime.
     
  5. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,170
    I'm not sure what you mean. Might help to see a screenshot.

    It really depends what you mean by "update". Like, change style? Change value (in case of field)? Change shape/size when setting myelement.style.width/height? All of these should result in immediate updating of the game view UI so something must be going wrong if you don't see any updates.

    That is, if you mean the UI in the Game view (or used in a custom EditorWindow). If you are still referring to the UI inside the UI Builder's Canvas, this UI is always going to be static. You can turn on Preview mode from the top right corner of the Viewport to be able to interact with the UI (like, see hover states and expand Foldouts), but any code not inside a custom C# element (inheriting from VisualElement) will not run. Like, the Panel Renderer code does not run inside the Builder.
     
  6. RealMTG

    RealMTG

    Joined:
    Jul 27, 2013
    Posts:
    402
    Here's what I mean with the empty group:


    And what I meant with not updating is like I have a string property, like a label, and when I change it, it does not update the text on the element. It works in the UI builder but not while in-game. I used your example and got that result. But I then realized I could peek into the other built-in UI elements to see how they were built and I've started making it find the right element and update it based on the property.
    Like this:
    Code (CSharp):
    1. public string Label
    2. {
    3.     get { return this.Q<Label>("progress-label").text; }
    4.     set { this.Q<Label>("progress-label").text = value; }
    5. }
    Is that the wrong way and has something just gone wrong with the example you provided?
     
  7. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,170
    This is a bug. Everything below "Packages" should be custom C# elements the UI Builder finds via reflection that have a
    UxmlFactory
    defined. It just uses the C# type name and namespace to generate the tree items in that tree, where each namespace is a "folder". Is
    GameProgressBar
    inside a namespace? We might just not handle no-namespace elements properly.

    The example element I posted is really just a test for the custom UXML attribute declaration. You'll notice that only the
    Init()
    function of the
    UxmlTraits
    implementation completely recreates all children inside the element. This
    Init()
    is what is called when element is first initialized from UXML and never again. The UI Builder cheats a bit by calling this
    Init()
    after the element is initialized every time you change the attribute values in the UI Builder's Inspector. But this does not happen at runtime.

    What you did with the
    public string Label
    property above is exactly what you need to do. The only suggestion I have is to cache the result of
    this.Q<Label>("progress-label")
    - can do this in the element's constructor - so you don't have to find it every time you change its text value.
     
    RealMTG likes this.
  8. RealMTG

    RealMTG

    Joined:
    Jul 27, 2013
    Posts:
    402
    It is not in a namespace so I guess it's the bug showing.

    But now I have a new issue that has popped up. When I build my game I get the error "Element '<my element>' has no registered factory method." Quickly looking at some other built-in UI elements I can't seem to find anything that relates to that. It only happens in a build too (Mono, no stripping). What could be causing this and is there a fix?
     
  9. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,170
    Make sure your element is not defined inside an Editor assembly (script lives inside an Editor folder). By extension, it also doesn't use anything from UnityEditor namespace.
     
  10. RealMTG

    RealMTG

    Joined:
    Jul 27, 2013
    Posts:
    402
    My element is not in the Editor assembly and does not use any UnityEditor namespace things... Now what?
     
  11. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,170
    Make sure your UxmlFactory inherits correctly:

    Code (CSharp):
    1. class MyElement : VisualElement
    2. {
    3.     public new class UxmlFactory : UxmlFactory<MyElement, UxmlTraits> { }
    4.     ...
    5. }
     
  12. RealMTG

    RealMTG

    Joined:
    Jul 27, 2013
    Posts:
    402
    It is correct and the problem persists. Just to be 100% sure, I checked the generated code and all the code is there, so absolutely nothing has been stripped out (still haven't used any stripping).
    Here is the error message if it helps:

    Element 'GameProgressBar' has no registered factory method.
    UnityEngine.DebugLogHandler:Internal_Log(LogType, LogOption, String, Object)
    UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])
    UnityEngine.Logger:LogFormat(LogType, String, Object[])
    UnityEngine.Debug:LogErrorFormat(String, Object[])
    UnityEngine.UIElements.VisualTreeAsset:Create(VisualElementAsset, CreationContext) (at C:\buildslave\unity\build\Modules\UIElements\UXML\VisualTreeAsset.cs:403)
    UnityEngine.UIElements.VisualTreeAsset:CloneSetupRecursively(VisualElementAsset, Dictionary`2, CreationContext) (at C:\buildslave\unity\build\Modules\UIElements\UXML\VisualTreeAsset.cs:195)
    UnityEngine.UIElements.VisualTreeAsset:CloneSetupRecursively(VisualElementAsset, Dictionary`2, CreationContext) (at C:\buildslave\unity\build\Modules\UIElements\UXML\VisualTreeAsset.cs:246)
    UnityEngine.UIElements.VisualTreeAsset:CloneTree(VisualElement, Dictionary`2, List`1) (at C:\buildslave\unity\build\Modules\UIElements\UXML\VisualTreeAsset.cs:182)
    UnityEngine.UIElements.VisualTreeAsset:CloneTree(VisualElement, Dictionary`2) (at C:\buildslave\unity\build\Modules\UIElements\UXML\VisualTreeAsset.cs:145)
    UnityEngine.UIElements.VisualTreeAsset:CloneTree(VisualElement) (at C:\buildslave\unity\build\Modules\UIElements\UXML\VisualTreeAsset.cs:134)
    Unity.UIElements.Runtime.PanelRenderer:RecreateUIFromUxml() (at Project\Packages\UIERuntime\Runtime\PanelRenderer.cs:235)
    Unity.UIElements.Runtime.PanelRenderer:Start() (at Project\Packages\UIERuntime\Runtime\PanelRenderer.cs:215)


    And here's a very stripped down version of my code but it still has the important parts:
    Code (CSharp):
    1. public class GameProgressBar : TemplateContainer
    2. {
    3.     [Preserve]
    4.     public new class UxmlFactory : UxmlFactory<GameProgressBar, UxmlTraits> { }
    5.  
    6.     [Preserve]
    7.     public new class UxmlTraits : VisualElement.UxmlTraits
    8.     {
    9.         UxmlStringAttributeDescription label = new UxmlStringAttributeDescription() { name = "label", defaultValue = "Progress" };
    10.  
    11.         public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription
    12.         {
    13.             get { yield break; }
    14.         }
    15.  
    16.         public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
    17.         {
    18.             base.Init(ve, bag, cc);
    19.             GameProgressBar ate = ve as GameProgressBar;
    20.  
    21.             ate.Label = label.GetValueFromBag(bag, cc);
    22.         }
    23.     }
    24.  
    25.     private Label label;
    26.  
    27.     public string Label
    28.     {
    29.         get { return label.text; }
    30.         set { label.text = value; }
    31.     }
    32.  
    33.     public GameProgressBar()
    34.     {
    35.         label = new Label() { name = "progress-label" };
    36.         hierarchy.Add(label);
    37.     }
    38. }
    39.  
     
  13. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,170
    Wait, why are you inheriting from
    TemplateContainer
    ? That may be the issue.

    But just to be sure, can you also paste the snippet of UXML that uses GameProgressBar.
     
  14. RealMTG

    RealMTG

    Joined:
    Jul 27, 2013
    Posts:
    402
    I'm actually not sure why I was inheriting from TemplateContainer. I changed it to VisualElement but it didn't change anything.
    Here's the UXML that use GameProgressBar:
    Code (CSharp):
    1. <ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements">
    2.     <ui:VisualElement name="TopHolder" style="align-items: center;">
    3.         <Style src="HUD.uss" />
    4.         <ui:VisualElement class="element-background" style="width: 260px; height: 80px;">
    5.             <ui:Label text="Wave 99" name="WaveLabel" class="label" style="font-size: 28px; -unity-text-align: middle-center; height: 40px;" />
    6.             <GameProgressBar Label="Label" MinValue="0.39" MaxValue="1.74" CurrentValue="4.89" BackgroundColor="#FF0000FF" TopColor="#84FF00FF" background-color="#484848FF" name="WaveProgressBar" top-color="#9A0003FF" direction="LeftToRight" style="height: 24px; margin-left: 10px; margin-right: 10px; margin-top: 4px;" />
    7.         </ui:VisualElement>
    8.     </ui:VisualElement>
    9. </ui:UXML>
     
  15. Vaspra

    Vaspra

    Joined:
    Apr 15, 2018
    Posts:
    34
    Bit of a UI Builder rookie so apologies if this is a dumb question, but I had this working using the tanks example and having a script containing:

    Code (CSharp):
    1.  
    2.     // UI elements to update with variable information
    3.     private Label resourceVolumeLabel;
    4.     private Label resourceEnergyLabel;
    5.     private Label resourceMineralsLabel;
    6.  
    7.     private void Awake()
    8.     {
    9.         panelRenderer = GetComponent<PanelRenderer>();
    10.     }
    11.  
    12.     void OnEnable()
    13.     {
    14.         panelRenderer.postUxmlReload = BindLogic;
    15.     }
    16.  
    17.     private IEnumerable<UnityEngine.Object> BindLogic()
    18.     {
    19.         var root = panelRenderer.visualTree;
    20.  
    21.         resourceVolumeLabel = root.Q<Label>("resource-label-volume");
    22.         resourceMineralsLabel = root.Q<Label>("resource-label-minerals");
    23.         resourceEnergyLabel = root.Q<Label>("resource-label-energy");
    24.  
    25.         return null;
    26.     }
    27.  
    This worked a couple of days ago, all values updating as necessary, and now without changing anything and reloading my project I seem to have somehow stopped this working properly. I can confirm that
    OnEnable
    is called, so
    panelRenderer.postUxmlReload
    gets this binding
    IEnum
    . Where my UI actually updated before, I now just get a null ref exception showing that
    resourceVolumeLabel
    is never assigned to.

    Is there anything missing from this barebones logic to get a panel renderer to work properly?

    Thanks
     
  16. stan-osipov

    stan-osipov

    Unity Technologies

    Joined:
    Feb 24, 2020
    Posts:
    31
    Can you make sure that you have a Label with resource-label-volume name in your UXML?
     
  17. Wspier

    Wspier

    Joined:
    May 3, 2017
    Posts:
    1
    I'm going to hop on this thread because I am also trying to make a custom VisualElement. I'll post my test class below. My issue is that in the inspector once a value is given to my "DataType" attribute and then the template is saved it removes the value within the inspector. The UXML looks fine and maintains the entered value. The only issue is in-editor. (The idea is to do something similar to Enums and resolve a type with a given name and assembly).

    Code (CSharp):
    1. public class TestElement : VisualElement
    2.     {
    3.         public class TestElementFactory : UxmlFactory<TestElement, TestElementTraits> { }
    4.  
    5.         public class TestElementTraits : UxmlTraits
    6.         {
    7.             private UxmlStringAttributeDescription m_typeStringAttribute;
    8.  
    9.             public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
    10.             {
    11.                 base.Init(ve, bag, cc);
    12.                 var te = ve as TestElement;
    13.                 try
    14.                 {
    15.                     if (te != null)
    16.                     {
    17.                         te.TypeString = m_typeStringAttribute.GetValueFromBag(bag, cc);
    18.                         var test = Type.GetType( te.TypeString);
    19.                         if (test != null)
    20.                         {
    21.                             ((TestElement) ve)._dataType = test;
    22.                         }
    23.                     }
    24.                 }
    25.                 catch
    26.                 {
    27.                     // ignored
    28.                 }
    29.             }
    30.  
    31.             public TestElementTraits()
    32.             {
    33.                 m_typeStringAttribute = new UxmlStringAttributeDescription { name = "DataType" };
    34.             }
    35.         }
    36.  
    37.         private Type _dataType;
    38.  
    39.         private string TypeString { get; set; }
    40.     }
     
    Last edited: Jun 6, 2020
  18. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,170
    andrew-lukasik likes this.
  19. WAYN_Games

    WAYN_Games

    Joined:
    Mar 16, 2019
    Posts:
    869
    Hello,

    I made a bit o UI for my ability system that using the UI Builder. It works fine but I' like to make a custom ViualElement as that bit of UI is composed of several child visual element.
    I'm currently adding them through script but I wonder if I can make use of a UXML template in that custom visual element (through the UxmlFactory maybe ?).

    Do you have any sample on how to do that sort of things ?

    Also is there a way to define a style sheet that can be overidden ?
     
  20. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,170
    Just get a reference to you UXML asset via the AssetDatabase and call CloneTree(this) on it in your custom C# element's constructor. This will populate the insides of your custom element with the contents of the UXML. You can then use uQuery (.Q<T> or .Query<T>) to find specific elements and register your desired events.

    Even better, you can embed any styles for your custom C# element inside a USS that is directly referenced by the UXML via the <Style> tag so there's no need to do the same AssetDatabase lookup for the USS. If you used the UI Builder, it will embed the USS in your UXML for you.

    Not too sure why you mean. StyleSheets (USS) are can be added to any element via element.AddStyleSheetPath(). The order matters, so if you add another USS later, if there are any duplicate selectors between the existing and new USS, the ones in the new USS will win.
     
    brinca, PrimalCoder and WAYN_Games like this.
  21. WAYN_Games

    WAYN_Games

    Joined:
    Mar 16, 2019
    Posts:
    869
    Yep that's what I meant. Thanks.
     
    uDamian likes this.
  22. Knedlo

    Knedlo

    Joined:
    Oct 15, 2012
    Posts:
    31
    Hi there,

    I am trying to inherit from ListView and slap a few extra properties on it, so that I can use them in a very simple binding system. However, I am getting an exception, every time I hover over the resulting element in UI Builder:

    InvalidOperationException: You can't add directly to this VisualElement. Use hierarchy.Add() if you know what you're doing.

    The new class is derived from an example at the top of this thread:

    Code (CSharp):
    1.  
    2. using UnityEngine.UIElements;
    3.  
    4. namespace Assets.Scripts.CustomVisualElements
    5. {
    6.     class BindableListView : ListView
    7.     {
    8.         public new class UxmlFactory : UxmlFactory<BindableListView, UxmlTraits> { }
    9.         public new class UxmlTraits : VisualElement.UxmlTraits
    10.         {
    11.             UxmlStringAttributeDescription dataTemplatePath = new UxmlStringAttributeDescription { name = "dataTemplatePath", defaultValue = string.Empty };
    12.  
    13.             public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
    14.             {
    15.                 base.Init(ve, bag, cc);
    16.  
    17.                 var ate = ve as BindableListView;
    18.                 ate.Clear();
    19.                 ate.DataTemplatePath = dataTemplatePath.GetValueFromBag(bag, cc);
    20.                 ate.Add(new TextField("DataTemplatePath") { value = ate.DataTemplatePath });
    21.             }
    22.         }
    23.         public string DataTemplatePath { get; set; }
    24.     }
    25. }
    26.  
    Any idea why is it happening and if there is a way around it? The example worked fine, but I guess there are some intricacies when inheriting from ListView. When I change the parent class to VisualElement, it works.
     
    Last edited: Aug 12, 2020
  23. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,170
    You cannot add elements directly to ListView. ListView assumes it is in full control of its children as it manages them (it's a virtual container). You need to create a new custom VisualElement that just inherits from VisualElement and _contains_ a ListView. You can then forward any parameters you wish to your internal ListView.

    Some background. The Add() function adds a new child element to the contentContainer element of the parent. By default, the contentContainer = this, so the Add() is trivially adding to the parent. But, custom elements can override this contentContainer to point to some deeper internal child of the parent so all future Add() calls on parent add new children to this deeper element. This can also be used to _not_ allow any children be added to your element by setting contentContainer to null. This is what ListView does.
     
  24. PlaycorpStudios

    PlaycorpStudios

    Joined:
    Aug 2, 2016
    Posts:
    47
    How do we apply uss styles to the child elements we use for those fields?
     
  25. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,170
  26. AKocourek

    AKocourek

    Joined:
    Nov 6, 2017
    Posts:
    6
    Hello guys,

    I am basically trying the same in runtime, but am stuck completly.
    I want to add childs to my UI dynamically via c# code. Lets call the parent 'View' and the childs 'Elements'
    So my for my 'Elements' I have an uxml and a c# file. Those 'Elements' have a child aswell (a Foldout), defined in the uxml file. But when ever I dynamicall add those 'Elements' via calling the c# constructor and add them to the 'View', the foldouts of the 'Elements' are not loaded. The same happens when I add my custom control of the 'Element' to the 'View' in the UI Builder. It seems that the c# class and the uxml file are not linked properly.

    My UXML:
    Code (CSharp):
    1. <ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" engine="UnityEngine.UIElements" editor-extension-mode="False">
    2.     <Xrp.SimpleEditor.UI.VisualElements.HierarchyElement>
    3.         <ui:Foldout text="Foldout" />
    4.     </Xrp.SimpleEditor.UI.VisualElements.HierarchyElement>
    5. </ui:UXML>
    My C# Class:
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine.UIElements;
    3.  
    4. namespace Xrp.SimpleEditor.UI.VisualElements
    5. {
    6.     public class HierarchyElement : VisualElement
    7.     {
    8.         private int index;
    9.         private Foldout foldout;
    10.         private string text;
    11.  
    12.         public HierarchyElement()
    13.         {
    14.             index = -1;
    15.             text = string.Empty;
    16.             RegisterCallback<AttachToPanelEvent>(OnAttachToPanel);
    17.         }
    18.  
    19.         public HierarchyElement(string text, int index)
    20.         {
    21.             this.index = index;
    22.             this.text = text;
    23.  
    24.             RegisterCallback<AttachToPanelEvent>(OnAttachToPanel);
    25.         }
    26.  
    27.         private void OnAttachToPanel(AttachToPanelEvent args)
    28.         {
    29.             foldout = this.Q<Foldout>();
    30.             foldout.text = text;
    31.             foldout.RegisterCallback<ClickEvent>(OnClicked);
    32.         }
    33.  
    34.         private void OnClicked(ClickEvent e)
    35.         {
    36.  
    37.         }
    38.  
    39.         #region Uxml Factory
    40.         public new class UxmlFactory : UxmlFactory<HierarchyElement, UxmlTraits> { }
    41.  
    42.         public new class UxmlTraits : VisualElement.UxmlTraits
    43.         {
    44.             public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription {
    45.                 get { yield break; }
    46.             }
    47.  
    48.             public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
    49.             {
    50.                 base.Init(ve, bag, cc);
    51.  
    52.                 var ate = ve as HierarchyElement;
    53.  
    54.                 ate.Clear();
    55.             }
    56.         }
    57.     #endregion
    58.     }
    59. }
    60.  
    Am I missing something?
    Can I somehow use the UI Debugger for Runtime UI, which is created dynamically?
     
  27. JuliaP_Unity

    JuliaP_Unity

    Unity Technologies

    Joined:
    Mar 26, 2020
    Posts:
    653
    You can still use the UI Toolkit Debugger for no matter how you create your UI with UI Toolkit, for Runtime UI you just have to find the Panel Settings in the list at the top left list and you're set :)
     
    AdamBebko likes this.
  28. WAYN_Games

    WAYN_Games

    Joined:
    Mar 16, 2019
    Posts:
    869
    Not sure but I think that this :

    Code (CSharp):
    1.             public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription {
    2.                 get { yield break; }
    3.             }
    means your HierarchyElement does not allow for any child elements.

    Try again after removing it, see if it solve the problem.
     
  29. AKocourek

    AKocourek

    Joined:
    Nov 6, 2017
    Posts:
    6
    Thank you.
    I had to select PanelsSettings, i just looked for my uxml name.

    Sadly, removing the overriden function didnt help. The element still has no foldout children.

    Thats the uxml dump from the debugger:
    Code (CSharp):
    1. <UXML xmlns:ui="UnityEngine.UIElements">
    2.   <HierarchyElement />
    3. </UXML>
     
  30. WAYN_Games

    WAYN_Games

    Joined:
    Mar 16, 2019
    Posts:
    869
    So your hierarchy element is your cutsom define visual element that relies on a UXML file to get it's structure (the foldout element).

    Rigth now there is no link between your C#class and the UXML. you should probably get something in hte lines of :

    Code (CSharp):
    1.        
    2. public new class UxmlFactory : UxmlFactory<HierarchyElement, UxmlTraits>
    3.         {
    4.             public override VisualElement Create(IUxmlAttributes bag, CreationContext cc)
    5.             {
    6.                 VisualTreeAsset visualTree = Resources.Load<VisualTreeAsset>("HierarchyElement"); // your UXML file name/path in a Resources folder in your project.
    7.                 VisualElement root = base.Create(bag, cc);
    8.                 visualTree.CloneTree(root);
    9.                 return root;
    10.             }
    11.         }
    12.  
    If you want to, you can try to take a look at the ability system I'm working on. I've not been able to work on it for the past few weeks so it's not up to the latest package but I don't think there has been so much changes in respect to what you are trying to do.
    https://github.com/WAYNGROUP/MGM-Ability/blob/0.4.0/Runtime/UI/AbilityUIElement.cs
     
  31. AKocourek

    AKocourek

    Joined:
    Nov 6, 2017
    Posts:
    6
    Thanks for your answer,

    I checked your repo and applied those things to my script.
    It seems the Create() method is only called very few times. I put a breakpoint and a debug log in it to test it.
    But I was never able to see the foldouts (testet with the Debugger, just an empty <HierarchyElement/> tag)
    Moving the uxml to the resource folder has a strange side effect. Whenever I hover over this uxml in the UI Builder, Unity chrashes.
     
  32. AKocourek

    AKocourek

    Joined:
    Nov 6, 2017
    Posts:
    6
    PS: I figured out Create() is only called when play mode is entered and the uxml is not in the resources folder, but then unity can obviously not load it.
     
  33. WAYN_Games

    WAYN_Games

    Joined:
    Mar 16, 2019
    Posts:
    869
    maybe try not to put the
    <Xrp.SimpleEditor.UI.VisualElements.HierarchyElement>
    in your UXML, it may make the creation/loading recursive ?
     
  34. AKocourek

    AKocourek

    Joined:
    Nov 6, 2017
    Posts:
    6
    Yeah you are right, this caused Unity to crash.
    Now I can add my "Custom Control (C#)" via the UI Builder and the foldout is added, but the foldouts are still missing when I add my HierarchyElement during runtime.

    This is the code I use for adding the element during runtime:
    Code (CSharp):
    1. public class HierarchyView : VisualElement
    2.     {
    3.         public void AddHierarchyElement(string name, int index)
    4.         {
    5.             var element = new HierarchyElement(name, index);
    6.             //element.RegisterCallback<ClickEvent>(OnElementClicked);
    7.             Add(element);
    8.         }
    9. }
    Adding other, not custom elements (e.g. scrollview) works fine, all its subelements are shown.
     
  35. Maverick

    Maverick

    Joined:
    Dec 18, 2009
    Posts:
    186
    I have a custom visual element that defines a bool attribute. Using @uDamian 's example. Everything works fine for that element.
    upload_2020-9-4_16-30-2.png

    But, if I use this element in a separated UXML and use it as template, I cannot modify it in UI Builder and template container does not have this new attribute.

    upload_2020-9-4_16-32-16.png

    Am I doing it wrong?

    Code (CSharp):
    1. public class ButtonVE : TextElement
    2.     {
    3.         public new class UxmlFactory : UxmlFactory<ButtonVE, UxmlTraits> { }
    4.         public new class UxmlTraits : TextElement.UxmlTraits
    5.         {
    6.             UxmlBoolAttributeDescription _useDoubleClick = new UxmlBoolAttributeDescription { name = "use-double-click", defaultValue = false };
    7.  
    8.             public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
    9.             {              
    10.                 base.Init(ve, bag, cc);
    11.                
    12.                 var ate = ve as ButtonVE;
    13.                 ate.Clear();
    14.                 ate.UseDoubleClick = _useDoubleClick.GetValueFromBag(bag, cc);
    15.             }
    16.         }
    17.  
    18.         public bool UseDoubleClick { get; set; }
    19. }
     
  36. WAYN_Games

    WAYN_Games

    Joined:
    Mar 16, 2019
    Posts:
    869
    I have not tryed to add a custom element through c# script but when calling new HierarchyElement(name, index); you are not going through the uxml factory I think.

    I tried to move the creation logic from the uxml factory to the constructor of the cutom element and it works teh same way for my use case.

    So in your case you can try something like

    Code (CSharp):
    1. public new class UxmlFactory : UxmlFactory<HierarchyElement, UxmlTraits>
    2.         {
    3.        
    4.         }
    5.  
    6.  
    7.         public HierachyElement()
    8.         {
    9.             VisualTreeAsset visualTree = Resources.Load<VisualTreeAsset>("HierachyElement");
    10.             visualTree.CloneTree(this);
    11.         }
     
    haruka_izumi and AKocourek like this.
  37. WAYN_Games

    WAYN_Games

    Joined:
    Mar 16, 2019
    Posts:
    869
    the boolean is on the buttonVE not on the template container, you are not selecting the proper element in hte ui builder to see the bool property I think.

    Edit : sorry, I read too quickly your post.
    Your issue is that you can't edit the bool property when the element is in a template.
    I don't think there is anything you can do about it, you should probably, either use the custom element directly or make a template for this with the bool set to true and another for the bool set to false. After all, the aim of the template is to provide a simple way to repeat identical element without having to duplicate everything every time. If the property is not the same, it's not the same element IMO.
     
    Last edited: Sep 4, 2020
  38. AKocourek

    AKocourek

    Joined:
    Nov 6, 2017
    Posts:
    6
    Thank you very much!!!!
    That does the trick.
     
  39. WAYN_Games

    WAYN_Games

    Joined:
    Mar 16, 2019
    Posts:
    869
    Glad it worked, I updated my code too since it seems to be a better way to do it ;).
     
    AKocourek likes this.
  40. Maverick

    Maverick

    Joined:
    Dec 18, 2009
    Posts:
    186
    Just wondering if there is a way to have List<T> as an attribute?
    My use case is that I need a set of default values for my custom visual element. I can add them as a const directly in the code, but that will require code change each time I need to update those values.
    Another, way would be to have a string attribute with value as a comma separated string and use it as starting point.

    Any advice?
     
  41. WAYN_Games

    WAYN_Games

    Joined:
    Mar 16, 2019
    Posts:
    869
    I have not worked with uxml attribuers yet but maybe try to look at :
    https://docs.unity3d.com/2020.1/Documentation/Manual/UIE-WritingUXMLTemplate.html if not done yet, it talks about attributes and overriding them.
    Maybe you can define your default values in your template UXML and override them when you use the template in the 'final' uxml.

    For List<T> attributes, I don't think they are supported now in the UI builder, but I would look into :
    https://docs.unity3d.com/Packages/c...IElements.UxmlEnumAttributeDescription-1.html

    or try to extend:
    https://docs.unity3d.com/Packages/c...Elements.TypedUxmlAttributeDescription-1.html
     
  42. jGate99

    jGate99

    Joined:
    Oct 22, 2013
    Posts:
    1,722
    This post should be pinned
     
    daneobyrd likes this.
  43. vsgt

    vsgt

    Joined:
    Jan 28, 2019
    Posts:
    2
    I have noticed that you must declare this variables as a property and property only. Field declaration wont work - the value will be resetting on save.
    Code (CSharp):
    1. public string path = "";
    This will not work. The resetting occurs
    Code (CSharp):
    1. private string path { get; set; }
    This will work. The value will be saved, despite it is a private property!

    My guess, it is related to the using the C# Reflection inside the UIT core
     
  44. Ferry_Proplanet

    Ferry_Proplanet

    Joined:
    Mar 19, 2020
    Posts:
    1
    This is exactly what i need to do, but somehow i can't figure out how to set the contentContainer to one of the child elements.
     
    Cuongdd likes this.
  45. AdamBebko

    AdamBebko

    Joined:
    Apr 8, 2016
    Posts:
    158
    Thanks for this info, but this is a bit counter intuitive. I've tried to use UI Debugger several times and gave up until I found this thread searching for something different.
     
  46. JuliaP_Unity

    JuliaP_Unity

    Unity Technologies

    Joined:
    Mar 26, 2020
    Posts:
    653
    Thanks for the feedback, but may I ask what was your expectation that made it "counter intuitive"? If we were to change this we'd need to know what users are actually expecting in order to get it right :)
     
  47. AdamBebko

    AdamBebko

    Joined:
    Apr 8, 2016
    Posts:
    158
    When I opened up the debugger I expected to see my ui documents listed. I wouldn’t have even thought about clicking on a settings file. I’m trying to debug my UI, not my settings. I expected the names of the UXMLs or the names of the UIDocument GameObjects in the current scene to be listed.

    It just seems very odd to click on something called "Panel settings" when I'm trying to debug my currently open UI assets. Once clicking on panel settings, it's immediately obvious how it works, but getting that initial step from intuition alone is a bit of a jump. In my dumb brain anyway :)


    Edit: Just a thought, perhaps just starting with the panel settings expanded would be enough to prompt the user that their own UI is in fact listed!
     
    Last edited: May 10, 2021
  48. JuliaP_Unity

    JuliaP_Unity

    Unity Technologies

    Joined:
    Mar 26, 2020
    Posts:
    653
    Thanks for replying! The "Panel Settings" name comes from the fact that it holds settings for the panel you see in the Game View. The UI Toolkit Debugger shows panels on the list so the panel you're using, which gets named the same as the PanelSettings asset, shows up there.

    The problem with using a UIDocument's name, or the UXML it holds, is that a single Panel, represented by using the same PanelSettings asset, can have multiple UIDocuments in it (and therefore potentially many UXMLs as well).

    The usability problem you're describing stands though, I'm just trying to explain where what you're seeing comes from, and now I can take this information back to the team and figure out how to prevent the next person from being confused as well - so thanks again :cool:
     
    AdamBebko likes this.
  49. AdamBebko

    AdamBebko

    Joined:
    Apr 8, 2016
    Posts:
    158
    Maybe just add a "Panel Name" field in the panel settings inspector. Have it default to "Un-named Custom Panel" or some such. Then, at least it's obvious that it's a panel in the debugger list and not some settings. Then users can overwrite the name of the panel if they like? Just any clue that that is actually YOUR panel in the debugger list, not just some random settings. I first clicked on scene view, then builder, the scene hierarchy window, thinking it was going to be in on of those, and then gave up
     
    Midiphony-panda likes this.
  50. Tehenauin

    Tehenauin

    Joined:
    Oct 18, 2018
    Posts:
    26
    Hey, What I don't understand is, how do I connect my custom VisualElement with the corresponding UXML?
    I basically copied the whole example class here, but I don't know how to continue. I Tried creating a template with the UI builder, but I could not achieve the desired result.
     
unityunity