Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions src/Maui/Prism.Maui/Navigation/Regions/RegionViewRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ namespace Prism.Navigation.Regions;
public class RegionViewRegistry : IRegionViewRegistry
{
private readonly ListDictionary<string, Func<IContainerProvider, object>> _registeredContent = new();
private readonly Dictionary<string, HashSet<Type>> _registeredViewTypes = new();
private readonly Dictionary<string, HashSet<string>> _registeredTargetNames = new();
private readonly WeakDelegatesManager _contentRegisteredListeners = new();

/// <summary>
Expand Down Expand Up @@ -49,6 +51,11 @@ public IEnumerable<object> GetContents(string regionName, IContainerProvider con
/// <param name="viewType">Content type to be registered for the <paramref name="regionName"/>.</param>
public void RegisterViewWithRegion(string regionName, Type viewType)
{
if (!TryAddRegisteredViewType(regionName, viewType))
{
return;
}

RegisterViewWithRegion(regionName, c =>
{
var registry = c.Resolve<IRegionNavigationRegistry>();
Expand All @@ -67,6 +74,11 @@ public void RegisterViewWithRegion(string regionName, Type viewType)
/// <param name="targetName">Content type to be registered for the <paramref name="regionName"/>.</param>
public void RegisterViewWithRegion(string regionName, string targetName)
{
if (!TryAddRegisteredTargetName(regionName, targetName))
{
return;
}

RegisterViewWithRegion(regionName, c =>
{
var registry = c.Resolve<IRegionNavigationRegistry>();
Expand All @@ -81,10 +93,50 @@ public void RegisterViewWithRegion(string regionName, string targetName)
/// <param name="getContentDelegate">Delegate used to retrieve the content associated with the <paramref name="regionName"/>.</param>
public void RegisterViewWithRegion(string regionName, Func<IContainerProvider, object> getContentDelegate)
{
if (IsContentDelegateRegistered(regionName, getContentDelegate))
{
return;
}

_registeredContent.Add(regionName, getContentDelegate);
OnContentRegistered(new ViewRegisteredEventArgs(regionName, getContentDelegate));
}

private bool TryAddRegisteredViewType(string regionName, Type viewType)
{
if (!_registeredViewTypes.TryGetValue(regionName, out HashSet<Type> viewTypes))
{
viewTypes = new HashSet<Type>();
_registeredViewTypes.Add(regionName, viewTypes);
}

return viewTypes.Add(viewType);
}

private bool TryAddRegisteredTargetName(string regionName, string targetName)
{
if (!_registeredTargetNames.TryGetValue(regionName, out HashSet<string> targetNames))
{
targetNames = new HashSet<string>();
_registeredTargetNames.Add(regionName, targetNames);
}

return targetNames.Add(targetName);
}

private bool IsContentDelegateRegistered(string regionName, Func<IContainerProvider, object> getContentDelegate)
{
foreach (var existingDelegate in _registeredContent[regionName])
{
if (existingDelegate == getContentDelegate)
{
return true;
}
}

return false;
}

private void OnContentRegistered(ViewRegisteredEventArgs e)
{
try
Expand Down
89 changes: 89 additions & 0 deletions src/Wpf/Prism.Wpf/Navigation/Regions/RegionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,48 @@ private static void OnSetRegionNameCallback(DependencyObject element, Dependency
if (!IsInDesignMode(element))
{
CreateRegion(element);
RegisterDefaultViewWithRegion(element);
}
}

private static void OnDefaultViewChanged(DependencyObject element, DependencyPropertyChangedEventArgs args)
{
if (!IsInDesignMode(element) && args.NewValue != null)
{
RegisterDefaultViewWithRegion(element, args.NewValue);
}
}

private static void RegisterDefaultViewWithRegion(DependencyObject element)
{
var defaultView = GetDefaultView(element);
if (defaultView != null)
{
RegisterDefaultViewWithRegion(element, defaultView);
}
}

private static void RegisterDefaultViewWithRegion(DependencyObject element, object defaultView)
{
var regionName = GetRegionName(element);
if (string.IsNullOrEmpty(regionName))
{
return;
}

var regionViewRegistry = ContainerLocator.Container.Resolve<IRegionViewRegistry>();

if (defaultView is string targetName)
{
regionViewRegistry.RegisterViewWithRegion(regionName, targetName);
}
else if (defaultView is Type viewType)
{
regionViewRegistry.RegisterViewWithRegion(regionName, viewType);
}
else
{
regionViewRegistry.RegisterViewWithRegion(regionName, _ => defaultView);
}
}

Expand Down Expand Up @@ -167,6 +209,52 @@ public static void SetRegionManager(DependencyObject target, IRegionManager valu
target.SetValue(RegionManagerProperty, value);
}

/// <summary>
/// Identifies the DefaultView attached property.
/// </summary>
/// <remarks>
/// Sets the default view to be displayed in a region when it is created.
/// This can be a view name, a type, or an instance of the view.
/// </remarks>
#if !AVALONIA
public static readonly DependencyProperty DefaultViewProperty =
DependencyProperty.RegisterAttached("DefaultView", typeof(object), typeof(RegionManager),
new PropertyMetadata(defaultValue: null, propertyChangedCallback: OnDefaultViewChanged));
#else
public static readonly AvaloniaProperty DefaultViewProperty =
AvaloniaProperty.RegisterAttached<AvaloniaObject, object>("DefaultView", typeof(RegionManager));
#endif

/// <summary>
/// Sets the <see cref="DefaultViewProperty"/> attached property for the specified region target.
/// </summary>
/// <param name="regionTarget">The object that will host the default view.</param>
/// <param name="viewNameTypeOrInstance">
/// The default view to display in the region. This can be a view name, a type, or an instance of the view.
/// </param>
public static void SetDefaultView(DependencyObject regionTarget, object viewNameTypeOrInstance)
{
if (regionTarget == null)
throw new ArgumentNullException(nameof(regionTarget));

regionTarget.SetValue(DefaultViewProperty, viewNameTypeOrInstance);
}

/// <summary>
/// Gets the value of the <see cref="DefaultViewProperty"/> attached property for the specified region target.
/// </summary>
/// <param name="regionTarget">The object that hosts the default view.</param>
/// <returns>
/// The default view associated with the region. This can be a view name, a type, or an instance of the view.
/// </returns>
public static object GetDefaultView(DependencyObject regionTarget)
{
if (regionTarget == null)
throw new ArgumentNullException(nameof(regionTarget));

return regionTarget.GetValue(DefaultViewProperty);
}

/// <summary>
/// Identifies the RegionContext attached property.
/// </summary>
Expand Down Expand Up @@ -272,6 +360,7 @@ static RegionManager()
// TODO: Could this go into the default constructor?
RegionNameProperty.Changed.Subscribe(args => OnSetRegionNameCallback(args?.Sender, args));
RegionContextProperty.Changed.Subscribe(args => OnRegionContextChanged(args?.Sender, args));
DefaultViewProperty.Changed.Subscribe(args => OnDefaultViewChanged(args?.Sender, args));
}
#endif

Expand Down
56 changes: 55 additions & 1 deletion src/Wpf/Prism.Wpf/Navigation/Regions/RegionViewRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public class RegionViewRegistry : IRegionViewRegistry
{
private readonly IContainerProvider _container;
private readonly ListDictionary<string, Func<IContainerProvider, object>> _registeredContent = new ListDictionary<string, Func<IContainerProvider, object>>();
private readonly Dictionary<string, HashSet<Type>> _registeredViewTypes = new Dictionary<string, HashSet<Type>>();
private readonly Dictionary<string, HashSet<string>> _registeredTargetNames = new Dictionary<string, HashSet<string>>();
private readonly WeakDelegatesManager _contentRegisteredListeners = new WeakDelegatesManager();

/// <summary>
Expand Down Expand Up @@ -57,6 +59,11 @@ public IEnumerable<object> GetContents(string regionName, IContainerProvider con
/// <param name="viewType">Content type to be registered for the <paramref name="regionName"/>.</param>
public void RegisterViewWithRegion(string regionName, Type viewType)
{
if (!TryAddRegisteredViewType(regionName, viewType))
{
return;
}

RegisterViewWithRegion(regionName, _ => CreateInstance(viewType));
}

Expand All @@ -67,6 +74,11 @@ public void RegisterViewWithRegion(string regionName, Type viewType)
/// <param name="getContentDelegate">Delegate used to retrieve the content associated with the <paramref name="regionName"/>.</param>
public void RegisterViewWithRegion(string regionName, Func<IContainerProvider, object> getContentDelegate)
{
if (IsContentDelegateRegistered(regionName, getContentDelegate))
{
return;
}

_registeredContent.Add(regionName, getContentDelegate);
OnContentRegistered(new ViewRegisteredEventArgs(regionName, getContentDelegate));
}
Expand All @@ -79,8 +91,15 @@ public void RegisterViewWithRegion(string regionName, Func<IContainerProvider, o
/// <param name="regionName">The name of the region to associate the view with.</param>
/// <param name="targetName">The type of the view to register with the </param>
/// <returns>The <see cref="IRegionManager"/>, for adding several views easily</returns>
public void RegisterViewWithRegion(string regionName, string targetName) =>
public void RegisterViewWithRegion(string regionName, string targetName)
{
if (!TryAddRegisteredTargetName(regionName, targetName))
{
return;
}

RegisterViewWithRegion(regionName, c => c.Resolve<object>(targetName));
}

/// <summary>
/// Creates an instance of a registered view <see cref="Type"/>.
Expand All @@ -94,6 +113,41 @@ protected virtual object CreateInstance(Type type)
return view;
}

private bool TryAddRegisteredViewType(string regionName, Type viewType)
{
if (!_registeredViewTypes.TryGetValue(regionName, out HashSet<Type> viewTypes))
{
viewTypes = new HashSet<Type>();
_registeredViewTypes.Add(regionName, viewTypes);
}

return viewTypes.Add(viewType);
}

private bool TryAddRegisteredTargetName(string regionName, string targetName)
{
if (!_registeredTargetNames.TryGetValue(regionName, out HashSet<string> targetNames))
{
targetNames = new HashSet<string>();
_registeredTargetNames.Add(regionName, targetNames);
}

return targetNames.Add(targetName);
}

private bool IsContentDelegateRegistered(string regionName, Func<IContainerProvider, object> getContentDelegate)
{
foreach (Func<IContainerProvider, object> existingDelegate in _registeredContent[regionName])
{
if (existingDelegate == getContentDelegate)
{
return true;
}
}

return false;
}

private void OnContentRegistered(ViewRegisteredEventArgs e)
{
try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ namespace Prism.Avalonia.Tests.Regions.Behaviors
{
public class AutoPopulateRegionBehaviorFixture
{
private class MockContentObject
{
}

[Fact]
public void ShouldGetViewsFromRegistryOnAttach()
{
Expand Down Expand Up @@ -64,6 +68,27 @@ public void NullRegionThrows()

}

[Fact]
public void WhenSameViewTypeRegisteredTwice_RegionContainsSingleView()
{
var containerMock = new Mock<IContainerExtension>();
ContainerLocator.SetContainerExtension(containerMock.Object);
containerMock.Setup(c => c.Resolve(typeof(MockContentObject))).Returns(new MockContentObject());
var registry = new RegionViewRegistry(containerMock.Object);
registry.RegisterViewWithRegion("MyRegion", typeof(MockContentObject));
registry.RegisterViewWithRegion("MyRegion", typeof(MockContentObject));

var region = new Region { Name = "MyRegion" };
var behavior = new AutoPopulateRegionBehavior(registry)
{
Region = region
};

behavior.Attach();

Assert.Single(region.Views);
}

[Fact]
public void CanAttachBeforeSettingName()
{
Expand Down
Loading
Loading