From 102eddb802eba52aae514ff12b1b0c05aee432ad Mon Sep 17 00:00:00 2001 From: Kir_Antipov Date: Mon, 25 Nov 2024 23:20:39 +0300 Subject: [PATCH] Detach control from its parents before populate Fixes #12 --- .../Helpers/AvaloniaControlHelper.cs | 70 +++++++++++++++++-- 1 file changed, 65 insertions(+), 5 deletions(-) diff --git a/src/HotAvalonia/Helpers/AvaloniaControlHelper.cs b/src/HotAvalonia/Helpers/AvaloniaControlHelper.cs index 714772e..da2d650 100644 --- a/src/HotAvalonia/Helpers/AvaloniaControlHelper.cs +++ b/src/HotAvalonia/Helpers/AvaloniaControlHelper.cs @@ -2,6 +2,8 @@ using System.Reflection; using System.Reflection.Emit; using Avalonia; +using Avalonia.Controls; +using Avalonia.LogicalTree; using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml.XamlIl.Runtime; using Avalonia.Styling; @@ -19,6 +21,11 @@ internal static class AvaloniaControlHelper /// private static readonly FieldInfo? s_stylesAppliedField; + /// + /// The `InheritanceParent` property of the class. + /// + private static readonly PropertyInfo? s_inheritanceParentProperty; + /// /// The `Type` property of the XamlX.IL.SreTypeSystem.SreType class. /// @@ -37,16 +44,18 @@ internal static class AvaloniaControlHelper /// static AvaloniaControlHelper() { - FieldInfo? stylesAppliedField = typeof(StyledElement).GetField("_stylesApplied", BindingFlags.NonPublic | BindingFlags.Instance); + const BindingFlags InstanceMember = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + + FieldInfo? stylesAppliedField = typeof(StyledElement).GetField("_stylesApplied", InstanceMember); s_stylesAppliedField = stylesAppliedField?.FieldType == typeof(bool) ? stylesAppliedField : null; + s_inheritanceParentProperty = typeof(AvaloniaObject).GetProperty("InheritanceParent", InstanceMember); Assembly xamlLoaderAssembly = typeof(AvaloniaRuntimeXamlLoader).Assembly; Type? sreType = xamlLoaderAssembly.GetType("XamlX.IL.SreTypeSystem+SreType"); s_xamlTypeProperty = sreType?.GetProperty("Type"); - BindingFlags ctorFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; Type? sreMethodBuilder = xamlLoaderAssembly.GetType("XamlX.IL.SreTypeSystem+SreTypeBuilder+SreMethodBuilder"); - ConstructorInfo? sreMethodBuilderCtor = sreMethodBuilder?.GetConstructors(ctorFlags).FirstOrDefault(x => x.GetParameters().Length > 1); + ConstructorInfo? sreMethodBuilderCtor = sreMethodBuilder?.GetConstructors(InstanceMember).FirstOrDefault(x => x.GetParameters().Length > 1); if (sreMethodBuilderCtor is not null) s_sreMethodBuilderInjection = CallbackInjector.Inject(sreMethodBuilderCtor, OnNewSreMethodBuilder); } @@ -105,8 +114,9 @@ public static object Load(string xaml, Uri uri, object? control, out MethodInfo? string xamlWithDynamicComponents = MakeStaticComponentsDynamic(xaml); HashSet oldPopulateMethods = new(AvaloniaRuntimeXamlScanner.FindDynamicPopulateMethods(uri)); - Clear(control); + Reset(control, out Action restore); object loadedControl = AvaloniaRuntimeXamlLoader.Load(xamlWithDynamicComponents, null, control, uri, designMode: false); + restore(); compiledPopulateMethod = AvaloniaRuntimeXamlScanner .FindDynamicPopulateMethods(uri) @@ -146,8 +156,9 @@ public static void Populate(MethodBase populate, IServiceProvider? serviceProvid control, }; - Clear(control); + Reset(control, out Action restore); populate.Invoke(null, args); + restore(); } /// @@ -206,6 +217,55 @@ public static void Clear(object? control) } } + /// + /// Fully resets the state of an Avalonia control and + /// provides a callback to restore its original state. + /// + /// The control to reset. + /// When this method returns, contains a callback to restore the control's original state. + public static void Reset(object? control, out Action restore) + { + Detach(control, out ILogical? logicalParent, out AvaloniaObject? inheritanceParent); + Clear(control); + restore = () => Attach(control, logicalParent, inheritanceParent); + } + + /// + /// Detaches an Avalonia control from its logical and inheritance parents. + /// + /// The control to detach. + /// + /// When this method returns, contains the control's logical parent, or null if it has none. + /// + /// + /// When this method returns, contains the control's inheritance parent, or null if it has none. + /// + private static void Detach(object? control, out ILogical? logicalParent, out AvaloniaObject? inheritanceParent) + { + logicalParent = (control as ILogical)?.GetLogicalParent(); + inheritanceParent = control is AvaloniaObject + ? s_inheritanceParentProperty?.GetValue(control) as AvaloniaObject + : null; + + (control as ISetLogicalParent)?.SetParent(null); + (control as ISetInheritanceParent)?.SetParent(null); + } + + /// + /// Attaches an Avalonia control to the specified logical and inheritance parents. + /// + /// The control to attach. + /// The logical parent to attach the control to. + /// The inheritance parent to attach the control to. + private static void Attach(object? control, ILogical? logicalParent, AvaloniaObject? inheritanceParent) + { + if (logicalParent is not null && control is ISetLogicalParent logical) + logical.SetParent(logicalParent); + + if (inheritanceParent is not null && control is ISetInheritanceParent inheritance) + inheritance.SetParent(inheritanceParent); + } + /// /// Replaces all static resources with their dynamic counterparts within a XAML markup. ///