VisualElement lacking equivalent to MonoBehaviour Messages

I’m sure this has been asked before, but could not see anything in the search.

Editing here to say that I’m not convinced by some of my original statements in the OP, but the discussion further down feels like it reveals that there is value in some enhancements to the event system to add events that are valuable for runtime UI, but maybe have limited use for Editor UI.

Original Text:

As far as I can tell with VisualElements, there is currently no system equivalent to the Messages that exist for MonoBehaviours (Start, OnEnable, OnDestroy etc.), or the overridable methods that exist for UIBehaviours (OnRectTransformDimensionsChange, etc.). This is probably fine for Editor UI, but is extremely limiting for Runtime UI, and greatly reduces the ability to create modular UI widgets

I would not expect the same events as MonoBehaviours, but absolutely would expect the following kinds of callbacks:

  • Awake / Start / Init - something after the XMLFactory.Init(), so gathering of references to other Visual Elements is possible. This I would consider essential for modularity

  • WillBecomeVisible / Invisible (usage equvalent to OnEnable / OnDisable in monobehaviours), again, pretty valuable for modularity

  • Update - This would be handy, but for retained mode UI, probably not all that sensible, however:

  • PreRepaint - called prior to the document repainting if the element will be redrawn is extremely valuable

  • OnDestroy or OnRemovedFromHierarchy (particularly useful if you use delegate-binding to drive UI as delegates cannot be unbound in destructors)

Some of these might be handled by the EventSystem (adding / removing from panel), though that seems to be very much about Input Handling and Editor Inspectors at the moment. But maybe some more general-purpose utility events could be added?

There are various ways to achieve what you’re looking for.

Awake/Start/Init
Either using constructors, or using events (AttachToPanelEvent).

WillBecomeVisible/Invisible
Depends on what you’re intending this for; Either custom events when changing style, or using the AttachToPanelEvent and DetachFromPanelEvent
Update
As you pointed out, this probably isn’t a great idea, but you could just emit a custom event from a MonoBehaviour if so desired. the GeometryChangedEvent may also be what you’re looking for in some scenarios.

OnDestroy/OnRemovedFromHierarcy
Either use a destructor, or use DetachFromPanelEvent, depending on your specific needs.

In some instances of using VisualElements managed by Unity Objects, I just dispatch an event from OnEnable/OnDisable or other unity event functions to handle specific scenarios (for instance, cleaning up when an editor window is closed).

For a (mostly) full list of built-in events, see here:

Hope that helps!

Thanks for the response! I figured some of this might be in the event system. Couple of questions, if you know the answers offhand, otherwise I’ll try digging in next week.

Does the inital AttachToPanelEvent happen after the entire hierarchy is there, or is it going to be triggered on each element immediately as it is attached?

I guess I’m most interested in “Display” style changing - there’s so may cases where I would previously do stuff in a UI widget OnEnable or OnDisable. I’m guessing that won’t trigger a Attach/Detach event? Could you point me in the direction of how a style change event would work - the API and reference for events is a bit vague beyond the obvious user input events. There’s a ChangeEvent that has no practical usage info, so I’m not sure if that’s the one I would want?

Yeah, I can see that being an approach, but I’ve seen what happens with a moderately complex ingame UI that tries to manage ALL of the functionality at the window level and things tend to become a mess of code. That’s why I’m attempting to push some responsibility into custom VisualElements, for the sake of modularity.

I’m definitely going to look a little further into this next week, but even if some of this is possible it feels very much like workarounds, so if anyone from Unity is reading, I’d still give the feedback that maybe the Event system is suitable for Editor UI but might want some beefing up for in-game things?

Just checked, it’ll be each element as it’s attached, and it doesn’t bubble/trickle. If you need to know when everything is ready, you would likely need to add handlers to each one, then call something on your parent, or emit an additional custom event the parent could listen to.

No, it may emit a GeometryChangedEvent though, but I haven’t checked. At the very least, the parent would receive a GeometryChangedEvent, changing the display type would trigger a relayout.

It would depend a lot on how you’re handling the show and hide; If you’re doing it via code, then as you change the style, you could just emit a custom event.

Something like:

public class ParentElement : VisualElement
{
  ParentElement()
  {
    var child = new ChildElement();
    Add(child);
    RegisterCallback<VisibilityChangedEvent>((evt) => { Debug.Log("do something"); });
  }
}

public class ChildElement : VisualElement
{
  public void ToggleVisibility(bool visible)
  {
    style.display = visible ? DisplayStyle.Flex : DisplayStyle.None;
    using (var evt = VisibilityChangedEvent.GetPooled())
    {
      evt.target = this;
      evt.target.SendEvent(evt);
    }
  }
}

public class VisibilityChangedEvent : EventBase<VisibilityChangedEvent>
{
   // more useful stuff here...
}

This is a super minimal example, but hopefully points you in the right direction to achieve what you want.

True, but events can be made to bubble/trickle through elements, so each element can handle it’s own cleanup, listening to one event.

public class DisableEvent : EventBase<DisableEvent>
{
    public DisableEvent()
    {
        LocalInit();
    }

    protected override void Init()
    {
        base.Init();
        LocalInit();
    }

    private void LocalInit()
    {
      bubbles = true;
      tricklesDown = true;
    }
}

public class UICanvas : MonoBehaviour
{
  private VisualElement _element;
  OnDisable()
  {
    using (var evt = DisableEvent.GetPooled())
    {
      evt.target = _element;
      evt.target.SendEvent(evt);
    }
  }
}

Then in each element that needs it, you can register a callback for the disabled event, and handle the cleanup independently.

Alright, thanks It’s well past working time on a Friday for me so I won’t be looking at this until Monday, but thanks a lot for taking the time to help and dropping the code snippets.

Cheers,

Not fond of having to handle custom events for Displayed/NotDisplayed or Visible/NotVisible switch.

On my side, I had some success relying on GeometryChangedEvents to monitor changes on style.display property :

        private void OnGeometryChanged(GeometryChangedEvent evt)
        {
            Debug.Log("[OnGeometryChanged] Resolved style : " + this.resolvedStyle.display);
            Debug.Log("[OnGeometryChanged] Previous display : " + this._previousDisplay);
           
            // Do things if (_previousDisplay != this.resolvedStyle.display)
           
            _previousDisplay = this.resolvedStyle.display;
        }

It works if the display property of this exact element is modified.

But it doesn’t work, for example, if we change the display of a parent element : resolvedStyle.display is always true, even if the GeometryChangedEvents seem to be triggered at the appropriate moments.

Just found this :

According to @antoine-unity , you could write something like :

private void OnGeometryChanged(GeometryChangedEvent evt)
{
         if (evt.newRect == Rect.zero)
         {
             // "Likely", DisplayStyle was set to None
         }
         else if(evt.oldRect == Rect.zero)
         {
             // "Likely", DisplayStyle was set to None and is back to Flex
         }
}

Agreed.

Thanks for this tip - this seems like a decent stopgap. But it does feel that the “likely” is doing an awful lot of work there and really just highlighting the issue: if we’re having to hack around to infer maybe some indication of what the engine probably is doing (except in undefined edge cases) it might be better if the engine just told us?

Going back to my original post - I don’t think OnDestroy actually has an sane equivalent for VisualElements, but still think the following are essential events for reliable behaviours that should be added at the engine level:

  • Awake/Start (after all hierarchy is in place but before the first draw. Also called for new elements that are spawned via code.)
  • OnDisplayChanged
  • WillRepaint (for elements that are dirty and will be repainted, probably at the very beginning of the GUI rendering phase of the execution order

Edit: Addiitonal note. UIToolkit has no concept of Editor vs Runtime for the events, which makes this stuff extremely valuable. We can’t even use things like EditorApplication.IsPlaying() as that will still return true in UIBuilder, so we have to resort to lots of defensive coding, null pointer checks, etc, on top of the hacky workarounds. Again, I think this comes back to the Runtime portion being very new and just still needing some more features

I wouldn’t hold your hopes up for getting MonoBehaviour events into UI Toolkit. From what I can gather, I think they want to keep this new UI system reasonably isolated from the traditional GameObject/Component model, likely to better future integrations with the new DOTS/ECS style approach (not that I’m very familiar with any of that). Furthermore, it’s fairly contrary to the ideal of simulating web-style retained UI models.

Personally, implementing custom specific methods and emitting events feels pretty safe and natural to me, however I do hail from the lands of front end web development, so I’m pretty used to these sorts of constraints. I’ve seen plenty of web frameworks come and go that have attempted implementing lifecycle methods, which are usually abandoned in favour of either events or handling checks on re-render.

Listening to Geometry changed events can work well in some cases, but it feels frankly quite flimsy for a lot of situations, and doesn’t give you much information as to the cause of the event, unless you do further checks against your current document state to determine what changed, which can also be expensive if there are frequent changes; which would become more likely as animation gets thrown into the mix. I find explicit methods and custom events generate much clearer, safer solutions for working with this web UI imitation. Of course, I don’t know precisely what you’re aiming for, and I’ve mostly used UI Toolkit for editor-only purposes thus far.

I’m not suggesting getting the same MonoBehaviour messages into the UI, I’m talking about the fact that these messages exist for good reason in game systems and get used extensively in uGUI (Which was also retained) too. They shouldn’t be the same, but adding events for important changes is just a sensible thing to do.

As you say, listening for geometry changed is very flimsy but so is the visibility change event example you posted. For your example, everything that listens to visibility change also needs to track it’s own visibility internally, in case multiple ancestors get hidden. Then of course you also need to track reparenting changes, and maybe try to infer new values after that happens which you probably can’t actually do reliably and then you’re just playing whack-a-mole with edge-cases, because it’s hacking together a solution to the problem without the right information. That’s not “cleaner and safer” than having a reliable event from the engine and suggesting that it does makes me wonder if maybe we are misunderstanding each other?

Edit - I’ve also noticed that the above example for a custom visibility event literally doesn’t do the desired behaviour. As in, when you set the element not visible, it will tell all the ancestors that one of their children is not visible any more, but not tell the descendents. So it’s basically useless for the purpose - it’s elements further down the heirarchy that need to get that event, and the event system does not do that.

As for not providing useful callbacks because they want this system to be more like ECS… That’s… just odd. Because this isn’t ECS, it’s not data oriented. It’s completely incompatible at the level of VisualElements, unless one or both of those systems completely change their underlying principles.

I’m also honestly not seeing the idea that GeometryChanged or Attach/Detach are “good, acceptable events” and "Display/NotDisplay are “bad, unacceptable events” (or “contrary” as you put it) for this style of GUI. And it’s also very important to remember that even if the inspiration is from Web UI, this is not, actually, a tool for the Web.

So I 100% agree that they shouldn’t try to mimic MonoBehaviours just for the hell of it. But there is an event system here and expanding to provide key events that are valuable for creating in-game UI is a good thing. And if those events represent engine-level data, then it’s a million times better that those are provided by the engine, than by hacky, unreliable, custom user events.

Hello there,

I am facing the very same problem right now, using Unity 2022 LTS.
I want to create a custom reusable component using a UI Document, and I need a way to call a piece of code only once during the lifetime of the custom VisualElement I have, just like the Awake is for MonoBehaviours.
The code I want to run is basically some initialization of the component (registering callbacks and running some functionality which is supposed to run immediately after the component is created).
The component is added via UXML and not at runtime.

Is there a solution for this problem yet? If not, how did you go about achieving it?

You can do this either in constructor of your custom visual element or in AttachToPanelEvent (of course you should register to AttachToPanelEvent in constructor).