Skip to content

Commit c0000fe

Browse files
committed
fix(Maui): parent nested region hosts in ElementParentedCallbackBehavior
Ensure region hosts inside region guests receive Parent so nested regions register correctly after layout (GitHub #3332). Add DryIoc region fixture test with explicit measure/arrange for nested ContentView regions.
1 parent 815aca3 commit c0000fe

8 files changed

Lines changed: 136 additions & 8 deletions

File tree

src/Maui/Prism.Maui/Behaviors/ElementParentedCallbackBehavior.cs

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ namespace Prism.Behaviors;
66

77
internal class ElementParentedCallbackBehavior : Behavior<VisualElement>
88
{
9-
private Action _callback { get; }
9+
private readonly Action _callback;
10+
private VisualElement? _target;
1011

1112
public ElementParentedCallbackBehavior(Action callback)
1213
{
@@ -15,11 +16,16 @@ public ElementParentedCallbackBehavior(Action callback)
1516

1617
protected override void OnAttachedTo(VisualElement view)
1718
{
19+
_target = view;
20+
1821
if (view.TryGetParentPage(out var page))
1922
{
2023
var container = page.GetContainerProvider();
2124
if (container is null)
25+
{
26+
page.PropertyChanged -= PagePropertyChanged;
2227
page.PropertyChanged += PagePropertyChanged;
28+
}
2329
else
2430
{
2531
view.SetContainerProvider(container);
@@ -32,22 +38,46 @@ protected override void OnAttachedTo(VisualElement view)
3238
}
3339
}
3440

41+
protected override void OnDetachingFrom(VisualElement view)
42+
{
43+
view.ParentChanged -= OnParentChanged;
44+
if (view.Parent is VisualElement directParent)
45+
directParent.ParentChanged -= OnParentChanged;
46+
47+
if (view.TryGetParentPage(out var page))
48+
page.PropertyChanged -= PagePropertyChanged;
49+
50+
_target = null;
51+
base.OnDetachingFrom(view);
52+
}
53+
3554
private void OnParentChanged(object sender, EventArgs e)
3655
{
37-
if (sender is not VisualElement view || view.Parent is null)
56+
// Use _target: when listening on Parent.ParentChanged, sender is the parent, not the region host (#3332).
57+
var view = _target;
58+
if (view?.Parent is null)
3859
return;
39-
else if (view.TryGetParentPage(out var page))
60+
61+
if (view.TryGetParentPage(out var page))
4062
{
41-
if(page.GetContainerProvider() is not null)
63+
if (page.GetContainerProvider() is not null)
4264
{
4365
view.ParentChanged -= OnParentChanged;
66+
if (view.Parent is VisualElement directParent)
67+
directParent.ParentChanged -= OnParentChanged;
68+
4469
_callback();
4570
return;
4671
}
72+
73+
page.PropertyChanged -= PagePropertyChanged;
4774
page.PropertyChanged += PagePropertyChanged;
4875
}
49-
else
50-
view.Parent.ParentChanged += OnParentChanged;
76+
else if (view.Parent is VisualElement parent)
77+
{
78+
parent.ParentChanged -= OnParentChanged;
79+
parent.ParentChanged += OnParentChanged;
80+
}
5181

5282
view.ParentChanged -= OnParentChanged;
5383
}
@@ -59,7 +89,7 @@ private void PagePropertyChanged(object sender, PropertyChangedEventArgs e)
5989

6090
var container = page.GetContainerProvider();
6191

62-
if(container is not null)
92+
if (container is not null)
6393
{
6494
page.PropertyChanged -= PagePropertyChanged;
6595
_callback();

tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Regions/RegionFixture.cs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
using Prism.DryIoc.Maui.Tests.Mocks.ViewModels;
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Maui.Graphics;
3+
using Microsoft.Maui.Layouts;
4+
using Prism.DryIoc.Maui.Tests.Mocks.ViewModels;
25
using Prism.DryIoc.Maui.Tests.Mocks.Views;
36
using Prism.Navigation.Xaml;
47

@@ -286,4 +289,46 @@ public void Issue3328_WhenNavigatingToUnregisteredView_ShouldFailWithKeyNotFound
286289
Assert.IsType<MockRegionViewA>(page.ContentRegion.Content);
287290
Assert.IsType<MockRegionViewAViewModel>(page.ContentRegion.Content.BindingContext);
288291
}
292+
293+
[Fact]
294+
public void Issue3332_NestedContentRegion_InnerRegionReceivesGuest_AfterLayout()
295+
{
296+
var mauiApp = CreateBuilder(prism => prism
297+
.RegisterTypes(container =>
298+
{
299+
container.RegisterForNavigation<MockNestedRegionPage, MockNestedRegionPageViewModel>();
300+
container.RegisterForRegionNavigation<MockOuterGuestView, MockOuterGuestViewModel>();
301+
container.RegisterForRegionNavigation<MockInnerGuestView, MockInnerGuestViewModel>();
302+
})
303+
.OnInitialized(container =>
304+
{
305+
var regionManager = container.Resolve<IRegionManager>();
306+
regionManager.RegisterViewWithRegion("OuterRegion", nameof(MockOuterGuestView));
307+
})
308+
.CreateWindow("MockNestedRegionPage"))
309+
.Build();
310+
311+
var window = GetWindow(mauiApp);
312+
var page = Assert.IsType<MockNestedRegionPage>(window.Page);
313+
Assert.NotNull(page.OuterRegion.Content);
314+
var outerGuest = Assert.IsType<MockOuterGuestView>(page.OuterRegion.Content);
315+
316+
const double w = 400;
317+
const double h = 800;
318+
page.OuterRegion.Measure(w, h, MeasureFlags.IncludeMargins);
319+
page.OuterRegion.Arrange(new Rect(0, 0, w, h));
320+
outerGuest.InnerRegionHost.Measure(w, h, MeasureFlags.IncludeMargins);
321+
outerGuest.InnerRegionHost.Arrange(new Rect(0, 0, w, h));
322+
323+
var regionManager = mauiApp.Services.GetRequiredService<IRegionManager>();
324+
Assert.Contains(regionManager.Regions, r => r.Name == "InnerRegion");
325+
326+
regionManager.RegisterViewWithRegion("InnerRegion", nameof(MockInnerGuestView));
327+
328+
outerGuest.InnerRegionHost.Measure(w, h, MeasureFlags.IncludeMargins);
329+
outerGuest.InnerRegionHost.Arrange(new Rect(0, 0, w, h));
330+
331+
Assert.NotNull(outerGuest.InnerRegionHost.Content);
332+
Assert.IsType<MockInnerGuestView>(outerGuest.InnerRegionHost.Content);
333+
}
289334
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace Prism.DryIoc.Maui.Tests.Mocks.ViewModels;
2+
3+
public class MockInnerGuestViewModel
4+
{
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace Prism.DryIoc.Maui.Tests.Mocks.ViewModels;
2+
3+
public class MockNestedRegionPageViewModel
4+
{
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace Prism.DryIoc.Maui.Tests.Mocks.ViewModels;
2+
3+
public class MockOuterGuestViewModel
4+
{
5+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace Prism.DryIoc.Maui.Tests.Mocks.Views;
2+
3+
/// <summary>Guest view injected into the nested inner region.</summary>
4+
public class MockInnerGuestView : ContentView
5+
{
6+
public MockInnerGuestView()
7+
{
8+
Content = new Label { Text = nameof(MockInnerGuestView) };
9+
}
10+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace Prism.DryIoc.Maui.Tests.Mocks.Views;
2+
3+
/// <summary>Shell page with a single outer <see cref="ContentView"/> region (GitHub #3332 repro shape).</summary>
4+
public class MockNestedRegionPage : ContentPage
5+
{
6+
public MockNestedRegionPage()
7+
{
8+
OuterRegion = new ContentView();
9+
OuterRegion.SetValue(Prism.Navigation.Regions.Xaml.RegionManager.RegionNameProperty, "OuterRegion");
10+
Content = OuterRegion;
11+
}
12+
13+
public ContentView OuterRegion { get; }
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace Prism.DryIoc.Maui.Tests.Mocks.Views;
2+
3+
/// <summary>Outer region guest that hosts an inner <see cref="ContentView"/> region.</summary>
4+
public class MockOuterGuestView : ContentView
5+
{
6+
public MockOuterGuestView()
7+
{
8+
InnerRegionHost = new ContentView();
9+
InnerRegionHost.SetValue(Prism.Navigation.Regions.Xaml.RegionManager.RegionNameProperty, "InnerRegion");
10+
Content = InnerRegionHost;
11+
}
12+
13+
public ContentView InnerRegionHost { get; }
14+
}

0 commit comments

Comments
 (0)