Search Unity

PropertyField without a label?

Discussion in 'UI Toolkit' started by dthurn, Aug 28, 2020.

  1. dthurn

    dthurn

    Joined:
    Feb 17, 2015
    Posts:
    77
    Is there a way to render `PropertyField` in UIToolkit without the attached label? I just want the textbox part of it without having to re-implement the binding stuff.

    If you pass null or the empty string it auto-generates a label. The label is hardcoded to take up a ton of horizontal space even if it's only 1 character.
     
    MostHated likes this.
  2. dthurn

    dthurn

    Joined:
    Feb 17, 2015
    Posts:
    77
    can I put in a feature request for this maybe? would be very useful for editor tools :)
     
  3. MostHated

    MostHated

    Joined:
    Nov 29, 2015
    Posts:
    1,235
    I have been trying to figure this out as well. I swore somewhere, I saw someone mention something about how to remove it the other day, but now when I need it, I can't find it.

    Edit - - Nevermind, what I saw was for IMGUI, using GuiContent.None.

    I tried this, just to see, but no luck.

    Code (CSharp):
    1. var emptyLabel = new Label("Test") {style = {display = DisplayStyle.None}};
    2. var listValue = new PropertyField(valueField.GetArrayElementAtIndex(i), emptyLabel.text);
    I am sure there has to be some way?

    --- Edit again: After some playing around, I have the beginnings of a "not so elegant" solution.
    I have been working on making a dictionary display, so mine has a key and a value, but it might still help you out.

    I moved the "Element" portion of the field out of the bounds of the box by using a negative margin and it stays fixed if you resize the inspector.

    I just need to see if I can make the right side "value" field size based on the width of the parent so that it can expand when you resize the window, but it's a start.

    Code (CSharp):
    1. var scroller = new ScrollView();
    2.  
    3. for (int i = 0; i < keyField.arraySize; i++)
    4. {
    5.  
    6.     var layerEntry = new VisualElement {focusable = true ,name = $"Entry: {i}", style = {flexDirection = FlexDirection.Row, justifyContent = Justify.FlexStart}};
    7.  
    8.     var listKey = new PropertyField(keyField.GetArrayElementAtIndex(i));
    9.     var listValue = new PropertyField(valueField.GetArrayElementAtIndex(i));
    10.  
    11.     listKey.style.minWidth = 275;
    12.     listKey.style.overflow = Overflow.Hidden;
    13.     listKey.style.left = -150;
    14.  
    15.     listValue.BringToFront();
    16.     listValue.style.width = 575;
    17.     listValue.style.marginLeft = -350;
    18.  
    19.     layerEntry.Add(listKey);
    20.     layerEntry.Add(listValue);
    21.  
    22.     scroller.Add(layerEntry);
    23. }
    24. box.Add(scroller);
    25.  

    Before:


    After:
     
    Last edited: Sep 7, 2020
  4. MostHated

    MostHated

    Joined:
    Nov 29, 2015
    Posts:
    1,235
    I figured this was best put in it's own post instead of an edit.

    I was able to do something of a workaround for the time being by setting the label to a single space " " then using .uss to set a negative margin on the field to move the actual data part over to the left.

    I was using mine to display the key/value of a small custom dictionary I was working on, so there is a bit more than needed if you just wanted to display something from an array but without the label "element 0", "element 1", etc. I set it up to use a different .uss selector depending on what the value type is.

    Smaller inspector:



    Expanded inspector:


    Code (CSharp):
    1. var scroller = new ScrollView();
    2.  
    3. for (int i = 0; i < keyField.arraySize; i++)
    4. {
    5.     var keyType = keyField.GetArrayElementAtIndex(i).type;
    6.     var valueType = valueField.GetArrayElementAtIndex(i).type;
    7.  
    8.     // ----------------------------------------------------------- Dictionary Container
    9.     // -- Dictionary Container --------------------------------------------------------
    10.     var layerEntry = new VisualElement {focusable = true, name = $"Entry_{i}"};
    11.     layerEntry.AddToClassList("serialDictionaryContainer");
    12.  
    13.     // ----------------------------------------------------------------- Dictionary Key
    14.     // -- Dictionary Key --------------------------------------------------------------
    15.     var listKey = new PropertyField(keyField.GetArrayElementAtIndex(i), " ");
    16.  
    17.     switch(valueType)
    18.     {
    19.         case string a when a.Contains("GuidReference"):
    20.             listKey.AddToClassList("serialDictionaryKeyGuid");
    21.             break;
    22.         default:
    23.             listKey.AddToClassList("serialDictionaryKey");
    24.             break;
    25.     }
    26.  
    27.     // --------------------------------------------------------------- Dictionary Value
    28.     // -- Dictionary Value ------------------------------------------------------------
    29.     var listValue = new PropertyField(valueField.GetArrayElementAtIndex(i), " ");
    30.     switch(valueType)
    31.     {
    32.         case string a when a.Contains("GuidReference"):
    33.             listValue.AddToClassList("serialDictionaryGuidValue");
    34.             break;
    35.         default:
    36.             listValue.AddToClassList("serialDictionaryValue");
    37.             break;
    38.     }
    39.  
    40.     listValue.AddToClassList("serialDictionaryValue");
    41.     listValue.BringToFront();
    42.  
    43.     layerEntry.Add(listKey);
    44.     layerEntry.Add(listValue);
    45.  
    46.     scroller.Add(layerEntry);
    47. }

    Code (JavaScript):
    1. /* -------------------------------------- Serial Dictionary
    2.    Serial Dictionary ----------------------------------- */
    3. .serialDictionaryContainer {
    4.     flex-direction: row;
    5.     justify-content: space-between;
    6. }
    7.  
    8. .serialDictionaryKeyGuid {
    9.     min-width: 415px;
    10.     max-width: 415px;
    11.     overflow: hidden;
    12.     left: -150;
    13. }
    14.  
    15. .serialDictionaryKey {
    16.     min-width: 275px;
    17.     overflow: hidden;
    18.     left: -150;
    19. }
    20.  
    21. .serialDictionaryValue {
    22.     width: auto;
    23.     flex-grow: 200;
    24.     margin-left: -300;
    25.     margin-right: 0;
    26. }
    27.  
    28. .serialDictionaryGuidValue {
    29.     width: auto;
    30.     flex-grow: 200;
    31.     margin-left: -300;
    32.     margin-right: 0;
    33. }

    I can't help but feel there is probably a better way to go about this, though.

    I was looking more into the query engine and I found that you don't *have* to be using a uxml termplate to make use of it, it seems you can use it on pretty much any container. I think there is more to it though, such as the Query().Build() functions and such, which I need to look more into.

    In the UI Debugger it shows the data being displayed in the Hierarchy as a normal VisualElement() object, so I was trying to see if I could get a hold of the actual ObjectField after it was done being used by the PropertyField serialization stuff, trying different variations of the lines below, but no luck so far. The closest I was able to get back was a result which was a "PropertyField:NameOfMyClass", I could not seem to get any of the individual items in it yet, though.

    Code (CSharp):
    1. // Tried things like this:
    2. var objectField = container.Query<PropertyField>().Descendents<ObjectField>().ToList();
    3. // As well as:
    4. var objectField = container.Query<PropertyField>().Children<ObjectField>().ToList();
    5. // None of them really got me what I was after though:
    6. var objectField = container.Query().Children<PropertyField>().Children<ObjectField>.ToList();
     
  5. AlexMares

    AlexMares

    Joined:
    Mar 4, 2015
    Posts:
    498
    Here is how to do it. Enjoy :)


    public enum EnumTypes
    {
    One,
    More,
    Time
    }

    [Serializable]
    public class MyClass
    {
    public int MyInt;
    public float MyFloat;
    public string MyString;
    public EnumTypes MyEnum;
    public Vector2 MyVector2;
    public Sprite MySprite;
    public GameObject MyGameObject;
    }

    [CustomPropertyDrawer(typeof(MyClass))]
    public class MyClassDrawer : PropertyDrawer
    {
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { }

    public override VisualElement CreatePropertyGUI(SerializedProperty property)
    {
    var root = new VisualElement();

    root.Add(new IntegerField()
    {
    bindingPath = "MyInt",
    tooltip = "This is my Integer Field",
    style =
    {
    minWidth = 80,
    flexGrow = 1
    }
    });

    root.Add(new FloatField()
    {
    bindingPath = "MyFloat",
    tooltip = "This is my Float Field",
    style =
    {
    minWidth = 80,
    flexGrow = 1
    }
    });

    root.Add(new TextField()
    {
    bindingPath = "MyString",
    tooltip = "This is my Text Field",
    style =
    {
    minWidth = 80,
    flexGrow = 1
    }
    });

    root.Add(new EnumField()
    {
    bindingPath = "MyEnum",
    tooltip = "This is my Enum Field",
    style =
    {
    minWidth = 80,
    flexGrow = 1
    }
    });

    root.Add(new Vector2Field()
    {
    bindingPath = "MyVector2",
    tooltip = "This is my Vector2 Field",
    style =
    {
    minWidth = 80,
    flexGrow = 1
    }
    });

    root.Add(new ObjectField()
    {
    bindingPath = "MySprite",
    tooltip = "This is my Object Field, set to receive Sprite types only (allowSceneObjects = false)",
    objectType = typeof(Sprite),
    allowSceneObjects = false,
    style =
    {
    minWidth = 80,
    flexGrow = 1
    }
    });

    root.Add(new ObjectField()
    {
    bindingPath = "MyGameObject",
    tooltip = "This is my Object Field, set to receive GameObject types only (allowSceneObjects = true)",
    objectType = typeof(GameObject),
    allowSceneObjects = true,
    style =
    {
    minWidth = 80,
    flexGrow = 1
    }
    });

    return root;
    }
    }
     
    jjbish likes this.
  6. broots

    broots

    Joined:
    Dec 20, 2019
    Posts:
    54
    +1 for feature request, this would be really useful and is currently just a jumble of hacks if you want this behaviour.
     
  7. robochase

    robochase

    Joined:
    Mar 1, 2014
    Posts:
    244
    +1, seems like an oversight in the API
     
  8. andrew_pearce_

    andrew_pearce_

    Joined:
    Nov 5, 2018
    Posts:
    169
    Adding GUIContent.none worked for me (Unity 2018.4.36):

    Code (CSharp):
    1. EditorGUILayout.PropertyField(this.serializedObject.FindProperty("xSize"), GUIContent.none);
    I hope it will save someone's time.
     
    chrismightier, baerbel, MUGIK and 5 others like this.
  9. Ashfid

    Ashfid

    Joined:
    Jun 8, 2019
    Posts:
    20
    This forum is for UI Toolkit not the EditorGUI one.
     
  10. andrew_pearce_

    andrew_pearce_

    Joined:
    Nov 5, 2018
    Posts:
    169
    Oh, I did not pay attention to that. This topic was in Google search for the keyword how to get rid of label for property in custom editor script. So I am sure someone else we find it the same way, as I found and it will save their time.
     
    baerbel, MUGIK, ajaxlex and 1 other person like this.
  11. hawaiian_lasagne

    hawaiian_lasagne

    Joined:
    May 15, 2013
    Posts:
    124
    I was able to clear the label after binding the property like this:
    upload_2022-9-29_23-42-6.png
     
  12. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    It's a core bug in UIToolkit, known for at least 5 years, still unfixed. By definition the API is required to work the same as the API it replaces - PropertyField always had this feature. The source code for UIToolkit attempts to honor the contract, but apparently the UIToolkit team never tested this, since it never worked in 2018, 2019, 2020 (that I remember). I think it works in some versions of 2021? 2022?

    @hawaiian_lasagne which version of Unity are you using? Because what you wrote won't / cannot work in most versions. The API is supposed to work with that - but also with passing-in that value as a parameter to the "PropertyField( ... )" constructor - the problem in this thread is: it simply doesn't do that.

    The correct approach in theory - given Unity's bug - is to post-update the Label eg:

    Code (CSharp):
    1. propertyField.Q<Label>().style.display = DisplayStyle.None;
    ...but this crashes UIToolkit because the general implementation of PropertyField is so poor.

    You can (again in theory) workaround that too by waiting for a GCE, and in the GCE do the above, so it ends up something like:

    Code (CSharp):
    1.  
    2. propertyField.RegisterCallback<GeometryChangeEvent>( evt =>
    3. {
    4. propertyField.Q<Label>().style.display = DisplayStyle.None;
    5. }
    6.  
    and this works, sometimes. But there are many problems with GCE. Most notably: It is never called for a lot of items. And it's called randomly for most items. (I believe: it's called for the top level item and then its random - based on a caching layer - whether child items get the callback too. This would be fine except that the UIToolkit team made this callback the only way to workaround a lot of bugs in UIToolkit, so we're reliant on it).

    TL;DR: to fix this, you have to write a complete "callback system for UIToolkit" that detects top-level GCEs, and filters them down to all child VisualElement instances, and then in your "fix for Unity's callbacks" system you can (finally!) do the one-line fix for each PropertyField that's gone wrong.
     
  13. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    (NB: I beleive the ultimately correct workaround would be to listen to the non-existent callback:

    Code (CSharp):
    1. propertyField.RegisterCallback<BindingCompleted>( evt => {
    2. propertyField.Q<Label>().style.display = DisplayStyle.none;
    3. });
    ...but as far as I know UIToolkit doesn't expose that callback)
     
  14. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Actually that gave me an idea for a solution that seems to work in multiple Unity versions:

    Code (CSharp):
    1. propertyField.RegisterCallback<ChangeEvent<string>>(evt =>
    2. {
    3.     propertyField.Q<ObjectField>().Q<Label>().style.display = DisplayStyle.None;
    4. });
    ...because this sits on the back of different bug in UIToolkit, where they generate false/fake "ChangEvent" every time an Inspector is rendered. Typically: "ChangeEvent: 0 goes to 0", or "ChangeEvent: true goes to false, false goes to true".

    This *approximates to* "binding just finished", so we can abuse that as the callback.
     
  15. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    Setting the PropertyField's label to an empty string works for me the same as GUIContent.none in IMGUI. Also, setting it to null assigns the property's displayName to the label. It works both by assigning the label through the PropertyField's constructor, and through its label propertyl.

    There are two cases in which this doesn't work in my experience. One is when using property drawers, although 2022.2 added the preferredLabel property for that. The other case is when the PropertyField is bound before setting the label. hawaiian_lasagne's example works because, even though it calls Bind before setting the label, the PropertyField won't be bound until it's added to a panel.

    Are there any other cases you've found where this doesn't work?
     
  16. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Which version of Unity are you using?

    Right now 2020 LTS is still broken (as I'm using it every day) - none of what you say is true there (unless there's a new build in the last week or so - last time I updated was end of January). What you describe is what the code has always existed for, but never worked.
     
  17. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    Oh. Support for this was added in 2021. I think this can't be added to 2020 because it is a breaking change.
     
  18. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    I ... guess? I mean ... all bugfixes are - in a way - 'breaking changes' :).
     
  19. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    I guess so, in a kind of twisted way; but this isn't a bug, it's the lack of a feature. Sometimes features can be added without breaking anything, but not this one. This changes how a property's value affects it's object. If they add the ability for a PropertyField's label to disappear when it's set to an empty string, they could cause some UIs to suddenly and unexpectedly be missing some labels.
     
    Last edited: Feb 21, 2023
  20. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Since Unity 3.x I believe (probably 2, but I did my first custom inspectors in 3.x):

    PropertyField:
    "label Optional label to use. If not specified the label of the property itself is used. Use GUIContent.none to not display a label at all."

    What the docs don't say is that both GUIContent.none, and "", and null ... all had the same effect: removed the label.

    UIToolkit docs have been terrible all along, but anyone who looked at a class called PropertyField, and assumed "this will work THE OPPOSITE of the identically-named class it replaces" deserves to have their code blow-up in their face. What person is going out of their way to pass null to make a label if they *don't* want it disappearing? The UIToolkit source code clearly says that it's going to remove the label (as I said: it's a bug that the code didn't work; the code was there)

    One of the recurring complaints I hear about UIToolkit from developers is how many excuses are made for its failings. If UIT did what it was supposed to, if it did what it claimed to, if it worked as it was designed to ... it would be great. But constantly seeing obvious broken parts and constantly getting excuse after excuse of how it's OK that it's broken, bugs don't need to be fixed, design mistakes don't need to be reconsidered etc ... really undermines confidence and adoption :(.
     
  21. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    That's not how it works as far as I know. EditorGUI.PropertyField and its EditorGUILayout counterpart use the property's default label when they receive null, and you can't pass "" as a label because it expects a GUIContent.

    I notice some anger in your text. I don't know if I'm imagining it and I don't know enough about your situation to know where these feelings come from. I've had my ups and downs with Unity's bug handling, as most Unity devs have, and those bugs include bugs in UITK. But, in my experience, the UI team at Unity is very talented, dedicated and they are genuinely trying to improve it constantly. Sometimes I wish they dogfooded UI Toolkit more before doing things like using it for default inspectors, but that doesn't mean I think they don't want to improve it.

    The docs can be missing some parts, but they are still full of useful information. I still miss some features for Unity docs in general, like a real path for documentation feedback; but, compared to most software projects, and especially compared to other game engines, Unity's docs are orders of magnitude above average.

    None of the things you've mentioned in your lasts posts are bugs in my opinion. Some are features that have been actually added; they aren't in 2020 LTS, but it's been a while since that version came out, and adding them to that version would mean breaking changes, which must be avoided for an LTS release.

    Some of the things you said are either user error or opinions about the implementation. For example, when you say that querying for a PropertyField's label "crashes UIToolkit because the general implementation of PropertyField is so poor", you are probably trying to get the label before the field has been attached to panel. Maybe the docs could be clearer about the relationship between panels and Editor bindings. Maybe the binding methods should have been called "requestBinding" to make this clearer (although it's probably too late to change them). Maybe it is your opinion that fields should bound immediately, but that could have big implications for performance and maintainability to support a use-case that's not very common and is easy to solve in other ways.

    I mean, I kind of get it, I've had to make many custom elements to support some use-cases too, many of which I think could have been handled directly by UIToolkit. But I don't see it as broken as you do, UITK has given power to my development process. Maybe I would see it as broken if I had had your experiences, I don't know. Many of the features I've been missing are being implemented in newer versions, and I don't feel it's fair to declare UITK as failing for not backporting those features; I expect them to fix bugs in the current versions and add features in the next ones.
     
    Last edited: Feb 21, 2023
  22. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    When UIToolkit went 'production ready' I started from code that had worked in EditorGUILayout since 2014, and worked in Unity 4, Unity 5, Unity 2017, Unity 2018, Unity 2019. I checked it and double-checked it. I looked at the source code (in both IMGUI and UIToolkit). When I say things like: 'it used to work, for many years, until UIToolkit replaced the class with an identically named one that appears to work the same except it's broken' I'm basing that on observation of 8+ years of using these API calls.

    Similarly, trying to get PropertyField to work nicely with these labels, I tried using PropertyField inside CustomPropertyDrawer (a common use case). I started from CustomPropertyDrawers (using PropertyField) that had worked in all previous versions of Unity, and which failed in UIToolkit. Eventually I was able to show that the UIToolkit team had retroactively broken *all* code using CustomPropertyDrawer, project-wide (i.e. if you have a CustomPropertyDrawer that works, then you create another one in UIToolkit, your original one breaks). When reported as a bug, after a lot of back-and-forth and further digging, they admitted this was true, but refused to fix it, if I remember correctly: on the grounds that they don't think people should be using IMGUI any more.

    These aren't 'features' they are serious bugs.
     
  23. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    Hi. Do you mean that if you have two PropertyDrawers like these:

    Code (CSharp):
    1. [CustomPropertyDrawer(typeof(MyType))]
    2. public class UITKDrawer : PropertyDrawer
    3. {
    4.     public override VisualElement CreatePropertyGUI(SerializedProperty property)
    5.     {
    6.        // UITK drawer code.
    7.     }
    8. }
    9.  
    10. [CustomPropertyDrawer(typeof(MyType))]
    11. public class IMGUIDrawer : PropertyDrawer
    12. {
    13.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    14.     {
    15.        // IMGUI drawer code.
    16.     }
    17. }
    18.  
    You expect UITKDrawer to be used with UI Toolkit's PropertyField and IMGUIDrawer with EditorGUI.PropertyField?
     
  24. risethesummer

    risethesummer

    Joined:
    Jul 3, 2023
    Posts:
    3
    I found a better event named GeometryChangedEvent Unity - Scripting API: GeometryChangedEvent (unity3d.com), I don't know which version it's supported but working well
     
  25. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    GCE has been in UIToolkit for many years (since it first launched?) - but creates many problems, and in general you should be avoiding it as much as possible today. If you only use it once, in one place - fine; but it's overloaded for a lot of things, and the definition of 'what' it does has changed in successive Unity versions (code written in 2019 broke in 2021, and again in 2022 if I remember correctly, because of changes to how/when UIToolkit invokes GCE) - so if you start using it a lot / in multiple places, it becomes a nightmare to maintain.

    If you can't solve something any other way, then use it - but using GCE is more of a temporary hack than a solution.
     
  26. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    No, I merely expected that both classes would compile and run; UIToolkit team broke it so that what you typed wouldn't work in any Unity project. The classes would compile, but at runtime one of them would be broken.

    In particular: if you had code ANYWHERE in your project (from your team, or from any asset or assetstore plugin that used UIToolkit-based CustomPropertyDrawers ... then all the otherwise correct, 'it was working 2 minutes ago', code that has worked for 15+ years, but which used IMGUI CustomPropertyDrawers ... instantly broke with no errors or warnings, and was unfixable without rewriting each asset/plugin/class that used them.

    I haven't checked if this is still broken in 2023 - I would hope they'd fixed it by now (e.g. when they ported the Inspector / IMGUIContainer stuff - in 2022 I think?) - but they never changed the bug report/resolution so it may still be there. I've been lucky enough not to work with any 3rd/1st party code or plugins that still use IMGUI containers recently, so it hasn't affected me for a while.
     
  27. GLeBaTi

    GLeBaTi

    Joined:
    Jan 12, 2013
    Posts:
    54
    My solution for properties without label and in one line:
    Code (CSharp):
    1.     public override VisualElement CreatePropertyGUI(SerializedProperty property)
    2.     {
    3.         // Create property container element.
    4.         var container = new VisualElement()
    5.         {
    6.             style = { flexDirection = FlexDirection.Row, justifyContent = Justify.SpaceBetween }
    7.         };
    8.  
    9.         container.style.alignItems = new StyleEnum<Align>(Align.Stretch);
    10.  
    11.         // Create property fields.
    12.         var fieldType = new PropertyField(property.FindPropertyRelative("Type"), string.Empty);
    13.         fieldType.style.flexGrow = new StyleFloat(1f);
    14.         var fieldTile = new PropertyField(property.FindPropertyRelative("Tile"), string.Empty);
    15.  
    16.         // Add fields to the container.
    17.         container.Add(fieldType);
    18.         container.Add(fieldTile);
    19.  
    20.         return container;
    21.     }