Search Unity

VisualElement's Children Added/Removed event

Discussion in 'UI Toolkit' started by svobodajak, Aug 7, 2020.

  1. svobodajak

    svobodajak

    Joined:
    Jun 27, 2017
    Posts:
    11
    TL;DR;:
    Hello, I am wondering if there is any way to tell whether some element was added or removed to a VisualElement's children (contentContainer)?

    As far as I can tell, there has to be some way of doing this, since the ScrollView would need something like that to display/hide the scrollbar and/or change the scroll position...

    Details:
    I am implementing something similar to the ShaderGraph's grouping selection. It's a node editor, where a node can have other nested nodes:

    upload_2020-8-7_15-51-53.png

    Basically you have a graph area (that's the RHS in the image), which is just a container. Inside the graph area are nodes which can be positioned anywhere by dragging (so their position is absolute as there is no layouting to be done). Then inside each node another nodes can be nested. In the image you can see "Parent" having nested two nodes "Other" and "Node".

    Now when I drag the the nested nodes around their parent nodes, the parent changes its size to accomodate them. That's why you can see the "Other" and "Node" in the corners. If I move the "Node" closer to the center the parent will resize like this:

    upload_2020-8-7_15-53-41.png

    I have implemented this resizing by manually registering the GeometryChangedEvent callback on the child nodes, which were already in the parent statically in the UXML. In the callback I just set the parent width and that's it. But now I want to do it dynamically (ie add and remove children nodes at runtime), in a clean way and encapsulate it inside its own FloatingResizeContainer class which would inherit from VisualElement. Then I would imagine that whenever a child gets added/removed I would just register/unregister the events.

    The problem is after lots of hours of searching for the solution I wasn't able to find out if there is any way to actually tell if a child was added or removed from an element.
     

    Attached Files:

    Denis-535 and _geo__ like this.
  2. antoine-unity

    antoine-unity

    Unity Technologies

    Joined:
    Sep 10, 2015
    Posts:
    780
    Hello!

    We actually don't have such an event. I see to main 2 main solutions.

    1) Use GeometryChangedEvent. This should be sent regardless of elements being added after creation from UXML or elements added later. This should work fine for something that should react to resizing, although this does not work in all situations this is fairly generic.

    2) Introduce some coupling. This is what we do in our graph implementations at Unity. Our main container for the GraphView has a type that every node knows about. When a node detects it's added (with AttachToPanelEvent) it walks its parent hierarchy until it finds a GraphView and registers itself.

    Alternatively you could force your whole system to call a "AddNode" method in your main Graph class.

    In the future, we could expose another mechanism we have internally to detect hierarchy changes, which does not rely on events and is a bit more efficient. Let us know if you think that'd be useful.
     
    oleg_v, Denis-535 and svobodajak like this.
  3. svobodajak

    svobodajak

    Joined:
    Jun 27, 2017
    Posts:
    11
    @antoine-unity Thank you so much for the answer!

    1) I think this won't work in my case, since the Parent registers a GeometryChangedEvent on the child Nodes and after that reacts to their geometry changes. But this won't work unless the parent can dynamically register the callback on whatever is being added.

    2) I will try to apply this solution, although, when there are multiple possible places for this (multiple nodes can have multiple children), it's not going to be as straightforward.

    Yup, this also crossed my mind and it's something I wanted to avoid.Although if the solution through 2) will end up being too complex, I will do it this way.

    I definitely think that these kind of hierarchy changes events would be useful for other applications as well. So if they could be exposed, that would be great! :) On the other hand, what would also work is if the VisualElement's Add method was virtual and could be overriden in the subclasses. Then the client could just call base implementation and add anything he wants after that (something like a decoration).
     
  4. LudiKha

    LudiKha

    Joined:
    Feb 15, 2014
    Posts:
    140
    Is there a way in which we can override the Add() method of a visual element? That would solve this.
     
  5. ErnestSurys

    ErnestSurys

    Joined:
    Jan 18, 2018
    Posts:
    94
    I'd like to see this feature added.
     
  6. Denis-535

    Denis-535

    Joined:
    Jun 26, 2022
    Posts:
    34
    Really, why can't we keep track of when an element is fully loaded?
     
    VenetianFox likes this.
  7. shanecelis

    shanecelis

    Joined:
    Mar 26, 2014
    Posts:
    22
    Hi all, I ran into this issue too. I wanted to have CSS pseudo classes like :first-child and :last-child. I created a manipulator that sends a ChildChangeEvent but it does require some kind of polling. One can provoke it manually or set an interval to check for changes. Given this stylesheet:

    Code (CSharp):
    1. .first-child {
    2.     -unity-background-image-tint-color: rgba(255, 100, 100, 255);
    3. }
    4.  
    5. .last-child {
    6.     -unity-background-image-tint-color: rgba(100, 100, 255, 255);
    7. }
    8.  
    Here's a gif of an effect:


    Here's the code. It's MIT licensed.
     
    BSimonSweet likes this.
  8. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    1,343
    Any news on this? It would be incredibly useful.

    @antoine-unity Could you guide us on that "internal mechanism" you were refering to? Maybe we can get to it using reflections to cover some edge cases.


    UPDATE:

    Have done a bit of digging and it seems the Panel class actually has an event for hierachy changes. Though it's a real pain to get to it (lots of reflections). I've also only tested this in the Editor, so not sure how compatible this is with the runtime.

    Code (CSharp):
    1. public void SubscribeToHierarchyChange(IPanel panel)
    2. {
    3.     // Find everything we need.
    4.     // See: https://github.com/Unity-Technologies/UnityCsReference/blob/master/Modules/UIElements/Core/Panel.cs#L647
    5.     var panelAssembly = typeof(IPanel).Assembly;
    6.     var panelType = panelAssembly.GetType("UnityEngine.UIElements.BaseVisualElementPanel");
    7.     var changeType = panelAssembly.GetType("UnityEngine.UIElements.HierarchyChangeType");
    8.     EventInfo eventInfo = panelType.GetEvent("hierarchyChanged", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy);
    9.  
    10.     var callbackMethod = GetType().GetMethod("OnHierarchyChanged", BindingFlags.Static | BindingFlags.Public);
    11.  
    12.     // Create the method
    13.     DynamicMethod method = new DynamicMethod("dynOnHierarchyChanged", typeof(void), new Type[] { typeof(VisualElement), changeType });
    14.     var generator = method.GetILGenerator(256);
    15.     generator.EmitCall(OpCodes.Call, callbackMethod, null);
    16.     generator.Emit(OpCodes.Ret);
    17.  
    18.     // Create and assign event handler
    19.     Delegate handler = method.CreateDelegate(eventInfo.EventHandlerType);
    20.     // Do it like this to allow private access.
    21.     var addMethod = eventInfo.GetAddMethod(true);
    22.     addMethod.Invoke(panel, new[] { handler });
    23. }
    24.  
    25. public static void OnHierarchyChanged()
    26. {
    27.     Debug.Log("Hierarchy changed");
    28. }
    29.  
     
    Last edited: Sep 26, 2023
  9. BSimonSweet

    BSimonSweet

    Joined:
    Aug 17, 2022
    Posts:
    67
    Running in to this issue right now ... An event when the hierarchy of a VisualElement changes should be implemented, this is very common to know when that happens. The GeometryChangeEvent doesn't fire when a child is added.
     
  10. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    1,343
  11. SudoCat

    SudoCat

    Joined:
    Feb 19, 2013
    Posts:
    65
    My favourite part about this missing feature is that a hierarchy changed event exists within the BaseVisualElementPanel, and is fired from any hierarchy.insert call, but of course, it's internal so we can't use it

    I'm currently getting around this by handling it in reverse for my use case, by using AttachToPanel events on the children. However, this only works in a very limited subset of scenarios, and doesn't work quite how I'd like.

    Ideally, it'd be great to be able to control the behaviour of adding children via UXML as well, to handle things such as slotting and more advanced child-parsing behaviour.
     
    Last edited: Dec 2, 2023
  12. BSimonSweet

    BSimonSweet

    Joined:
    Aug 17, 2022
    Posts:
    67
    Hi all !

    I've implemented something that allows to access the internal events that are triggered when an element is added or removed.

    I use it in my project, and you can find it here : https://github.com/BSimonSweet/Unity-VisualElementEvents

    It is a bit hacky but it works great. It use IL Post Processor and a bit of Reflection to get an access to internal stuff, and there is almost no cost at runtime. It also works for IL2CPP.

    Only tested on 2022.3.7f1. You will find an example in the repo.
     
    achimmihca likes this.