OpenRA/OpenRA · error · InvalidOperationException

Widget type `{GetType().Name}` is not cloneable.

Error message

Widget type `{GetType().Name}` is not cloneable.

What it means

Widget.Clone() is a virtual method whose base implementation always throws, signaling that the widget type does not support cloning. Subclasses that need to be cloneable must override Clone() to perform a deep copy including children. The exception names the actual runtime type via GetType().Name so the developer knows exactly which widget type is missing the override. This fires during widget duplication (e.g. template-based UI construction).

Source

Thrown at OpenRA.Game/Widgets/Widget.cs:269

			Logic = widget.Logic;
			Visible = widget.Visible;

			Bounds = widget.Bounds;
			Parent = widget.Parent;

			IsVisible = widget.IsVisible;
			IgnoreChildMouseOver = widget.IgnoreChildMouseOver;
			IgnoreMouseOver = widget.IgnoreMouseOver;

			defaultCursor = widget.defaultCursor;

			foreach (var child in widget.Children)
				AddChild(child.Clone());
		}

		public virtual Widget Clone()
		{
			throw new InvalidOperationException($"Widget type `{GetType().Name}` is not cloneable.");
		}

		public virtual int2 RenderOrigin
		{
			get
			{
				var offset = (Parent == null) ? int2.Zero : Parent.ChildOrigin;
				return new int2(Bounds.X, Bounds.Y) + offset;
			}
		}

		public virtual int2 ChildOrigin => RenderOrigin;

		public virtual Rectangle RenderBounds
		{
			get
			{
				var ro = RenderOrigin;

View on GitHub (pinned to a520984d91)

Solutions

  1. Override Clone() in the widget subclass to return a deep copy (including copying all fields and recursively cloning children).
  2. If the widget should never be cloned, ensure it is not used in contexts that clone children (remove from template/repeater containers).
  3. Follow the existing pattern: call the copy constructor, copy fields, and clone each child via AddChild(child.Clone()).

Example fix

// before
public class MyWidget : Widget
{
    // no Clone() override — Clone() throws
}

// after
public class MyWidget : Widget
{
    public MyWidget() {}
    public MyWidget(MyWidget other) : base(other)
    {
        // copy MyWidget-specific fields here
    }

    public override Widget Clone()
    {
        return new MyWidget(this);
    }
}
Defensive patterns

Strategy: validation

Validate before calling

// Check if a widget type is cloneable before calling Clone
var cloneable = widget as ICloneable;
// Note: Widget.Clone() always throws in the base class, so only
// subclasses that override Clone() are safe to clone.
// Validate by checking the type has a copy constructor:
static bool IsCloneable(Widget w)
{
    return w.GetType().GetConstructor(new[] { w.GetType() }) != null;
}

Type guard

// Check whether the widget overrides Clone before calling it
static bool IsWidgetCloneable(Widget widget)
{
    var method = widget.GetType().GetMethod("Clone");
    return method != null && method.DeclaringType != typeof(Widget);
}

Prevention

When it happens

Trigger: Code calls Clone() on a Widget subclass that has not overridden the virtual Clone() method. This can happen during chrome template instantiation, widget duplication for list/repeater widgets, or any code path that deep-copies the widget tree.

Common situations: Creating a new custom widget type and forgetting to override Clone(), or adding a widget to a container that clones its children (like an IterableWidgetGroup or scroll list) without implementing Clone(). The error surfaces at the point of duplication, not at definition time.

Related errors


AI-assisted analysis of OpenRA/OpenRA@a520984d91 (2026-08-13). Data as JSON: /api/errors/558f33d799b74a5c. Report an issue: GitHub.