Problem using GUI.GetNameOfFocusedControl() with dynamic GUI

Hi,

I would like to make a different `TextField` appear in the GUI depending on the selected `Button`. My problem is that when I click in the displayed TextField, the function `GUI.GetNameOfFocusedControl()` seems confused and can not tell which TextField is currently active. The function seems to erroneously remember the TextField that was previously displayed and returns its name instead of the name of the currently selected TextField.

Here is my code:

public class DynamicGUITest: MonoBehaviour {

private int choosenMenu = 0;
private GUIContent[] listButtons;

void Start () 
{
    listButtons = new GUIContent[2];
    listButtons[0] = new GUIContent("menu 1");
    listButtons[1] = new GUIContent("menu 2");  
}

void OnGUI()
{
    //menu selection
    GUILayout.BeginArea(new Rect(0, 20, 100, 200));
    GUILayout.BeginVertical();
    choosenMenu = GUILayout.SelectionGrid(choosenMenu, listButtons, 1);
    GUILayout.EndVertical();
    GUILayout.EndArea();

    //menu
    GUILayout.BeginArea(new Rect(140, 20, 100, 200));
    GUILayout.BeginVertical();
    if (choosenMenu == 0)
    {
        GUI.SetNextControlName("field0");
        GUILayout.TextField("field0");
    }
    else
    {
        GUI.SetNextControlName("field1");
        GUILayout.TextField("field1");
    }
    GUILayout.Label(GUI.GetNameOfFocusedControl() + " has focus");
    //Error : always shows "field0 has focus" even when field1 has focus

    GUILayout.EndVertical();
    GUILayout.EndArea();
}

}

OK, I’ve found a work-around for this bug that appears to work. The trick is, make sure your control names are based on GUIUtility.GetNextControlID, rather than your own counter. Unity will be reusing those internal control IDs anyway, so it seems that if you work this into your control name, then you’ll be reusing control names in the same way Unity does, and won’t get bitten by the bug.

Example:

string controlName = "ValueFld" + GUIUtility.GetControlID(FocusType.Keyboard);
GUI.SetNextControlName(controlName);
value = GUI.TextField(readoutR, value, GUI.skin.GetStyle("Text"));
bool hasFocus = (GUI.GetNameOfFocusedControl() == controlName);

This works, whereas when using my own identifier (incremented only when the GUI actually changed), GetNameOfFocusedControl would return one of the old control names instead of the name I set.

Still, this is a nasty bug. Has anybody filed this with Unity? Even with the new GUI stuff coming in 4.6, GUI scripting won’t entirely go away, and this gotcha really ought to be fixed.

Do not use control name with gadgets that are not always present or better don't "let it remain on it". What that means is add a dummy gadget at the beginning with an own name and whenver you switch the text field or whatever you do, ensure that you focus this dummy object.

otherwise the "no longer present one" will basically hook the focus name out of my experience.

This example might help:

http://forum.unity3d.com/threads/188202-Simple-dynamic-list-editor

This is technically a bug, but only because the API is used in a way not intended. When GUI.SetNextControlName() is called, it associates a control name key with a control ID value. This means that if the control name for a field changes while an editor window is alive, there will be multiple entries with the same control ID.

If multiple entries happen, this causes a problem with how GUI.GetNameOfFocusedControl() works. This function iterates through the container until it finds the first pair whose value matches the control ID.

This explains why the control names in my example code will occasionally match, but only on their first occurence; if a newly generated control name ends up in first the dictionary, there will be a match, but that name it will remain the name that returns until another name supersedes it.

This also explains why @Oliver_3’s code always returns “field0” instead of “field1”; both TextFields have the same control ID because of the order in which they are drawn, so during lookup, “field0” is always chosen since it is the first of the two in the container.

The current fix then depends on how you are using it. For my particular problem, I based the control name on a hash unique to the object it modified. For instance, my fields modified the members of a class, so I based the control names off of the class instance hash and the MemberInfo hash using System.Object.GetHashCode().

For Oliver’s problem, a fix like this should work:

// they share the same control ID, so let's just give both the same name
GUI.SetNextControlName("field");

if (choosenMenu == 0)
{
    GUILayout.TextField("field0");
}
else
{
    GUILayout.TextField("field1");
}

var focusedControlName = GUI.GetNameOfFocusedControl();

if (focusedControlName == "field")
{
    if (choosenMenu == 0)
    {
        GUILayout.Label("field0 has focus");
    }
    else
    {
        GUILayout.Label("field1 has focus");
    }
}

The proper fix would be to invert the relationship between control names and control IDs, since IDs are the values that are truly unique per field per draw, but that’s not in any of our hands.

I think that possible solution would be separating selection process from Oliver’s question between 2 frames.

This is familiar to my answer in this thread:
http://forum.unity3d.com/threads/188202-Simple-dynamic-list-editor?p=1327207#post1327207

The problem is in showing/hiding controls in OnGUI method, in same frame. Unity will perform multiple calls to OnGUI for one frame, one for layouting, one for painting, and maybe few more. You can’t show a TextField in layout call, and hide it on paint, it causes wired errors and breaks GetNameOfFocusedControl method either.

Instead, you can accumulate all the user’s input in variable. Say, store which menu item was selected. Wait for Update event, and only then, in subsequent OnGUI, draw appropriate TextField, and name it.

This technique worked for me pretty good. I think that if need to persist gui elements inside frame is so important, Unity should immediately throw exception when someone does the opposite. And not wait for, say, GetNameOfFocusedControl call, and return strange result from it.

I have many TextFields and FloatFields that display and hide for parameter lists that pop up when editing nodes in the Archimatix node graph. I solved my control name problems when I finally made sure that each name set with SetNextControlName was unique.

This was easy since, each parameter has a GUID. By adding this GUID to the control name, each GUI field was sure to have a unique name.

foreach(parameter in node.parameters)
{
    GUI.SetNextControlName("parameter_field_"+parameter.GUID);
    // ...
}

I have a similar problem where this seemed to be the solution:

if (Event.current.type != EventType.Layout &&
    Event.current.type != EventType.Used     ) // If I don't filter like this, the focused control name turns into empty string
    GUI.SetNextControlName(controlName);