Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 12 Next capture
2021 2022 2023
1 capture
12 Jun 22 - 12 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
This question was closed Jan 19, 2018 at 05:08 PM by $$anonymous$$ for the following reason:

The question is answered, right answer was accepted

avatar image
1
Question by $$anonymous$$ · Jan 18, 2018 at 11:23 PM · inspectorcustom editorscrollvieweditorguilayoutargumentexception

(ArgumentException: GUILayout: Mismatched LayoutGroup.Repaint) when holding the Up/Down buttons of EditorGUILayout.BeginScrollView()!!

Currently the Unity's EditorGUILayout.BeginScrollView() is terrible in Performance for Lists and Arrays to display a huge amount of Contents into a Scrollview, even though the Scrollview only sees a certain amount of Contents only, so i decided to make it only Render/Display the current Contents that are only visible, it works fine if you move the ScrollView's Slider up and down, but if you hold the Up/Down buttons of the ScrollView, it will throw errors and warnings and some contents disappear for 1 frame and re-appear like normal, for that i made a small example script that you can test and see for your self:

 using System.Collections.Generic;
 using UnityEngine;
 using UnityEditor;
 public class TestCode : MonoBehaviour
 {
     public List<int> myList = new List<int>();
     public Vector2 myScrollView = Vector2.zero;
 }
 [CustomEditor(typeof(TestCode))]
 public class TestCodeEditor : Editor
 {
     private TestCode testCode {get {return (TestCode)target;}}
     public override void OnInspectorGUI ()
     {
         serializedObject.Update();
         MyVoid();
         if(GUI.changed)
         {
             serializedObject.ApplyModifiedProperties();
             EditorUtility.SetDirty(testCode);
         }
     }
     private void MyVoid ()
     {
         int startIndex = 0;
         if(testCode.myList.Count >= 5)
         {
             testCode.myScrollView = EditorGUILayout.BeginScrollView(testCode.myScrollView,GUILayout.Height(117));
             startIndex = (int)testCode.myScrollView.y / 30;
         }
         if(testCode.myList.Count > 0)for(int a = 0; a < testCode.myList.Count; a++)
         {
             //Checks if the current index isn't in the view
             if(a < startIndex || a > startIndex + 4)GUILayout.Space(30);
             //Checks if the current index is in the view
             if(a >= startIndex && a <= startIndex + 4)
             {
                 EditorGUILayout.BeginHorizontal("Box");
                 {
                     //The Index Of The Property
                     GUILayout.Box(a.ToString("000"));
                     //The Integer Property
                     EditorGUILayout.PropertyField(serializedObject.FindProperty("myList").GetArrayElementAtIndex(a),GUIContent.none,true);
                     //These Buttons Will Swap/Remove Items From The List
                     GUI.enabled = a != 0;
                     if(GUILayout.Button("▲",GUILayout.Width(20),GUILayout.Height(20)))
                     {
                         int current = testCode.myList[a];
                         int previous = testCode.myList[a - 1];
                         testCode.myList[a] = previous;
                         testCode.myList[a - 1] = current;
                         break;
                     }
                     if(GUI.enabled != (a != testCode.myList.Count - 1))
                         GUI.enabled = a != testCode.myList.Count - 1;
                     if(GUILayout.Button("▼",GUILayout.Width(20),GUILayout.Height(20)))
                     {
                         int current = testCode.myList[a];
                         int next = testCode.myList[a + 1];
                         testCode.myList[a] = next;
                         testCode.myList[a + 1] = current;
                         break;
                     }
                     if(!GUI.enabled)GUI.enabled = true;
                     if(GUILayout.Button("-",GUILayout.Width(20),GUILayout.Height(20)))
                     {
                         testCode.myList.RemoveAt(a);
                         break;
                     }
                 }
                 EditorGUILayout.EndHorizontal();
             }
         }
         if(testCode.myList.Count >= 5)EditorGUILayout.EndScrollView();
         EditorGUILayout.BeginHorizontal();
         if(GUILayout.Button("Add 10"))
         {
             int count = testCode.myList.Count;
             while(testCode.myList.Count < count + 10)
                 testCode.myList.Add(0);
         }
         if(GUILayout.Button("Add 100"))
         {
             int count = testCode.myList.Count;
             while(testCode.myList.Count < count + 100)
                 testCode.myList.Add(0);
         }
         EditorGUILayout.EndHorizontal();
     }
 }

The console throws these messages:
ArgumentException: GUILayout: Mismatched LayoutGroup.Repaint
Unexpected top level layout group! Missing GUILayout.EndScrollView/EndVertical/EndHorizontal?
GUI Error: You are pushing more GUIClips than you are popping. Make sure they are balanced)
I know what the middle warning message talks about, but the Console itself is wrong, not me or my code!
However if you replace the StartIndex with a number from the script, none of these happen, but the ScrollView will display a range of Contents only, i replaced it with "1" like:

 //Checks if the current index isn't in the view
 if(a < 1 || a > 1 + 4)GUILayout.Space(30);
 //Checks if the current index is in the view
 if(a >= 1 && a <= 1 + 4)

So is there a solution to fix that?
Thanks.
[EDIT] The error points at the line that Begins Horizontal with the "Box" GUIStyle, removing everything but keeping the EditorGUILayout.PropertyField() it will work fine, so there is something wrong with the EditorGUILayout.BeginHorizontal("Box); line.

Comment
Add comment · Show 3
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Bunny83 · Jan 19, 2018 at 12:56 PM 0
Share

Just a few comments on your code. First of all, do not call a method "a void". It's "a method". Void is just a special type which indicates "nothing". So while a method with the signature

 int $$anonymous$$y$$anonymous$$ethod()

returns an integer a method with the signature

 void $$anonymous$$y$$anonymous$$ethod2()

simply returns nothing. When you talk about a blue car you don't say "I have a blue" ^^


The If condition here is unnecessary and makes the line hard to read:

 if(testCode.myList.Count > 0)for(int a = 0; a < testCode.myList.Count; a++)
 {
     // [body]
 }

A for loop already has an if statement as you might know your for loop is equivalent to this construct:

 {
     int a = 0;
     while (a < testCode.myList.Count)
     {
         // [body]
         a++;
     }
 }

In case Count is 0 the loop body won't be entered at all since 0 is not smaller than 0


This:

 if(GUI.enabled != (a != testCode.myList.Count - 1))
     GUI.enabled = a != testCode.myList.Count - 1;

could be simplified to

 GUI.enabled = a != testCode.myList.Count - 1;

or better to

 GUI.enabled = a < testCode.myList.Count - 1;

this does already include all edge cases. When there is only a single element in the list the index will be 0 and "Count-1" will also be 0 so the button will be disabled.


Currently you mix direct object editing (using the target property) and the serializedObject. This is not recommended. It's difficult to suggest a specific solution as we don't know the exact purpose. Generally the serializedObject allows multi-object editor out-of-the-box while when using target / targets you have to handle it manually or do not have the feature at all.


SetDirty shouldn't be used for scene objects. You should use the Undo system. Note that RecordObject needs to be called before you do changes to the object.

avatar image $$anonymous$$ Bunny83 · Jan 19, 2018 at 01:13 PM 0
Share

Actually i was thinking of a name for that test script, and came up with "$$anonymous$$yVoid", so yeah.
And i always use:

 if(list.Count > 0)for(int a = 0; a < list.Count; a++)

Because i think it won't create the Integer from the first place,like:

 if(list.Count > 0)
 {
      //It won't pass here, because the condition is false at the first place!
      int a = 0;
      while(a < list.Count)
      {
           [Body]
           a++;
      }
 }

And this line might increase a very little performance, because if the List.Count is one, the previous button gets disabled, then for the next button, it won't need to be disabled again if the GUI.enabled is false in the previous button!

 if(GUI.enabled != (a != testCode.myList.Count - 1))
      GUI.enabled = a != testCode.myList.Count - 1;

Also i already have Undo.RecordObject() in my full scripts, but i removed them in this example script. :)

avatar image Bunny83 $$anonymous$$ · Jan 19, 2018 at 01:26 PM 0
Share

No, that won't change anything. Local variables are not "created". When a method is entered it reserves a stackframe on the stack that includes all local variables which may be used or not. In the end your solution doesn't really improve anything as you just add another check which wouldn't be necessary.


GUI.enabled is just a state stored in an internal field. There is no overhead involved setting that state. It just makes the code harder to read and to understand ^^.


Note that if you have really a lot elements (say 10k or 100k) it would be better to combine all the out-of-view elements into a single GUILayout.Space. Since you have a fix height contant per item you can simply do GUILayout.Space(30 * startIndex) before the loop and another one with the remaining items after the loop. This should also significantly reduce the generated garbage as every layout element that is recorded during the layout event requires some memory.

1 Reply

  • Sort: 
avatar image
2
Best Answer

Answer by Bunny83 · Jan 19, 2018 at 12:29 PM

The GUI layout system works in a two step procedure. Before any event is processed by OnGUI / OnInspectorGUI the system will issue a Layout event. Just to be clear here: Every event is paired with it's own layout event which is executed right before the actual event. So if a Repaint event should be processed you first get a Layout event and after that the Repaint event. Likewise when a MouseDown or MouseUp event should be processed there always will be a corresponding Layout event right before.


During the layout event Unity collects information about all the content that is there. This is necessary to be able to distribute the available space between the things it should draw. During the actual event Unity uses the information stored during the layout event to actually get the concrete rects for each element.


However that means you have to ensure that the layout event and the actual event "sees" and processes the same elements. Your fault here is that you just break out of your for loop when you press one of the buttons. That means you introduce several errors: First of all in line 38 you open a new horizontal layout group which you usually close at line 71. Though when you break out you do not close the group. That means at the end of OnGUI the layout stack has not been cleared. However you should already get an error at line 75 since you're still inside the horizontal layout group of the current loop item the layout stack doesn't expect opening a new group at this point.


Since there are many cases where you want to remove an element inplace it would be a pain to delay the removal of the element until the end of the OnGUI method. That's why Unity provides you a little helper tool GUIUtility.ExitGUI();. This is actually a sledgehammer solution to the problem ^^. ExitGUI simply does this:

 public static void ExitGUI()
 {
     GUIUtility.guiIsExiting = true;
     throw new ExitGUIException();
 }

So it raises an exception which will cause the OnGUI method to terminate immediately. Unity will catch the ExitGUIException silently. This method is still undocumented but it's used in a lot of built-in editor code to exactly prevent such errors.


Where you also need to use ExitGUI is when you open a new EditorWindow from within inspector code. Opening a new EditorWindow causes a new layout stack to be created which makes it impossible to finish the current GUI procedure.


Note that the body of a GUI.Button will only be executed during a MouseUp event. So all you do is interrupting the MouseUp event processing. It will have no effect on the subsequent drawing as the repaint event is issued seperately with it's own layout event.


For more general information about the IMGUI system see my GUI crash course

Comment
Add comment · Show 3 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image $$anonymous$$ · Jan 19, 2018 at 12:45 PM 0
Share

Thanks for your answer, breaking out of the loop isn't the main problem, they work fine, but the problem is with the ScrollView's Up/Down buttons, because when you move the ScrollView's Slider to up or down, it works fine, but if you use the Up/Down buttons of the ScrollView, it will cause these errors!
I tested the GUIUtility.ExitGUI(); but i think it won't help, because i want it to draw the contents non stop, even for a frame, so the user won't notice something like that.
Or is there something that i'm missing? thanks.

avatar image Bunny83 $$anonymous$$ · Jan 19, 2018 at 01:14 PM 0
Share

Yes, that's why highly dynamic content changes are difficult to handle. Your problem is probably that you calculate "startIndex" in between based on the current y scroll position. This will result in a different behaviour between the layout and the actual event. There are essentially two solutions:

  • $$anonymous$$ake startIndex a member variable and set it based on the y position at the beginning only during the layout event.

  • $$anonymous$$ake startIndex a member variable and set it based on the y position at the end of your method only during an event that is not a layout event. This will ensure that you use the same startIndex for both, the layout event and the actual event.

avatar image $$anonymous$$ Bunny83 · Jan 19, 2018 at 01:16 PM 0
Share

Thanks, i will try that later and check if it works, now my PC is about to shutdown, 12% Battery, Thanks again.

Follow this Question

Answers Answers and Comments

126 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Unable to solve: ArgumentException: Getting control 3's position in a group with only 3 controls when doing Repaint Aborting 1 Answer

Can not you change the default GUI style of EditorGUI in OnInspectorGUI? 1 Answer

Custom Display for system.object Editor 1 Answer

How can i get SerializedProperty from UnityEvent which in List. Sorry for my Eng. 2 Answers

Is it possible to choose which custom editor load for specific script? 0 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges