- Home /
Unity doesn't support generic editor windows? (GetWindow )
So I made a cool GameObject
selection window that picks up all GOs in the scene, you could search, filter, etc.
It looks something like this:
Now I'm trying to parametrize it, make it generic and inject what's needed - Having "Re-usability" and good coding practice in mind.
The idea is for example, let's say I wanted a selection window for GameObjects
, is to call SelectionWindow < GameObject > .Show(this, () => GameObject.FindObjectsOfType);
Or maybe I want to view all scenes in my project, I would pass SelectionWindow < string > .Show(this, () => Utils.GetAssets<object>("Assets", "*.unity", SearchOption.AllDirectories));
etc...
'this' is the caller, which should be implementing an ISelectionWindowUser
interface.
The delegate, is the item assignment delegate - basically what to show up in the window.
What I'm failing at however, is showing up the window:
public static void Show(ISelectionWindowUser<T> user, Func<T[]> refreshAction)
{
var window = GetWindow<SelectionWindow<T>>(); // BREAKS! NullReferenceException :((
//var window = GetWindow(typeof(SelectTargetGoWindow<T>)) as SelectTargetGoWindow<T>; // also tried this...
window.Init(user, refreshAction);
window.ShowUtility();
}
Full code:
SelectionWindow.cs
using UnityEngine;
using System.Linq;
using System.Collections.Generic;
using UnityEditor;
using System.Text.RegularExpressions;
using System;
using Object = UnityEngine.Object;
public class SelectionWindow<T> : EditorWindow where T : Object
{
private ISelectionWindowUser<T> user;
private Func<T[]> refreshAction;
private bool hasInitStyles;
private Vector2 scrollPosition;
private string search = "";
private const float INDENT_WIDTH = 20f;
private T[] items;
private T[] filteredItems;
private StyleDue styleDue;
// GUI Styles...
private void InitStyles()
{
// Initializing styles...
}
public static void Show(ISelectionWindowUser<T> user, Func<T[]> refreshAction)
{
var window = GetWindow(typeof(SelectionWindow<T>)) as SelectionWindow<T>;
//var window = GetWindow<SelectTargetGoWindow<T>>();
window.Init(user, refreshAction);
window.ShowUtility();
}
private void Init(ISelectionWindowUser<T> user, Func<T[]> refreshAction)
{
this.user = user;
this.refreshAction = refreshAction;
styleDue = new StyleDue(Utils.HexToColor("CCCCCC"), Utils.HexToColor("BABABA"));
}
void OnGUI()
{
if (!hasInitStyles) {
hasInitStyles = true;
InitStyles();
}
GUIHelper.HorizontalBlock(() =>
{
GUILayout.Label("GameObjects", GameObjectsLabel);
search = EditorGUILayout.TextField("", search);
filteredItems = items.Where(go => Regex.IsMatch(go.name, search, RegexOptions.IgnoreCase)).ToArray();
if (GUILayout.Button(new GUIContent("↶", "Refresh"), RefreshButton, GUILayout.Width(20)))
Refresh();
});
scrollPosition = GUIHelper.ScrollViewBlock(scrollPosition, false, false, () =>
{
foreach (var item in filteredItems) {
bool selected = item == user.target;
var nextStyle = styleDue.NextStyle;
GUIHelper.HorizontalBlock(selected ? SelectedStyle : nextStyle, () =>
{
GUILayout.Space(INDENT_WIDTH);
var spaceRect = GUILayoutUtility.GetLastRect();
GUILayout.Label(item.name, selected ? SelectedLabel : UnselectedLabel);
var labelRect = GUILayoutUtility.GetLastRect();
var buttonRect = GUIHelper.CombineRects(spaceRect, labelRect);
if (!selected) {
EditorGUIUtility.AddCursorRect(buttonRect, MouseCursor.Link);
if (GUI.Button(buttonRect, "", GUIStyle.none)) {
user.target = item;
}
}
});
}
});
}
void Refresh()
{
items = refreshAction();
}
void OnFocus()
{
Refresh();
}
}
ISelectionWindowUser.cs
public interface ISelectionWindowUser<T> where T : Object
{
T target { get; set; }
}
User code:
if (GUILayout.Button(new GUIContent("Select target", "Select a target game object to inspect"), SelectButton, GUILayout.Height(20))) {
SelectionWindow<GameObject>.Show(this, () => GameObject.FindObjectsOfType<GameObject>());
}
As mentioned before the user should implement ISelectionWindowUser where T is what he's interested in (GameObject, etc)
I'll be really sad if I hear that Unity doesn't support this, I mean come on...
Again, my problem is this GetWindow < SelectionWindow < GameObject > >();
Any help would be very appreciated!
Thanks.
EDIT:
Full error:
NullReferenceException: Object reference not set to an instance of an object
UnityEditor.EditorWindow.GetWindow (System.Type t, Boolean utility, System.String title, Boolean focus) (at C:/BuildAgent/work/d3d49558e4d408f4/artifacts/EditorGenerated/EditorWindow.cs:423)
UnityEditor.EditorWindow.GetWindow[SelectionWindow`1] (Boolean utility, System.String title, Boolean focus) (at C:/BuildAgent/work/d3d49558e4d408f4/artifacts/EditorGenerated/EditorWindow.cs:461)
UnityEditor.EditorWindow.GetWindow[SelectionWindow`1] () (at C:/BuildAgent/work/d3d49558e4d408f4/artifacts/EditorGenerated/EditorWindow.cs:434)
SelectionWindow`1[UnityEngine.GameObject].Show (ISelectionWindowUser`1 user, System.Func`1 refreshAction) (at Assets/Editor/SelectionWindow.cs:89)
ShowInspectorWindow.<OnGUI>m__22 () (at Assets/Editor/ShowInspectorWindow.cs:125)
GUIHelper.HorizontalBlock (System.Action block) (at Assets/Editor/GUIHelper.cs:54)
ShowInspectorWindow.OnGUI () (at Assets/Editor/ShowInspectorWindow.cs:111)
System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at /Users/builduser/buildslave/monoAndRuntimeClassLibs/build/mcs/class/corlib/System.Reflection/MonoMethod.cs:222)
Rethrow as TargetInvocationException: Exception has been thrown by the target of an invocation.
System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at /Users/builduser/buildslave/monoAndRuntimeClassLibs/build/mcs/class/corlib/System.Reflection/MonoMethod.cs:232)
System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) (at /Users/builduser/buildslave/monoAndRuntimeClassLibs/build/mcs/class/corlib/System.Reflection/MethodBase.cs:115)
UnityEditor.HostView.Invoke (System.String methodName, System.Object obj) (at C:/BuildAgent/work/d3d49558e4d408f4/Editor/Mono/GUI/DockArea.cs:231)
UnityEditor.HostView.Invoke (System.String methodName) (at C:/BuildAgent/work/d3d49558e4d408f4/Editor/Mono/GUI/DockArea.cs:224)
UnityEditor.HostView.OnGUI () (at C:/BuildAgent/work/d3d49558e4d408f4/Editor/Mono/GUI/DockArea.cs:120)
Interesting - can you use CreateInstance to create a that editor window?
Thanks for your fast reply! - Creating a new instance yielded the same error. I'll try your 2nd suggestion.
Answer by Loius · Feb 11, 2014 at 06:39 PM
While you can't create a generic window, you could have a generic class with all your functionality in it wrapped by a non-generic window that accepts a type parameter to create a strongly-typed version of your generic class.
I've been using Reflection and System.Type shenanigans a lot lately gosh it's fun. I made a very basic example, you should be able to plug'n'play without too much work:
using UnityEngine;
using UnityEditor;
// this just lets you use "myImplementation as ISelectionWindow" instead of going through reflection to convert to a real selectionwindow
// i value the code legibility over the extra interface
public interface ISelectionWindow {
void OnGUI();
}
public class SelectionWindow<T> : ISelectionWindow {
// your code here
public void OnGUI() {
GUILayout.Label("I'm of type " + typeof(T));
}
}
public class SelectionWindowWrapper : EditorWindow {
System.Type _type=null;
// I wasn't able to quickly find a solution to "have a variable that could represent a specific generic class of any type"
// but object works fine - note the little o, this is C#'s object not Unity's Object
object myImplementation=null;
[MenuItem("Window/Custom Tools/Selection Window")] // for testing purposes
public static void Init() {
SelectionWindowWrapper sww = EditorWindow.CreateInstance<SelectionWindowWrapper>();
sww.Show();
}
public System.Type Type {
get { return _type.GetGenericArguments()[0]; }
set {
// These two lines are how you get your specific implementation
_type = typeof(SelectionWindow<>).MakeGenericType(value);
myImplementation = System.Activator.CreateInstance(_type);
}
}
void OnGUI() {
if ( null == myImplementation ) { // provide selection options or prevent this condition
if ( GUILayout.Button("GameObject") ) Type = typeof(GameObject); // notice capital T, we're using the setter
return;
}
(myImplementation as ISelectionWindow).OnGUI();
}
}
Whaooo!! you sneaky!! I love that!! IT ALSO WOR$$anonymous$$ED!! XD THAN$$anonymous$$ YOU!!
For the user and func, I had to do this - here's the full Init method now:
public void Init<T>(ISelectionWindowUser<T> user, Func<T[]> func)
{
var t = typeof(T);
windowType = typeof(SelectionWindow<>).$$anonymous$$akeGenericType(t);
implementation = Activator.CreateInstance(windowType); // SelectionWindow<T>
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
var fields = windowType.GetFields(flags);
// set the user
FieldInfo userField = fields.FirstOrDefault(f => f.FieldType == typeof(ISelectionWindowUser<>).$$anonymous$$akeGenericType(t));
userField.SetValue(implementation, user);
// set the delegate
FieldInfo funcField = fields.FirstOrDefault(f => f.FieldType == typeof(Func<>).$$anonymous$$akeGenericType(t.$$anonymous$$akeArrayType()));
funcField.SetValue(implementation, func);
}
lol why did I manually assign the fields? I already have an init method, so just:
var init = methods.FirstOrDefault(m => m.Name == "Init");
init.Invoke(implementation, new object[] { user, func });
Pretty slick. I won't lie, now that I've done created a generic-type window I'm probably going to end up porting my own gameobject filters into actual windows. :)
Answer by whydoidoit · Feb 11, 2014 at 04:46 PM
I guess at worse you could try:
public class SomeEditorWindow : SelectionWindow<GameObject> {
}
And then GetWindow on SomeEditorWindow...
ahh, it seems you're right. I took it down to the barebone. I created a GameObjectSelectionWindow which is just:
public class GameObjectSelectionWindow : SelectionWindow<GameObject>
{
}
and then replaced var window = GetWindow < SelectionWindow < GameObject > >();
with var window = GetWindow < GameObjectSelectionWindow > ();
and it seemed to have worked! This is plain stupid!.....
This ruins the whole idea, which is NOT writing new window classes for each type... Unity seems to does well in encouraging code repetition...
If you find out of a way to get it to work how I like it to, please let me know. Thanks. For now I'll convert your comment to answer and tick it.
Answer by yazZ6va · Apr 25, 2021 at 09:23 AM
public class SelectionWindow : EditorWindow
{
private IGenericWindow genericWindow;
public static void Show<T>(T t)
{
SelectionWindow window = EditorWindow.GetWindow<SelectionWindow>(false, "ADD");
window.minSize = new Vector2(300.0f, 500.0f);
GenericWindow<T> genericWindow = new GenericWindow<T>();
genericWindow.t = t;
window.genericWindow = genericWindow;
}
public void OnGUI()
{
genericWindow.OnGUI();
}
}
public class GenericWindow<T> : IGenericWindow
{
public T t;
public void OnGUI()
{
}
}
public interface IGenericWindow
{
void OnGUI();
}
Your answer
![](https://koobas.hobune.stream/wayback/20220613133216im_/https://answers.unity.com/themes/thub/images/avi.jpg)