(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.

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