Skip to content
Open
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
26 changes: 26 additions & 0 deletions Common/Adapters/ContentControlRegionAdapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using SimpleNavigation.Interface.Adapters;
using System.Windows;
using System.Windows.Controls;

namespace SimpleNavigation.Common.Adapters;

internal sealed class ContentControlRegionAdapter : IContentRegionHostAdapter
{
public RegionHostKind Kind => RegionHostKind.Content;

public bool CanHandle(FrameworkElement region)
=> region is ContentControl && region is not Frame;

public void Present(FrameworkElement host, FrameworkElement content)
{
if (host is not ContentControl contentControl || host is Frame)
{
throw new ArgumentException(
$"Host type '{host.GetType().FullName}' must be a non-Frame ContentControl.",
nameof(host));
}

host.Dispatcher.VerifyAccess();
contentControl.Content = content;
}
}
30 changes: 30 additions & 0 deletions Common/Adapters/FrameRegionAdapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using SimpleNavigation.Interface.Adapters;
using System.Windows;
using System.Windows.Controls;

namespace SimpleNavigation.Common.Adapters;

internal sealed class FrameRegionAdapter : IPageRegionHostAdapter
{
public RegionHostKind Kind => RegionHostKind.Page;

public bool CanHandle(FrameworkElement region) => region is Frame;

public bool Navigate(Frame frame, Page page)
{
frame.Dispatcher.VerifyAccess();
return frame.Navigate(page);
}

public bool CanGoBack(Frame frame)
{
frame.Dispatcher.VerifyAccess();
return frame.CanGoBack;
}

public void GoBack(Frame frame)
{
frame.Dispatcher.VerifyAccess();
frame.GoBack();
}
}
49 changes: 49 additions & 0 deletions Common/Adapters/RegionHostAdapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using SimpleNavigation.Interface.Adapters;
using System.Windows;

namespace SimpleNavigation.Common.Adapters;

/// <summary>
/// ���������ɽ��ܵ���������
/// </summary>
internal enum RegionHostKind
{
Page,
Content,
}

/// <summary>
/// ��ȡ���䵼��Ԫ�ص� Adapter
/// </summary>
internal static class RegionHostAdapterResolver
{
private static readonly IRegionHostAdapter[] HostAdapters =
{
new FrameRegionAdapter(),
new ContentControlRegionAdapter(),
};

/// <summary>
/// ���� ���Ӷ�������ͣ�ɸѡ��ƥ��� Adapter
/// </summary>
/// <param name="attachedRegion">���Ӷ���</param>
/// <returns>��ƥ��� Adapter <see cref="IRegionHostAdapter"/></returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public static IRegionHostAdapter GetRequired(FrameworkElement attachedRegion)
{
if (attachedRegion == null)
throw new ArgumentNullException(nameof(attachedRegion));

foreach (var adapter in HostAdapters)
{
if (adapter.CanHandle(attachedRegion))
return adapter;
}

var regionType = attachedRegion.GetType();
throw new ArgumentException(
$"Region host type '{regionType.FullName}' is not supported.",
nameof(attachedRegion));
}
}
4 changes: 2 additions & 2 deletions Common/DialogManager.cs → Common/Managers/DialogManager.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System.Windows;
using Microsoft.Extensions.DependencyInjection;
using SimpleNavigation.Interface;
using SimpleNavigation.Interface.Managers;

namespace SimpleNavigation.Common
namespace SimpleNavigation.Common.Managers
{
public class DialogManager : IDialogManager
{
Expand Down
274 changes: 274 additions & 0 deletions Common/Managers/RegionManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
using System.Windows;
using SimpleNavigation.Common.Adapters;
using SimpleNavigation.Interface.Managers;
using SimpleNavigation.Services;

namespace SimpleNavigation.Common.Managers
{
public sealed class RegionManager : IRegionManager, IDisposable
{
private readonly Dictionary<string, RegionEntry> regions = new(StringComparer.Ordinal);
private readonly Action<RegionDeclarationChange> declarationSubscriber;
private readonly List<RegionDeclarationChange> pendingDeclarationChanges = new();
private readonly object syncRoot = new();
private bool isImportingDeclarations = true;
private bool isDisposed;

public RegionManager()
{
declarationSubscriber = OnDeclarationChanged;
Region.Subscribe(declarationSubscriber);

try
{
var snapshot = Region.GetActiveSnapshot();

lock (syncRoot)
{
foreach (var change in snapshot)
ApplyDeclarationChangeUnderLock(change);

foreach (var change in pendingDeclarationChanges)
ApplyDeclarationChangeUnderLock(change);

pendingDeclarationChanges.Clear();
isImportingDeclarations = false;
}
}
catch
{
lock (syncRoot)
{
isDisposed = true;
isImportingDeclarations = false;
pendingDeclarationChanges.Clear();
regions.Clear();
}

Region.Unsubscribe(declarationSubscriber);
throw;
}
}

public void RegisterRegion(string regionName, FrameworkElement region)
{
ValidateRegionName(regionName);
if (region == null) throw new ArgumentNullException(nameof(region));

RegionHostAdapterResolver.GetRequired(region); // ��ƥ���Adapter���׳��쳣

lock (syncRoot)
{
ThrowIfDisposedUnderLock();

if (TryGetLiveEntryUnderLock(regionName, out var existingEntry, out var existingRegion))
{
if (ReferenceEquals(existingRegion, region))
{
existingEntry.HasProgrammaticOwnership = true;
return;
}

throw new InvalidOperationException($"Region '{regionName}' is already registered.");
}

regions[regionName] = new RegionEntry(region)
{
HasProgrammaticOwnership = true,
};
}
}

public bool UnregisterRegion(string regionName, FrameworkElement region)
{
ValidateRegionName(regionName);

if (region == null)
{
throw new ArgumentNullException(nameof(region));
}

lock (syncRoot)
{
ThrowIfDisposedUnderLock();

if (!TryGetLiveEntryUnderLock(regionName, out var existingEntry, out var existingRegion))
{
return false;
}

if (!ReferenceEquals(existingRegion, region) ||
!existingEntry.HasProgrammaticOwnership)
{
return false;
}

existingEntry.HasProgrammaticOwnership = false;
if (existingEntry.AttachedActivationTokens.Count == 0)
{
regions.Remove(regionName);
}

return true;
}
}

public FrameworkElement? GetRegion(string regionName)
{
ValidateRegionName(regionName);

lock (syncRoot)
{
ThrowIfDisposedUnderLock();

if (TryGetLiveEntryUnderLock(regionName, out _, out var region))
{
return region;
}

return null;
}
}

public TRegion? GetRegion<TRegion>(string regionName) where TRegion : FrameworkElement
=> GetRegion(regionName) as TRegion;

public void Dispose()
{
lock (syncRoot)
{
if (isDisposed)
{
return;
}

isDisposed = true;
isImportingDeclarations = false;
pendingDeclarationChanges.Clear();
regions.Clear();
}

Region.Unsubscribe(declarationSubscriber);
}

/// <summary>
/// �������������Է����仯�¼��ص�
/// </summary>
/// <param name="change">�仯�¼�Я����Ϣ</param>
private void OnDeclarationChanged(RegionDeclarationChange change)
{
lock (syncRoot)
{
if (isDisposed)
return;

if (isImportingDeclarations)
{
pendingDeclarationChanges.Add(change);
return;
}

ApplyDeclarationChangeUnderLock(change);
}
}

private void ApplyDeclarationChangeUnderLock(RegionDeclarationChange change)
{
if (change.Kind == RegionDeclarationChangeKind.Add)
{
AddAttachedOwnershipUnderLock(change);
return;
}

RemoveAttachedOwnershipUnderLock(change);
}

private void AddAttachedOwnershipUnderLock(RegionDeclarationChange change)
{
var host = change.Host;

if (TryGetLiveEntryUnderLock(change.Name, out var existingEntry, out var existingHost))
{
if (!ReferenceEquals(existingHost, host))
{
throw new InvalidOperationException($"Region '{change.Name}' is already registered.");
}

existingEntry.AttachedActivationTokens.Add(change.ActivationToken);
return;
}

var entry = new RegionEntry(host);
entry.AttachedActivationTokens.Add(change.ActivationToken);
regions[change.Name] = entry;
}

private void RemoveAttachedOwnershipUnderLock(RegionDeclarationChange change)
{
if (!TryGetLiveEntryUnderLock(change.Name, out var existingEntry, out var existingHost) ||
!ReferenceEquals(existingHost, change.Host))
{
return;
}

existingEntry.AttachedActivationTokens.Remove(change.ActivationToken);
if (!existingEntry.HasProgrammaticOwnership &&
existingEntry.AttachedActivationTokens.Count == 0)
{
regions.Remove(change.Name);
}
}

/// <summary>
/// ��ȡ��Ծ��
/// </summary>
/// <param name="regionName"></param>
/// <param name="entry"></param>
/// <param name="region"></param>
/// <returns></returns>
private bool TryGetLiveEntryUnderLock(string regionName, out RegionEntry entry, out FrameworkElement region)
{
if (!regions.TryGetValue(regionName, out entry!))
{
region = null!;
return false;
}

if (entry.Host.TryGetTarget(out region!))
return true;

regions.Remove(regionName);
entry = null!;
region = null!;
return false;
}

private void ThrowIfDisposedUnderLock()
{
if (isDisposed)
throw new ObjectDisposedException(nameof(RegionManager));
}

private static void ValidateRegionName(string regionName)
{
if (string.IsNullOrWhiteSpace(regionName))
throw new ArgumentException("Region name cannot be null, empty, or whitespace.", nameof(regionName));
}
}

internal sealed class RegionEntry
{
public RegionEntry(FrameworkElement host)
{
Host = new WeakReference<FrameworkElement>(host);
}

public WeakReference<FrameworkElement> Host { get; }

public bool HasProgrammaticOwnership { get; set; }

public HashSet<long> AttachedActivationTokens { get; } = new();
}
}


Loading