Layout serialization doesnt work in AOT

Docking/MDI for Avalonia Forum

Posted 1 days ago by Dirk Zellerfeld
Version: 25.2.1
Platform: .NET 9
Environment: Windows 11 (64-bit)
Avatar

Running with AOT compiled, when calling _layoutSerializer.SaveToFile("layout.xml", dockSite); it creates an empty layout.xml file (im calling it in Closing event but I guess it crashes here and thats why its empty). With non-AOT it works fine.

My docksite just has one toolwindow and that's it. Settings are:

_layoutSerializer = new DockSiteLayoutSerializer(){
SerializationBehavior = SockSiteSerializationBehavior.ToolWindowsOnly,
DocumentWindowDeserializationBehavior = DockingWindowDeserializationBehavior.Discard,
ToolWindowDeserializationBehavior = DockingWindowDeserializationBehavior.Discard
};

Toolwindow CanSerialize is true and it has a unique SerializationId. AOT is one of the main reasons why we are currently evaluating switching from WPF to Avalonia. If docking serilization or any other important feature doesn't work with AOT it would be a huge problem for us.

Edit: Yeah it's crashing, looks like AOT has trimmed too much?

Exception Info: System.InvalidOperationException: 
ActiproSoftware.UI.Avalonia.Controls.Docking.Serialization.XmlAutoHideContainers cannot be serialized because it does not have a parameterless constructor.
at System.Xml.Serialization.TypeDesc.CheckSupported() + 0x4bat 
System.Xml.Serialization.TypeScope.GetTypeDesc(Type, MemberInfo, Boolean, Boolean) + 0xddat 
System.Xml.Serialization.ModelScope.GetTypeModel(Type, Boolean) + 0x50at 
System.Xml.Serialization.XmlReflectionImporter.IncludeType(Type, RecursionLimiter) + 0x52at 
System.Xml.Serialization.XmlSerializer.GenerateXmlTypeMapping(Type, XmlAttributeOverrides, Type[], XmlRootAttribute, String) + 0x79at 
ActiproSoftware.UI.Avalonia.Controls.Docking.Serialization.DockSiteLayoutSerializer.GetXmlSerializer() + 0x152at ActiproSoftware.UI.Avalonia.Serialization.XmlSerializerBase`2.yem() + 0x1bat 
ActiproSoftware.UI.Avalonia.Serialization.XmlSerializerBase`2.Ye0(XmlWriter, Object) + 0x18at 
ActiproSoftware.UI.Avalonia.Serialization.XmlSerializerBase`2.NeH(String, Object) + 0x5cat 
Avalonia.Controls.Window.ShouldCancelClose(WindowClosingEventArgs) + 0xd6at 

[Modified 1 days ago]

Comments (3)

Posted 1 days ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Dirk,

Thank you for all of the details.  Our current serialization logic for Docking was ported from WPF and uses XmlSerializer.  XmlSerializer relies heavily on reflection and, when it comes to trimming, reflection == bad!

You should have at least seen AOT warnings when trying to use the serializer because we marked it as relying on dynamic code.  Our expectation at the time was that anyone wanting to use AOT could just exclude the Docking library from trimming if they wanting to use both AOT and layout serialization.  After spending hours working on a simple sample application, we have not been able to get Docking to be excluded from trimming at all.  We did make some progress and at least got the sample application to stop crashing in Visual Studio, but then the application would still crash if published/executed from the command line.

Supporting AOT is important to us.  Since trim exclusion is not working as expected, we're going to start working on a serializer that does not rely on reflection so it can work with AOT.

We are making this a top priority and will update this post as soon as we have an update.


Actipro Software Support

Posted 19 hours ago by Dirk Zellerfeld
Avatar

Thank you for the reply. Luckily it is currently only one tool window we need to save and restore. So we created our own logic for this as a workaround. It seems to work fine in docked state but we have trouble to restore the tool window when it was floating. How can we save the floating window position and size which is required for calling the Float() method?

private bool _layoutRestored;
private void dockSite_Loaded(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
	if (_layoutRestored)
		return;

	_layoutRestored = true;
	try
	{
		if (File.Exists("layout.json"))
			RestoreLayout(System.Text.Json.JsonSerializer.Deserialize(File.ReadAllBytes("layout.json"), AppJsonContext.Default.ToolWindowLayoutState));
		else
			labelToolWindow.IsOpen = true;
	}
	catch (Exception) { }
}
public void RestoreLayout(ToolWindowLayoutState? layout)
{
	if (layout is null)
	{
		labelToolWindow.IsOpen = true;
		return;
	}
	labelToolWindow.IsOpen = false;

	// Size first
	if (!layout.IsFloating && layout.State != DockingWindowState.Document)
	{
		if (layout.DockSide.HasValue)
			labelToolWindow.Dock(dockSite, layout.DockSide.Value);
		labelToolWindow.ContainerDockedSize = new Size(layout.DockedWidth, layout.DockedHeight);
	}

	if (layout.IsFloating && layout.FloatWidth.HasValue && layout.FloatHeight.HasValue)
	{
		// Restore floating
		var size = new Size(layout.FloatWidth.Value, layout.FloatHeight.Value);

		Point? location = null;
		if (layout.FloatX.HasValue && layout.FloatY.HasValue)
			location = new Point(layout.FloatX.Value, layout.FloatY.Value);

		// Open and float
		labelToolWindow.IsOpen = true;
		labelToolWindow.Float(location, size);
	}
	else
		labelToolWindow.IsOpen = layout.IsOpen;

	labelToolWindow.State = layout.State;
}
public ToolWindowLayoutState CaptureLayout()
{
	var w = labelToolWindow;
	var layout = new ToolWindowLayoutState
	{
		IsOpen = w.IsOpen,
		State = w.State,
		IsFloating = w.IsFloating,
		DockedWidth = w.ContainerDockedSize.Width,
		DockedHeight = w.ContainerDockedSize.Height,
		DockSide = w.GetCurrentSide(),
	};
	if (w.IsOpen && w.IsFloating)
	{
		var dockHost = w.DockHost;
		var dockSite = w.DockSite;
		// Get floating position and size


	}
	return layout;
}
[JsonSerializable(typeof(ToolWindowLayoutState))]
public partial class AppJsonContext : JsonSerializerContext
{
}
public sealed class ToolWindowLayoutState
{
	public bool IsOpen { get; set; }
	public Dock? DockSide { get; set; }
	public DockingWindowState State { get; set; }
	public double DockedWidth { get; set; }
	public double DockedHeight { get; set; }
	public bool IsFloating { get; set; }
	public double? FloatX { get; set; }
	public double? FloatY { get; set; }
	public double? FloatWidth { get; set; }
	public double? FloatHeight { get; set; }
}

[Modified 19 hours ago]

Posted 3 hours ago by Actipro Software Support - Cleveland, OH, USA
Avatar

You may not want to spend much time on a custom solution since we plan to ship an AOT-compatible solution very soon and have already made substantial progress.  But if you want to keep working on a workaround, you could access the Window that is an ancestor of a floating DockHost and use the Position/Bounds property of that Window.  The following shows how you could access the Window:

var floatingWindow = toolWindow.FindAncestorOfType<Window>()

Docking layout can be very complex, so I would not recommend a custom solution beyond the very simple scenario you are describing.


Actipro Software Support

Add Comment

Please log in to a validated account to post comments.