4using System.Collections.ObjectModel;
5using System.Collections.Specialized;
6using System.Diagnostics;
7using System.Globalization;
9using System.Threading.Tasks;
10using System.Windows.Input;
11using Microsoft.Maui.Controls;
12using Microsoft.Maui.Dispatching;
21 [ContentProperty(nameof(Views))]
24 public static readonly BindableProperty SelectedIndexProperty =
25 BindableProperty.Create(nameof(SelectedIndex), typeof(
int), typeof(
ViewSwitcher), -1, BindingMode.TwoWay, propertyChanged: OnSelectedIndexPropertyChanged);
27 public static readonly BindableProperty SelectedItemProperty =
28 BindableProperty.Create(nameof(SelectedItem), typeof(
object), typeof(
ViewSwitcher),
default(
object), BindingMode.TwoWay, propertyChanged: OnSelectedItemPropertyChanged);
30 public static readonly BindableProperty SelectedStateKeyProperty =
31 BindableProperty.Create(nameof(SelectedStateKey), typeof(
string), typeof(
ViewSwitcher),
default(
string), BindingMode.TwoWay, propertyChanged: OnSelectedStateKeyPropertyChanged);
33 public static readonly BindableProperty ItemsSourceProperty =
34 BindableProperty.Create(nameof(ItemsSource), typeof(IEnumerable), typeof(
ViewSwitcher),
default(IEnumerable), propertyChanged: OnItemsSourcePropertyChanged);
36 public static readonly BindableProperty ItemTemplateProperty =
37 BindableProperty.Create(nameof(ItemTemplate), typeof(DataTemplate), typeof(
ViewSwitcher),
default(DataTemplate), propertyChanged: OnItemTemplatePropertyChanged);
39 public static readonly BindableProperty ItemTemplateSelectorProperty =
40 BindableProperty.Create(nameof(ItemTemplateSelector), typeof(DataTemplateSelector), typeof(
ViewSwitcher),
default(DataTemplateSelector), propertyChanged: OnItemTemplateSelectorPropertyChanged);
42 public static readonly BindableProperty CacheViewsProperty =
43 BindableProperty.Create(nameof(CacheViews), typeof(
bool), typeof(
ViewSwitcher),
true, propertyChanged: OnCacheViewsPropertyChanged);
45 public static readonly BindableProperty AnimateProperty =
46 BindableProperty.Create(nameof(Animate), typeof(
bool), typeof(
ViewSwitcher),
true, propertyChanged: OnAnimatePropertyChanged);
48 public static readonly BindableProperty TransitionDurationProperty =
49 BindableProperty.Create(nameof(TransitionDuration), typeof(uint), typeof(
ViewSwitcher), (uint)250, propertyChanged: OnTransitionDurationPropertyChanged);
51 public static readonly BindableProperty TransitionEasingProperty =
52 BindableProperty.Create(nameof(TransitionEasing), typeof(Easing), typeof(
ViewSwitcher), Easing.Linear, propertyChanged: OnTransitionEasingPropertyChanged);
54 public static readonly BindableProperty TransitionProperty =
57 public static readonly BindableProperty SelectedIndexBehaviorProperty =
60 public static readonly BindableProperty AutomationDescriptionTemplateProperty =
61 BindableProperty.Create(nameof(AutomationDescriptionTemplate), typeof(
string), typeof(
ViewSwitcher),
default(
string));
63 private readonly ObservableCollection<View> views;
64 private readonly ObservableCollection<ViewSwitcherStateView> stateViews;
65 private readonly List<ViewSwitcherItemDescriptor> descriptors;
66 private readonly Dictionary<string, ViewSwitcherItemDescriptor> stateKeyLookup;
67 private readonly ViewSwitcherSelectionState selectionState;
68 private readonly ViewSwitcherViewFactory viewFactory;
69 private readonly ViewSwitcherViewCache viewCache;
70 private readonly Dictionary<View, ViewSwitcherItemDescriptor> viewDescriptorMap;
71 private readonly HashSet<object> initializedLifecycleTargets;
72 private readonly ViewSwitcherTransitionCoordinator transitionCoordinator;
73 private readonly Grid presenter;
74 private readonly Command nextCommand;
75 private readonly Command previousCommand;
77 private INotifyCollectionChanged? itemsSourceNotifier;
79 private bool disposed;
80 private bool isInitialLoad;
84 RowDefinition rootRow =
new RowDefinition { Height = GridLength.Star };
85 this.RowDefinitions.Add(rootRow);
87 this.views =
new ObservableCollection<View>();
88 this.views.CollectionChanged += this.OnViewsCollectionChanged;
90 this.stateViews =
new ObservableCollection<ViewSwitcherStateView>();
91 this.stateViews.CollectionChanged += this.OnStateViewsCollectionChanged;
93 this.descriptors =
new List<ViewSwitcherItemDescriptor>();
94 this.stateKeyLookup =
new Dictionary<string, ViewSwitcherItemDescriptor>(StringComparer.Ordinal);
96 this.selectionState =
new ViewSwitcherSelectionState
98 IndexBehavior = this.SelectedIndexBehavior
101 this.viewFactory =
new ViewSwitcherViewFactory(
this)
103 ItemTemplate = this.ItemTemplate,
104 ItemTemplateSelector = this.ItemTemplateSelector
107 this.viewCache =
new ViewSwitcherViewCache
109 IsEnabled = this.CacheViews
112 this.viewDescriptorMap =
new Dictionary<View, ViewSwitcherItemDescriptor>(ReferenceEqualityComparer.Instance);
113 this.initializedLifecycleTargets =
new HashSet<object>(ReferenceEqualityComparer.Instance);
117 this.presenter =
new Grid();
123 catch (ArgumentException)
127 this.transitionCoordinator =
new ViewSwitcherTransitionCoordinator(this.presenter,
AnimationCoordinator)
129 Animate = this.Animate,
130 Duration = this.TransitionDuration,
131 Easing = this.TransitionEasing,
132 Transition = this.Transition
134 this.Children.Add(this.presenter);
136 this.nextCommand =
new Command(this.ExecuteNextCommand, this.CanExecuteNextCommand);
137 this.previousCommand =
new Command(this.ExecutePreviousCommand, this.CanExecutePreviousCommand);
139 this.Loaded += this.OnLoaded;
140 this.Unloaded += this.OnUnloaded;
142 this.isInitialLoad =
true;
145 public event EventHandler<ViewSwitcherSelectionChangingEventArgs>? SelectionChanging;
147 public event EventHandler<ViewSwitcherSelectionChangedEventArgs>? SelectionChanged;
149 public int SelectedIndex
151 get => (int)this.GetValue(SelectedIndexProperty);
152 set => this.SetValue(SelectedIndexProperty, value);
155 public object? SelectedItem
157 get => this.GetValue(SelectedItemProperty);
158 set => this.SetValue(SelectedItemProperty, value);
161 public string? SelectedStateKey
163 get => (
string?)this.GetValue(SelectedStateKeyProperty);
164 set => this.SetValue(SelectedStateKeyProperty, value);
167 public IEnumerable? ItemsSource
169 get => (IEnumerable?)this.GetValue(ItemsSourceProperty);
170 set => this.SetValue(ItemsSourceProperty, value);
173 public DataTemplate? ItemTemplate
175 get => (DataTemplate?)this.GetValue(ItemTemplateProperty);
176 set => this.SetValue(ItemTemplateProperty, value);
179 public DataTemplateSelector? ItemTemplateSelector
181 get => (DataTemplateSelector?)this.GetValue(ItemTemplateSelectorProperty);
182 set => this.SetValue(ItemTemplateSelectorProperty, value);
185 public bool CacheViews
187 get => (bool)this.GetValue(CacheViewsProperty);
188 set => this.SetValue(CacheViewsProperty, value);
193 get => (bool)this.GetValue(AnimateProperty);
194 set => this.SetValue(AnimateProperty, value);
197 public uint TransitionDuration
199 get => (uint)this.GetValue(TransitionDurationProperty);
200 set => this.SetValue(TransitionDurationProperty, value);
203 public Easing? TransitionEasing
205 get => (Easing?)this.GetValue(TransitionEasingProperty);
206 set => this.SetValue(TransitionEasingProperty, value);
212 set => this.SetValue(TransitionProperty, value);
218 set => this.SetValue(SelectedIndexBehaviorProperty, value);
221 public string? AutomationDescriptionTemplate
223 get => (
string?)this.GetValue(AutomationDescriptionTemplateProperty);
224 set => this.SetValue(AutomationDescriptionTemplateProperty, value);
227 public ObservableCollection<View> Views => this.views;
229 public ObservableCollection<ViewSwitcherStateView> StateViews => this.stateViews;
231 public ICommand NextCommand => this.nextCommand;
233 public ICommand PreviousCommand => this.previousCommand;
237 public Task SwitchToAsync(
int index,
bool animate =
true, CancellationToken cancellationToken =
default)
239 Task task = this.RunSelectionTaskAsync(
240 token => this.ApplySelectionAsync(index,
null,
null, ViewSwitcherSelectionChangeSource.Index, animate, token),
242 this.ObserveSelectionTask(task);
246 public Task SwitchToStateAsync(
string stateKey,
bool animate =
true, CancellationToken cancellationToken =
default)
248 Task task = this.RunSelectionTaskAsync(
249 token => this.ApplySelectionAsync(
null,
null, stateKey, ViewSwitcherSelectionChangeSource.StateKey, animate, token),
251 this.ObserveSelectionTask(task);
255 protected override void OnBindingContextChanged()
257 base.OnBindingContextChanged();
259 object? bindingContext = this.BindingContext;
260 foreach (View view
in this.views)
262 if (!ReferenceEquals(view.BindingContext, bindingContext))
264 view.BindingContext = bindingContext;
270 if (stateView.Content is not
null && stateView.Content.BindingContext is
null)
272 stateView.Content.BindingContext = bindingContext;
277 private static void OnSelectedIndexPropertyChanged(BindableObject bindable,
object oldValue,
object newValue)
281 int requestedIndex = (int)newValue;
282 viewSwitcher.RequestSelection(ViewSwitcherSelectionChangeSource.Index, requestedIndex,
null,
null);
286 private static void OnSelectedItemPropertyChanged(BindableObject bindable,
object oldValue,
object newValue)
290 viewSwitcher.RequestSelection(ViewSwitcherSelectionChangeSource.Item,
null, newValue,
null);
294 private static void OnSelectedStateKeyPropertyChanged(BindableObject bindable,
object oldValue,
object newValue)
298 string? requestedKey = newValue as string;
299 viewSwitcher.RequestSelection(ViewSwitcherSelectionChangeSource.StateKey,
null,
null, requestedKey);
303 private static void OnItemsSourcePropertyChanged(BindableObject bindable,
object oldValue,
object newValue)
307 viewSwitcher.OnItemsSourceChanged(oldValue as IEnumerable, newValue as IEnumerable);
311 private static void OnItemTemplatePropertyChanged(BindableObject bindable,
object oldValue,
object newValue)
315 viewSwitcher.viewFactory.ItemTemplate = newValue as DataTemplate;
316 viewSwitcher.RefreshDescriptors();
320 private static void OnItemTemplateSelectorPropertyChanged(BindableObject bindable,
object oldValue,
object newValue)
324 viewSwitcher.viewFactory.ItemTemplateSelector = newValue as DataTemplateSelector;
325 viewSwitcher.RefreshDescriptors();
329 private static void OnCacheViewsPropertyChanged(BindableObject bindable,
object oldValue,
object newValue)
333 bool isEnabled = (bool)newValue;
334 viewSwitcher.viewCache.IsEnabled = isEnabled;
337 viewSwitcher.viewCache.Clear();
338 viewSwitcher.viewDescriptorMap.Clear();
343 private static void OnAnimatePropertyChanged(BindableObject bindable,
object oldValue,
object newValue)
347 viewSwitcher.transitionCoordinator.Animate = (bool)newValue;
351 private static void OnTransitionDurationPropertyChanged(BindableObject bindable,
object oldValue,
object newValue)
355 viewSwitcher.transitionCoordinator.Duration = (uint)newValue;
359 private static void OnTransitionEasingPropertyChanged(BindableObject bindable,
object oldValue,
object newValue)
363 viewSwitcher.transitionCoordinator.Easing = newValue as Easing;
367 private static void OnTransitionPropertyChanged(BindableObject bindable,
object oldValue,
object newValue)
371 viewSwitcher.transitionCoordinator.Transition = transition;
375 private static void OnSelectedIndexBehaviorPropertyChanged(BindableObject bindable,
object oldValue,
object newValue)
379 viewSwitcher.selectionState.IndexBehavior = behavior;
380 viewSwitcher.UpdateNavigationCommands();
384 private void RequestSelection(ViewSwitcherSelectionChangeSource source,
int? index,
object? item,
string? stateKey)
386 if (this.selectionState.ShouldIgnore(source))
389 Task task = this.RunSelectionTaskAsync(
390 token => this.ApplySelectionAsync(index, item, stateKey, source,
null, token),
391 CancellationToken.None);
392 this.ObserveSelectionTask(task);
395 private readonly
struct SelectionWorkItem
397 public SelectionWorkItem(Func<CancellationToken, Task> factory, CancellationToken externalToken)
399 this.Factory = factory ??
throw new ArgumentNullException(nameof(factory));
400 this.ExternalToken = externalToken;
403 public Func<CancellationToken, Task> Factory {
get; }
405 public CancellationToken ExternalToken {
get; }
408 private Task RunSelectionTaskAsync(Func<CancellationToken, Task> taskFactory, CancellationToken externalToken)
410 SelectionWorkItem workItem =
new SelectionWorkItem(taskFactory, externalToken);
411 this.selectionOperation.Run<SelectionWorkItem>(async (item, context) =>
413 CancellationToken effectiveToken = context.CancellationToken;
414 if (item.ExternalToken.CanBeCanceled)
416 using CancellationTokenSource linked = CancellationTokenSource.CreateLinkedTokenSource(effectiveToken, item.ExternalToken);
417 effectiveToken = linked.Token;
418 await item.Factory.Invoke(effectiveToken).ConfigureAwait(
false);
422 await item.Factory.Invoke(effectiveToken).ConfigureAwait(
false);
426 Task? task = this.selectionOperation.CurrentTask;
427 return task ?? Task.CompletedTask;
430 private void ObserveSelectionTask(Task task)
432 task.ContinueWith(t =>
434 if (t.Exception is not
null)
436 this.LogException(t.Exception);
438 }, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
441 private async Task ApplySelectionAsync(
443 object? requestedItem,
444 string? requestedStateKey,
445 ViewSwitcherSelectionChangeSource source,
446 bool? animateOverride,
447 CancellationToken cancellationToken)
451 await this.Dispatcher.DispatchAsync(async () =>
454 if (cancellationToken.IsCancellationRequested)
459 this.RefreshDescriptors();
461 int itemCount =
this.descriptors.Count;
462 int targetIndex =
this.ResolveTargetIndex(requestedIndex, requestedItem, requestedStateKey, source, itemCount);
463 int oldIndex =
this.selectionState.SelectedIndex;
464 object? oldItem =
this.selectionState.SelectedItem;
465 string? oldStateKey =
this.selectionState.SelectedStateKey;
467 if (targetIndex >= itemCount)
469 targetIndex = this.selectionState.NormalizeIndex(targetIndex, itemCount);
472 ViewSwitcherItemDescriptor? descriptor =
null;
473 object? newItem =
null;
474 string? newStateKey =
null;
476 if (targetIndex >= 0 && targetIndex < itemCount)
478 descriptor = this.descriptors[targetIndex];
479 newItem = descriptor.Item;
480 newStateKey = descriptor.StateKey;
485 this.UpdateNavigationCommands();
489 if (descriptor is
null)
491 int FallbackIndex = this.selectionState.SelectedIndex;
492 if (FallbackIndex < 0 || FallbackIndex >= itemCount)
497 descriptor = this.descriptors[FallbackIndex];
498 targetIndex = FallbackIndex;
499 newItem = descriptor.Item;
500 newStateKey = descriptor.StateKey;
511 this.SelectionChanging?.Invoke(
this, changingArgs);
514 this.RevertSelectionProperties();
518 this.selectionState.UpdateSnapshot(targetIndex, newItem, newStateKey);
520 using (this.selectionState.BeginUpdate(ViewSwitcherSelectionChangeSource.Index))
522 this.SelectedIndex = targetIndex;
525 using (this.selectionState.BeginUpdate(ViewSwitcherSelectionChangeSource.Item))
527 this.SelectedItem = newItem;
530 using (this.selectionState.BeginUpdate(ViewSwitcherSelectionChangeSource.StateKey))
532 this.SelectedStateKey = newStateKey;
535 this.UpdateAutomationSemantics(newItem, newStateKey);
536 this.UpdateNavigationCommands();
538 View? oldViewInstance = this.transitionCoordinator.CurrentView;
539 View? viewToPresent = descriptor is not
null ? this.ResolveView(descriptor, targetIndex) :
null;
541 if (!ReferenceEquals(oldViewInstance, viewToPresent))
543 await this.InvokeLifecycleDisappearingAsync(oldViewInstance, cancellationToken).ConfigureAwait(
false);
546 if (viewToPresent is not
null)
548 await this.EnsureLifecycleInitializedAsync(viewToPresent, cancellationToken).ConfigureAwait(
false);
551 bool previousAnimate = this.transitionCoordinator.Animate;
552 uint previousDuration = this.transitionCoordinator.Duration;
553 Easing? previousEasing = this.transitionCoordinator.Easing;
555 if (animateOverride.HasValue)
557 this.transitionCoordinator.Animate = animateOverride.Value;
560 this.transitionCoordinator.Duration = this.TransitionDuration;
561 this.transitionCoordinator.Easing = this.TransitionEasing;
566 ct => this.transitionCoordinator.SwitchAsync(
this, viewToPresent,
this.isInitialLoad, ct),
567 cancellationToken).ConfigureAwait(
false);
568 if (!ReferenceEquals(oldViewInstance, viewToPresent))
570 await this.InvokeLifecycleAppearingAsync(viewToPresent, cancellationToken).ConfigureAwait(
false);
572 await this.DisposeLifecycleIfRequiredAsync(oldViewInstance, viewToPresent).ConfigureAwait(
false);
576 this.transitionCoordinator.Animate = previousAnimate;
577 this.transitionCoordinator.Duration = previousDuration;
578 this.transitionCoordinator.Easing = previousEasing;
581 this.isInitialLoad =
false;
582 if (cancellationToken.IsCancellationRequested)
594 this.SelectionChanged?.Invoke(
this, changedArgs);
595 }).ConfigureAwait(
false);
597 catch (OperationCanceledException)
605 private void RevertSelectionProperties()
607 using (this.selectionState.BeginUpdate(ViewSwitcherSelectionChangeSource.Index))
609 this.SelectedIndex = this.selectionState.SelectedIndex;
612 using (this.selectionState.BeginUpdate(ViewSwitcherSelectionChangeSource.Item))
614 this.SelectedItem = this.selectionState.SelectedItem;
617 using (this.selectionState.BeginUpdate(ViewSwitcherSelectionChangeSource.StateKey))
619 this.SelectedStateKey = this.selectionState.SelectedStateKey;
623 private int ResolveTargetIndex(
625 object? requestedItem,
626 string? requestedStateKey,
627 ViewSwitcherSelectionChangeSource source,
630 if (!
string.IsNullOrWhiteSpace(requestedStateKey))
632 if (this.stateKeyLookup.TryGetValue(requestedStateKey, out ViewSwitcherItemDescriptor descriptor))
634 return descriptor.Index;
636 return this.selectionState.SelectedIndex;
639 if (requestedItem is not
null)
641 for (
int i = 0; i < this.descriptors.Count; i++)
643 ViewSwitcherItemDescriptor descriptor = this.descriptors[i];
644 if (ReferenceEquals(descriptor.Item, requestedItem))
645 return descriptor.Index;
646 if (descriptor.StateView is not
null && ReferenceEquals(descriptor.StateView, requestedItem))
647 return descriptor.Index;
650 return this.selectionState.SelectedIndex;
653 if (requestedIndex.HasValue)
655 return this.selectionState.NormalizeIndex(requestedIndex.Value, itemCount);
658 return this.selectionState.SelectedIndex;
661 private View? ResolveView(ViewSwitcherItemDescriptor descriptor,
int index)
663 if (!
string.IsNullOrWhiteSpace(descriptor.StateKey))
665 if (this.viewCache.TryGetByStateKey(descriptor.StateKey!, out View? cachedByKey))
667 this.ApplyBindingContext(cachedByKey, descriptor);
668 this.viewDescriptorMap[cachedByKey] = descriptor;
673 if (this.viewCache.TryGetByIndex(index, out View? cachedByIndex))
675 this.ApplyBindingContext(cachedByIndex, descriptor);
676 this.viewDescriptorMap[cachedByIndex] = descriptor;
677 return cachedByIndex;
680 View view = this.viewFactory.CreateView(descriptor);
681 this.ApplyBindingContext(view, descriptor);
682 this.viewDescriptorMap[view] = descriptor;
684 if (this.viewCache.IsEnabled)
686 if (!
string.IsNullOrWhiteSpace(descriptor.StateKey))
688 this.viewCache.StoreByStateKey(descriptor.StateKey!, view);
692 this.viewCache.StoreByIndex(index, view);
699 private void ApplyBindingContext(View view, ViewSwitcherItemDescriptor descriptor)
701 if (descriptor.StateView is not
null)
703 if (descriptor.StateView.ViewModelType is not
null)
706 if (descriptor.StateView.Content is not
null && descriptor.StateView.Content.BindingContext is not
null)
710 if (descriptor.Item is not
null && descriptor.Item is not View)
712 view.BindingContext = descriptor.Item;
716 if (view.BindingContext is
null)
718 view.BindingContext = this.BindingContext;
722 private void RefreshDescriptors()
724 this.descriptors.Clear();
725 this.stateKeyLookup.Clear();
727 if (this.stateViews.Count > 0)
732 ViewSwitcherItemDescriptor descriptor =
new ViewSwitcherItemDescriptor
735 StateView = stateView,
736 StateKey = stateView.StateKey,
737 InlineView = stateView.Content,
740 this.descriptors.Add(descriptor);
741 if (!
string.IsNullOrWhiteSpace(stateView.StateKey))
743 this.stateKeyLookup[stateView.StateKey] = descriptor;
750 if (this.ItemsSource is not
null)
753 foreach (
object? item
in this.ItemsSource)
755 ViewSwitcherItemDescriptor descriptor =
new ViewSwitcherItemDescriptor
759 InlineView = item as View
761 this.descriptors.Add(descriptor);
767 if (this.views.Count > 0)
770 foreach (View view
in this.views)
772 ViewSwitcherItemDescriptor descriptor =
new ViewSwitcherItemDescriptor
778 this.descriptors.Add(descriptor);
784 private void OnViewsCollectionChanged(
object? sender, NotifyCollectionChangedEventArgs e)
786 this.RefreshDescriptors();
787 this.viewCache.Clear();
788 this.viewDescriptorMap.Clear();
789 this.RequestSelection(ViewSwitcherSelectionChangeSource.Index,
this.SelectedIndex,
null,
null);
792 private void OnStateViewsCollectionChanged(
object? sender, NotifyCollectionChangedEventArgs e)
794 this.RefreshDescriptors();
795 this.viewCache.Clear();
796 this.viewDescriptorMap.Clear();
797 this.RequestSelection(ViewSwitcherSelectionChangeSource.StateKey,
null,
null,
this.SelectedStateKey);
800 private void OnItemsSourceChanged(IEnumerable? oldSource, IEnumerable? newSource)
802 if (this.itemsSourceNotifier is not
null)
804 this.itemsSourceNotifier.CollectionChanged -= this.OnItemsSourceCollectionChanged;
805 this.itemsSourceNotifier =
null;
808 if (oldSource != newSource && newSource is INotifyCollectionChanged notifier)
810 this.itemsSourceNotifier = notifier;
811 notifier.CollectionChanged += this.OnItemsSourceCollectionChanged;
814 this.RefreshDescriptors();
815 this.viewCache.Clear();
816 this.viewDescriptorMap.Clear();
817 this.RequestSelection(ViewSwitcherSelectionChangeSource.Index,
this.SelectedIndex,
null,
null);
820 private void OnItemsSourceCollectionChanged(
object? sender, NotifyCollectionChangedEventArgs e)
822 this.RefreshDescriptors();
823 this.viewCache.Clear();
824 this.viewDescriptorMap.Clear();
825 this.RequestSelection(ViewSwitcherSelectionChangeSource.Index,
this.SelectedIndex,
null,
null);
828 private void OnLoaded(
object? sender, EventArgs e)
830 if (this.isInitialLoad)
832 this.RequestSelection(ViewSwitcherSelectionChangeSource.Index,
this.SelectedIndex,
null,
null);
836 private void OnUnloaded(
object? sender, EventArgs e)
846 View? currentView = this.transitionCoordinator.CurrentView;
847 return this.InvokeLifecycleAppearingAsync(currentView, cancellationToken);
856 View? currentView = this.transitionCoordinator.CurrentView;
857 return this.InvokeLifecycleDisappearingAsync(currentView, cancellationToken);
860 private bool CanExecuteNextCommand()
862 if (this.descriptors.Count == 0)
868 return this.selectionState.SelectedIndex < this.descriptors.Count - 1;
871 private bool CanExecutePreviousCommand()
873 if (this.descriptors.Count == 0)
879 return this.selectionState.SelectedIndex > 0;
882 private void ExecuteNextCommand()
884 int currentIndex = this.selectionState.SelectedIndex;
885 int targetIndex = currentIndex >= 0 ? currentIndex + 1 : 0;
886 this.ObserveSelectionTask(this.SwitchToAsync(targetIndex));
889 private void ExecutePreviousCommand()
891 int currentIndex = this.selectionState.SelectedIndex;
892 int targetIndex = currentIndex > 0 ? currentIndex - 1 : -1;
893 this.ObserveSelectionTask(this.SwitchToAsync(targetIndex));
896 private void UpdateNavigationCommands()
898 this.nextCommand.ChangeCanExecute();
899 this.previousCommand.ChangeCanExecute();
902 private void UpdateAutomationSemantics(
object? item,
string? stateKey)
904 string?
template = this.AutomationDescriptionTemplate;
905 if (
string.IsNullOrWhiteSpace(
template))
907 SemanticProperties.SetDescription(
this,
null);
911 string placeholder = stateKey ?? item?.ToString() ??
string.Empty;
913 if (
template.Contains(
"{0}", StringComparison.Ordinal))
915 description =
template.Replace(
"{0}", placeholder, StringComparison.Ordinal);
919 description =
string.Format(CultureInfo.CurrentCulture,
template, placeholder);
922 SemanticProperties.SetDescription(
this, description);
925 private async Task EnsureLifecycleInitializedAsync(View? view, CancellationToken token)
930 await this.EnsureLifecycleInitializedCoreAsync(view, token).ConfigureAwait(
false);
931 object? bindingContext = view.BindingContext;
932 if (bindingContext is not
null && !ReferenceEquals(bindingContext, view))
934 await this.EnsureLifecycleInitializedCoreAsync(bindingContext, token).ConfigureAwait(
false);
938 private async Task EnsureLifecycleInitializedCoreAsync(
object target, CancellationToken token)
943 if (this.initializedLifecycleTargets.Contains(target))
946 await
PolicyRunner.RunAsync(
_ => lifecycleView.OnInitializeAsync(), token).ConfigureAwait(
false);
947 this.initializedLifecycleTargets.Add(target);
950 private async Task InvokeLifecycleAppearingAsync(View? view, CancellationToken token)
955 await this.InvokeLifecycleAppearingCoreAsync(view, token).ConfigureAwait(
false);
956 object? bindingContext = view.BindingContext;
957 if (bindingContext is not
null && !ReferenceEquals(bindingContext, view))
959 await this.InvokeLifecycleAppearingCoreAsync(bindingContext, token).ConfigureAwait(
false);
963 private Task InvokeLifecycleAppearingCoreAsync(
object target, CancellationToken token)
966 return Task.CompletedTask;
968 return PolicyRunner.RunAsync(
_ => lifecycleView.OnAppearingAsync(), token);
971 private async Task InvokeLifecycleDisappearingAsync(View? view, CancellationToken token)
976 await this.InvokeLifecycleDisappearingCoreAsync(view, token).ConfigureAwait(
false);
977 object? bindingContext = view.BindingContext;
978 if (bindingContext is not
null && !ReferenceEquals(bindingContext, view))
980 await this.InvokeLifecycleDisappearingCoreAsync(bindingContext, token).ConfigureAwait(
false);
984 private Task InvokeLifecycleDisappearingCoreAsync(
object target, CancellationToken token)
987 return Task.CompletedTask;
989 return PolicyRunner.RunAsync(
_ => lifecycleView.OnDisappearingAsync(), token);
992 private async Task InvokeLifecycleDisposeAsync(View? view)
997 await this.InvokeLifecycleDisposeCoreAsync(view).ConfigureAwait(
false);
998 object? bindingContext = view.BindingContext;
999 if (bindingContext is not
null && !ReferenceEquals(bindingContext, view))
1001 await this.InvokeLifecycleDisposeCoreAsync(bindingContext).ConfigureAwait(
false);
1005 private async Task InvokeLifecycleDisposeCoreAsync(
object target)
1010 await
PolicyRunner.RunAsync(
_ => lifecycleView.OnDisposeAsync(), CancellationToken.None).ConfigureAwait(
false);
1011 this.initializedLifecycleTargets.Remove(target);
1014 private async Task DisposeLifecycleIfRequiredAsync(View? oldView, View? newView)
1016 if (oldView is
null)
1019 if (ReferenceEquals(oldView, newView))
1022 if (!this.ShouldDisposeView(oldView))
1025 await this.InvokeLifecycleDisposeAsync(oldView).ConfigureAwait(
false);
1026 this.viewDescriptorMap.Remove(oldView);
1029 private bool ShouldDisposeView(View view)
1031 if (this.viewCache.IsEnabled)
1034 if (this.viewDescriptorMap.TryGetValue(view, out ViewSwitcherItemDescriptor descriptor))
1036 if (descriptor.InlineView is not
null && ReferenceEquals(descriptor.InlineView, view) &&
this.views.Contains(view))
1039 if (descriptor.StateView is not
null && ReferenceEquals(descriptor.StateView.Content, view))
1046 private void LogException(Exception exception)
1052 catch (Exception ex)
1054 Debug.WriteLine(ex);
1058 public void Dispose()
1061 GC.SuppressFinalize(
this);
1064 protected virtual void Dispose(
bool disposing)
1071 this.Loaded -= this.OnLoaded;
1072 this.Unloaded -= this.OnUnloaded;
1074 this.views.CollectionChanged -= this.OnViewsCollectionChanged;
1075 this.stateViews.CollectionChanged -= this.OnStateViewsCollectionChanged;
1077 if (this.itemsSourceNotifier is not
null)
1079 this.itemsSourceNotifier.CollectionChanged -= this.OnItemsSourceCollectionChanged;
1080 this.itemsSourceNotifier =
null;
1083 this.transitionCoordinator.Dispose();
1084 this.selectionOperation.Dispose();
1085 this.viewDescriptorMap.Clear();
1086 this.initializedLifecycleTargets.Clear();
1089 this.disposed =
true;
Default implementation of IAnimationCoordinator.
Executes async operations through a pipeline of policies (timeout, retry, bulkhead,...
Base class that references services in the app.
static ILogService LogService
Log service.
Task NotifyHostAppearingAsync(CancellationToken cancellationToken=default)
Notifies the view switcher that the hosting page is appearing. Triggers lifecycle callbacks on the cu...
Task NotifyHostDisappearingAsync(CancellationToken cancellationToken=default)
Notifies the view switcher that the hosting page is disappearing. Triggers lifecycle callbacks on the...
Provides data for the ViewSwitcher.SelectionChanged event.
Provides data for the ViewSwitcher.SelectionChanging event.
bool Cancel
Gets or sets a value indicating whether the selection change should be canceled.
Provides a data-binding friendly mechanism to manage and report the status of asynchronous operations...
Coordinates animation execution across the application.
Defines a strategy used by ViewSwitcher to animate between two views.
Interface for views who need to react to life-cycle events.
ViewSwitcherSelectedIndexBehavior
Determines how ViewSwitcher handles attempts to set a selection outside the available range.