UI Text is not being rebuilt/updated in edit mode, when changing text from OnInspectorGUI
I have a MonoBehaviour which references a UnityEngine.UI.Text component and contains an UpdateText() method. When I call this method from within my custom inspector, the UI Text text value is changed correctly in the inspector, but the text mesh is not updated in the scene or game view.
When I resize any window in the editor, the text display updates. Apparently, there is some sort of rebuild/repaint call that I need to do manually, but I couldn't figure out which one. Does anyone have experience with this type of custom UI modifications and can tell me the correct procedure?
I've tried: Inspector.Repaint(), SceneView.RepaintAll(), Text.Rebuild(), Canvas.ForceUpdateCanvases(), but none of these called worked.
Thanks for any ideas!
Edit
Here is my original problematic code:
using UnityEngine;
using UnityEngine.UI;
public class LocalizedText : MonoBehaviour
{
public Text targetText;
public string myString = "Bla Bla";
public void UpdateText()
{
targetText.text = myString;
}
}
[CustomEditor(typeof(LocalizedText))]
public class LocalizedTextEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if(GUILayout.Button("UpdateText"))
{
LocalizedText locaText = (LocalizedText)target;
locaText.UpdateText();
// Things I've tried adding to this:
Repaint();
UnityEditor.SceneView.RepaintAll();
locaText.targetText.Rebuild(CanvasUpdate.Prelayout);
Canvas.ForceUpdateCanvases();
locaText.targetText.SetAllDirty();
// + basically any combination of things I could update or force to rebuild on UI components...
}
}
}
As said earlier, the "targetText" component updates correctly in the inspector (meaning I can see the TextArea/TextField change in the inspector), but it wouldn't redraw the actual text mesh in the scene or game view.
Of course, soon after posting, I realized another mistake/missing feature: Undo...
if(GUILayout.Button("UpdateText"))
{
LocalizedText locaText = (LocalizedText)target;
UnityEditor.Undo.RecordObject(locaText.targetText, "Update text");
locaText.UpdateText();
}
The documentation states that Undo.RecordObject is the correct way to handle this and that it will also mark the object as dirty, so I wouldn't need to use anything else. However, this didn't solve my problem.
This code does in fact solve my issue:
if(GUILayout.Button("UpdateText"))
{
LocalizedText locaText = (LocalizedText)target;
locaText.UpdateText();
UnityEditor.EditorUtility.SetDirty(locaText);
}
This updates the "targetText" in the inspector and appears to be forcing a rebuild/redraw of the scene and game view, to also show the updated text. It also enabled the generic "Inspector changed" undo functionality. Everything works fine, but according to the docs, it shouldn't.
The documentation states:
Marks target object as dirty. (Only suitable for non-scene objects). NOTE: ''Prior to Unity 5.3, this was the primary method of marking objects as dirty. From 5.3 onwards, with the introduction of Multi-Scene Editing, this function should no-longer be used for modifying objects in scenes. Instead, you should use Undo.RecordObject prior to making changes to the object. This will mark the object's scene as dirty and provide an undo entry in the editor.''
To me this says, that I should not be using the SetDirty function, and instead just record undo and everything would work, but it doesn't. Is this a bug or am I misinterpreting my findings? Is SetDirty calling anything else, which I could also trigger myself with a more "correct" method?
This actually sounds to me like it could be a bug, if resizing the game view makes the difference. Could you please file a bug report?
I had the exact same problem doing this while trying to write a custom inspector for a UI.Text derived class. The only solution I found in that particular case was changing the underlying variable m_Text via SerializedProperty and call serializedObject.Apply$$anonymous$$odifiedProperties. $$anonymous$$aybe it's a bug.
Originally, I didn't want to use the serializedProperty of the text component, because I don't really need it. For now, I only want to update the text at edit time via the public property setter and potentially save it manually. But now that I think of it, your approach might be safer, just creating a new SerializedObject from the component, gettings the underlying text and then changing it with builtin undo and update. At least I assume it would work this way then. So thanks for the idea!
As far as i know serializedObject.Apply$$anonymous$$odifiedProperties pushes the values from the object stream back to the class. serializedObject.Update(), however, pushes the values from the class to the serialized object stream. What i a using is first Update(), show all fields and then as last line Apply$$anonymous$$odifiedPropteries(). But maybe this is how you code looks like already :/
Yes, I wasn't using the serializedObject system before, because I didn't want to dig this deep into the text component. I thought that setting the text and later manually saving the value would be enough, but it didn't update the scene view. I assume that the serializedProperty approach would work better, so I'm trying this now.
I've updated my original post with code samples and new findings. Basically, I found that I could make it work by calling EditorUtility.SetDirty(textComponent), but the documentation says, that I shouldn't be doing it this way, so the question remains, what the "correct" approach is.
I say if it ain't broke, don't fix it. I'd keep using SetDirty, like below.
if (GUI.changed) {
EditorUtility.SetDirty(locaText.targetText);
}
Shouldn't you set LocalizedTextEditor as a CustomEditor? I couldn't get the button to show in the inspector without it.
using UnityEditor;
[CustomEditor(typeof(LocalizedText))]
public class LocalizedTextEditor : Editor {
...
Answer by Adam-Mechtley · Nov 11, 2016 at 07:45 AM
The issue here is that the correct way to dirty a scene object is to use Undo.RecordObject(), as the documentation states. The problem is that this does not currently work as expected when you are modifying a prefab parent (e.g., an asset selected in the project view). In those cases you need to either call EditorUtility.SetDirty(), as you discovered, or PrefabUtility.MergeAllPrefabInstances(). A colleague and I actually just discovered this and brought it to the attention of the Asset Management team earlier this week, as we agree it should work the same in both cases. That said, using SerializedProperty to modify the text should theoretically work the same in both cases, which also obviates the need to do anything special for handling undo.
We are still experiencing this issue, and it's not only with a prefab parent but also with a prefab instance (that lives in the scene). The state of the object seems to be recorded correctly, and undo operations work, but the object itself doesn't seem to be marked as dirty. Property UI style doesn't become bold and the properties are lost when we enter playmode.
Calling EditorUtility.SetDirty works well for actually setting the object dirty, but the docs advise against it and say the RecordObject should take care of that. Is there any way to follow any progress regarding this issue?
Can you please file a bug report that contains a) a scene where this can be reproduced with a prefab instance and b) a screen recording of the problem happening in this scene on your machine? If you post the case number here I will take a look.
Here's the case number: 886301
Thank you for looking into it.