From afdbeaf37a445f6f3ef8fb02753432033b0c1241 Mon Sep 17 00:00:00 2001 From: Raul Avila Catala Date: Thu, 27 Aug 2026 23:53:16 -0600 Subject: [PATCH] =?UTF-8?q?feat:=20modelo=20de=20materializaci=C3=B3n=20di?= =?UTF-8?q?ferida=20descriptor=20=E2=86=92=20Initialize()=20fases=201-5=20?= =?UTF-8?q?(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fase 1: View pasa a descriptor diferido (List> + PendingName), ViewModel agrega ControlType/Configure, ChildrenBuilderImpl deja de llamar ControlActivatorRegistry en Layout - Fase 2: INativeHandleFactory + Win32NativeHandleFactory ya integrados, ControlActivatorRegistry extiende RegisterFactory/CreateFactoryFor y Win32ControlActivator registra factories - Fase 3: WindowImpl/AlternativeWindowImpl.MaterializeContent materializa descriptores (CreateFor + Configure + CreateHandle + Register) y ejecuta LayoutEngine batching - Fase 4: Application.Behavior difiere configure (_pendingBehavior) y lo ejecuta en Initialize tras RegisterMain/RegisterAlternative con HandleRegistry poblado - Fase 5: HandleRegistry.Unregister/Clear + TrackChildWindow/UnregisterWindowControls y MasterRouter.WM_DESTROY limpieza determinística (evita leak _nameToControl/_hwndToControl), Sample E2E sin excepciones --- .../Core/Application.cs | 12 +- .../Core/HandleRegistry.cs | 36 ++++ .../Core/LayoutEngine/ChildrenBuilderImpl.cs | 16 +- .../Core/LayoutModels.cs | 2 + .../Core/MasterRouter.cs | 3 +- .../Core/Windowing/AlternativeWindowImpl.cs | 21 ++- .../Core/Windowing/WindowImpl.cs | 22 ++- src/EasyWindowsApplication/Share/View.cs | 167 +++++++++++++++--- .../Backend/Win32NativeHandleFactory.cs | 3 + 9 files changed, 241 insertions(+), 41 deletions(-) diff --git a/src/EasyWindowsApplication/Core/Application.cs b/src/EasyWindowsApplication/Core/Application.cs index eaff0b6..0089381 100644 --- a/src/EasyWindowsApplication/Core/Application.cs +++ b/src/EasyWindowsApplication/Core/Application.cs @@ -17,6 +17,7 @@ internal sealed class Application : private readonly List _windows = new(); private MasterRouter _router = null!; private readonly HandleRegistry _registry = new(); + private Action? _pendingBehavior; public IApplicationLayoutPhase Resources(Action configure) { @@ -37,9 +38,7 @@ public IApplicationPostLayoutPhase Layout(Action configure) public IApplicationPostBehaviorPhase Behavior(Action configure) { - BehaviorBuilder.Registry = _registry; - ControlAccess.SetController(BehaviorBuilder); - configure(BehaviorBuilder); + _pendingBehavior += configure; return this; } @@ -70,6 +69,13 @@ public void Initialize() RegisterMain(window); } + if (_pendingBehavior is not null) + { + BehaviorBuilder.Registry = _registry; + ControlAccess.SetController(BehaviorBuilder); + _pendingBehavior(BehaviorBuilder); + } + Procedures.RunMessageLoop(); } diff --git a/src/EasyWindowsApplication/Core/HandleRegistry.cs b/src/EasyWindowsApplication/Core/HandleRegistry.cs index 004bb39..175c6ef 100644 --- a/src/EasyWindowsApplication/Core/HandleRegistry.cs +++ b/src/EasyWindowsApplication/Core/HandleRegistry.cs @@ -16,6 +16,7 @@ internal sealed class HandleRegistry private readonly Dictionary> _nameToControl = new(StringComparer.Ordinal); private readonly Dictionary _windowByName = new(StringComparer.Ordinal); private readonly Dictionary _hwndToWindow = new(); + private readonly Dictionary> _windowToControls = new(); internal void Register(nint hwnd, IControl control) { @@ -42,6 +43,41 @@ internal void Unregister(nint hwnd) } } + internal void ClearAllControls() + { + _hwndToControl.Clear(); + _nameToControl.Clear(); + _windowToControls.Clear(); + } + + internal void TrackChildWindow(nint parentHwnd, nint childHwnd) + { + if (!_windowToControls.TryGetValue(parentHwnd, out var list)) + { + list = new List(); + _windowToControls[parentHwnd] = list; + } + list.Add(childHwnd); + } + + internal void UnregisterWindowControls(nint parentHwnd) + { + if (!_windowToControls.TryGetValue(parentHwnd, out var direct)) + return; + var stack = new Stack(direct); + _windowToControls.Remove(parentHwnd); + while (stack.Count > 0) + { + var h = stack.Pop(); + Unregister(h); + if (_windowToControls.TryGetValue(h, out var sub)) + { + foreach (var ch in sub) stack.Push(ch); + _windowToControls.Remove(h); + } + } + } + internal void RegisterWindow(IBaseWindow window) { if (!string.IsNullOrEmpty(window.Name)) diff --git a/src/EasyWindowsApplication/Core/LayoutEngine/ChildrenBuilderImpl.cs b/src/EasyWindowsApplication/Core/LayoutEngine/ChildrenBuilderImpl.cs index 68cfeeb..f39cc3c 100644 --- a/src/EasyWindowsApplication/Core/LayoutEngine/ChildrenBuilderImpl.cs +++ b/src/EasyWindowsApplication/Core/LayoutEngine/ChildrenBuilderImpl.cs @@ -13,28 +13,28 @@ internal sealed class ChildrenBuilderImpl : IChildrenBuilder public IChildrenBuilder View(Action> configure) where T : class, IControl { - var control = ControlActivatorRegistry.Shared.Create(); - var view = new View(control); + var view = new View(); configure(view); _content.Children.Add(new ViewModel { - Name = view.Instance.Name, - Control = view.Instance + Name = view.PendingName ?? "", + ControlType = typeof(T), + Configure = view.BuildConfigure() }); return this; } public IChildrenBuilder View(Func, View> configure) where T : class, IControl { - var control = ControlActivatorRegistry.Shared.Create(); - var view = new View(control); + var view = new View(); var result = configure(view); _content.Children.Add(new ViewModel { - Name = result.Instance.Name, - Control = result.Instance + Name = result.PendingName ?? "", + ControlType = typeof(T), + Configure = result.BuildConfigure() }); return this; } diff --git a/src/EasyWindowsApplication/Core/LayoutModels.cs b/src/EasyWindowsApplication/Core/LayoutModels.cs index afea273..035ce8b 100644 --- a/src/EasyWindowsApplication/Core/LayoutModels.cs +++ b/src/EasyWindowsApplication/Core/LayoutModels.cs @@ -31,4 +31,6 @@ internal sealed class ViewModel : IViewModel internal string Name { get; set; } = ""; internal IControl? Control { get; set; } internal ContentModel? SubContent { get; set; } + internal Type? ControlType { get; set; } + internal Action? Configure { get; set; } } diff --git a/src/EasyWindowsApplication/Core/MasterRouter.cs b/src/EasyWindowsApplication/Core/MasterRouter.cs index 30cf595..ce6eead 100644 --- a/src/EasyWindowsApplication/Core/MasterRouter.cs +++ b/src/EasyWindowsApplication/Core/MasterRouter.cs @@ -207,8 +207,9 @@ internal nint WndProc(nint hwnd, uint msg, nint wParam, nint lParam) else if (win is AlternativeWindowImpl aw) aw.RaiseClosed(); } - // Limpieza determinística del registry (Fase 1: evita memory leak) + // Limpieza determinística del registry (Fase 5: evita memory leak de controles) _registry.Unregister(hwnd); + _registry.UnregisterWindowControls(hwnd); _registry.UnregisterWindowByHwnd(hwnd); HandleRegistry.UnregisterRouter(hwnd); _windowBrushes.Remove(hwnd); diff --git a/src/EasyWindowsApplication/Core/Windowing/AlternativeWindowImpl.cs b/src/EasyWindowsApplication/Core/Windowing/AlternativeWindowImpl.cs index 7bab828..ee1a5ee 100644 --- a/src/EasyWindowsApplication/Core/Windowing/AlternativeWindowImpl.cs +++ b/src/EasyWindowsApplication/Core/Windowing/AlternativeWindowImpl.cs @@ -2,6 +2,7 @@ using EasyWindowsApplication.Core; using EasyWindowsApplication.Core.LayoutEngine; using EasyWindowsApplication.Share; +using EasyWindowsApplication.Win32ControlsModule.Frontend; namespace EasyWindowsApplication.Core.Windowing; @@ -154,10 +155,26 @@ private static void MaterializeChildren(ContentModel model, nint parentHwnd, Han { if (vmBase is not ViewModel vm) continue; var control = vm.Control; - if (control is null) continue; + if (control is null) + { + if (vm.ControlType is null) continue; + control = (IControl)ControlActivatorRegistry.Shared.CreateFor(vm.ControlType); + if (control is ControlBase cbPre) + { + cbPre.Router = router; + cbPre.Registry = registry; + } + vm.Configure?.Invoke(control); + vm.Control = control; + } if (!ControlActivatorRegistry.Shared.TryGetFactoryForControl(control, out var factory) || factory is null) - throw new InvalidOperationException($"No handle factory registered for control '{control.GetType().Name}' (name='{control.Name}')."); + { + if (vm.ControlType is not null && ControlActivatorRegistry.Shared.TryGetFactory(vm.ControlType) is { } fallback) + factory = fallback; + else + throw new InvalidOperationException($"No handle factory registered for control '{control.GetType().Name}' (name='{control.Name}')."); + } nint hwnd = factory.CreateHandle(parentHwnd, control, registry); if (hwnd == 0) diff --git a/src/EasyWindowsApplication/Core/Windowing/WindowImpl.cs b/src/EasyWindowsApplication/Core/Windowing/WindowImpl.cs index 33a6ffc..2db81da 100644 --- a/src/EasyWindowsApplication/Core/Windowing/WindowImpl.cs +++ b/src/EasyWindowsApplication/Core/Windowing/WindowImpl.cs @@ -2,6 +2,7 @@ using EasyWindowsApplication.Core; using EasyWindowsApplication.Core.LayoutEngine; using EasyWindowsApplication.Share; +using EasyWindowsApplication.Win32ControlsModule.Frontend; namespace EasyWindowsApplication.Core.Windowing; @@ -429,11 +430,28 @@ private static void MaterializeChildren(ContentModel model, nint parentHwnd, Han { if (vmBase is not ViewModel vm) continue; var control = vm.Control; - if (control is null) continue; + if (control is null) + { + if (vm.ControlType is null) continue; + control = (IControl)ControlActivatorRegistry.Shared.CreateFor(vm.ControlType); + // Asignar Router/Registry antes de Configure para que OnMessage tenga contexto (mejora sobre referencia del plan) + if (control is ControlBase cbPre) + { + cbPre.Router = router; + cbPre.Registry = registry; + } + vm.Configure?.Invoke(control); + vm.Control = control; + } // Resolver factory por tipo concreto/interfaz if (!ControlActivatorRegistry.Shared.TryGetFactoryForControl(control, out var factory) || factory is null) - throw new InvalidOperationException($"No handle factory registered for control '{control.GetType().Name}' (name='{control.Name}'). Registre un INativeHandleFactory para ese tipo."); + { + if (vm.ControlType is not null && ControlActivatorRegistry.Shared.TryGetFactory(vm.ControlType) is { } fallback && fallback is not null) + factory = fallback; + else + throw new InvalidOperationException($"No handle factory registered for control '{control.GetType().Name}' (name='{control.Name}'). Registre un INativeHandleFactory para ese tipo."); + } nint hwnd = factory.CreateHandle(parentHwnd, control, registry); if (hwnd == 0) diff --git a/src/EasyWindowsApplication/Share/View.cs b/src/EasyWindowsApplication/Share/View.cs index 98cd7cb..7342fe9 100644 --- a/src/EasyWindowsApplication/Share/View.cs +++ b/src/EasyWindowsApplication/Share/View.cs @@ -22,49 +22,166 @@ internal ViewBase() { } } } -public readonly struct View where T : class, IControl +public sealed class View where T : class, IControl { - public T Instance { get; } + private readonly List> _configure = new(); + internal string? PendingName { get; private set; } - public View(T instance) => Instance = instance; + public View() { } - public View Name(string name) { Instance.Name = name; return this; } + public View Name(string name) + { + PendingName = name; + _configure.Add(c => c.Name = name); + return this; + } + + public View Margin(float uniform) + { + var thickness = new Thickness(uniform); + _configure.Add(c => c.Margin = thickness); + return this; + } + + public View Margin(float vertical, float horizontal) + { + var thickness = new Thickness(vertical, horizontal); + _configure.Add(c => c.Margin = thickness); + return this; + } + + public View Margin(float top, float right, float bottom, float left) + { + var thickness = new Thickness(top, right, bottom, left); + _configure.Add(c => c.Margin = thickness); + return this; + } + + public View Padding(float uniform) + { + var thickness = new Thickness(uniform); + _configure.Add(c => c.Padding = thickness); + return this; + } + + public View Padding(float vertical, float horizontal) + { + var thickness = new Thickness(vertical, horizontal); + _configure.Add(c => c.Padding = thickness); + return this; + } + + public View Padding(float top, float right, float bottom, float left) + { + var thickness = new Thickness(top, right, bottom, left); + _configure.Add(c => c.Padding = thickness); + return this; + } + + public View Width(float width) + { + var layout = LayoutLength.Absolute(width); + _configure.Add(c => c.LayoutWidth = layout); + return this; + } + + public View Height(float height) + { + var layout = LayoutLength.Absolute(height); + _configure.Add(c => c.LayoutHeight = layout); + return this; + } + + public View Background(Color color) + { + _configure.Add(c => c.BackgroundColor = color); + return this; + } - public View Margin(float uniform) { Instance.Margin = new Thickness(uniform); return this; } - public View Margin(float vertical, float horizontal) { Instance.Margin = new Thickness(vertical, horizontal); return this; } - public View Margin(float top, float right, float bottom, float left) { Instance.Margin = new Thickness(top, right, bottom, left); return this; } + public View Dock(DockPosition dock) + { + _configure.Add(c => c.Dock = dock); + return this; + } - public View Padding(float uniform) { Instance.Padding = new Thickness(uniform); return this; } - public View Padding(float vertical, float horizontal) { Instance.Padding = new Thickness(vertical, horizontal); return this; } - public View Padding(float top, float right, float bottom, float left) { Instance.Padding = new Thickness(top, right, bottom, left); return this; } + public View DockLeft() + { + _configure.Add(c => c.Dock = DockPosition.Left); + return this; + } - public View Width(float width) { Instance.LayoutWidth = LayoutLength.Absolute(width); return this; } - public View Height(float height) { Instance.LayoutHeight = LayoutLength.Absolute(height); return this; } + public View DockTop() + { + _configure.Add(c => c.Dock = DockPosition.Top); + return this; + } - public View Background(Color color) { Instance.BackgroundColor = color; return this; } + public View DockRight() + { + _configure.Add(c => c.Dock = DockPosition.Right); + return this; + } - public View Dock(DockPosition dock) { Instance.Dock = dock; return this; } - public View DockLeft() { Instance.Dock = DockPosition.Left; return this; } - public View DockTop() { Instance.Dock = DockPosition.Top; return this; } - public View DockRight() { Instance.Dock = DockPosition.Right; return this; } - public View DockBottom() { Instance.Dock = DockPosition.Bottom; return this; } + public View DockBottom() + { + _configure.Add(c => c.Dock = DockPosition.Bottom); + return this; + } - public View Row(int row) { Instance.GridRow = row; return this; } - public View Column(int column) { Instance.GridColumn = column; return this; } - public View RowSpan(int span) { Instance.GridRowSpan = span; return this; } - public View ColumnSpan(int span) { Instance.GridColumnSpan = span; return this; } + public View Row(int row) + { + _configure.Add(c => c.GridRow = row); + return this; + } - public View OnClick(Action handler) { Instance.OnClick(handler); return this; } + public View Column(int column) + { + _configure.Add(c => c.GridColumn = column); + return this; + } + + public View RowSpan(int span) + { + _configure.Add(c => c.GridRowSpan = span); + return this; + } + + public View ColumnSpan(int span) + { + _configure.Add(c => c.GridColumnSpan = span); + return this; + } + + public View OnClick(Action handler) + { + _configure.Add(c => c.OnClick(handler)); + return this; + } public View Text(string text) { - if (Instance is IText t) t.Text = text; + _configure.Add(c => { if (c is IText t) t.Text = text; }); return this; } public View OnMessage(uint msg, Win32MessageHandler handler) { - if (Instance is ControlBase cb) cb.OnMessage(msg, handler); + _configure.Add(c => { if (c is ControlBase cb) cb.OnMessage(msg, handler); }); return this; } + + internal void Apply(T instance) + { + foreach (var a in _configure) a(instance); + } + + internal Action BuildConfigure() + { + var snapshot = _configure.ToArray(); + return control => + { + var typed = (T)control; + foreach (var a in snapshot) a(typed); + }; + } } diff --git a/src/EasyWindowsApplication/Win32ControlsModule/Backend/Win32NativeHandleFactory.cs b/src/EasyWindowsApplication/Win32ControlsModule/Backend/Win32NativeHandleFactory.cs index 64593c3..05e5bfb 100644 --- a/src/EasyWindowsApplication/Win32ControlsModule/Backend/Win32NativeHandleFactory.cs +++ b/src/EasyWindowsApplication/Win32ControlsModule/Backend/Win32NativeHandleFactory.cs @@ -53,6 +53,9 @@ public nint CreateHandle(nint parentHwnd, IControl control, HandleRegistry regis // Registrar en HandleRegistry (hwnd + name) registry.Register(hwnd, control); + // Track parent->child para limpieza determinística en WM_DESTROY (Fase 5) + if (parentHwnd != 0) + registry.TrackChildWindow(parentHwnd, hwnd); } return hwnd;