I wanted to find a way to show a specific folder and display its content in the Unity project panel from custom editor code. Too bad, there is no official API available for this. This is why I went down the rabbit hole to find out how Unity does it internally and used reflection to achieve what I wanted.
To make it a little more clear: I basically want to use a similar behavior like
EditorGUIUtility.PingObject(instanceID);
but to show the contents of a specific folder asset, whose instance id I already know, instead.
A simple way of doing this would be to get the folder path and then search the content for an arbitrary asset and then ping it. However, this has two drawbacks: First, it requires the folder to actually have content. You can’t open empty folders this way. Secondly, it is not easy to reproduce the project window’s sorting and it’s generally a little ugly to ping and select an arbitrary asset from a folder when I actually just want to open it like any other browser. This is why I decided to search for the internal functionality of actually displaying the folder contents, which I found in the internal method ShowFolderContents of the ProjectBrowser class.
I hope this helps someone:
[Tested with Unity 2017.2.0 and 2020.2.1]
/// <summary>
/// Selects a folder in the project window and shows its content.
/// Opens a new project window, if none is open yet.
/// </summary>
/// <param name="folderInstanceID">The instance of the folder asset to open.</param>
private static void ShowFolderContents(int folderInstanceID)
{
// Find the internal ProjectBrowser class in the editor assembly.
Assembly editorAssembly = typeof(Editor).Assembly;
System.Type projectBrowserType = editorAssembly.GetType("UnityEditor.ProjectBrowser");
// This is the internal method, which performs the desired action.
// Should only be called if the project window is in two column mode.
MethodInfo showFolderContents = projectBrowserType.GetMethod(
"ShowFolderContents", BindingFlags.Instance | BindingFlags.NonPublic);
// Find any open project browser windows.
Object[] projectBrowserInstances = Resources.FindObjectsOfTypeAll(projectBrowserType);
if (projectBrowserInstances.Length > 0)
{
for (int i = 0; i < projectBrowserInstances.Length; i++)
ShowFolderContentsInternal(projectBrowserInstances[i], showFolderContents, folderInstanceID);
}
else
{
EditorWindow projectBrowser = OpenNewProjectBrowser(projectBrowserType);
ShowFolderContentsInternal(projectBrowser, showFolderContents, folderInstanceID);
}
}
private static void ShowFolderContentsInternal(Object projectBrowser, MethodInfo showFolderContents, int folderInstanceID)
{
// Sadly, there is no method to check for the view mode.
// We can use the serialized object to find the private property.
SerializedObject serializedObject = new SerializedObject(projectBrowser);
bool inTwoColumnMode = serializedObject.FindProperty("m_ViewMode").enumValueIndex == 1;
if (!inTwoColumnMode)
{
// If the browser is not in two column mode, we must set it to show the folder contents.
MethodInfo setTwoColumns = projectBrowser.GetType().GetMethod(
"SetTwoColumns", BindingFlags.Instance | BindingFlags.NonPublic);
setTwoColumns.Invoke(projectBrowser, null);
}
bool revealAndFrameInFolderTree = true;
showFolderContents.Invoke(projectBrowser, new object[] { folderInstanceID, revealAndFrameInFolderTree });
}
private static EditorWindow OpenNewProjectBrowser(System.Type projectBrowserType)
{
EditorWindow projectBrowser = EditorWindow.GetWindow(projectBrowserType);
projectBrowser.Show();
// Unity does some special initialization logic, which we must call,
// before we can use the ShowFolderContents method (else we get a NullReferenceException).
MethodInfo init = projectBrowserType.GetMethod("Init", BindingFlags.Instance | BindingFlags.Public);
init.Invoke(projectBrowser, null);
return projectBrowser;
}
As you can see, the code used a lot of reflection and can (should) be optimized in production code. Instead of performing all the type and method searching in every call, we can do it once at initialization and store delegates of the reflected methods. Also, I have omitted null checks. You probably want to handle failed reflection attempts.
Disclaimer: My code assumes internal, undocumented and unsupported functionality, which might change at any point. It might not work in other Unity versions. However, I personally am not afraid of using internal methods, because they still tend to be fairly stable over many years, and I can usually update my own code relatively easily.
Feature request: I’d like to see an official API to show folder content in the project browser.
Also see: https://feedback.unity3d.com/suggestions/navigate-to-a-specific-folder-in-the-project-window-via-editor-scripting