Part of the controls lose their bind after run in editor

Godot Version

Godot .NET 4.4

Question

I’m making some custom UI element in Godot, One is a container with some pre-added child controls. a strange thing happened that some kinds of the child will lost their binding after the UI scene run. all the children are binded with [Export] label.

After a run, the panel at right shows they lost their binding

Here is the code of one kind of Control that will lost binding after running the scene:

using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using static LabeledInputField;

public partial class MotionEventExpandable : Control
{
	//Control objects
	[Export]
	public ScrollContainer UIContainer;
	[Export]
	public VBoxContainer UIAutoResizeContainer;
	[Export]
	public VBoxContainer UINonChildContainer;
	[Export]
	public Clickable UIHeader;
	[Export]
	public Label UIHeaderLabel;
	[Export]
	public Line2D UISeparateLine;
	[Export]
	public VBoxContainer UIChildContainer;
	[Export]
	public LabeledInputField UIStartTime;
	[Export]
	public LabeledInputField UIEndTime;
	[Export]
	public LabeledInputField UIStartBeat;
	[Export]
	public LabeledInputField UIEndBeat;
	[Export]
	public LabeledVector3InputField UITargetRelatiaveVector3;
	[Export]
	public LabeledEventTypeDropDown UIEventType;
	[Export]
	public LabeledEasingDropDown UIEasing;

	//Public settings
	[Export]
	public bool Animate = true;
	[Export]
	public float AnimationDuration = 0.3f;


	//Signals

	//UI related events
	[Signal]
	public delegate void ChildChangedEventHandler();
	[Signal]
	public delegate void SizeChangedEventHandler();
	[Signal]
	public delegate void IsExpandChangedEventHandler();
	[Signal]
	public delegate void ChildEnteredEventHandler();
	[Signal]
	public delegate void ChildExitedEventHandler();
	[Signal]
	public delegate void DataChangedEventHandler();

	//Private value properties
	private string _label = "Label";
	private bool _isExpand;
	private float _parentBPM = 60f;
	private MRDataType.MREditorMotionEvent _data;



	//Private UI properties
	private float _height = UIPreset.DefaultControlHeight;
	private float _width = UIPreset.DefaultControlWidth;
	private int _fontSize = UIPreset.DefaultTextFieldFontSize;

	//Private running properties
	private Tween _tween;


	//Public UI Properties
	[Export]
	public float Height
	{
		get => _height;
		set
		{
			_height = value;
			if (UIContainer != null)
			{
				UIContainer.Size = new Vector2(UIContainer.Size.X, _height);
				EmitSignal(nameof(SizeChangedEventHandler));
			}
		}
	}

	[Export]
	public float Width
	{
		get => _width;
		set
		{
			_width = value;
			if (UIContainer != null)
			{
				UIContainer.Size = new Vector2(_width, UIContainer.Size.Y);
				EmitSignal(SignalName.SizeChanged);
			}
		}
	}

	[Export]
	public int FontSize
	{
		get => _fontSize;
		set
		{
			_fontSize = value;
			if (UIHeaderLabel != null)
			{
				UIHeaderLabel.AddThemeFontSizeOverride("font_size", value);
			}
		}
	}

	[Export]
	public bool IsExpand
	{
		get => _isExpand;
		set
		{
			if (_isExpand != value)
			{
				_isExpand = value;
				EmitSignal(SignalName.IsExpandChanged);
			}
		}
	}


	//Public value properties
	[Export]
	public string Label
	{
		get => _label;
		set
		{
			_label = value;
			if (UIHeaderLabel != null)
			{
				UIHeaderLabel.Text = value;
			}
		}
	}

	public float ParentBPM
	{
		get => _parentBPM;
		set
		{
			CheckTimeValue();
			_parentBPM = value;
			float BeatSpan = float.Parse(UIEndBeat.Text) - float.Parse(UIStartBeat.Text);
			float Endtime = float.Parse(UIStartTime.Text) + 60f / value * BeatSpan;
			UIEndTime.Text = Endtime.ToString();
		}
	}

	public MRDataType.MREditorMotionEvent Data
	{
		get => _data;
		set
		{
			_data = value;
			EmitSignal(nameof(DataChanged));
		}
	}


	public override void _Ready()
	{
		UIStartTime.AllowedInputType = InputType.Float;
		UIEndTime.AllowedInputType = InputType.Float;
		UIStartBeat.AllowedInputType = InputType.Float;
		UIEndBeat.AllowedInputType = InputType.Float;

		//Register events
		AddUserSignal(nameof(ChildChangedEventHandler));
		AddUserSignal(nameof(SizeChangedEventHandler));
		AddUserSignal(nameof(IsExpandChangedEventHandler));
		AddUserSignal(nameof(ChildEnteredEventHandler));
		AddUserSignal(nameof(ChildExitedEventHandler));

		//Bind signals
		UIHeader.Clicked += UIHeader_Clicked;
		UIStartTime.TextChanged += UIStartTime_TextChanged;
		UIEndTime.TextChanged += UIEndTime_TextChanged;
		UIStartBeat.TextChanged += UIStartBeat_TextChanged;
		UIEndBeat.TextChanged += UIEndBeat_TextChanged;

		InitializeUI();
		CheckTimeValue();
	}

	private void UIEndBeat_TextChanged()
	{
		CheckTimeValue();
		UIEndTime.TextChanged -= UIEndBeat_TextChanged;
		float beatSpan = float.Parse(UIEndBeat.Text) - float.Parse(UIStartBeat.Text);
		float timeSpan = beatSpan * 60f / ParentBPM;
		UIEndTime.Text = (float.Parse(UIStartTime.Text) + timeSpan).ToString();
		UIEndTime.TextChanged += UIEndBeat_TextChanged;
	}

	private void UIStartBeat_TextChanged()
	{
		CheckTimeValue();
		UIStartTime.TextChanged -= UIStartBeat_TextChanged;
		float beatSpan = float.Parse(UIEndBeat.Text) - float.Parse(UIStartBeat.Text);
		float timeSpan = beatSpan * 60f / ParentBPM;
		UIStartTime.Text = (float.Parse(UIEndTime.Text) - timeSpan).ToString();
		UIStartTime.TextChanged += UIStartBeat_TextChanged;
	}

	private void UIEndTime_TextChanged()
	{
		CheckTimeValue();
		UIEndBeat.TextChanged -= UIEndBeat_TextChanged;
		float timeSpan = float.Parse(UIEndTime.Text) - float.Parse(UIStartTime.Text);
		float beatSpan = timeSpan / (ParentBPM / 60f);
		UIEndBeat.Text = (float.Parse(UIStartBeat.Text) + beatSpan).ToString();
		UIEndBeat.TextChanged += UIEndBeat_TextChanged;
	}

	private void UIStartTime_TextChanged()
	{
		CheckTimeValue();
		UIStartBeat.TextChanged -= UIStartBeat_TextChanged;
		float timeSpan = float.Parse(UIEndTime.Text) - float.Parse(UIStartTime.Text);
		float beatSpan = timeSpan / (ParentBPM / 60f);
		UIStartBeat.Text = (float.Parse(UIEndBeat.Text) - beatSpan).ToString();
		UIStartBeat.TextChanged += UIStartBeat_TextChanged;
	}

	private void CheckTimeValue()
	{
		if (!float.TryParse(UIStartTime.Text, out float a))
		{
			UIStartTime.TextChanged -= UIStartTime_TextChanged;
			UIStartTime.Text = "0";
			UIStartTime.TextChanged += UIStartTime_TextChanged;
		}
		if (!float.TryParse(UIEndTime.Text, out float b))
		{
			UIEndTime.TextChanged -= UIEndTime_TextChanged;
			UIEndTime.Text = "0";
			UIEndTime.TextChanged += UIEndTime_TextChanged;
		}
		if (!float.TryParse(UIStartBeat.Text, out float c))
		{
			UIStartBeat.TextChanged -= UIStartBeat_TextChanged;
			UIStartBeat.Text = "0";
			UIStartBeat.TextChanged += UIStartBeat_TextChanged;
		}
		if (!float.TryParse(UIEndBeat.Text, out float d))
		{
			UIEndBeat.TextChanged -= UIEndBeat_TextChanged;
			UIEndBeat.Text = "0";
			UIEndBeat.TextChanged += UIEndBeat_TextChanged;
		}

	}

	private void InitializeUI()
	{
		IsExpand = true;
		UISeparateLine.SetPointPosition(0, new Vector2(0, 0));
		UISeparateLine.SetPointPosition(1, new Vector2(UIContainer.Size.X, 0));
		UIChildContainer.QueueRedraw();
		UIStartTime.AllowedInputType = InputType.Float;
		UIEndTime.AllowedInputType = InputType.Float;
		UIStartBeat.AllowedInputType = InputType.Float;
		UIEndBeat.AllowedInputType = InputType.Float;
	}



	//Public function
	/// <summary>
	/// Add child to Container
	/// </summary>
	/// <param name="node">Child control</param>
	public void AddChild(Node node)
	{
		UIChildContainer.AddChild(node);
		UIChildContainer.QueueSort();
	}

	/// <summary>
	/// Insert child to Container
	/// </summary>
	/// <param name="node">Child control</param>
	/// <param name="index">Insert index</param>
	public void InsertChild(Node node, int index)
	{
		UIChildContainer.AddChild(node);
		UIChildContainer.MoveChild(node, index);
		UIChildContainer.QueueSort();
	}

	/// <summary>
	/// Clear all children in Container
	/// </summary>
	public void ClearChild()
	{
		foreach (Node child in UIChildContainer.GetChildren())
		{
			UIChildContainer.RemoveChild(child);
		}
	}

	/// <summary>
	/// Remove target child
	/// </summary>
	/// <param name="node">Target child node</param>
	public new void RemoveChild(Node node)
	{
		UIChildContainer.RemoveChild(node);
	}

	/// <summary>
	/// Remove tarrget child with index
	/// </summary>
	/// <param name="index">Target child index</param>
	public void RemoveChildFromIndex(int index)
	{
		foreach (Node child in UIChildContainer.GetChildren())
		{
			if (child.GetIndex() == index)
			{
				UIChildContainer.RemoveChild(child);
			}
		}
	}


	//Private functions
	private void UIContainer_Resized()
	{
		EmitSignal(SignalName.SizeChanged);
	}

	private void UIHeader_Clicked()
	{
		IsExpand = !IsExpand;
		ToggleExpand();
	}

	private void ToggleExpand()
	{
		if (IsExpand)
		{
			UpdateContentState();
		}
		if (_tween != null && _tween.IsValid())
		{
			_tween.Kill();
		}
		float targetHeight = IsExpand? UIAutoResizeContainer.Size.Y : UINonChildContainer.Size.Y;
		Vector2 startVector2 = new Vector2(Mathf.Abs(UISeparateLine.Points[0].X - UISeparateLine.Points[1].X), 0);
		Vector2 targetstartVector2 = new Vector2(IsExpand ? UIContainer.Size.X : 0, 0);

		if (!Animate)
		{
			UIContainer.Size = new Vector2(_width, targetHeight);
			return;
		}

		_tween = CreateTween()
			.SetEase(Tween.EaseType.Out)
			.SetTrans(Tween.TransitionType.Circ);

		_tween.TweenProperty(UIContainer, "size", new Vector2(UIContainer.Size.X, targetHeight), AnimationDuration);
		_tween.Parallel().TweenMethod(
			Callable.From<Vector2>(vec =>
			{
				startVector2 = vec;
				UISeparateLine.SetPointPosition(0, new Vector2(0.5f * UIContainer.Size.X - 0.5f * startVector2.X, 0));
				UISeparateLine.SetPointPosition(1, new Vector2(0.5f * UIContainer.Size.X + 0.5f * startVector2.X, 0));
			}),
			startVector2,
			targetstartVector2,
			AnimationDuration
			);
		_tween.Finished += () =>
		{
			UpdateContentState();
		};
	}

	private void UpdateContentState()
	{
		//Force update layout
		if (IsExpand)
		{
			foreach (Control child in UIChildContainer.GetChildren())
			{
				child.Visible = IsExpand;
				child.ProcessMode = IsExpand ?
					ProcessModeEnum.Inherit : ProcessModeEnum.Disabled;
				child.MouseFilter = IsExpand ?
					Control.MouseFilterEnum.Stop : Control.MouseFilterEnum.Ignore;
				child.QueueRedraw();
			}
			UIChildContainer.QueueRedraw();
		}
	}
}

Here is the code of the parent that having this issue:

using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using static LabeledInputField;

public partial class MotionEventExpandable : Control
{
	//Control objects
	[Export]
	public ScrollContainer UIContainer;
	[Export]
	public VBoxContainer UIAutoResizeContainer;
	[Export]
	public VBoxContainer UINonChildContainer;
	[Export]
	public Clickable UIHeader;
	[Export]
	public Label UIHeaderLabel;
	[Export]
	public Line2D UISeparateLine;
	[Export]
	public VBoxContainer UIChildContainer;
	[Export]
	public LabeledInputField UIStartTime;
	[Export]
	public LabeledInputField UIEndTime;
	[Export]
	public LabeledInputField UIStartBeat;
	[Export]
	public LabeledInputField UIEndBeat;
	[Export]
	public LabeledVector3InputField UITargetRelatiaveVector3;
	[Export]
	public LabeledEventTypeDropDown UIEventType;
	[Export]
	public LabeledEasingDropDown UIEasing;

	//Public settings
	[Export]
	public bool Animate = true;
	[Export]
	public float AnimationDuration = 0.3f;


	//Signals

	//UI related events
	[Signal]
	public delegate void ChildChangedEventHandler();
	[Signal]
	public delegate void SizeChangedEventHandler();
	[Signal]
	public delegate void IsExpandChangedEventHandler();
	[Signal]
	public delegate void ChildEnteredEventHandler();
	[Signal]
	public delegate void ChildExitedEventHandler();
	[Signal]
	public delegate void DataChangedEventHandler();

	//Private value properties
	private string _label = "Label";
	private bool _isExpand;
	private float _parentBPM = 60f;
	private MRDataType.MREditorMotionEvent _data;



	//Private UI properties
	private float _height = UIPreset.DefaultControlHeight;
	private float _width = UIPreset.DefaultControlWidth;
	private int _fontSize = UIPreset.DefaultTextFieldFontSize;

	//Private running properties
	private Tween _tween;


	//Public UI Properties
	[Export]
	public float Height
	{
		get => _height;
		set
		{
			_height = value;
			if (UIContainer != null)
			{
				UIContainer.Size = new Vector2(UIContainer.Size.X, _height);
				EmitSignal(nameof(SizeChangedEventHandler));
			}
		}
	}

	[Export]
	public float Width
	{
		get => _width;
		set
		{
			_width = value;
			if (UIContainer != null)
			{
				UIContainer.Size = new Vector2(_width, UIContainer.Size.Y);
				EmitSignal(SignalName.SizeChanged);
			}
		}
	}

	[Export]
	public int FontSize
	{
		get => _fontSize;
		set
		{
			_fontSize = value;
			if (UIHeaderLabel != null)
			{
				UIHeaderLabel.AddThemeFontSizeOverride("font_size", value);
			}
		}
	}

	[Export]
	public bool IsExpand
	{
		get => _isExpand;
		set
		{
			if (_isExpand != value)
			{
				_isExpand = value;
				EmitSignal(SignalName.IsExpandChanged);
			}
		}
	}


	//Public value properties
	[Export]
	public string Label
	{
		get => _label;
		set
		{
			_label = value;
			if (UIHeaderLabel != null)
			{
				UIHeaderLabel.Text = value;
			}
		}
	}

	public float ParentBPM
	{
		get => _parentBPM;
		set
		{
			CheckTimeValue();
			_parentBPM = value;
			float BeatSpan = float.Parse(UIEndBeat.Text) - float.Parse(UIStartBeat.Text);
			float Endtime = float.Parse(UIStartTime.Text) + 60f / value * BeatSpan;
			UIEndTime.Text = Endtime.ToString();
		}
	}

	public MRDataType.MREditorMotionEvent Data
	{
		get => _data;
		set
		{
			_data = value;
			EmitSignal(nameof(DataChanged));
		}
	}


	public override void _Ready()
	{
		UIStartTime.AllowedInputType = InputType.Float;
		UIEndTime.AllowedInputType = InputType.Float;
		UIStartBeat.AllowedInputType = InputType.Float;
		UIEndBeat.AllowedInputType = InputType.Float;

		//Register events
		AddUserSignal(nameof(ChildChangedEventHandler));
		AddUserSignal(nameof(SizeChangedEventHandler));
		AddUserSignal(nameof(IsExpandChangedEventHandler));
		AddUserSignal(nameof(ChildEnteredEventHandler));
		AddUserSignal(nameof(ChildExitedEventHandler));

		//Bind signals
		UIHeader.Clicked += UIHeader_Clicked;
		UIStartTime.TextChanged += UIStartTime_TextChanged;
		UIEndTime.TextChanged += UIEndTime_TextChanged;
		UIStartBeat.TextChanged += UIStartBeat_TextChanged;
		UIEndBeat.TextChanged += UIEndBeat_TextChanged;

		InitializeUI();
		CheckTimeValue();
	}

	private void UIEndBeat_TextChanged()
	{
		CheckTimeValue();
		UIEndTime.TextChanged -= UIEndBeat_TextChanged;
		float beatSpan = float.Parse(UIEndBeat.Text) - float.Parse(UIStartBeat.Text);
		float timeSpan = beatSpan * 60f / ParentBPM;
		UIEndTime.Text = (float.Parse(UIStartTime.Text) + timeSpan).ToString();
		UIEndTime.TextChanged += UIEndBeat_TextChanged;
	}

	private void UIStartBeat_TextChanged()
	{
		CheckTimeValue();
		UIStartTime.TextChanged -= UIStartBeat_TextChanged;
		float beatSpan = float.Parse(UIEndBeat.Text) - float.Parse(UIStartBeat.Text);
		float timeSpan = beatSpan * 60f / ParentBPM;
		UIStartTime.Text = (float.Parse(UIEndTime.Text) - timeSpan).ToString();
		UIStartTime.TextChanged += UIStartBeat_TextChanged;
	}

	private void UIEndTime_TextChanged()
	{
		CheckTimeValue();
		UIEndBeat.TextChanged -= UIEndBeat_TextChanged;
		float timeSpan = float.Parse(UIEndTime.Text) - float.Parse(UIStartTime.Text);
		float beatSpan = timeSpan / (ParentBPM / 60f);
		UIEndBeat.Text = (float.Parse(UIStartBeat.Text) + beatSpan).ToString();
		UIEndBeat.TextChanged += UIEndBeat_TextChanged;
	}

	private void UIStartTime_TextChanged()
	{
		CheckTimeValue();
		UIStartBeat.TextChanged -= UIStartBeat_TextChanged;
		float timeSpan = float.Parse(UIEndTime.Text) - float.Parse(UIStartTime.Text);
		float beatSpan = timeSpan / (ParentBPM / 60f);
		UIStartBeat.Text = (float.Parse(UIEndBeat.Text) - beatSpan).ToString();
		UIStartBeat.TextChanged += UIStartBeat_TextChanged;
	}

	private void CheckTimeValue()
	{
		if (!float.TryParse(UIStartTime.Text, out float a))
		{
			UIStartTime.TextChanged -= UIStartTime_TextChanged;
			UIStartTime.Text = "0";
			UIStartTime.TextChanged += UIStartTime_TextChanged;
		}
		if (!float.TryParse(UIEndTime.Text, out float b))
		{
			UIEndTime.TextChanged -= UIEndTime_TextChanged;
			UIEndTime.Text = "0";
			UIEndTime.TextChanged += UIEndTime_TextChanged;
		}
		if (!float.TryParse(UIStartBeat.Text, out float c))
		{
			UIStartBeat.TextChanged -= UIStartBeat_TextChanged;
			UIStartBeat.Text = "0";
			UIStartBeat.TextChanged += UIStartBeat_TextChanged;
		}
		if (!float.TryParse(UIEndBeat.Text, out float d))
		{
			UIEndBeat.TextChanged -= UIEndBeat_TextChanged;
			UIEndBeat.Text = "0";
			UIEndBeat.TextChanged += UIEndBeat_TextChanged;
		}

	}

	private void InitializeUI()
	{
		IsExpand = true;
		UISeparateLine.SetPointPosition(0, new Vector2(0, 0));
		UISeparateLine.SetPointPosition(1, new Vector2(UIContainer.Size.X, 0));
		UIChildContainer.QueueRedraw();
		UIStartTime.AllowedInputType = InputType.Float;
		UIEndTime.AllowedInputType = InputType.Float;
		UIStartBeat.AllowedInputType = InputType.Float;
		UIEndBeat.AllowedInputType = InputType.Float;
	}



	//Public function
	/// <summary>
	/// Add child to Container
	/// </summary>
	/// <param name="node">Child control</param>
	public void AddChild(Node node)
	{
		UIChildContainer.AddChild(node);
		UIChildContainer.QueueSort();
	}

	/// <summary>
	/// Insert child to Container
	/// </summary>
	/// <param name="node">Child control</param>
	/// <param name="index">Insert index</param>
	public void InsertChild(Node node, int index)
	{
		UIChildContainer.AddChild(node);
		UIChildContainer.MoveChild(node, index);
		UIChildContainer.QueueSort();
	}

	/// <summary>
	/// Clear all children in Container
	/// </summary>
	public void ClearChild()
	{
		foreach (Node child in UIChildContainer.GetChildren())
		{
			UIChildContainer.RemoveChild(child);
		}
	}

	/// <summary>
	/// Remove target child
	/// </summary>
	/// <param name="node">Target child node</param>
	public new void RemoveChild(Node node)
	{
		UIChildContainer.RemoveChild(node);
	}

	/// <summary>
	/// Remove tarrget child with index
	/// </summary>
	/// <param name="index">Target child index</param>
	public void RemoveChildFromIndex(int index)
	{
		foreach (Node child in UIChildContainer.GetChildren())
		{
			if (child.GetIndex() == index)
			{
				UIChildContainer.RemoveChild(child);
			}
		}
	}


	//Private functions
	private void UIContainer_Resized()
	{
		EmitSignal(SignalName.SizeChanged);
	}

	private void UIHeader_Clicked()
	{
		IsExpand = !IsExpand;
		ToggleExpand();
	}

	private void ToggleExpand()
	{
		if (IsExpand)
		{
			UpdateContentState();
		}
		if (_tween != null && _tween.IsValid())
		{
			_tween.Kill();
		}
		float targetHeight = IsExpand? UIAutoResizeContainer.Size.Y : UINonChildContainer.Size.Y;
		Vector2 startVector2 = new Vector2(Mathf.Abs(UISeparateLine.Points[0].X - UISeparateLine.Points[1].X), 0);
		Vector2 targetstartVector2 = new Vector2(IsExpand ? UIContainer.Size.X : 0, 0);

		if (!Animate)
		{
			UIContainer.Size = new Vector2(_width, targetHeight);
			return;
		}

		_tween = CreateTween()
			.SetEase(Tween.EaseType.Out)
			.SetTrans(Tween.TransitionType.Circ);

		_tween.TweenProperty(UIContainer, "size", new Vector2(UIContainer.Size.X, targetHeight), AnimationDuration);
		_tween.Parallel().TweenMethod(
			Callable.From<Vector2>(vec =>
			{
				startVector2 = vec;
				UISeparateLine.SetPointPosition(0, new Vector2(0.5f * UIContainer.Size.X - 0.5f * startVector2.X, 0));
				UISeparateLine.SetPointPosition(1, new Vector2(0.5f * UIContainer.Size.X + 0.5f * startVector2.X, 0));
			}),
			startVector2,
			targetstartVector2,
			AnimationDuration
			);
		_tween.Finished += () =>
		{
			UpdateContentState();
		};
	}

	private void UpdateContentState()
	{
		//Force update layout
		if (IsExpand)
		{
			foreach (Control child in UIChildContainer.GetChildren())
			{
				child.Visible = IsExpand;
				child.ProcessMode = IsExpand ?
					ProcessModeEnum.Inherit : ProcessModeEnum.Disabled;
				child.MouseFilter = IsExpand ?
					Control.MouseFilterEnum.Stop : Control.MouseFilterEnum.Ignore;
				child.QueueRedraw();
			}
			UIChildContainer.QueueRedraw();
		}
	}
}

There is a lot of code here, so I only skimmed it (I think you posted the same class twice?) - I don’t see the problem right away.

  • You seem to have some error output. Can you post it here? The problem might be that the scene is not properly saved due to some error.
  • What happens when you reload the scene in the editor?
  • Which exported properties are gone after you run the game - all of them?

Btw., you don’t have to register the signals manually. This is handled by the [Signal] attribute. Currently, you basically create the signals twice. So you should get rid of AddUserSignal.
When using [Signal] the source-generator creates SignalName and a EmitSignal{SignalName} function, so you can emit like this:

// with the generated function - this way you also have type safety for parameters
EmitSignalChildChanged();

// or with the SignalName
EmitSignal(SignalName.ChildChanged);

Of course you can also use C# native events instead of signals if you don’t need any specific signal functionality (e.g. connecting via the editor).

1 Like

Sorry, you’re right, I’ve copied one part of the code from a wrong place. Sure, I’ll post the error here.

  • When I open my project, MotionEventExpandable.tscn was opened in a tab window, and now the child controls have their subchildren binded.
  • Then I press Run Current Scene button to run this scene, and then the child controls lose their subchildren binding, with Output showing:
ERROR:    at LabeledVector3InputField.set_Label(String value) in D:\LocalProj\tmr-editor-godot-test\tmr-editor-godot-test\UILib\LabeledVector3InputField.cs:line 127
ERROR:    at LabeledVector3InputField.RestoreGodotObjectData(GodotSerializationInfo info) in D:\LocalProj\tmr-editor-godot-test\tmr-editor-godot-test\.godot\mono\temp\obj\Debug\Godot.SourceGenerators\Godot.SourceGenerators.ScriptSerializationGenerator\LabeledVector3InputField_ScriptSerialization.generated.cs:line 54
ERROR:    at Godot.Bridge.CSharpInstanceBridge.DeserializeState(IntPtr godotObjectGCHandle, godot_dictionary* propertiesState, godot_dictionary* signalEventsState) in /root/godot/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs:line 255

The error seems occured at here, it’s strange that I’ve already set the value of UI objects at the editor.

And here’s all the code code of the file

using Godot;
using System;
using System.Xml.Linq;
using static LabeledInputField;
using static System.Net.Mime.MediaTypeNames;

[Tool]
public partial class LabeledVector3InputField : Control
{
    //Control objects
    [Export]
    public HBoxContainer UIContainer;
    [Export]
    public Label UILabel;
    [Export]
    public HBoxContainer UIValueFieldPanel;
    [Export]
    public LabeledInputField UIXEdit;
    [Export]
    public LabeledInputField UIYEdit;
    [Export]
    public LabeledInputField UIZEdit;

    //Public settings
    public enum TargetCoordinateValue { x,y,z };
    public InputType AllowedInputType = InputType.Float;
    [Export]
    public Color ErrorColor = new Color(255, 0, 0);
    [Export]
    public float ShakeDuration = 0.9f;
    [Export]
    private float initialShakeStrength = 20f;
    [Export]
    private float shakeDecayRate = 0.5f;


    //Signals

    //Value related events
    [Signal]
    public delegate void TextChangedEventHandler();
    [Signal]
    public delegate void IllegalValueInputEventHandler();

    //UI related events
    [Signal]
    public delegate void SizeChangedEventHandler();


    //Private value properties
    private string _label;
    private float _x;
    private float _y;
    private float _z;
    private Vector3 _vector;


    //Private UI properties
    private float _height = UIPreset.DefaultControlHeight;
    private float _width = UIPreset.DefaultControlWidth;
    private int _fontSize = UIPreset.DefaultTextFieldFontSize;


    //Public UI Properties
    [Export]
    public float Height
    {
        get => _height;
        set
        {
            _height = value;
            if (UIContainer != null)
            {
                UIContainer.Size = new Vector2(UIContainer.Size.X, _height);
                EmitSignal(SignalName.SizeChanged);
            }
        }
    }

    [Export]
    public float Width
    {
        get => _width;
        set
        {
            _width = value;
            if (UIContainer != null)
            {
                UIContainer.Size = new Vector2(_width, UIContainer.Size.Y);
                EmitSignal(SignalName.SizeChanged);
            }
        }
    }

    [Export]
    public int FontSize
    {
        get => _fontSize;
        set
        {
            _fontSize = value;
            if (UIValueFieldPanel != null)
            {
                UILabel.AddThemeFontSizeOverride("font_size", value);
                UIXEdit.AddThemeFontSizeOverride("font_size", value);
                UIYEdit.AddThemeFontSizeOverride("font_size", value);
                UIZEdit.AddThemeFontSizeOverride("font_size", value);
            }
        }
    }


    //Public value properties
    [Export]
    public string Label
    {
        get => _label;
        set
        {
            _label = value;
            if (UILabel != null)
            {
                UILabel.Text = value;
            }
            else
            {
                UILabel.Text = value;
            }
        }
    }

    [Export]
    public float X
    {
        get => _x;
        set
        {
            _x = value;
            _vector = new Vector3(value, _vector.Y, _vector.Z);
        }
    }

    [Export]
    public float Y
    {
        get => _y;
        set
        {
            _y = value;
            _vector = new Vector3(_vector.X, value, _vector.Z);
        }
    }

    [Export]
    public float Z
    {
        get => _z;
        set
        {
            _z = value;
            _vector = new Vector3(_vector.X, _vector.Y, value);
        }
    }

    [Export]
    public Vector3 Vector
    {
        get => _vector;
        set
        {
            if (_x != value.X)
                _x = value.X;
            if (_y != value.Y)
                _y = value.Y;
            if (_z != value.Z)
                _z = value.Z;

            if (UIXEdit.Text != value.X.ToString())
                UIZEdit.Text = value.X.ToString();
            if (UIYEdit.Text != value.Y.ToString())
                UIYEdit.Text = value.Y.ToString();
            if (UIZEdit.Text != value.Z.ToString())
                UIZEdit.Text = value.Z.ToString();
            EmitSignal(SignalName.IllegalValueInput);
        }
    }


    public override void _Ready()
    {
        UIXEdit.AllowedInputType = InputType.Float;
        UIYEdit.AllowedInputType = InputType.Float;
        UIZEdit.AllowedInputType = InputType.Float;


        //Register events
        AddUserSignal(nameof(TextChangedEventHandler));
        AddUserSignal(nameof(IllegalValueInputEventHandler));
        AddUserSignal(nameof(SizeChangedEventHandler));

        UIContainer.Resized += () => { Width = UIContainer.Size.X; Height = UIContainer.Size.Y; };

        UIXEdit.IllegalValueInput += () => { EmitSignal(SignalName.IllegalValueInput); };
        UIYEdit.IllegalValueInput += () => { EmitSignal(SignalName.IllegalValueInput); };
        UIZEdit.IllegalValueInput += () => { EmitSignal(SignalName.IllegalValueInput); };

        UIXEdit.TextChanged += () => { X = float.TryParse(UIXEdit.Text, out float value)? value : 0.0f; };
        UIYEdit.TextChanged += () => { Y = float.TryParse(UIYEdit.Text, out float value)? value : 0.0f; };
        UIZEdit.TextChanged += () => { Z = float.TryParse(UIZEdit.Text, out float value)? value : 0.0f; };

        //Apply UI presets
        Height = _height;
        Width = _width;
        FontSize = _fontSize;
        if (UILabel.Text == string.Empty)
        {
            Label = UILabel.Text;
        }
    }
}
  • Then I restarted the scene, the errors are gone but the lost children are still not coming back.
  • Finally I closed the tab window and repoened the tscn file, the lost sunchildren binds are coming back. But every time I open Godot, the problem repeats.

Thank you for your help!

Plus, thanks for correcting my mistake! I’ve removed all the AddUserSignal()
and EmitSignal(nameof()) from my code!

In your else branch you are trying to assign the text even though you UILabel is null. You should remove that else branch. But this shouldn’t cause the re-assigning.
My guess would be, that the you [Tool] script resets the properties when loaded. What happens when you remove [Tool] and run it?

It would be great if you could create an example that reproduces the issue but gets rid of all the other stuff. E.g. a script where you export one property where it happens. This would make it a lot easier to analyze.

1 Like

Thanks, I removed the else branch and [Tool] label, but it still cause re-assigning. Plus, the child not infected also have a [Tool] label.

Your reply inspired me and I checked for null judge. Then I solved this problem but I still don’t know how it happens, The issue happens at class LabeledVector3InputField.cs and after I added if(UIContainer != null) or something else to judge if the UI element exists before changing its value. I still not knowing why it lead to re-assigning, and also affecting the other UI elements after it. Confusing.

Code edited:

using Godot;
using System;
using System.Xml.Linq;
using static LabeledInputField;
using static System.Net.Mime.MediaTypeNames;

[Tool]
public partial class LabeledVector3InputField : Control
{
    //Control objects
    [Export]
    public HBoxContainer UIContainer;
    [Export]
    public Label UILabel;
    [Export]
    public HBoxContainer UIValueFieldPanel;
    [Export]
    public LabeledInputField UIXEdit;
    [Export]
    public LabeledInputField UIYEdit;
    [Export]
    public LabeledInputField UIZEdit;

    //Public settings
    public enum TargetCoordinateValue { x,y,z };
    public InputType AllowedInputType = InputType.Float;
    [Export]
    public Color ErrorColor = new Color(255, 0, 0);
    [Export]
    public float ShakeDuration = 0.9f;
    [Export]
    private float initialShakeStrength = 20f;
    [Export]
    private float shakeDecayRate = 0.5f;


    //Signals

    //Value related events
    [Signal]
    public delegate void TextChangedEventHandler();
    [Signal]
    public delegate void IllegalValueInputEventHandler();

    //UI related events
    [Signal]
    public delegate void SizeChangedEventHandler();


    //Private value properties
    private string _label;
    private float _x;
    private float _y;
    private float _z;
    private Vector3 _vector;


    //Private UI properties
    private float _height = UIPreset.DefaultControlHeight;
    private float _width = UIPreset.DefaultControlWidth;
    private int _fontSize = UIPreset.DefaultTextFieldFontSize;


    //Public UI Properties
    [Export]
    public float Height
    {
        get => _height;
        set
        {
            _height = value;
            if (UIContainer != null)
            {
                UIContainer.Size = new Vector2(UIContainer.Size.X, _height);
                EmitSignal(SignalName.SizeChanged);
            }
        }
    }

    [Export]
    public float Width
    {
        get => _width;
        set
        {
            _width = value;
            if (UIContainer != null)
            {
                UIContainer.Size = new Vector2(_width, UIContainer.Size.Y);
                EmitSignal(SignalName.SizeChanged);
            }
        }
    }

    [Export]
    public int FontSize
    {
        get => _fontSize;
        set
        {
            _fontSize = value;
            if (UIValueFieldPanel != null)
            {
                UILabel.AddThemeFontSizeOverride("font_size", value);
                UIXEdit.AddThemeFontSizeOverride("font_size", value);
                UIYEdit.AddThemeFontSizeOverride("font_size", value);
                UIZEdit.AddThemeFontSizeOverride("font_size", value);
            }
        }
    }


    //Public value properties
    [Export]
    public string Label
    {
        get => _label;
        set
        {
            _label = value;
            if (UILabel != null)
            {
                UILabel.Text = value;
            }
        }
    }

    [Export]
    public float X
    {
        get => _x;
        set
        {
            _x = value;
            _vector = new Vector3(value, _vector.Y, _vector.Z);
        }
    }

    [Export]
    public float Y
    {
        get => _y;
        set
        {
            _y = value;
            _vector = new Vector3(_vector.X, value, _vector.Z);
        }
    }

    [Export]
    public float Z
    {
        get => _z;
        set
        {
            _z = value;
            _vector = new Vector3(_vector.X, _vector.Y, value);
        }
    }

    [Export]
    public Vector3 Vector
    {
        get => _vector;
        set
        {
            if (_x != value.X)
                _x = value.X;
            if (_y != value.Y)
                _y = value.Y;
            if (_z != value.Z)
                _z = value.Z;
            if (UIXEdit != null && UIYEdit != null && UIZEdit != null)
            {
                if (UIXEdit.Text != value.X.ToString())
                    UIZEdit.Text = value.X.ToString();
                if (UIYEdit.Text != value.Y.ToString())
                    UIYEdit.Text = value.Y.ToString();
                if (UIZEdit.Text != value.Z.ToString())
                    UIZEdit.Text = value.Z.ToString();
            }
            EmitSignal(SignalName.IllegalValueInput);
        }
    }


    public override void _Ready()
    {
        UIXEdit.AllowedInputType = InputType.Float;
        UIYEdit.AllowedInputType = InputType.Float;
        UIZEdit.AllowedInputType = InputType.Float;

        UIContainer.Resized += () => { Width = UIContainer.Size.X; Height = UIContainer.Size.Y; };

        UIXEdit.IllegalValueInput += () => { EmitSignal(SignalName.IllegalValueInput); };
        UIYEdit.IllegalValueInput += () => { EmitSignal(SignalName.IllegalValueInput); };
        UIZEdit.IllegalValueInput += () => { EmitSignal(SignalName.IllegalValueInput); };

        UIXEdit.TextChanged += () => { X = float.TryParse(UIXEdit.Text, out float value)? value : 0.0f; };
        UIYEdit.TextChanged += () => { Y = float.TryParse(UIYEdit.Text, out float value)? value : 0.0f; };
        UIZEdit.TextChanged += () => { Z = float.TryParse(UIZEdit.Text, out float value)? value : 0.0f; };

        //Apply UI presets
        Height = _height;
        Width = _width;
        FontSize = _fontSize;
        if (UILabel.Text == string.Empty)
        {
            Label = UILabel.Text;
        }
    }
}