introduce a TransientNavigationStore to share objects from one state to the next state

This commit is contained in:
TheXamlGuy
2024-10-08 15:12:50 +01:00
parent 12ed99f191
commit f809f3d221
9 changed files with 224 additions and 173 deletions
+19 -23
View File
@@ -7,7 +7,7 @@ using Toolkit.UI.Controls.Avalonia;
namespace Toolkit.Avalonia; namespace Toolkit.Avalonia;
public class FrameHandler : public class FrameHandler(ITransientNavigationStore<Frame> navigationStore) :
INotificationHandler<NavigateEventArgs<Frame>>, INotificationHandler<NavigateEventArgs<Frame>>,
INotificationHandler<NavigateBackEventArgs<Frame>> INotificationHandler<NavigateBackEventArgs<Frame>>
{ {
@@ -18,19 +18,16 @@ public class FrameHandler :
frame.NavigationPageFactory ??= new NavigationPageFactory(); frame.NavigationPageFactory ??= new NavigationPageFactory();
if (args.Template is Control control) if (args.Template is Control control)
{ {
void NavigatedTo(Control sender) void Navigated(Control sender)
{ {
async void HandleNavigatedTo(object? _, async void HandleNavigatedTo(object? _, NavigationEventArgs __)
NavigationEventArgs __)
{ {
async void HandleNavigatingFrom(object? _, async void HandleNavigatingFrom(object? _, NavigatingCancelEventArgs args)
NavigatingCancelEventArgs args)
{ {
sender.RemoveHandler(Frame.NavigatingFromEvent, HandleNavigatingFrom); sender.RemoveHandler(Frame.NavigatingFromEvent, HandleNavigatingFrom);
control.Unloaded -= HandleUnloaded; control.Unloaded -= HandleUnloaded;
async void HandleNavigatedFrom(object? _, async void HandleNavigatedFrom(object? _, NavigationEventArgs args)
NavigationEventArgs args)
{ {
sender.RemoveHandler(Frame.NavigatedFromEvent, HandleNavigatedFrom); sender.RemoveHandler(Frame.NavigatedFromEvent, HandleNavigatedFrom);
if (sender.DataContext is object content) if (sender.DataContext is object content)
@@ -40,9 +37,11 @@ public class FrameHandler :
await deactivated.OnDeactivated(); await deactivated.OnDeactivated();
} }
if (content is not IKeepAlive)
{
if (content is IDisposable disposable) if (content is IDisposable disposable)
{
FrameNavigationOptions? options = navigationStore.Get<FrameNavigationOptions>(frame);
if (options is not FrameNavigationOptions frameOptions ||
!frameOptions.IsNavigationStackEnabled)
{ {
disposable.Dispose(); disposable.Dispose();
} }
@@ -51,6 +50,7 @@ public class FrameHandler :
} }
sender.AddHandler(Frame.NavigatedFromEvent, HandleNavigatedFrom); sender.AddHandler(Frame.NavigatedFromEvent, HandleNavigatedFrom);
if (sender.DataContext is object content) if (sender.DataContext is object content)
{ {
if (content is IConfirmation confirmation && if (content is IConfirmation confirmation &&
@@ -106,7 +106,6 @@ public class FrameHandler :
} }
control.DataContext = args.Content; control.DataContext = args.Content;
NavigatedTo(control);
FrameNavigationOptions navigationOptions = new(); FrameNavigationOptions navigationOptions = new();
List<Action> postNavigateActions = []; List<Action> postNavigateActions = [];
@@ -133,22 +132,19 @@ public class FrameHandler :
if (args.Parameters is not null) if (args.Parameters is not null)
{ {
if (args.Parameters.TryGetValue("Transition", if (args.Parameters.TryGetValue("Transition", out object? transition))
out object? transition))
{ {
switch ($"{transition}") switch ($"{transition}")
{ {
case "Suppress": case "Suppress":
navigationOptions.TransitionInfoOverride = navigationOptions.TransitionInfoOverride = new SuppressNavigationTransitionInfo();
new SuppressNavigationTransitionInfo();
break; break;
case "FromLeft": case "FromLeft":
case "FromRight": case "FromRight":
case "FromTop": case "FromTop":
case "FromBottom": case "FromBottom":
navigationOptions.TransitionInfoOverride = navigationOptions.TransitionInfoOverride = new SlideNavigationTransitionInfo
new SlideNavigationTransitionInfo
{ {
Effect = Enum.Parse<SlideNavigationTransitionEffect>($"{transition}") Effect = Enum.Parse<SlideNavigationTransitionEffect>($"{transition}")
}; };
@@ -156,8 +152,7 @@ public class FrameHandler :
} }
} }
if (args.Parameters.TryGetValue("IsBackStackEnabled", if (args.Parameters.TryGetValue("IsBackStackEnabled", out object? isBackStackEnabled))
out object? isBackStackEnabled))
{ {
if (isBackStackEnabled is bool value) if (isBackStackEnabled is bool value)
{ {
@@ -165,8 +160,7 @@ public class FrameHandler :
} }
} }
if (args.Parameters.TryGetValue("ClearBackStack", if (args.Parameters.TryGetValue("ClearBackStack", out object? clearBackStack))
out object? clearBackStack))
{ {
if (clearBackStack is bool clearBool) if (clearBackStack is bool clearBool)
{ {
@@ -178,8 +172,7 @@ public class FrameHandler :
if (clearBackStack is string clearString) if (clearBackStack is string clearString)
{ {
if (clearString.StartsWith('[') && clearString.EndsWith(']') && if (clearString.StartsWith('[') && clearString.EndsWith(']') && clearString.Contains('-'))
clearString.Contains('-'))
{ {
string range = clearString.Trim('[', ']'); string range = clearString.Trim('[', ']');
string[] parts = range.Split('-'); string[] parts = range.Split('-');
@@ -202,6 +195,9 @@ public class FrameHandler :
} }
} }
Navigated(control);
navigationStore.Set(frame, navigationOptions);
frame.NavigateFromObject(control, navigationOptions); frame.NavigateFromObject(control, navigationOptions);
foreach (Action postAction in postNavigateActions) foreach (Action postAction in postNavigateActions)
@@ -33,7 +33,10 @@ public static class IServiceCollectionExtensions
services.AddHandler<ClassicDesktopStyleApplicationHandler>(nameof(IClassicDesktopStyleApplicationLifetime)); services.AddHandler<ClassicDesktopStyleApplicationHandler>(nameof(IClassicDesktopStyleApplicationLifetime));
services.AddHandler<SingleViewApplicationHandler>(nameof(ISingleViewApplicationLifetime)); services.AddHandler<SingleViewApplicationHandler>(nameof(ISingleViewApplicationLifetime));
services.AddHandler<ContentControlHandler>(nameof(ContentControl)); services.AddHandler<ContentControlHandler>(nameof(ContentControl));
services.AddHandler<FrameHandler>(nameof(Frame)); services.AddHandler<FrameHandler>(nameof(Frame));
services.TryAddSingleton<ITransientNavigationStore<Frame>, TransientNavigationStore<Frame>>();
services.AddHandler<ContentDialogHandler>(nameof(ContentDialog)); services.AddHandler<ContentDialogHandler>(nameof(ContentDialog));
services.AddHandler<TaskDialogHandler>(nameof(TaskDialog)); services.AddHandler<TaskDialogHandler>(nameof(TaskDialog));
@@ -66,7 +69,11 @@ public static class IServiceCollectionExtensions
services.AddHandler<SelectFilesHandler>(); services.AddHandler<SelectFilesHandler>();
services.AddHandler<ContentControlHandler>(nameof(ContentControl)); services.AddHandler<ContentControlHandler>(nameof(ContentControl));
services.AddHandler<FrameHandler>(nameof(Frame)); services.AddHandler<FrameHandler>(nameof(Frame));
services.TryAddSingleton(provider.GetRequiredService<ITransientNavigationStore<Frame>>());
services.AddHandler<ContentDialogHandler>(nameof(ContentDialog)); services.AddHandler<ContentDialogHandler>(nameof(ContentDialog));
services.AddHandler<TaskDialogHandler>(nameof(TaskDialog)); services.AddHandler<TaskDialogHandler>(nameof(TaskDialog));
}))); })));
@@ -0,0 +1,15 @@
namespace Toolkit.Avalonia;
public interface ITransientNavigationStore<TControl>
where TControl : class
{
void Clear();
T? Get<T>(TControl control)
where T : class;
void Remove(TControl control);
void Set(TControl control,
object parameters);
}
@@ -0,0 +1,36 @@
namespace Toolkit.Avalonia;
public class TransientNavigationStore<TControl> : ITransientNavigationStore<TControl>
where TControl : class
{
private readonly Dictionary<TControl, Dictionary<Type, object>> controlDataMap = [];
public void Set(TControl control,
object parameters)
{
if (!controlDataMap.TryGetValue(control, out var typeMap))
{
typeMap = new Dictionary<Type, object>();
controlDataMap[control] = typeMap;
}
typeMap[parameters.GetType()] = parameters;
}
public T? Get<T>(TControl control)
where T : class
{
if (controlDataMap.TryGetValue(control, out var typeMap) &&
typeMap.TryGetValue(typeof(T), out var parameters))
{
typeMap.Remove(typeof(T));
return parameters as T;
}
return default;
}
public void Remove(TControl control) => controlDataMap.Remove(control);
public void Clear() => controlDataMap.Clear();
}
+4 -3
View File
@@ -1,9 +1,10 @@
namespace Toolkit.Foundation; namespace Toolkit.Foundation;
public class ContentFactory(IServiceProvider provider, public class ContentFactory(IServiceProvider provider,
IServiceFactory factory) : IContentFactory IServiceFactory factory) :
IContentFactory
{ {
public Task<object?> CreateAsync(IContentTemplateDescriptor descriptor, public object? Create(IContentTemplateDescriptor descriptor,
object[] parameters) object[] parameters)
{ {
object? content = parameters is { Length: > 0 } object? content = parameters is { Length: > 0 }
@@ -22,6 +23,6 @@ public class ContentFactory(IServiceProvider provider,
} }
}, descriptor.Key); }, descriptor.Key);
return Task.FromResult<object?>(content); return content;
} }
} }
+2 -2
View File
@@ -2,7 +2,7 @@
{ {
public interface IContentFactory public interface IContentFactory
{ {
Task<object?> CreateAsync(IContentTemplateDescriptor descriptor, object? Create(IContentTemplateDescriptor descriptor,
object[] resolvedArguments); object[] parameters);
} }
} }
-3
View File
@@ -1,3 +0,0 @@
namespace Toolkit.Foundation;
public interface IKeepAlive;
+1 -1
View File
@@ -62,7 +62,7 @@ public class Navigation(IServiceProvider provider,
if (region is not null) if (region is not null)
{ {
object? content = await contentFactory.CreateAsync(descriptor, resolvedArguments); object? content = contentFactory.Create(descriptor, resolvedArguments);
if (content is not null) if (content is not null)
{ {
Type navigationType = region is Type type ? type : region.GetType(); Type navigationType = region is Type type ? type : region.GetType();
+134 -135
View File
@@ -46,6 +46,8 @@ public partial class ObservableCollection<TViewModel> :
[ObservableProperty] [ObservableProperty]
private int count; private int count;
private Func<TViewModel>? defaultSelectionFactory;
[ObservableProperty] [ObservableProperty]
private bool isActivated; private bool isActivated;
@@ -145,86 +147,27 @@ public partial class ObservableCollection<TViewModel> :
} }
} }
private Func<TViewModel>? defaultSelectionFactory; public void Activate(Func<ActivationBuilder> activateDelegate,
bool reset = false)
public void SetSource(IList<TViewModel> source,
Func<TViewModel>? defaultSelectionFactory)
{ {
foreach (TViewModel item in source) if (reset)
{ {
Add(item);
}
if (defaultSelectionFactory is not null)
{
this.defaultSelectionFactory = defaultSelectionFactory;
SelectedItem = defaultSelectionFactory.Invoke();
}
if (source is INotifyCollectionChanged observableSource)
{
observableSource.CollectionChanged -= SourceCollectionChanged;
observableSource.CollectionChanged += SourceCollectionChanged;
}
}
private void SourceCollectionChanged(object? sender,
NotifyCollectionChangedEventArgs args)
{
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
if (args.NewItems is not null)
{
foreach (TViewModel newItem in args.NewItems)
{
Add(newItem);
}
}
break;
case NotifyCollectionChangedAction.Remove:
if (args.OldItems is not null)
{
foreach (TViewModel oldItem in args.OldItems)
{
if (this.FirstOrDefault(x => x.Equals(oldItem)) is TViewModel removedItem)
{
Remove(removedItem);
}
}
}
break;
case NotifyCollectionChangedAction.Reset:
Clear(); Clear();
if (sender is IEnumerable<TViewModel> collection)
{
foreach (TViewModel item in collection)
{
Add(item);
} }
if (defaultSelectionFactory is not null) ActivationBuilder builder = activateDelegate.Invoke();
{ Publisher.Publish(builder.Value, builder.Key);
SelectedItem = defaultSelectionFactory.Invoke();
}
}
break;
}
} }
public virtual Task OnActivated() public void Activate(bool reset = false)
{ {
IsActivated = true; if (reset)
while (pendingEvents.Count > 0)
{ {
object current = pendingEvents.Dequeue(); Clear();
Handle((dynamic)current);
} }
return Task.CompletedTask; ActivationBuilder builder = ActivationBuilder();
Publisher.PublishUI(builder.Value, builder.Key);
} }
public TViewModel Add<T>(params object?[] parameters) public TViewModel Add<T>(params object?[] parameters)
@@ -285,14 +228,6 @@ public partial class ObservableCollection<TViewModel> :
} }
} }
public void Reset(Action<ObservableCollection<TViewModel>> factory, bool disposeItems = true)
{
SelectedItem = default;
Clear(disposeItems);
factory.Invoke(this);
}
public void Clear(bool disposeItems = false) public void Clear(bool disposeItems = false)
{ {
isClearing = true; isClearing = true;
@@ -342,33 +277,12 @@ public partial class ObservableCollection<TViewModel> :
void ICollection.CopyTo(Array array, int index) => void ICollection.CopyTo(Array array, int index) =>
collection.CopyTo((TViewModel[])array, index); collection.CopyTo((TViewModel[])array, index);
public virtual Task OnDeactivated()
{
IsActivated = false;
return Task.CompletedTask;
}
public virtual Task OnDeactivating() =>
Task.CompletedTask;
public virtual void Dispose() public virtual void Dispose()
{ {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
Disposer.Dispose(this); Disposer.Dispose(this);
} }
public void Activate(Func<ActivationBuilder> activateDelegate,
bool reset = false)
{
if (reset)
{
Clear();
}
ActivationBuilder builder = activateDelegate.Invoke();
Publisher.Publish(builder.Value, builder.Key);
}
public IEnumerator<TViewModel> GetEnumerator() => public IEnumerator<TViewModel> GetEnumerator() =>
collection.GetEnumerator(); collection.GetEnumerator();
@@ -505,10 +419,6 @@ public partial class ObservableCollection<TViewModel> :
IsCompatibleObject(value) ? IsCompatibleObject(value) ?
IndexOf((TViewModel)value!) : -1; IndexOf((TViewModel)value!) : -1;
public virtual void OnInitialize()
{
}
[RelayCommand] [RelayCommand]
public virtual void Initialize() public virtual void Initialize()
{ {
@@ -519,37 +429,12 @@ public partial class ObservableCollection<TViewModel> :
IsInitialized = true; IsInitialized = true;
Subscriber.Subscribe(this); Subscriber.Subscribe(this);
OnInitialize(); OnInitialize();
Activate(); Activate();
} }
public bool Replace<T>(int index,
params object?[] parameters)
where T :
TViewModel
{
if (index <= Count - 1)
{
RemoveItem(index);
}
else
{
index = Count;
}
T? item = Factory.Create<T>(args =>
{
if (args is IInitialization initialization)
{
initialization.Initialize();
}
}, parameters);
Insert(index, item);
return true;
}
public TViewModel Insert<T>(int index = 0, public TViewModel Insert<T>(int index = 0,
params object?[] parameters) params object?[] parameters)
where T : where T :
@@ -634,6 +519,31 @@ public partial class ObservableCollection<TViewModel> :
return true; return true;
} }
public virtual Task OnActivated()
{
IsActivated = true;
while (pendingEvents.Count > 0)
{
object current = pendingEvents.Dequeue();
Handle((dynamic)current);
}
return Task.CompletedTask;
}
public virtual Task OnDeactivated()
{
IsActivated = false;
return Task.CompletedTask;
}
public virtual Task OnDeactivating() =>
Task.CompletedTask;
public virtual void OnInitialize()
{
}
public bool Remove(TViewModel item) public bool Remove(TViewModel item)
{ {
int index = collection.IndexOf(item); int index = collection.IndexOf(item);
@@ -669,6 +579,32 @@ public partial class ObservableCollection<TViewModel> :
public void RemoveAt(int index) => public void RemoveAt(int index) =>
RemoveItem(index); RemoveItem(index);
public bool Replace<T>(int index,
params object?[] parameters)
where T :
TViewModel
{
if (index <= Count - 1)
{
RemoveItem(index);
}
else
{
index = Count;
}
T? item = Factory.Create<T>(args =>
{
if (args is IInitialization initialization)
{
initialization.Initialize();
}
}, parameters);
Insert(index, item);
return true;
}
public bool Replace(int index, public bool Replace(int index,
TViewModel item) TViewModel item)
{ {
@@ -685,6 +621,14 @@ public partial class ObservableCollection<TViewModel> :
return true; return true;
} }
public void Reset(Action<ObservableCollection<TViewModel>> factory, bool disposeItems = true)
{
SelectedItem = default;
Clear(disposeItems);
factory.Invoke(this);
}
public void Revert() public void Revert()
{ {
foreach (object trackedProperty in trackedProperties.Values) foreach (object trackedProperty in trackedProperties.Values)
@@ -693,15 +637,24 @@ public partial class ObservableCollection<TViewModel> :
} }
} }
public void Activate(bool reset = false) public void SetSource(IList<TViewModel> source, Func<TViewModel>? defaultSelectionFactory)
{ {
if (reset) foreach (TViewModel item in source)
{ {
Clear(); Add(item);
} }
ActivationBuilder builder = ActivationBuilder(); if (defaultSelectionFactory is not null)
Publisher.PublishUI(builder.Value, builder.Key); {
this.defaultSelectionFactory = defaultSelectionFactory;
SelectedItem = defaultSelectionFactory.Invoke();
}
if (source is INotifyCollectionChanged observableSource)
{
observableSource.CollectionChanged -= SourceCollectionChanged;
observableSource.CollectionChanged += SourceCollectionChanged;
}
} }
public void Track<T>(string propertyName, Func<T> getter, Action<T> setter) public void Track<T>(string propertyName, Func<T> getter, Action<T> setter)
@@ -725,7 +678,7 @@ public partial class ObservableCollection<TViewModel> :
Disposer.Add(this, item); Disposer.Add(this, item);
Disposer.Add(item, Disposable.Create(() => Disposer.Add(item, Disposable.Create(() =>
{ {
if (item is IRemovable && !isClearing) if (item is IDisposable && !isClearing)
{ {
if (item is IList collection) if (item is IList collection)
{ {
@@ -739,6 +692,10 @@ public partial class ObservableCollection<TViewModel> :
collection.Insert(index > Count ? Count : index, item); collection.Insert(index > Count ? Count : index, item);
} }
protected virtual void OnSelectedItemChanged()
{
}
protected virtual void RemoveItem(int index) => protected virtual void RemoveItem(int index) =>
collection.RemoveAt(index); collection.RemoveAt(index);
@@ -782,10 +739,52 @@ public partial class ObservableCollection<TViewModel> :
OnSelectedItemChanged(); OnSelectedItemChanged();
} }
protected virtual void OnSelectedItemChanged() private void SourceCollectionChanged(object? sender,
NotifyCollectionChangedEventArgs args)
{ {
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
if (args.NewItems is not null)
{
foreach (TViewModel newItem in args.NewItems)
{
Add(newItem);
}
}
break;
case NotifyCollectionChangedAction.Remove:
if (args.OldItems is not null)
{
foreach (TViewModel oldItem in args.OldItems)
{
if (this.FirstOrDefault(x => x.Equals(oldItem)) is TViewModel removedItem)
{
Remove(removedItem);
}
}
}
break;
case NotifyCollectionChangedAction.Reset:
Clear();
if (sender is IEnumerable<TViewModel> collection)
{
foreach (TViewModel item in collection)
{
Add(item);
} }
if (defaultSelectionFactory is not null)
{
SelectedItem = defaultSelectionFactory.Invoke();
}
}
break;
}
}
private void UpdateSelection(TViewModel item) private void UpdateSelection(TViewModel item)
{ {
if (item is ISelectable newSelection) if (item is ISelectable newSelection)