Any plans for a Begin/EndProperty equivalent?

One of the things I’m missing the most when making Editors with UI Toolkit is BeginProperty and EndProperty. It’s needed for correct behavior of Prefab Overrides and Localized fields, as it shows the blue/yellow indicators, and adds the proper context menus.

I know these cases can be covered by binding fields but that’s not enough sometimes. It’s specially needed when doing complex stuff without property fields in custom editors, or when property drawers have some nested stuff that’s not very simple. Also, sometimes one needs to do their own custom binding for fields (i.e. for custom Undo/Redo behavior), and it’d be nice to still be able to mark those fields as prefab overrides.

I think this is even more needed now that there’s a push to convert stuff in the Editor from IMGUI to UI Toolkit. In 2022.2, there’s more reason than ever to convert all my custom property drawers to UITK, but some of them are very hard to make work because of this missing feature. I’ve found some very tricky ways around it using bound foldouts, but it’s not a nice solution.


On a related note, it’d be nice if the property menu added by Unity used ContextualMenuPopulateEvent. Right now it uses PointerDown or MouseDownEvent, so we can’t add menu items to it.

EDIT
My suggestion for implementing this would be an extension method for VisualElements that marks an element as a SerializedProperty container, like this:

BindingExtensions.MarkAsPropertyContainer(this VisualElement ve, SerializedProperty prop);

I’ll be fine if it has to be done with a special VisualElement subclass, though.

1 Like

Would you mind providing an example of a case where PropertyDrawers do not work? Not saying there are no such cases, but it would help me be more specific in my answer.

One of the difficulties is that the entire concept of Begin/EndProperty doesn’t translate well to a retained-mode UI like UI Toolkit. We need something like an in-place type definition for a custom PropertyDrawer, inside the custom Editor class. It’s not impossible, it just can’t be copied 1:1 from IMGUI. So knowing more about specific use cases would help us prioritize this feature better.

If you don’t mind, can you report this as a bug?

: ) Of course. I think a good example for it is stuff similar to UnityEvents. In UnityEvents you can right click on the Event header to manage overrides for the whole event, you also get a blue indicator next to the header when the event contains any overrides. The fields for the listeners still get their own override indicators and menus.

Here’s an example of mine that’s very similar to the UnityEvent case. In my custom messaging system, there are Message Transmitters where one can assign a message ScriptableObject, then select a target to receive that message, and assign the appropriate values to be sent with the message. I have a header override indicator that represents the whole Message Transmitter, and then there are separate indicators for each of the relevant parameters:
8394207--1107819--Capture.PNG

The mere need of using PropertyDrawers would be limiting sometimes. It’s common to do custom editors where fields are drawn directly without a PropertyField; using property drawers in those cases would add a lot of complexity and classes.

Maybe it doesn’t need to translate all that Begin/EndProperty does. The mixed value functionality maybe doesn’t make a lot of sense. Personally, I’d be happy with just a way for adding a property menu and the respective prefab/localization indicators to a VisualElement that’s not an INotifyValueChanged. It kind of already works by binding a foldout to the parent property, but the foldout functionallity gets in the way.

Sure. I’ll make some time tomorrow to make a reproducible case.

Thanks for looking at this : ). I hope you have a nice day.

EDIT
I should add, in my Transmitter example, those three nested fields are actually structs that have their own multiple nested fields. By using Begin/EndProperty, I can treat them whole as a single overridable property in IMGUI; the right click menu shows items for the whole struct, instead of just showing menu items for an individual bound field like it would in UI Toolkit.

EDIT 2
The example I showed was in IMGUI. I’m realizing now that a UITK drawer might look the same, but the override indicator and the context menu in the header would be just for the object field inside it.

Maybe it wasn’t the best example…

Sorry for the double post, here’s another example, in case it helps. I have a plain serializable class called DataInput; if you ever watched RyanHipple’s ScriptableObject talk, it build ups on his value reference idea. It basically has multiple modes to assign a value either from a ScriptableObject, from a direct field in the inspector, or from other places. In the UI, it looks like a single field with an extra button to choose the mode for assigning the value, but inside it contains multiple different fields. So the same DataInput field could look like either of these two different images depending on its mode:

8394588--1107924--fieldExample1.PNG 8394588--1107927--fieldExample2.PNG

I want to be able to copy all the nested data inside the parent DataInput field from its menu. I also need to show an override marker even if the overridden field is hidden, and I want to be able to revert and apply that override from the context menu. It makes sense because one thinks of this group of fields as a single unit. Plus, otherwise I’d have some bad UX problems; e.g. if a single nested field is hidden and overridden, its component will say it contains prefab overrides, but I won’t be able to see where is that overridden field.

In IMGUI I can do it by using Begin/EndProperty with the parent DataInput property to surround the whole drawer. I can even use Begin/EndProperty with the parent property just on the little > button on the right, that way I can access the menu for the whole field by right-clicking that button, while being able to access the nested field’s menu from its label.

I’ve reported it now. It’s got the IN-15128 id. Please let me know if you need any additional information. :slight_smile:

EDIT
Corrected the report id

The thing is, without the INotifyValueChanged (ie. value { get, set }), what does it mean for this VisualElement to have the blue bar of “prefab override”? Even for the Foldout case, the Foldout itself only has blue bars because of INotifyValueChanged elements inside.

Apart from bugs, if you put all of this logic inside a custom property drawer (which in turn is instantiated by a PropertyField), wouldn’t you be able to do all this inside while gaining the right-click menu and prefab override support for the whole control?

I might be missing something still (very likely), but your examples seem like exactly what custom PropertyDrawers do best. I understand it’s a bit more code than doing something more “inline” in the custom Inspector (like Begin/EndProp), but it’s also a good forcing function to split UI code a bit more.

Just to be clear, I still agree there’s a need for something equivalent to Begin/EndProperty in UITK. In the meantime, I’m trying to dig a good workaround for you and ya, make sure there’s no hard blocker in UITK for your use cases.

Thanks! Will do.

It would mean that the element represents a composite property and that its nested properties are overridden. There are multiple cases in IMGUI where that characteristic is used, even in Unity’s own types (i.e. UnityEvent’s header).

Those are not the blue bars that I was talking about. The Foldout gets a blue bar that’s independent of any other INotifyValueChanged elements inside. A Foldout can be assigned a bindingPath, and then it’s bound to the isExpanded of a SerializedProperty; that makes it the only element that can currently be bound to a custom composite property. Since the blue bars and the property’s context menu currently can only be assigned through the binding system, it means it’s currently the only way to have blue bars and menu for a composite property.

At least in 2022.2 beta and 2021.3, PropertyDrawers don’t offer that capability. Only INotifyValueChanged fields get prefab and context menu support for their bound property. If you added that capability, it could solve some of these issues, but, in my opinion, it wouldn’t be the best way to do it, as it’d be very limiting and it could make it impossible to have feature parity with IMGUI.

I’m thinking that I should use UnityEvents to make an example. Imagine a prefab asset that has a UnityEvent like this:
8431880--1116539--Screenshot 2022-09-11 130246.png

Then, in a prefab instance we decide to make the name “ham” instead of “ha” when the event is fired. The first image is how it looks in IMGUI, the second image is how it looks in UI Toolkit:
8431880--1116557--Screenshot 2022-09-11 130611.png 8431880--1116560--Screenshot 2022-09-11 130653.png

IMGUI allows us to handle overrides for the whole event in the Event’s header. That means if we have multiple overridden fields in the event, even if those that aren’t shown currently in the UI, we can handle them together. It also means that we can right click in the header and copy/paste the whole event. Finally, having a dedicated area to handle the property as a whole means that we can still copy/paste and handle the overrides of individual fields inside the listeners.

In UITK we get none of that functionality in the header. We can only handle the override inside the individual fields (the string field that says “ham” in this case). Actually, we can’t even do that because the string field doesn’t have a label, and UI Toolkit fields currently require a label to show the property’s menu, but that’s a separate issue.

It’s specially ugly when that string field gets hidden. Imagine that now we change the GameObject.name popup to GameObject.isStatic. That would hide the string field. Then, imagine we apply the change from name to isStatic to the prefab asset. The string field that is hidden is still overridden, and the prefab instance says that it contains an overridden field, but without the functionality of IMGUI, we don’t have an idea of where is the overridden field nor a way to revert it. In UITK the event field would appear to contain no overrides, while in IMGUI the hidden override is handled in the header like this:
8431880--1116575--Screenshot 2022-09-11 132631.png

That functionality in the IMGUI header gets even more useful when more listeners are added. I’m pretty sure it’s implemented by having a BeginProperty/EndProperty block surrounding the header. If the whole property drawer had that functionality, we’d lose the individual markers on the individual fields that are overridden, and it’d be very confusing to know which property is supposed to be copied or pasted from the right click menu in any given place.

Finally, if we wanted to fix Unity’s UI Toolkit drawer for UnityEvents, to add that functionality in the header, we could do it by replacing the “On Click ()” Label with an empty Foldout. We’d bind that Foldout to the whole UnityEvent. Then we’d use USS to hide the Foldout’s arrow and make it look like a simple label. It’s kind of ugly and complicated.

If we had the ability of a Foldout to bind to any composite property without all the other foldout functionality, things would be a lot easier. Ideally, it would work for any VisualElement. I know that VisualElement’s have an internal bag where they can store all kinds of data, it’s used for things like TrackPropertyValue, so I imagine it could also be used for this functionality. But even if it was implemented with a separate Bindable element without INotifyValueChanged, it could solve this issue completely.

EDIT
I apologize if I’m still not explaining this well enough. Please tell me if there’s anything I could make clearer. This issue is important to me. If you think there’s another type of content or another form of communication that would help, I’d be happy to try to provide it.

1 Like

Hi, I’ve just noticed that the issue I was asked to report related to ContextualMenuPopulateEvent has been reproduced and added to the Issue Tracker. Here’s the link if anyone wants to see it.

I have a use case as well,
8988355--1237345--upload_2023-5-3_8-9-40.png
Here the “Grounded State” has a property drawer with other class objects inside them which just use the property field (and they work as expected) but as you can see I have the blue bar for the Factory(it’s a class with multiple state data inside it) label but not for the grounded state when I change something inside the Data dropdown.

So it’s really a pain point for me to find which state data I have exactly changed as I can’t really see any blue bars or bold labels in my case.
8988355--1237351--upload_2023-5-3_8-12-34.png

This is UILayout for property field:
8988355--1237354--upload_2023-5-3_8-13-59.png
.
And, I have a lot of elements with similar problems this is just an example.

Another example:
8988355--1237357--upload_2023-5-3_8-15-0.png

In this case, after I update the value of F I have to click on the float field itself to get the option to apply changes to the prefab.

Any help is really appreciated.

1 Like

I ended up making my own workaround for this. It’s in my UITK Editor Aid package. The element that fixes it is called Property Container (it’s code should work without the rest of the package, if you only want that). Please let me know know if you find any problems. :slight_smile:

I’d still love an official solution to handle prefabs and property menus properly, though.

Could you show a small example of how to use the PropertyContainer with foldouts?
Thanks a lot.

You may not need it for foldouts. Foldouts are the one element that can be bound to any SerializedProperty in Unity, at least for now. So you can do something like this:

// myCompositeProperty would be a SerializedProperty of your choosing
myFoldout.bindingPath = myCompositeProperty.propertyPath;
// Or just pass the path to the property if you know it:
myFoldout.bindingPath = "pathToMyCompositeField";

A nice thing of binding the foldout in this way is that it will remember it’s closed/open state throughout the session.

You can use a PropertyContainer like you would use a normal VisualElement. If you set its bindingPath property in the same way that the foldout’s bindingPath is set in the previous example, it will show the prefab override blue bar and the right-click menu for that property.

EDIT:
In both cases (Foldouts and PropertyContainer) you can also use the attribute “binding-path” in UXML instead of using code.

EDIT 2:
In both cases, like with any other field or element with a bindingPath, the elements need to be actually bound for it to work. This happens automatically in custom inspectors, but not in custom EditorWindows. Hmmm… I don’t know how much I should explain about this; let me know if you get stuck. :slight_smile:

1 Like

Yeah it works for now thanks a lot.

But I still couldn’t figure out how to use your PropertyContainer in my case.
Can you show an example of it?
I’ll show you my setup:
8988535--1237423--upload_2023-5-3_12-14-27.png

Custom Drawer:

8988535--1237426--upload_2023-5-3_12-15-1.png

I tried doing this:
8988541--1237429--upload_2023-5-3_12-16-5.png

But it didn’t do anything for me

I think I don’t see anything incorrect. It may be hard to see its effects because, since all elements are in a single row, all their blue bars would appear in the same place.

Can you right-click on the name label to see if you get a context menu for the whole SpringSetting? If you do, then it is working.

If you don’t, and you’re sure the field contains prefab overrides, could you remove everything from inside the PropertyContainer and just put something inside it that takes physical space to see if it works? That would help us diagnose whether there’s some weird interaction between the PropertyContainer and your elements, and we can try some more stuff to find out what.

1 Like

I recently had the exact same problem of needing BeginProperty / EndProperty and oscar’s PropertyContainer would only work partially on my unity version so I reverse engineered what unity is actually doing and came up with the following solution which unfortunately needs access to the internal BindingStyleHelpers which you either need to call via reflection/expressiontrees or by using one of the internalsvisibleto tricks (which is what I am using).

public static class BindingsHelper
{
    public static void RegisterPropertyFieldEvents(this VisualElement element, SerializedProperty property)
    {
        element.userData = property.Copy();

        var eventData = (element, property);

        element.RegisterCallback(AttachToPanelCallback, eventData);
        element.RegisterCallback(StopContextClicksCallback);
        element.RegisterCallback(BindingsStyleHelpers.RightClickFieldMenuEvent);
       
        element.TrackPropertyValue(property, UpdateState);
       
        void UpdateState(SerializedProperty obj)
        {
            BindingsStyleHelpers.UpdateElementStyle(element, property);
        }
    }

    static void StopContextClicksCallback(ContextClickEvent evt)
    {
        evt.StopImmediatePropagation();
        evt.PreventDefault();
    }

    static void AttachToPanelCallback(AttachToPanelEvent _, (VisualElement element, SerializedProperty property) data)
    {
        BindingsStyleHelpers.UpdateElementStyle(data.element, data.property);
    }
}

And the usage is just

objectField.RegisterPropertyFieldEvents(property);
1 Like

Hi, would you mind telling me what didn’t work and in which version in case I can fix it?

It doesn’t work if I just add a simple label inside it either:
8989105--1237549--upload_2023-5-3_18-11-31.png

8989105--1237552--upload_2023-5-3_18-11-47.png

Oh! Is this inside the UI Builder? It should be in an actual inspector or Editor Window so it can be bound, so it can work. Do this steps do anything different outside the UI Builder preview? Also, would you mind telling me what version of Unity is this?

EDIT
Oh. I see now that the first image isn’t from the builder. Sorry. It’s weird; it works for me in the latest 2021.3, and it’s worked in some older 2022.2 before. I’ll test in newer versions and report back. Thanks.

1 Like

it’s in the latest beta version 2023.1.0b14