Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

[UIElements] Binding

Discussion in 'UI Toolkit' started by Devi-User, Oct 19, 2018.

  1. Devi-User

    Devi-User

    Joined:
    Apr 30, 2016
    Posts:
    61
    I tried to understand the question about data binding, but it turned out to be too complicated.

    I found that there are Bind / Unbind methods in VisualElement. Unfortunately, these methods work with SerializedObject and are not suitable for binding other data.
    I also found the IBindable interface, IBinding, and the BindProperty method, which also implements the binding. But SerializedProperty is an object derived from SerializedObject, which also does not suit me. What scares me most of all in such a matter is the binding for arrays, and the need to synchronize them later.

    Who, where and how carries out this binding a riddle for me.

    And I do not understand the boundaries of binding. What exactly should it be able to do within this system? I assume that this should give the opportunity to both read and assign data to the fields automatically. For example, if I have a Label and some text that I want to substitute there, then it will be replaced if the field value of the class with which I associated it changes. Or if I have an IntField, then it allows you to both read and write the value in the specified int field. Confirm or disprove this, please.

    Quite often, we are not dealing with any data set stored within a SerializedObject. Is there any way to bind data if I just store it in an instance of a class?

    I also looked at the examples provided by Unity, but it gave me a rather vague idea of how to use it in real tasks and in cases where we have third-party serialization.
     
    Last edited: Oct 19, 2018
    a436t4ataf likes this.
  2. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    Binding in UIElements was primarily designed to work with SerializedObjects and SerializedProperties. This is the primary way binding is done in IMGUI as well. Most data in Unity proper is stored within Serializable objects (things like ScriptableObject, GameObject, MonoBehaviour, etc), so this lets you bind UI to any those objects with ease.

    To confirm, yes, you can bind an IntField value to a field on a SerializedObject and the values will be kept in sync between the two.

    That said, there's not much magic happening here. You can still do custom binding yourself to your own serialization system. Basically, you detect value changes from the UI via ChangeEvent<>'s, and you can use the built-in UIElements scheduler to monitor for changes on the model. Obviously, if you already know when your model changes and already have those hooks, simply update the UI values accordingly.

    This is covered in the intro video posted at the top of this forum, here:
     
    shekalo likes this.
  3. Devi-User

    Devi-User

    Joined:
    Apr 30, 2016
    Posts:
    61
    wow, that's cool! Thanks, this is exactly what I need.
    Forgive me my carelessness. I will definitely look at it before asking questions here next time.
     
  4. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,924
    Just to note: 2 years later and (still) little or none of this is described in the main docs page on Binding. It's magic-code and non-examples, with no explanation of what is happening, why, or how to use it (just trivial examples that can't work on non-trivial cases).

    Also, while I'm personally OK using SP/SO, in general SerializedProperty/SerializedObject add boilerplate code to your classes (and the benefits often aren't valuable, depending on the project), so are still avoided by a lot of people. Even if it's been designed for working with SP/SO (which is fine), it still needs to be documented for plain-old-C# code.

    (https://docs.unity3d.com/2020.2/Documentation/Manual/UIE-Binding.html)
     
    Pecek, grimmgames and viridisamor like this.
  5. Xarbrough

    Xarbrough

    Joined:
    Dec 11, 2014
    Posts:
    1,188
    The docs are still pretty sparse on how binding for specific controls work. e.g. PopupField.

    PopupField seems to support some sort of binding, but it's a little confusing to understand what exactly. It seems to support binding to an Enum serialized property. I assume this means it'll assign the enum value index correctly as the selected value and display the enum names as options, but not entirely sure. Does it use a consecutive index or does it respect enums with indices like Red = 0, Blue = 5, Green = 7?

    I'd actually like to use automatic binding for custom class/structs as well, but that doesn't seem possible. I can pass List<MyClass> to the options, but it runs into a lot of issues with serialization (internally it compared the selected instance with the list of instances, but after deserialization, the instances are different than before).

    I'd also like to bind options separately from the selected value, for example, List<string> and another string property to hold the selected value. Alternatively, the selected index, maybe that would be even better.
     
  6. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,924
    Binding sucks, and the team needs to write the documentation.

    For PopupField in particular: be aware that they use .ToString() as a key (!) and you better make sure that every item has a unique value for that C# method, or else things go badly wrong.

    (is this documented? Of course not! Why document the fact you've hardcoded your class to rely upon a officially "display" only method https://docs.microsoft.com/en-us/dotnet/api/system.object.tostring?view=net-5.0 :))
     
    Last edited: Feb 27, 2021
    viridisamor, RoaBen and JasonC_ like this.
  7. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,924
    A year and a half later ... after more adventures in Binding ... ugh. Docs now (at bet) basically say: it doesn't work at all except for a dozen or so Unity proprietary classes, and a lot of Unity's own classes are still broken/can't work with serializedProperty etc ... and we're not going to document how you can fix any of this, just accept you can't write custom inspectors, or any Editor UI.
     
    Pecek, Xelnath and Kerihobo like this.
  8. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,924
    Returning to this thread because it's popped up for me:

    Unity 2022.1.latest appears to break some parts of Bind() / bindings completely (all my property drawers are now corrupt) - my project code that works 100% in 2019, 2020, 2021 ... silently fails and corrupts data in 2022, and it does it somewhere between the Bind and UIToolkit.

    Right now I'm declaring "2022 is a BETA release of Unity and unsupported" because I'm assuming this has broken a bunch of other people's projects too (people who actually develop on the betas - #yolo!) and they have the time to debug what UIToolkit changed (or what changed outside UIToolkit that has messed-up UIT). My initial investigations: there are no errors, the data is correct in the serializedobjet, but UIToolkit corrupts it when reading it in.
     
  9. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    Something that changed in 2022.1 is that the InspectorWindow UI bindings are now time-sliced. This should really not break any normal PropertyDrawers, but for very custom use cases, it's possible this delay can introduce a bug.

    That said, I need a lot more details about your use cases to give any kind of useful answers. Like:
    1. what do you mean by "corrupt"?
    2. by "all my property drawers", do you mean all the property drawers you wrote yourself, or ALL property drawers?
    3. by "UIToolkit corrupts it when reading it in", do you mean that it doesn't display the correct data, or that it actually writes corrupt date into the SerializedObject?

    As always, a proper bug report with repro steps would be far more helpful for us, but if you prefer to dive into details here, we'll see what we can do.
     
    a436t4ataf likes this.
  10. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,924
    Great, thanks! I haven't submitted a bug yet because I couldn't figure out where to even start in trying to isolate it.

    Observations so far:
    1. My subclasses of CustomPropertyDrawer are broken (I have a lot of custom drawers). Other drawers *appear* to be working fine - but NB I haven't got any code in my project that uses 3rd party (non-Unity) drawers, so I don't currenlty know if it's just me, or all CPD subclasses (if you have any suggestions of a 3rd party asset/lib/something to add to the project I can do that to see if that narrows it further)
    2. Dropdowns in my custom drawers that display Enums are now displaying as a blank dropdown (no value). It's as if I had replaced all the types of my Enum objects with different types (e.g. if it had been: Fruit { APPLE=0, ORANGE=1 } and I had changed it to: Fruit { APPLE = 2, ORANGE = 3} ... which would cause deserialization to be unable to hook-up the 'stored' value to the 'possible' current values)
    3. If I go to Debug inspector mode, the data is correct: it's deserializing correctly. In the past whenever I've had any kind of bug/data problem with this stuff then the 'data is wrong' has shown up in Debug inspector mode too, verifying that there was something wrong with the data I'd written/read from serialized. In this case: the serialized data appears fine
    4. BUT: ... my inspector does two things at once: it constructs the drawers for display (adds PropertyField instances etc) *and* it reads the current values out of them to display some cosmetic extra info (e.g. - not a real example, this is simplified - but if I were displaying a pair of Vector2's using the default drawers (or my customer drawers) then I might *also* display "the Rectangle represented by these Vector2's (first is size, second is position) has total area of 56.7 square pixels". i.e. I do some procedural read-only calculation using the current value of the fields). *This data is being read as '0' for everything, even when it's not. BUT: see the final point below.
    5. AND: ... the same/similar data is read direct from the MonoBehaviours (no use of SerializedProperty) at runtime -- that data is *correct* and displaying in-Editor correctly.
    6. NB: I have a lot of 'idiot-proofing' code, such that if a user puts some really stupid values in to their MonoBehaviours, or if Unity is returning null or missing data because something else crashed, then my code will generally catch it, convert it to sensible defaults (i.e. 0), and prevent spamming console errors. There are a few edge cases in UIToolkit that historically triggered that - briefly e.g. during an inspector being resized in certain cases - so I have those 'safeguards' in place, and it is entirely possible that Unity is actually returning data like '-1' for things and I'm automatically converting it to 0. It will take some digging to disable all safeguards and verify what the precise 'wrong' data is that's coming back from Unity.

    So ... TL;DR current guesses: *something* is going wrong at the point of deserializing the type data and/or the object values. Having written the above out, I think the next step for me is to disable all safeguards and check what precise data the Customer PropertyDrawers are seeing when they try to SetValueWithoutNotify on the newly-created PropertyFields -- it is possible (not guaranteed) that this data being wrong in just this one place ... has a chain-reaction effect that breaks it everywhere else.
     
  11. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,924
    Can you point me to more detail on what this means, and what impact it has on how we design/write code related to Bindings? I don't understand why any time-slicing would be appropraite in this scenario - I don't understans what this change is for (so ... it's highly possible that I will write code that breaks it :( ).
     
  12. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    Worth trying a very simple custom PropertyDrawer for a test struct, and see if that works for you.
    How do you assign/init your EnumFields?
    While in the debug inspector, try forcing the use of UI Toolkit default inspector generation by turning it on via the ... menu of the Inspector (might need developer mode on). Does everything still look ok?
    I really can't help but feel this detail is also the reason for your issues. There's a lot of questions here, like, how do you read that data? How long do you wait? How do you wait? How often do you read it? WHY are you reading it from the UI controls instead of the SerializedObject? This is not the most treaded path, use case wise, so it may be a little sensitive to changes in how we bind and draw the Inspector.
    Right. Ya, it's definitely starting to look like a UI-side issue.
    Curious where this investigation leads you.

    In 2022.2 (not 2022.1), we switched to using UI Toolkit to generate default inspectors instead of IMGUI. So, for example, in 2022.2+, you can create CustomPropertyDrawers that are entirely and only implemented in UI Toolkit and they will "just work" in default inspectors.

    While working on this, we ran into the very obvious and equally non-trivial issue of first-frame-performance. For heavy inspectors with lots of controls, IMGUI still is significantly faster to draw the first frame compared to UI Toolkit. This is because UITK needs to spend the entirety of that first frame actually creating 100s of C# objects, the VisualElements, and do the first-time layout and styling computations. This is really a problem specific to the first-frame. UITK was designed to be faster for all-but-the-first-frame (ie. the vast majority of cases), which is why it was made to be retained mode instead of immediate mode. But the stutter for UITK inspectors was too much to ignore.

    With that context, and since this is not an easy fix as it's an inherent con of being retained mode (and being in C#), we chose to alleviate this issue by time-slicing the binding process in the Inspector. This means that we'll bind (and therefore generate UI for) components and fields over multiple frames. On the first frame, we generate only as much as can be visible within the current size of the Inspector. After that, you'll notice the scrollbar getting smaller and smaller over time as we generate the rest of the components and fields. This time-slicing IS already present in 2022.1.

    So that's the context and the new situation. As for how this can break custom UI code, it mostly comes down to expectations around timing. If you expect the PropertyField to be fully initialized the very next frame after your call to CreateInspectorGUI() or CreatePropertyGUI(), then this will no longer always work. If you relied on first-GeometryChangeEvent capture from a PropertyField to know when it changed, this should continue to work.

    Hope that helps.
     
    a436t4ataf likes this.
  13. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,924
    Ah! That makes a lot of sense, thank you.

    I will explore all the above directions and come back to you. Either with 'it's my fault, I've found out what I did that messed it up', or a concrete bug report. Or ... if I can't figure out, at least more info.