Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Question How to properly remove a VisualElement with its childs?

Discussion in 'UI Toolkit' started by MacStanley, Jan 3, 2022.

  1. MacStanley

    MacStanley

    Joined:
    Jul 18, 2013
    Posts:
    118
    Hey there,

    I have a template visual element with 4 childs (you can think of it as a row of table and each visual element I add adds a new row of content). As part of the template there is a button that removes the whole line (VisualElement). I did it as follows:

    Code (CSharp):
    1. var myElements = rootUi.Q<VisualElement>("ScrollView").Children();
    2.         foreach (VisualElement child in myElements)
    3.         {
    4.             rootUi.Q<VisualElement>("ScrollView").Remove(child);
    5.         }
    I know the VisualElement itself needs to be deleted afterwards as well. I have prepared but not tested the following code for that:

    Code (CSharp):
    1. rootUi.Q<VisualElement>("ScrollView").Remove(rootUi.Q<VisualElement>("Template_Tabelleninhalt_Exchanges" + _id.ToString()))
    After only executing the first part of code the table lines are successfully removed, but I receive the following error:


    InvalidOperationException: Collection was modified; enumeration operation may not execute.
    System.Collections.Generic.List`1+Enumerator[T].MoveNextRare () (at <31c0f51ac5a24a22ba784db24f4ba023>:0)
    System.Collections.Generic.List`1+Enumerator[T].MoveNext () (at <31c0f51ac5a24a22ba784db24f4ba023>:0)
    UIManager.DeleteExchangeEntry (System.Int32 _id) (at Assets/Scripts/UIManager.cs:83)
    UIManager+<>c__DisplayClass3_0.<CreateExchangeEntry>b__0 (UnityEngine.UIElements.MouseUpEvent evt) (at Assets/Scripts/UIManager.cs:60)
    UnityEngine.UIElements.EventCallbackFunctor`1[TEventType].Invoke (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.PropagationPhase propagationPhase) (at <66054a7773f64b399021c1a3f19995a3>:0)
    UnityEngine.UIElements.EventCallbackRegistry.InvokeCallbacks (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.PropagationPhase propagationPhase) (at <66054a7773f64b399021c1a3f19995a3>:0)
    UnityEngine.UIElements.CallbackEventHandler.HandleEvent (UnityEngine.UIElements.EventBase evt) (at <66054a7773f64b399021c1a3f19995a3>:0)
    UnityEngine.UIElements.TextElement.HandleEvent (UnityEngine.UIElements.EventBase evt) (at <66054a7773f64b399021c1a3f19995a3>:0)
    UnityEngine.UIElements.EventDispatchUtilities.PropagateEvent (UnityEngine.UIElements.EventBase evt) (at <66054a7773f64b399021c1a3f19995a3>:0)
    UnityEngine.UIElements.MouseEventDispatchingStrategy.SendEventToRegularTarget (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.BaseVisualElementPanel panel) (at <66054a7773f64b399021c1a3f19995a3>:0)
    UnityEngine.UIElements.MouseEventDispatchingStrategy.SendEventToTarget (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.BaseVisualElementPanel panel) (at <66054a7773f64b399021c1a3f19995a3>:0)
    UnityEngine.UIElements.MouseEventDispatchingStrategy.DispatchEvent (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.IPanel iPanel) (at <66054a7773f64b399021c1a3f19995a3>:0)
    UnityEngine.UIElements.EventDispatcher.ApplyDispatchingStrategies (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.IPanel panel, System.Boolean imguiEventIsInitiallyUsed) (at <66054a7773f64b399021c1a3f19995a3>:0)
    UnityEngine.UIElements.EventDispatcher.ProcessEvent (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.IPanel panel) (at <66054a7773f64b399021c1a3f19995a3>:0)
    UnityEngine.UIElements.EventDispatcher.ProcessEventQueue () (at <66054a7773f64b399021c1a3f19995a3>:0)
    UnityEngine.UIElements.EventDispatcher.OpenGate () (at <66054a7773f64b399021c1a3f19995a3>:0)
    UnityEngine.UIElements.EventDispatcherGate.Dispose () (at <66054a7773f64b399021c1a3f19995a3>:0)
    UnityEngine.UIElements.EventDispatcher.ProcessEvent (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.IPanel panel) (at <66054a7773f64b399021c1a3f19995a3>:0)
    UnityEngine.UIElements.EventDispatcher.Dispatch (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.IPanel panel, UnityEngine.UIElements.DispatchMode dispatchMode) (at <66054a7773f64b399021c1a3f19995a3>:0)
    UnityEngine.UIElements.BaseVisualElementPanel.SendEvent (UnityEngine.UIElements.EventBase e, UnityEngine.UIElements.DispatchMode dispatchMode) (at <66054a7773f64b399021c1a3f19995a3>:0)
    UnityEngine.UIElements.VisualElement.SendEvent (UnityEngine.UIElements.EventBase e) (at <66054a7773f64b399021c1a3f19995a3>:0)
    UnityEngine.UIElements.DefaultEventSystem.SendPositionBasedEvent[TArg] (UnityEngine.Vector3 mousePosition, UnityEngine.Vector3 delta, System.Int32 pointerId, System.Nullable`1[T] targetDisplay, System.Func`4[T1,T2,T3,TResult] evtFactory, TArg arg, System.Boolean deselectIfNoTarget) (at <66054a7773f64b399021c1a3f19995a3>:0)
    UnityEngine.UIElements.DefaultEventSystem.SendIMGUIEvents () (at <66054a7773f64b399021c1a3f19995a3>:0)
    UnityEngine.UIElements.DefaultEventSystem.Update (UnityEngine.UIElements.DefaultEventSystem+UpdateMode updateMode) (at <66054a7773f64b399021c1a3f19995a3>:0)
    UnityEngine.UIElements.UIElementsRuntimeUtility.UpdateRuntimePanels () (at <66054a7773f64b399021c1a3f19995a3>:0)
    UnityEngine.UIElements.UIElementsRuntimeUtilityNative.UpdateRuntimePanels () (at <7704c4432ba4461c83d3520cc4d20ead>:0)


    Sooo ... how do I do it the right way?
     
  2. dlorre

    dlorre

    Joined:
    Apr 12, 2020
    Posts:
    700
    This is because of the enumerator trying to access elements that you have removed already. You can do it with some magickery, for example storing these elements in a list or array, or always removing the first one.

    However in your case you can use the Clear() method that will do the job for you:

    Code (CSharp):
    1. var scrollView = rootUi.Q<VisualElement>("ScrollView");
    2. scrollView.Clear();
    3.  
    ETA:

    clarification:

    Code (CSharp):
    1.             foreach (var element in myList)
    2.             {
    3.                 visualElement.Remove(element);
    4.             }
    5.  
    6.  
    7.             var count = visualElement.childCount;
    8.             for (int i=0; i< count; i++)
    9.             {
    10.                 visualElement.RemoveAt(0);
    11.             }
    12.    
    Also, it can be tricky to remove all elements of a scrollview, you might want to clear its container instead.

    Code (CSharp):
    1. var scrollView = rootUi.Q<VisualElement>("ScrollView");
    2. var container = scrollView.Q<VisualElement>("unity-content-container");
    3. container.Clear();
    4.  
     
    Last edited: Jan 4, 2022
  3. MacStanley

    MacStanley

    Joined:
    Jul 18, 2013
    Posts:
    118
    Hey,

    thanks for your reply. I noticed some mistakes in my code that made it look like I was going to clear the whole ScrollView. My real intent was to only delete one line. I think this picture should help visualize that:

    upload_2022-1-4_16-15-13.png

    So I hoped that for each new line I could just instantiate the template and later on delete the whole line (without looking for each single item by name and removing it ... which would work as far as I've tested).
     
  4. dlorre

    dlorre

    Joined:
    Apr 12, 2020
    Posts:
    700
    What is #Tabelleninhalt? Do you get one per line or is it the container for all the lines?
     
  5. MacStanley

    MacStanley

    Joined:
    Jul 18, 2013
    Posts:
    118
    Thats the container for all the lines. All the other elements (within the template) are being renamed after being instantiated to append their id:
    upload_2022-1-4_18-41-50.png

    (some of the VisualElements still need to be renamed, the above code isn't complete yet)
     
  6. MacStanley

    MacStanley

    Joined:
    Jul 18, 2013
    Posts:
    118
    I've now changed how I add new entries. I add all the labels and Buttons manually without a template. That way I do not have any problems with removing them later on.

    This is my new UI Hirarchy:
    upload_2022-1-4_19-51-47.png

    Code for adding entries:
    upload_2022-1-4_19-52-6.png

    Code for removing them (can delete the whole line with childs):
    upload_2022-1-4_19-52-41.png
     
  7. dlorre

    dlorre

    Joined:
    Apr 12, 2020
    Posts:
    700
    Given:

    Code (csharp):
    1. var scrollView = rootUi.Q<VisualElement>("ScrollView");
    2. var container = scrollView.Q<VisualElement>("Tabelleninhalt");
    3.  
    You can get the line and then remove it:

    Code (csharp):
    1.  
    2. var line = container.Q<VisualElement>("ExchangesTabellenzeile" + _id.ToString());
    3. container.Remove(line);
    4.  
    Also, you should be able to do it in a more direct way:

    Code (csharp):
    1.  
    2. var btnDelete = ...
    3. btnDelete.clicked += () => {
    4.     testTabellenInhalt.RemoveFromHierarchy();
    5. }
    6.