Rework navigation so we can resolve by key now

This commit is contained in:
Dan Clark
2024-11-17 23:09:26 +00:00
parent 796ef41e3f
commit 7a9028bbeb
26 changed files with 371 additions and 88 deletions
@@ -6,9 +6,9 @@ using Toolkit.Foundation;
namespace Toolkit.Avalonia;
public class ClassicDesktopStyleApplicationHandler :
IHandler<NavigateEventArgs<IClassicDesktopStyleApplicationLifetime>>
IHandler<NavigateTemplateEventArgs>
{
public void Handle(NavigateEventArgs<IClassicDesktopStyleApplicationLifetime> args)
public void Handle(NavigateTemplateEventArgs args)
{
if (Application.Current?.ApplicationLifetime is
IClassicDesktopStyleApplicationLifetime lifeTime)
+2 -2
View File
@@ -5,9 +5,9 @@ using Toolkit.Foundation;
namespace Toolkit.Avalonia;
public class ContentControlHandler :
IHandler<NavigateEventArgs<ContentControl>>
IHandler<NavigateTemplateEventArgs>
{
public void Handle(NavigateEventArgs<ContentControl> args)
public void Handle(NavigateTemplateEventArgs args)
{
if (args.Region is ContentControl contentControl)
{
+2 -2
View File
@@ -5,9 +5,9 @@ using Toolkit.UI.Controls.Avalonia;
namespace Toolkit.Avalonia;
public class ContentDialogHandler :
IHandler<NavigateEventArgs<ContentDialog>>
IHandler<NavigateTemplateEventArgs>
{
public async void Handle(NavigateEventArgs<ContentDialog> args)
public async void Handle(NavigateTemplateEventArgs args)
{
if (args.Template is ContentDialog dialog)
{
+2 -2
View File
@@ -8,10 +8,10 @@ using Toolkit.UI.Controls.Avalonia;
namespace Toolkit.Avalonia;
public class FrameHandler(ITransientNavigationStore<Frame> navigationStore) :
IHandler<NavigateEventArgs<Frame>>,
IHandler<NavigateTemplateEventArgs>,
IHandler<NavigateBackEventArgs<Frame>>
{
public void Handle(NavigateEventArgs<Frame> args)
public void Handle(NavigateTemplateEventArgs args)
{
if (args.Region is Frame frame)
{
@@ -30,16 +30,16 @@ public static class IServiceCollectionExtensions
services.AddAsyncHandler<SelectionEventArgs<FolderFilter>, IReadOnlyCollection<string>?, SelectFoldersHandler>();
services.AddAsyncHandler<SelectionEventArgs<FileFilter>, IReadOnlyCollection<string>?, SelectFilesHandler>();
services.AddHandler<NavigateEventArgs<IClassicDesktopStyleApplicationLifetime>, ClassicDesktopStyleApplicationHandler>(nameof(IClassicDesktopStyleApplicationLifetime));
services.AddHandler<NavigateEventArgs<ISingleViewApplicationLifetime>, SingleViewApplicationHandler>(nameof(ISingleViewApplicationLifetime));
services.AddHandler<NavigateTemplateEventArgs, ClassicDesktopStyleApplicationHandler>(nameof(IClassicDesktopStyleApplicationLifetime));
services.AddHandler<NavigateTemplateEventArgs, SingleViewApplicationHandler>(nameof(ISingleViewApplicationLifetime));
services.AddHandler<NavigateEventArgs<ContentControl>, ContentControlHandler>(nameof(ContentControl));
services.AddHandler<NavigateTemplateEventArgs, ContentControlHandler>(nameof(ContentControl));
services.AddHandler<NavigateEventArgs<Frame>, FrameHandler>(nameof(Frame));
services.AddHandler<NavigateTemplateEventArgs, FrameHandler>(nameof(Frame));
services.TryAddSingleton<ITransientNavigationStore<Frame>, TransientNavigationStore<Frame>>();
services.AddHandler<NavigateEventArgs<ContentDialog>, ContentDialogHandler>(nameof(ContentDialog));
services.AddHandler<NavigateEventArgs<TaskDialog>, TaskDialogHandler>(nameof(TaskDialog));
services.AddHandler<NavigateTemplateEventArgs, ContentDialogHandler>(nameof(ContentDialog));
services.AddHandler<NavigateTemplateEventArgs, TaskDialogHandler>(nameof(TaskDialog));
services.AddScoped<INavigationRegionCollection, NavigationRegionCollection>(provider => new NavigationRegionCollection
{
@@ -69,13 +69,13 @@ public static class IServiceCollectionExtensions
services.AddAsyncHandler<SelectionEventArgs<FolderFilter>, IReadOnlyCollection<string>?, SelectFoldersHandler>();
services.AddAsyncHandler<SelectionEventArgs<FileFilter>, IReadOnlyCollection<string>?, SelectFilesHandler>();
services.AddHandler<NavigateEventArgs<ContentControl>, ContentControlHandler>(nameof(ContentControl));
services.AddHandler<NavigateTemplateEventArgs, ContentControlHandler>(nameof(ContentControl));
services.AddHandler<NavigateEventArgs<Frame>, FrameHandler>(nameof(Frame));
services.AddHandler<NavigateTemplateEventArgs, FrameHandler>(nameof(Frame));
services.TryAddSingleton<ITransientNavigationStore<Frame>, TransientNavigationStore<Frame>>();
services.AddHandler<NavigateEventArgs<ContentDialog>, ContentDialogHandler>(nameof(ContentDialog));
services.AddHandler<NavigateEventArgs<TaskDialog>, TaskDialogHandler>(nameof(TaskDialog));
services.AddHandler<NavigateTemplateEventArgs, ContentDialogHandler>(nameof(ContentDialog));
services.AddHandler<NavigateTemplateEventArgs, TaskDialogHandler>(nameof(TaskDialog));
})));
return services;
@@ -6,9 +6,9 @@ using Toolkit.Foundation;
namespace Toolkit.Avalonia;
public class SingleViewApplicationHandler :
IHandler<NavigateEventArgs<ISingleViewApplicationLifetime>>
IHandler<NavigateTemplateEventArgs>
{
public void Handle(NavigateEventArgs<ISingleViewApplicationLifetime> args)
public void Handle(NavigateTemplateEventArgs args)
{
if (Application.Current?.ApplicationLifetime is
ISingleViewApplicationLifetime lifeTime)
+2 -2
View File
@@ -6,9 +6,9 @@ using Toolkit.UI.Controls.Avalonia;
namespace Toolkit.Avalonia;
public class TaskDialogHandler(ITopLevelProvider topLevelProvider) :
IHandler<NavigateEventArgs<TaskDialog>>
IHandler<NavigateTemplateEventArgs>
{
public async void Handle(NavigateEventArgs<TaskDialog> args)
public async void Handle(NavigateTemplateEventArgs args)
{
if (args.Template is TaskDialog dialog)
{
+1 -1
View File
@@ -5,7 +5,7 @@ public class ContentFactory(IServiceProvider provider,
IContentFactory
{
public object? Create(IContentTemplateDescriptor descriptor,
object[] parameters)
object?[] parameters)
{
object? content = parameters is { Length: > 0 }
? factory.Create(descriptor.ContentType, args =>
+1 -1
View File
@@ -3,6 +3,6 @@
public interface IContentFactory
{
object? Create(IContentTemplateDescriptor descriptor,
object[] parameters);
object?[] parameters);
}
}
+2 -1
View File
@@ -7,7 +7,8 @@ public record NavigateEventArgs(string Route,
EventHandler? Navigated = null,
IDictionary<string, object>? Parameters = null);
public record NavigateEventArgs<TNavigation>(object Region,
public record NavigateTemplateEventArgs(object Region,
object Template,
object Content,
object? Sender = null,
+9 -10
View File
@@ -32,13 +32,17 @@ public class Navigation(IServiceProvider provider,
is IContentTemplateDescriptor descriptor)
{
Dictionary<string, object>? arguments = parameters?.ToDictionary(x => x.Key, x => x.Value, StringComparer.InvariantCultureIgnoreCase) ?? [];
object[]? resolvedArguments = parameters is not null ? [.. descriptor.ContentType
object?[] resolvedArguments = parameters is not null
? descriptor.ContentType
.GetConstructors()
.FirstOrDefault()?
.GetParameters()
.Select(x => x?.Name is not null && arguments
.TryGetValue(x.Name, out object? argument) ? argument : default)
.Where(argument => argument is not null)] : [];
.Select(x =>
x?.Name is not null && arguments is not null && arguments.TryGetValue(x.Name, out object? argument)
? argument
: null)
.Where(argument => argument is not null)
.ToArray() ?? [] : [];
if (provider.GetRequiredKeyedService(descriptor.TemplateType, descriptor.Key)
is object template)
@@ -67,12 +71,8 @@ public class Navigation(IServiceProvider provider,
if (content is not null)
{
Type navigationType = region is Type type ? type : region.GetType();
Type navigateEventType = typeof(NavigateEventArgs<>).MakeGenericType(navigationType);
if (Activator.CreateInstance(navigateEventType, [region, template, content, sender, parameters])
is object navigateEvent)
{
messenger.Send(navigateEvent, navigationType.Name);
messenger.Send(new NavigateTemplateEventArgs(region, template, content, sender, parameters), navigationType.Name);
if (currentSegmentIndex == segmentCount)
{
navigated?.Invoke(this, EventArgs.Empty);
@@ -83,7 +83,6 @@ public class Navigation(IServiceProvider provider,
}
}
}
}
public void Back(object? region)
{
+1 -1
View File
@@ -24,7 +24,7 @@ public partial class Observable(IServiceProvider provider,
public IServiceProvider Provider { get; } = provider;
public IMessenger Messenger { get; } = messenger;
public new IMessenger Messenger { get; } = messenger;
public void Commit()
{
+2 -1
View File
@@ -35,7 +35,8 @@ public class NavigateRegionAction :
{
if (control.DataContext is IObservableViewModel observableViewModel)
{
if (observableViewModel.Provider.GetRequiredService<INavigationRegion>() is INavigationRegion navigationRegion)
if (observableViewModel.Provider.GetRequiredService<INavigationRegion>()
is INavigationRegion navigationRegion)
{
navigationRegion.Register(Name, sender);
Interaction.ExecuteActions(sender, Actions, parameter);
+13
View File
@@ -0,0 +1,13 @@
using Microsoft.Xaml.Interactivity;
namespace Toolkit.UI.WinUI;
public class AttachedBehaviour :
Trigger
{
protected override void OnAttached()
{
Interaction.ExecuteActions(AssociatedObject, Actions, null);
base.OnAttached();
}
}
+84
View File
@@ -0,0 +1,84 @@
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Xaml.Interactivity;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Toolkit.Foundation;
using Toolkit.WinUI;
using Windows.UI.Xaml.Markup;
namespace Toolkit.UI.WinUI;
[ContentProperty(Name = nameof(Parameters))]
public class NavigateAction :
DependencyObject,
IAction
{
public static readonly DependencyProperty RegionProperty =
DependencyProperty.Register(nameof(Region),
typeof(object), typeof(NavigateAction),
new PropertyMetadata(null));
public static readonly DependencyProperty RouteProperty =
DependencyProperty.Register(nameof(Route),
typeof(string), typeof(NavigateAction),
new PropertyMetadata(null));
public static readonly DependencyProperty ScopeProperty =
DependencyProperty.Register(nameof(Scope),
typeof(string), typeof(NavigateAction),
new PropertyMetadata(null));
public static readonly DependencyProperty ParametersProperty =
DependencyProperty.Register(nameof(Parameters),
typeof(ParameterCollection), typeof(NavigateAction),
new PropertyMetadata(null));
private ParameterCollection parameterCollection = [];
public event EventHandler? Navigated;
public object Region
{
get => GetValue(RegionProperty);
set => SetValue(RegionProperty, value);
}
public ParameterCollection Parameters =>
parameterCollection ??= [];
public string Route
{
get => (string)GetValue(RouteProperty);
set => SetValue(RouteProperty, value);
}
public string Scope
{
get => (string)GetValue(ScopeProperty);
set => SetValue(ScopeProperty, value);
}
public object Execute(object? sender,
object? parameter)
{
if (sender is Control content)
{
Dictionary<string, object> arguments =
new(StringComparer.InvariantCultureIgnoreCase);
if (content.DataContext is IObservableViewModel observableViewModel)
{
ImmutableDictionary<string, object>? parameters = Parameters is { Count: > 0 } ? Parameters.ToImmutableDictionary(x => x.Key, x => x.Value) :
ImmutableDictionary<string, object>.Empty;
observableViewModel.Messenger.Send(new NavigateEventArgs(Route, Region.Equals(this) ? content : Region, Scope ?? null,
content.DataContext, Navigated, parameters));
}
}
return true;
}
}
+53
View File
@@ -0,0 +1,53 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Xaml.Interactivity;
using Toolkit.Foundation;
using Windows.UI.Xaml.Markup;
namespace Toolkit.UI.WinUI;
[ContentProperty(Name = nameof(Actions))]
public class NavigateRegionAction :
DependencyObject,
IAction
{
public static readonly DependencyProperty ActionsProperty =
DependencyProperty.Register(nameof(Actions),
typeof(ActionCollection), typeof(NavigateRegionAction),
new PropertyMetadata(null));
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register(nameof(Name),
typeof(string), typeof(NavigateRegionAction),
new PropertyMetadata(null));
private ActionCollection? actions;
public ActionCollection Actions => actions ??= [];
public string Name
{
get => (string)GetValue(NameProperty);
set => SetValue(NameProperty, value);
}
public object? Execute(object? sender,
object? parameter)
{
if (sender is Control control)
{
if (control.DataContext is IObservableViewModel observableViewModel)
{
if (observableViewModel.Provider.GetRequiredService<INavigationRegion>()
is INavigationRegion navigationRegion)
{
navigationRegion.Register(Name, sender);
Interaction.ExecuteActions(sender, Actions, parameter);
}
}
}
return true;
}
}
+30
View File
@@ -0,0 +1,30 @@
using Microsoft.UI.Xaml;
using System.Xml.Linq;
namespace Toolkit.WinUI;
public class Parameter :
DependencyObject
{
public static readonly DependencyProperty KeyProperty =
DependencyProperty.Register(nameof(Key),
typeof(object), typeof(Parameter),
new PropertyMetadata(null));
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value),
typeof(string), typeof(Parameter),
new PropertyMetadata(null));
public string Key
{
get => (string)GetValue(KeyProperty);
set => SetValue(KeyProperty, value);
}
public object Value
{
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
}
+6
View File
@@ -0,0 +1,6 @@
using System.Collections.ObjectModel;
namespace Toolkit.WinUI;
public class ParameterCollection :
ObservableCollection<Parameter>;
+1
View File
@@ -19,6 +19,7 @@
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.1742" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.6.240923002" />
<PackageReference Include="WinUIEx" Version="2.4.2" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
</ItemGroup>
<ItemGroup>
+58
View File
@@ -0,0 +1,58 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Toolkit.Foundation;
namespace Toolkit.WinUI;
public class ContentControlHandler :
IHandler<NavigateTemplateEventArgs>
{
public void Handle(NavigateTemplateEventArgs args)
{
if (args.Region is ContentControl contentControl)
{
if (args.Template is Control control)
{
TaskCompletionSource taskCompletionSource = new();
void HandleLoaded(object? sender, RoutedEventArgs args)
{
control.Loaded -= HandleLoaded;
if (control.DataContext is object content)
{
if (content is IActivation activation)
{
activation.IsActive = true;
}
}
taskCompletionSource.SetResult();
}
void HandleUnloaded(object? sender, RoutedEventArgs args)
{
control.Unloaded -= HandleLoaded;
if (control.DataContext is object content)
{
if (content is IActivation activation)
{
activation.IsActive = false;
}
if (content is IDisposable disposable)
{
disposable.Dispose();
}
}
}
control.Loaded += HandleLoaded;
control.Unloaded += HandleUnloaded;
control.DataContext = args.Content;
contentControl.Content = null;
contentControl.Content = control;
}
}
}
}
@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
using Toolkit.Foundation;
namespace Toolkit.WinUI;
@@ -12,6 +13,9 @@ public static class IServiceCollectionExtensions
services.AddSingleton<IWindowRegistry, WindowRegistry>();
services.AddTransient<IContentTemplate, ContentTemplate>();
services.AddTransient<INavigationRegion, NavigationRegion>();
services.AddHandler<NavigateTemplateEventArgs, ContentControlHandler>(nameof(ContentControl));
services.AddTransient((Func<IServiceProvider, IProxyServiceCollection<IComponentBuilder>>)(provider =>
new ProxyServiceCollection<IComponentBuilder>(services =>
+28
View File
@@ -0,0 +1,28 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Toolkit.Foundation;
namespace Toolkit.WinUI;
public class NavigationRegion(INavigationRegionCollection collection) :
INavigationRegion
{
public void Register(string name,
object target)
{
if (target is Control control)
{
if (!collection.ContainsKey(name))
{
collection.Add(name, control);
void HandleUnloaded(object? sender, RoutedEventArgs args)
{
control.Unloaded -= HandleUnloaded;
collection.Remove(name);
}
control.Unloaded += HandleUnloaded;
}
}
}
}
+9
View File
@@ -0,0 +1,9 @@
using Microsoft.UI.Xaml.Controls;
namespace Toolkit.WinUI;
public class PageControl :
Page
{
}
+4 -8
View File
@@ -23,12 +23,10 @@ public class TemplateControl :
if (DataContext is IObservableViewModel observableViewModel)
{
if (observableViewModel.Provider is IServiceProvider provider)
{
if (provider.GetRequiredKeyedService<IContentTemplateDescriptor>(DataContext.GetType().Name.Replace("ViewModel", ""))
is IContentTemplateDescriptor descriptor)
{
if (provider.GetRequiredKeyedService(descriptor.TemplateType, descriptor.Key)
if (observableViewModel.Provider is IServiceProvider provider &&
provider.GetRequiredKeyedService<IContentTemplateDescriptor>(DataContext.GetType().Name.Replace("ViewModel", ""))
is IContentTemplateDescriptor descriptor &&
provider.GetRequiredKeyedService(descriptor.TemplateType, descriptor.Key)
is FrameworkElement control)
{
void HandleLoaded(object? sender, RoutedEventArgs args)
@@ -74,6 +72,4 @@ public class TemplateControl :
}
}
}
}
}
}