From d3e8cd537f9089889e2ee6dfe866c2ee974dcfe6 Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Mon, 1 Aug 2022 15:05:02 +0600 Subject: [PATCH 01/28] cleaned NavigationBase --- src/Packages.props | 1 + src/Wpf.Ui.Demo/Views/Container.xaml.cs | 10 +- .../Views/Pages/ExperimentalDashboard.xaml.cs | 2 +- .../Views/Windows/ExperimentalWindow.xaml.cs | 11 +- src/Wpf.Ui/Controls/Interfaces/INavigation.cs | 117 +----- .../Navigation/NavigationBackButton.cs | 2 +- .../Controls/Navigation/NavigationBase.cs | 397 +++--------------- src/Wpf.Ui/Controls/NavigationItem.cs | 2 +- .../Mvvm/Contracts/INavigationService.cs | 27 +- .../Mvvm/Contracts/INavigationWindow.cs | 21 +- src/Wpf.Ui/Mvvm/Services/NavigationService.cs | 57 ++- src/Wpf.Ui/Services/Internal/FrameManager.cs | 74 ++++ .../Services/Internal/NavigationManager.cs | 24 ++ src/Wpf.Ui/Wpf.Ui.csproj | 4 + 14 files changed, 236 insertions(+), 513 deletions(-) create mode 100644 src/Wpf.Ui/Services/Internal/FrameManager.cs create mode 100644 src/Wpf.Ui/Services/Internal/NavigationManager.cs diff --git a/src/Packages.props b/src/Packages.props index f2ca092b9..d3ada50a2 100644 --- a/src/Packages.props +++ b/src/Packages.props @@ -7,5 +7,6 @@ + \ No newline at end of file diff --git a/src/Wpf.Ui.Demo/Views/Container.xaml.cs b/src/Wpf.Ui.Demo/Views/Container.xaml.cs index e9e158b98..4b9c6f646 100644 --- a/src/Wpf.Ui.Demo/Views/Container.xaml.cs +++ b/src/Wpf.Ui.Demo/Views/Container.xaml.cs @@ -96,8 +96,11 @@ public Frame GetFrame() public INavigation GetNavigation() => RootNavigation; - public bool Navigate(Type pageType) - => RootNavigation.Navigate(pageType); + public void NavigateTo(Type type, object dataContext = null) + => RootNavigation.NavigateTo(type, dataContext); + + public void NavigateTo(string pageTag, object dataContext = null) + => RootNavigation.NavigateTo(pageTag, dataContext); public void SetPageService(IPageService pageService) => RootNavigation.PageService = pageService; @@ -108,6 +111,7 @@ public void ShowWindow() public void CloseWindow() => Close(); + #endregion INavigationWindow methods private void InvokeSplashScreen() @@ -133,7 +137,7 @@ await Dispatcher.InvokeAsync(() => RootWelcomeGrid.Visibility = Visibility.Hidden; RootMainGrid.Visibility = Visibility.Visible; - Navigate(typeof(Pages.Dashboard)); + NavigateTo(typeof(Pages.Dashboard)); _taskBarService.SetState(this, TaskBarProgressState.None); }); diff --git a/src/Wpf.Ui.Demo/Views/Pages/ExperimentalDashboard.xaml.cs b/src/Wpf.Ui.Demo/Views/Pages/ExperimentalDashboard.xaml.cs index e1fd83569..a8ece4934 100644 --- a/src/Wpf.Ui.Demo/Views/Pages/ExperimentalDashboard.xaml.cs +++ b/src/Wpf.Ui.Demo/Views/Pages/ExperimentalDashboard.xaml.cs @@ -58,7 +58,7 @@ private void ButtonExternal_OnClick(object sender, RoutedEventArgs e) if (DataContext is not ExperimentalViewModel viewData) return; - viewData.ParentWindow.Navigate(typeof(ExperimentalDashboard)); + viewData.ParentWindow.NavigateTo(typeof(ExperimentalDashboard)); } private void TaskbarStateComboBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) diff --git a/src/Wpf.Ui.Demo/Views/Windows/ExperimentalWindow.xaml.cs b/src/Wpf.Ui.Demo/Views/Windows/ExperimentalWindow.xaml.cs index 9e6b6d692..5f5f58364 100644 --- a/src/Wpf.Ui.Demo/Views/Windows/ExperimentalWindow.xaml.cs +++ b/src/Wpf.Ui.Demo/Views/Windows/ExperimentalWindow.xaml.cs @@ -101,7 +101,7 @@ public ExperimentalWindow(ExperimentalViewModel viewModel, IThemeService themeSe private void RootNavigationOnLoaded(object sender, RoutedEventArgs e) { - RootNavigation.Navigate(0, DataContext); + //RootNavigation.NavigateTo(0, DataContext); } private void NavigationButtonTheme_OnClick(object sender, RoutedEventArgs e) @@ -130,9 +130,6 @@ public Frame GetFrame() public INavigation GetNavigation() => RootNavigation; - public bool Navigate(Type pageType) - => RootNavigation.Navigate(pageType); - public void SetPageService(IPageService pageService) => RootNavigation.PageService = pageService; @@ -141,4 +138,10 @@ public void ShowWindow() public void CloseWindow() => Close(); + + public void NavigateTo(Type type, object dataContext = null) + => RootNavigation.NavigateTo(type, dataContext); + + public void NavigateTo(string pageTag, object dataContext = null) + => RootNavigation.NavigateTo(pageTag, dataContext); } diff --git a/src/Wpf.Ui/Controls/Interfaces/INavigation.cs b/src/Wpf.Ui/Controls/Interfaces/INavigation.cs index 3e2592e12..a8de97f0c 100644 --- a/src/Wpf.Ui/Controls/Interfaces/INavigation.cs +++ b/src/Wpf.Ui/Controls/Interfaces/INavigation.cs @@ -3,6 +3,7 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. +#nullable enable using System; using System.Collections.ObjectModel; using System.ComponentModel; @@ -29,11 +30,6 @@ public interface INavigation /// int SelectedPageIndex { get; set; } - /// - /// Navigation item ID of the previous page. - /// - int PreviousPageIndex { get; } - /// /// Creates an instance of all pages defined with after the is loaded. /// @@ -102,117 +98,22 @@ public interface INavigation [Bindable(true), Category("Appearance")] TransitionType TransitionType { get; set; } - /// - /// Clears all navigation items. - /// - void Flush(); - /// /// Clears all initialized instances of the pages. /// void ClearCache(); /// - /// Navigates to the previous page using the . - /// - /// - bool NavigateBack(); - - /// - /// Navigates to the page using the . - /// - /// Type of the page to navigate. - /// if the operation was successful. - bool Navigate(Type pageType); - - /// - /// Navigates to the page using the . - /// - /// Type of the page to navigate. - /// When an DataContext changes, all data-bound properties (on this element or any other element) whose Bindings use this DataContext will change to reflect the new value. - /// if the operation was successful. - bool Navigate(Type pageType, object dataContext); - - /// - /// Loads a instance into based on the tag of . - /// - /// ID of the page to be loaded. - /// if the operation was successful. - bool Navigate(int pageIndex); - - /// - /// Loads a instance into based on the tag of . - /// - /// ID of the page to be loaded. - /// When an DataContext changes, all data-bound properties (on this element or any other element) whose Bindings use this DataContext will change to reflect the new value. - /// if the operation was successful. - bool Navigate(int pageIndex, object dataContext); - - /// - /// Loads a instance into based on the tag of . - /// - /// to be loaded. - /// if the operation was successful. - bool Navigate(string pageTag); - - /// - /// Loads a instance into based on the tag of . - /// - /// to be loaded. - /// When an DataContext changes, all data-bound properties (on this element or any other element) whose Bindings use this DataContext will change to reflect the new value. - bool Navigate(string pageTag, object dataContext); - - /// - /// Navigate to the given object that is outside the current navigation. - /// - /// The element you want to navigate to a that is not in the or pool. - /// if the operation was successful. - bool NavigateExternal(object frameworkElement); - - /// - /// Navigate to the given object that is outside the current navigation. - /// - /// The element you want to navigate to a that is not in the or pool. - /// Context of the data for data binding. - /// if the operation was successful. - bool NavigateExternal(object frameworkElement, object dataContext); - - /// - /// Navigate to the given that is outside the current navigation. - /// - /// to the element you want to navigate to a that is not in the or pool. - /// if the operation was successful. - bool NavigateExternal(Uri absolutePageUri); - - /// - /// Navigate to the given that is outside the current navigation. - /// - /// to the element you want to navigate to a that is not in the or pool. - /// Context of the data for data binding. - /// if the operation was successful. - bool NavigateExternal(Uri absolutePageUri, object dataContext); - - /// - /// Sets of the page. - /// If the page is not in the Cache, and is defined based on , its object will be created and then its DataContext will be defined. - /// - /// Id of the page from or . - /// Context of the data for data binding. - /// if the operation was successful. - bool SetContext(int pageId, object dataContext); - - /// - /// Sets of the page. - /// If the page is not in the Cache, and is defined based on , its object will be created and then its DataContext will be defined. + /// TODO /// - /// Tag of the page from or . - /// Context of the data for data binding. - /// if the operation was successful. - bool SetContext(string pageTag, object dataContext); + /// + /// + void NavigateTo(string pageTag, object? dataContext = null); /// - /// Tires to set the DataContext for the currently displayed page. + /// TODO /// - /// Data context to be set. - void SetCurrentContext(object dataContext); + /// + /// + void NavigateTo(Type type, object? dataContext = null); } diff --git a/src/Wpf.Ui/Controls/Navigation/NavigationBackButton.cs b/src/Wpf.Ui/Controls/Navigation/NavigationBackButton.cs index d1ffefb16..40f0b4d88 100644 --- a/src/Wpf.Ui/Controls/Navigation/NavigationBackButton.cs +++ b/src/Wpf.Ui/Controls/Navigation/NavigationBackButton.cs @@ -34,6 +34,6 @@ public INavigation? Navigation public NavigationBackButton() { - SetValue(CommandProperty, new Common.RelayCommand(_ => Navigation?.NavigateBack(), () => Navigation is not null && Navigation.CanGoBack)); + SetValue(CommandProperty, new Common.RelayCommand(_ => Navigation?.NavigateTo(".."), () => Navigation is not null && Navigation.CanGoBack)); } } diff --git a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs index bc8ea838c..c9b69d3eb 100644 --- a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs +++ b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs @@ -4,19 +4,19 @@ // All Rights Reserved. #nullable enable -#pragma warning disable CS8600 -#pragma warning disable CS8603 using System; +using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Collections.Specialized; +using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Input; +using Microsoft.Toolkit.Diagnostics; using Wpf.Ui.Common; using Wpf.Ui.Controls.Interfaces; using Wpf.Ui.Mvvm.Contracts; -using Wpf.Ui.Mvvm.Interfaces; +using Wpf.Ui.Services.Internal; namespace Wpf.Ui.Controls.Navigation; @@ -25,24 +25,24 @@ namespace Wpf.Ui.Controls.Navigation; /// public abstract class NavigationBase : System.Windows.Controls.Control, INavigation { - /// - /// Service used for navigation purposes. - /// - private readonly Services.Internal.NavigationService? _navigationService; + private FrameManager _frameManager = null!; + private readonly NavigationManager _navigationManager; + + #region DependencyProperties /// /// Property for . /// public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(nameof(Items), typeof(ObservableCollection), typeof(NavigationBase), - new PropertyMetadata((ObservableCollection)null, OnItemsChanged)); + new PropertyMetadata(new ObservableCollection())); /// /// Property for . /// public static readonly DependencyProperty FooterProperty = DependencyProperty.Register(nameof(Footer), typeof(ObservableCollection), typeof(NavigationBase), - new PropertyMetadata((ObservableCollection)null, OnFooterChanged)); + new PropertyMetadata(new ObservableCollection())); /// /// Property for . @@ -57,7 +57,7 @@ public abstract class NavigationBase : System.Windows.Controls.Control, INavigat /// public static readonly DependencyProperty FrameProperty = DependencyProperty.Register(nameof(Frame), typeof(Frame), typeof(NavigationBase), - new PropertyMetadata((Frame)null, OnFrameChanged)); + new PropertyMetadata()); /// /// Property for . @@ -98,17 +98,21 @@ public abstract class NavigationBase : System.Windows.Controls.Control, INavigat nameof(NavigationParent), typeof(INavigation), typeof(NavigationBase), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits)); + #endregion + + #region Properties + /// public ObservableCollection Items { - get => GetValue(ItemsProperty) as ObservableCollection; + get => (GetValue(ItemsProperty) as ObservableCollection)!; set => SetValue(ItemsProperty, value); } /// public ObservableCollection Footer { - get => GetValue(FooterProperty) as ObservableCollection; + get => (GetValue(FooterProperty) as ObservableCollection)!; set => SetValue(FooterProperty, value); } @@ -135,9 +139,9 @@ public Animations.TransitionType TransitionType } /// - public Frame? Frame + public Frame Frame { - get => GetValue(FrameProperty) as Frame; + get => (GetValue(FrameProperty) as Frame)!; set => SetValue(FrameProperty, value); } @@ -161,6 +165,8 @@ internal INavigation NavigationParent private set => SetValue(NavigationParentProperty, value); } + #endregion + #region Events /// @@ -207,17 +213,10 @@ public event RoutedNavigationEvent NavigatedBackward #endregion /// - public IPageService? PageService - { - get => _navigationService?.GetService(); - set => _navigationService?.SetService(value); - } + public IPageService? PageService { get; set; } /// - public int PreviousPageIndex => _navigationService?.GetPreviousId() ?? 0; - - /// - public bool CanGoBack => _navigationService is not null && _navigationService.CanGoBack; + public bool CanGoBack { get; private set; } /// public INavigationItem? Current { get; internal set; } @@ -241,18 +240,7 @@ static NavigationBase() /// protected NavigationBase() { - Current = (INavigationItem)null; - - // Prepare individual collections for this navigation - Items ??= new ObservableCollection(); - Footer ??= new ObservableCollection(); - - _navigationService = new Wpf.Ui.Services.Internal.NavigationService(); - _navigationService.TransitionDuration = TransitionDuration; - _navigationService.TransitionType = TransitionType; - - if (Frame != null) - _navigationService.SetFrame(Frame); + _navigationManager = new NavigationManager(); // Let the NavigationItem children be able to get me. NavigationParent = this; @@ -261,204 +249,40 @@ protected NavigationBase() Loaded += OnLoaded; } - public bool NavigateBack() - { - if (_navigationService is null) return false; - - if (!_navigationService.NavigateBack()) - return false; - - NavigateInternal(0, true); - - return true; - } - /// - public bool Navigate(Type pageType) - { - return Navigate(pageType, null); - } - - /// - public bool Navigate(Type pageType, object? dataContext) - { - if (!_navigationService.Navigate(pageType, dataContext)) - return false; - - NavigateInternal(0, true); - - return true; - } - - /// - public bool Navigate(string pageTag) - { - return Navigate(pageTag, null); - } - - /// - public bool Navigate(string pageTag, object? dataContext) - { - if (!_navigationService.Navigate(pageTag, dataContext)) - return false; - - NavigateInternal(0, true); - - return true; - } - - - /// - public bool Navigate(int pageId) - { - return Navigate(pageId, null); - } - - /// - public bool Navigate(int pageId, object? dataContext) - { - if (_navigationService != null) - if (!_navigationService.Navigate(pageId, dataContext)) - return false; - - NavigateInternal(-1, true); - - return true; - } - - /// - public bool NavigateExternal(object frameworkElement) - { - return NavigateExternal(frameworkElement, null); - } - - /// - public bool NavigateExternal(object frameworkElement, object? dataContext) - { - if (_navigationService != null) - if (!_navigationService.NavigateExternal(frameworkElement, dataContext)) - return false; - - NavigateInternal(-1, true); - - return true; - } - - /// - public bool NavigateExternal(Uri absolutePageUri) - { - return NavigateExternal(absolutePageUri, null); - } - - /// - public bool NavigateExternal(Uri absolutePageUri, object? dataContext) - { - if (_navigationService != null) - if (!_navigationService.NavigateExternal(absolutePageUri, dataContext)) - return false; - - NavigateInternal(-1, false); - - return true; - } - - /// - public void SetCurrentContext(object dataContext) - { - if (Frame?.Content is not FrameworkElement) - return; - - ((FrameworkElement)Frame.Content).DataContext = dataContext; - - if (dataContext is IViewModel) - ((IViewModel)dataContext).OnMounted(((FrameworkElement)Frame.Content)); - } - - /// - public bool SetContext(string pageTag, object dataContext) - { - if (_navigationService == null) - return false; - - return _navigationService.SetContext(pageTag, dataContext); - } - - /// - public bool SetContext(int pageId, object dataContext) + public void ClearCache() { - if (_navigationService == null) - return false; - - return _navigationService.SetContext(pageId, dataContext); + } /// - public void Flush() + public void NavigateTo(string pageTag, object? dataContext = null) { - Items.Clear(); - Footer.Clear(); - - Current = (INavigationItem)null; + } /// - public void ClearCache() + public void NavigateTo(Type type, object? dataContext = null) { - if (_navigationService == null) - return; - - _navigationService.ClearCache(); + } /// - /// Updates property and modifies Active attribute of navigation items. + /// This virtual method is called when is loaded. /// - private void UpdateItems() + protected virtual void OnLoaded(object sender, RoutedEventArgs e) { - var currentTag = _navigationService?.GetCurrentTag() ?? String.Empty; + if (DesignerProperties.GetIsInDesignMode(this)) + return; - foreach (var singleNavigationControl in Items) - { - if (singleNavigationControl is not INavigationItem) - continue; - - if (((INavigationItem)singleNavigationControl).PageTag == currentTag) - { - ((INavigationItem)singleNavigationControl).IsActive = true; - Current = (INavigationItem)singleNavigationControl; - } - else - { - ((INavigationItem)singleNavigationControl).IsActive = false; - } - } + Guard.IsNotNull(Frame, nameof(Frame)); - foreach (var singleNavigationControl in Footer) - { - if (singleNavigationControl is not INavigationItem) - continue; - - if (((INavigationItem)singleNavigationControl).PageTag == currentTag) - { - ((INavigationItem)singleNavigationControl).IsActive = true; - Current = (INavigationItem)singleNavigationControl; - } - else - { - ((INavigationItem)singleNavigationControl).IsActive = false; - } - } - } + _frameManager = new FrameManager(Frame, TransitionDuration, TransitionType); - /// - /// This virtual method is called when is loaded. - /// - protected virtual void OnLoaded(object sender, RoutedEventArgs e) - { - UpdateServiceItems(); + if (SelectedPageIndex > -1) + _navigationManager.NavigateToById(SelectedPageIndex); - if (PageService == null && Frame != null && SelectedPageIndex > -1) - Navigate(SelectedPageIndex); + InitializeServiceItems(); // If we are using the MVVM model, do not use the cache. if (Precache) @@ -554,91 +378,7 @@ protected virtual void OnNavigationItemClicked(object sender, RoutedEventArgs e) if (navigationItem.AbsolutePageSource == null && navigationItem.PageType == null) return; - if (PageService == null) - { - Navigate(navigationItem.PageTag); - - return; - } - - if (navigationItem.PageType == null) - throw new InvalidOperationException("When navigating through the IPageService, the navigated page type must be defined the INavigationItem.PageType."); - - Navigate(navigationItem.PageType); - } - - /// - /// This virtual method is called when something is added, deleted or changed in or . - /// - protected virtual void OnNavigationCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) - { - if (IsLoaded) - UpdateServiceItems(); - - if (e.NewItems != null) - foreach (var addedItem in e.NewItems) - if (addedItem is INavigationItem) - { - ((INavigationItem)addedItem).Click -= OnNavigationItemClicked; // Unsafe - Remove duplicates - ((INavigationItem)addedItem).Click += OnNavigationItemClicked; - } - - if (e.OldItems == null) - return; - - foreach (var deletedItem in e.OldItems) - ((INavigationItem)deletedItem).Click -= OnNavigationItemClicked; - } - - /// - /// Triggered when is changed. - /// - private static void OnItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is not NavigationBase navigationBase) - return; - - navigationBase.InitializeServiceItems(); - - if (e.NewValue is not ObservableCollection itemsCollection) - return; - - itemsCollection.CollectionChanged += navigationBase.OnNavigationCollectionChanged; - } - - /// - /// Triggered when is changed. - /// - private static void OnFooterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is not NavigationBase navigationBase) - return; - - navigationBase.InitializeServiceItems(); - - if (e.NewValue is not ObservableCollection itemsCollection) - return; - - itemsCollection.CollectionChanged += navigationBase.OnNavigationCollectionChanged; - } - - /// - /// This virtual method is called when one of the navigation items is clicked. - /// - protected virtual void OnFrameChanged(Frame frame) - { - _navigationService?.SetFrame(frame); - } - - /// - /// Triggered when is changed. - /// - private static void OnFrameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is not NavigationBase navigationBase || e.NewValue is not Frame frame) - return; - - navigationBase.OnFrameChanged(frame); + NavigateTo(navigationItem.PageTag); } private static void OnTransitionDurationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) @@ -646,10 +386,7 @@ private static void OnTransitionDurationChanged(DependencyObject d, DependencyPr if (d is not NavigationBase navigation) return; - if (navigation._navigationService == null) - return; - - navigation._navigationService.TransitionDuration = (int)e.NewValue; + //navigation._navigationService.TransitionDuration = (int)e.NewValue; } private static void OnTransitionTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) @@ -657,10 +394,7 @@ private static void OnTransitionTypeChanged(DependencyObject d, DependencyProper if (d is not NavigationBase navigation) return; - if (navigation._navigationService == null) - return; - - navigation._navigationService.TransitionType = (Animations.TransitionType)e.NewValue; + //navigation._navigationService.TransitionType = (Animations.TransitionType)e.NewValue; } /// @@ -676,47 +410,18 @@ private static void OnTransitionTypeChanged(DependencyObject d, DependencyProper private void InitializeServiceItems() { - var navigationItems = GetValue(ItemsProperty) as ObservableCollection ?? new ObservableCollection { }; - var navigationFooter = GetValue(FooterProperty) as ObservableCollection ?? new ObservableCollection { }; - - foreach (var addedItem in navigationItems) - if (addedItem is INavigationItem) - { - ((INavigationItem)addedItem).Click -= OnNavigationItemClicked; // Unsafe - Remove duplicates - ((INavigationItem)addedItem).Click += OnNavigationItemClicked; - } - - foreach (var addedItem in navigationFooter) - if (addedItem is INavigationItem) - { - ((INavigationItem)addedItem).Click -= OnNavigationItemClicked; // Unsafe - Remove duplicates - ((INavigationItem)addedItem).Click += OnNavigationItemClicked; - } - - UpdateServiceItems(); - } - - private void UpdateServiceItems() - { - var navigationItems = GetValue(ItemsProperty) as ObservableCollection ?? new ObservableCollection { }; - var navigationFooter = GetValue(FooterProperty) as ObservableCollection ?? new ObservableCollection { }; - - if (_navigationService != null) - _navigationService.UpdateItems(navigationItems, navigationFooter); + SubscribeToClickEventForEnumerable(Items); + SubscribeToClickEventForEnumerable(Footer); } - private void NavigateInternal(int arg, bool updateItems) + private void SubscribeToClickEventForEnumerable(IEnumerable enumerable) { - SelectedPageIndex = _navigationService?.GetCurrentId() ?? +arg; - - if (updateItems) - UpdateItems(); - - OnNavigated(); + foreach (var addedItem in enumerable) + { + if (addedItem is not INavigationItem item) continue; - if (SelectedPageIndex > (_navigationService?.GetPreviousId() ?? +arg)) - OnNavigatedForward(); - else - OnNavigatedBackward(); + item.Click -= OnNavigationItemClicked; // Unsafe - Remove duplicates + item.Click += OnNavigationItemClicked; + } } } diff --git a/src/Wpf.Ui/Controls/NavigationItem.cs b/src/Wpf.Ui/Controls/NavigationItem.cs index 36fc11fb3..496a57364 100644 --- a/src/Wpf.Ui/Controls/NavigationItem.cs +++ b/src/Wpf.Ui/Controls/NavigationItem.cs @@ -293,7 +293,7 @@ protected override void OnKeyDown(KeyEventArgs e) { e.Handled = true; - navigation.Navigate(pageTag); + navigation.NavigateTo(pageTag); } break; } diff --git a/src/Wpf.Ui/Mvvm/Contracts/INavigationService.cs b/src/Wpf.Ui/Mvvm/Contracts/INavigationService.cs index 1914de8c0..bbf3feca5 100644 --- a/src/Wpf.Ui/Mvvm/Contracts/INavigationService.cs +++ b/src/Wpf.Ui/Mvvm/Contracts/INavigationService.cs @@ -15,6 +15,14 @@ namespace Wpf.Ui.Mvvm.Contracts; /// public interface INavigationService { + + /// + /// TODO + /// + /// + /// + void Initialize(INavigation navigation, IPageService pageService); + /// /// Provides direct access to the used in navigation. /// @@ -38,7 +46,6 @@ public interface INavigationService /// /// Instance of the . void SetNavigationControl(INavigation navigation); - /// /// Lets you attach the service that delivers page instances to . /// @@ -46,20 +53,14 @@ public interface INavigationService void SetPageService(IPageService pageService); /// - /// Lets you navigate to the selected page based on it's type. Should be used with . - /// - /// of the page. - bool Navigate(Type pageType); - - /// - /// Lets you navigate to the selected page based on it's id. Should be used with . + /// Lets you navigate to the selected page based on it's tag. Should be used with . /// - /// Id of the page. - bool Navigate(int pageId); + /// Tag of the page. + void Navigate(string pageTag); /// - /// Lets you navigate to the selected page based on it's tag. Should be used with . + /// TODO /// - /// Tag of the page. - bool Navigate(string pageTag); + /// + void Navigate(Type type); } diff --git a/src/Wpf.Ui/Mvvm/Contracts/INavigationWindow.cs b/src/Wpf.Ui/Mvvm/Contracts/INavigationWindow.cs index 6d46035a2..62515ebe4 100644 --- a/src/Wpf.Ui/Mvvm/Contracts/INavigationWindow.cs +++ b/src/Wpf.Ui/Mvvm/Contracts/INavigationWindow.cs @@ -3,6 +3,7 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. +#nullable enable using System; using System.Windows.Controls; using Wpf.Ui.Controls.Interfaces; @@ -27,12 +28,6 @@ public interface INavigationWindow /// Instance of the control. INavigation GetNavigation(); - /// - /// Lets you navigate to the selected page based on it's type. Should be used with . - /// - /// of the page. - bool Navigate(Type pageType); - /// /// Lets you attach the service that delivers page instances to . /// @@ -48,4 +43,18 @@ public interface INavigationWindow /// Triggers the command to close a window. /// void CloseWindow(); + + /// + /// TODO + /// + /// + /// + void NavigateTo(string pageTag, object? dataContext = null); + + /// + /// TODO + /// + /// + /// + void NavigateTo(Type type, object? dataContext = null); } diff --git a/src/Wpf.Ui/Mvvm/Services/NavigationService.cs b/src/Wpf.Ui/Mvvm/Services/NavigationService.cs index 4818c102d..92ea1ee0f 100644 --- a/src/Wpf.Ui/Mvvm/Services/NavigationService.cs +++ b/src/Wpf.Ui/Mvvm/Services/NavigationService.cs @@ -3,8 +3,10 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. +#nullable enable using System; using System.Windows.Controls; +using Microsoft.Toolkit.Diagnostics; using Wpf.Ui.Controls.Interfaces; using Wpf.Ui.Mvvm.Contracts; @@ -15,28 +17,41 @@ namespace Wpf.Ui.Mvvm.Services; /// public partial class NavigationService : INavigationService { + private INavigation? _navigationControl; + /// /// Locally attached page service. /// - private IPageService _pageService; + private IPageService? _pageService; /// /// Control representing navigation. /// - protected INavigation NavigationControl; + private INavigation NavigationControl + { + get + { + Guard.IsNotNull(_navigationControl, nameof(NavigationControl)); + return _navigationControl; + } + set => _navigationControl = value; + } + + public void Initialize(INavigation navigation, IPageService pageService) + { + NavigationControl = navigation; + NavigationControl.PageService = pageService; + } /// public Frame GetFrame() { - return NavigationControl?.Frame; + return NavigationControl.Frame; } /// public void SetFrame(Frame frame) { - if (NavigationControl == null) - return; - NavigationControl.Frame = frame; } @@ -46,22 +61,19 @@ public INavigation GetNavigationControl() return NavigationControl; } - /// public void SetNavigationControl(INavigation navigation) { NavigationControl = navigation; - if (_pageService != null) + if (_pageService is not null) NavigationControl.PageService = _pageService; } - /// public void SetPageService(IPageService pageService) { - if (NavigationControl == null) + if (_navigationControl is null) { _pageService = pageService; - return; } @@ -69,29 +81,14 @@ public void SetPageService(IPageService pageService) } /// - public bool Navigate(Type pageType) - { - if (NavigationControl == null) - return false; - - return NavigationControl.Navigate(pageType); - } - - /// - public bool Navigate(int pageId) + public void Navigate(string pageTag) { - if (NavigationControl == null) - return false; - - return NavigationControl.Navigate(pageId); + NavigationControl.NavigateTo(pageTag); } /// - public bool Navigate(string pageTag) + public void Navigate(Type type) { - if (NavigationControl == null) - return false; - - return NavigationControl.Navigate(pageTag); + NavigationControl.NavigateTo(type); } } diff --git a/src/Wpf.Ui/Services/Internal/FrameManager.cs b/src/Wpf.Ui/Services/Internal/FrameManager.cs new file mode 100644 index 000000000..596dd60ca --- /dev/null +++ b/src/Wpf.Ui/Services/Internal/FrameManager.cs @@ -0,0 +1,74 @@ +#nullable enable +using System.Windows; +using System.Windows.Controls; +using System.Windows.Navigation; +using Wpf.Ui.Animations; +using Wpf.Ui.Common.Interfaces; + +namespace Wpf.Ui.Services.Internal; + +internal sealed class FrameManager +{ + private readonly Frame _frame; + private readonly int _transitionDuration; + private readonly TransitionType _transitionType; + + public FrameManager(Frame frame, int transitionDuration, TransitionType transitionType) + { + _frame = frame; + _transitionDuration = transitionDuration; + _transitionType = transitionType; + + _frame.NavigationUIVisibility = NavigationUIVisibility.Hidden; + + _frame.Navigating += OnFrameNavigating; + _frame.Navigated += OnFrameNavigated; + } + + + private void OnFrameNavigating(object sender, NavigatingCancelEventArgs e) + { + NotifyFrameContentAboutLeave(); + } + + private void OnFrameNavigated(object sender, NavigationEventArgs e) + { + _frame.NavigationService.RemoveBackEntry(); + + if (_transitionDuration > 0 && e.Content != null) + Transitions.ApplyTransition(e.Content, _transitionType, _transitionDuration); + + //TODO + /*// Finally, the navigation took place internally, + // the context was set from extra data, the cache has to be saved, + // so we save it, notify it and this is the end of the method + _navigationServiceItems[extraData.PageId].Instance = _frame.Content;*/ + + NotifyFrameContentAboutEnter(); + } + + private void NotifyFrameContentAboutEnter() + { + var navigationAware = GetINavigationAwareOrDefault(); + navigationAware?.OnNavigatedTo(); + } + + private void NotifyFrameContentAboutLeave() + { + var navigationAware = GetINavigationAwareOrDefault(); + navigationAware?.OnNavigatedFrom(); + } + + private INavigationAware? GetINavigationAwareOrDefault() + { + INavigationAware? navigationAware = _frame.Content switch + { + INavigationAware aware => aware, + INavigableView {ViewModel: INavigationAware viewModelNavigationAware} => viewModelNavigationAware, + FrameworkElement {DataContext: INavigationAware dataContextNavigationAware} => dataContextNavigationAware, + _ => null + }; + + return navigationAware; + } +} diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs new file mode 100644 index 000000000..13cfc5e09 --- /dev/null +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -0,0 +1,24 @@ +#nullable enable +using System; + +namespace Wpf.Ui.Services.Internal; + +internal sealed class NavigationManager +{ + public string CurrentTag { get; private set; } + + public void NavigateToByTag(string tag, object? dataContext = null) + { + + } + + public void NavigateToByType(Type type, object? dataContext = null) + { + + } + + public void NavigateToById(int id, object? dataContext = null) + { + + } +} diff --git a/src/Wpf.Ui/Wpf.Ui.csproj b/src/Wpf.Ui/Wpf.Ui.csproj index c723d1d40..01cbf0cce 100644 --- a/src/Wpf.Ui/Wpf.Ui.csproj +++ b/src/Wpf.Ui/Wpf.Ui.csproj @@ -71,6 +71,10 @@ + + + + From 068856e9d369e9bcb0dc049f4c36d441f9112e99 Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Mon, 1 Aug 2022 18:04:23 +0600 Subject: [PATCH 02/28] initially implemented navigation --- src/Wpf.Ui.Demo/Views/Container.xaml.cs | 2 +- .../Views/Windows/ExperimentalWindow.xaml.cs | 2 +- src/Wpf.Ui/Controls/Interfaces/INavigation.cs | 11 +- .../Controls/Navigation/NavigationBase.cs | 71 +++++---- src/Wpf.Ui/Mvvm/Services/NavigationService.cs | 6 +- src/Wpf.Ui/Services/Internal/FrameManager.cs | 8 +- .../Services/Internal/NavigationManager.cs | 143 +++++++++++++++++- 7 files changed, 200 insertions(+), 43 deletions(-) diff --git a/src/Wpf.Ui.Demo/Views/Container.xaml.cs b/src/Wpf.Ui.Demo/Views/Container.xaml.cs index 4b9c6f646..1ef25e160 100644 --- a/src/Wpf.Ui.Demo/Views/Container.xaml.cs +++ b/src/Wpf.Ui.Demo/Views/Container.xaml.cs @@ -103,7 +103,7 @@ public void NavigateTo(string pageTag, object dataContext = null) => RootNavigation.NavigateTo(pageTag, dataContext); public void SetPageService(IPageService pageService) - => RootNavigation.PageService = pageService; + => RootNavigation.SetIPageService(pageService); public void ShowWindow() => Show(); diff --git a/src/Wpf.Ui.Demo/Views/Windows/ExperimentalWindow.xaml.cs b/src/Wpf.Ui.Demo/Views/Windows/ExperimentalWindow.xaml.cs index 5f5f58364..a90b5fb71 100644 --- a/src/Wpf.Ui.Demo/Views/Windows/ExperimentalWindow.xaml.cs +++ b/src/Wpf.Ui.Demo/Views/Windows/ExperimentalWindow.xaml.cs @@ -131,7 +131,7 @@ public INavigation GetNavigation() => RootNavigation; public void SetPageService(IPageService pageService) - => RootNavigation.PageService = pageService; + => RootNavigation.SetIPageService(pageService); public void ShowWindow() => Show(); diff --git a/src/Wpf.Ui/Controls/Interfaces/INavigation.cs b/src/Wpf.Ui/Controls/Interfaces/INavigation.cs index a8de97f0c..24598007c 100644 --- a/src/Wpf.Ui/Controls/Interfaces/INavigation.cs +++ b/src/Wpf.Ui/Controls/Interfaces/INavigation.cs @@ -19,11 +19,6 @@ namespace Wpf.Ui.Controls.Interfaces; /// public interface INavigation { - /// - /// Service providing views. - /// - IPageService PageService { get; set; } - /// /// Navigation item ID of the current page. /// If set to a value less than , no will be loaded during initialization. @@ -98,6 +93,12 @@ public interface INavigation [Bindable(true), Category("Appearance")] TransitionType TransitionType { get; set; } + + /// + /// TODO + /// + void SetIPageService(IPageService pageService); + /// /// Clears all initialized instances of the pages. /// diff --git a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs index c9b69d3eb..2ebbaa772 100644 --- a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs +++ b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs @@ -26,7 +26,9 @@ namespace Wpf.Ui.Controls.Navigation; public abstract class NavigationBase : System.Windows.Controls.Control, INavigation { private FrameManager _frameManager = null!; - private readonly NavigationManager _navigationManager; + private NavigationManager _navigationManager = null!; + private IPageService? _pageService; + private INavigationItem[] _items = null!; #region DependencyProperties @@ -213,13 +215,10 @@ public event RoutedNavigationEvent NavigatedBackward #endregion /// - public IPageService? PageService { get; set; } + public bool CanGoBack => _navigationManager.CanGoBack; /// - public bool CanGoBack { get; private set; } - - /// - public INavigationItem? Current { get; internal set; } + public INavigationItem Current => _navigationManager.Current; /// /// Static constructor overriding default properties. @@ -240,13 +239,17 @@ static NavigationBase() /// protected NavigationBase() { - _navigationManager = new NavigationManager(); - // Let the NavigationItem children be able to get me. NavigationParent = this; // Loaded does not have override Loaded += OnLoaded; + Unloaded += OnUnloaded; + } + + public void SetIPageService(IPageService pageService) + { + _pageService = pageService; } /// @@ -258,13 +261,15 @@ public void ClearCache() /// public void NavigateTo(string pageTag, object? dataContext = null) { - + _navigationManager.NavigateTo(pageTag, dataContext); + OnNavigated(); } /// public void NavigateTo(Type type, object? dataContext = null) { - + _navigationManager.NavigateTo(type, dataContext); + OnNavigated(); } /// @@ -277,21 +282,26 @@ protected virtual void OnLoaded(object sender, RoutedEventArgs e) Guard.IsNotNull(Frame, nameof(Frame)); + _items = MergeItems(Items, Footer); + _frameManager = new FrameManager(Frame, TransitionDuration, TransitionType); + _navigationManager = new NavigationManager(Frame, _pageService, _items); if (SelectedPageIndex > -1) - _navigationManager.NavigateToById(SelectedPageIndex); + { + _navigationManager.NavigateTo(SelectedPageIndex); + OnNavigated(); + } + } - InitializeServiceItems(); + protected virtual void OnUnloaded(object sender, RoutedEventArgs e) + { + _frameManager.Dispose(); + _navigationManager.Dispose(); - // If we are using the MVVM model, do not use the cache. - if (Precache) + foreach (var item in _items) { - if (PageService != null) - throw new InvalidOperationException("The cache cannot be used if you are using IPageService."); - - // TODO: Precache - //await PrecacheInstances(); + item.Click -= OnNavigationItemClicked; } } @@ -408,20 +418,27 @@ private static void OnTransitionTypeChanged(DependencyObject d, DependencyProper return (NavigationBase?)navigationItem.GetValue(NavigationParentProperty); } - private void InitializeServiceItems() + private INavigationItem[] MergeItems(IReadOnlyList items, IReadOnlyList footer) { - SubscribeToClickEventForEnumerable(Items); - SubscribeToClickEventForEnumerable(Footer); - } + var overallCount = items.Count + footer.Count; + List overallItems = new List(); - private void SubscribeToClickEventForEnumerable(IEnumerable enumerable) - { - foreach (var addedItem in enumerable) + foreach (var addedItem in items) { if (addedItem is not INavigationItem item) continue; - item.Click -= OnNavigationItemClicked; // Unsafe - Remove duplicates item.Click += OnNavigationItemClicked; + overallItems.Add(item); } + + foreach (var addedItem in footer) + { + if (addedItem is not INavigationItem item) continue; + + item.Click += OnNavigationItemClicked; + overallItems.Add(item); + } + + return overallItems.ToArray(); } } diff --git a/src/Wpf.Ui/Mvvm/Services/NavigationService.cs b/src/Wpf.Ui/Mvvm/Services/NavigationService.cs index 92ea1ee0f..83fef0202 100644 --- a/src/Wpf.Ui/Mvvm/Services/NavigationService.cs +++ b/src/Wpf.Ui/Mvvm/Services/NavigationService.cs @@ -40,7 +40,7 @@ private INavigation NavigationControl public void Initialize(INavigation navigation, IPageService pageService) { NavigationControl = navigation; - NavigationControl.PageService = pageService; + NavigationControl.SetIPageService(pageService); } /// @@ -66,7 +66,7 @@ public void SetNavigationControl(INavigation navigation) NavigationControl = navigation; if (_pageService is not null) - NavigationControl.PageService = _pageService; + NavigationControl.SetIPageService(_pageService); } public void SetPageService(IPageService pageService) @@ -77,7 +77,7 @@ public void SetPageService(IPageService pageService) return; } - NavigationControl.PageService = pageService; + NavigationControl.SetIPageService(pageService); } /// diff --git a/src/Wpf.Ui/Services/Internal/FrameManager.cs b/src/Wpf.Ui/Services/Internal/FrameManager.cs index 596dd60ca..179d9def7 100644 --- a/src/Wpf.Ui/Services/Internal/FrameManager.cs +++ b/src/Wpf.Ui/Services/Internal/FrameManager.cs @@ -1,4 +1,5 @@ #nullable enable +using System; using System.Windows; using System.Windows.Controls; using System.Windows.Navigation; @@ -7,7 +8,7 @@ namespace Wpf.Ui.Services.Internal; -internal sealed class FrameManager +internal sealed class FrameManager : IDisposable { private readonly Frame _frame; private readonly int _transitionDuration; @@ -25,6 +26,11 @@ public FrameManager(Frame frame, int transitionDuration, TransitionType transiti _frame.Navigated += OnFrameNavigated; } + public void Dispose() + { + _frame.Navigating -= OnFrameNavigating; + _frame.Navigated -= OnFrameNavigated; + } private void OnFrameNavigating(object sender, NavigatingCancelEventArgs e) { diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index 13cfc5e09..1edd9ae4e 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -1,24 +1,157 @@ #nullable enable using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using Microsoft.Toolkit.Diagnostics; +using Wpf.Ui.Controls.Interfaces; +using Wpf.Ui.Mvvm.Contracts; namespace Wpf.Ui.Services.Internal; -internal sealed class NavigationManager +internal sealed class NavigationManager : IDisposable { - public string CurrentTag { get; private set; } + private readonly Frame _frame; + private readonly INavigationItem[] _navigationItems; + private readonly FrameworkElement[] _instances; + private readonly List _history = new(); + private readonly List _navigationStack = new(); + private readonly IPageService? _pageService; - public void NavigateToByTag(string tag, object? dataContext = null) + private bool _isBackwardsNavigated; + private int _previousItemId = -1; + + public bool CanGoBack => _history.Count > 1; + public INavigationItem Current { get; private set; } = null!; + + public NavigationManager(Frame frame, IPageService? pageService, INavigationItem[] navigationItems) + { + _instances = new FrameworkElement[navigationItems.Length]; + + _navigationItems = navigationItems; + _frame = frame; + _pageService = pageService; + } + + public void Dispose() + { + + } + + public void NavigateTo(string tag, object? dataContext = null) + { + Guard.IsNotNullOrEmpty(tag, nameof(tag)); + + if (tag == "..") + { + NavigateBack(); + return; + } + + var itemId = GetItemId(item => item.PageTag == tag); + NavigateInternal(itemId, dataContext); + } + + public void NavigateTo(Type type, object? dataContext = null) + { + var itemId = GetItemId(serviceItem => serviceItem.PageType == type); + NavigateInternal(itemId, dataContext); + } + + public void NavigateTo(int id, object? dataContext = null) { + NavigateInternal(id, dataContext); + } + + private void NavigateBack() + { + if (_history.Count <= 1) + return; + var itemId = _history[_history.Count - 2]; + _isBackwardsNavigated = true; + NavigateInternal(itemId, null); } - public void NavigateToByType(Type type, object? dataContext = null) + private void NavigateInternal(int itemId, object? dataContext) { + if (ReferenceEquals(Current, _navigationItems.ElementAtOrDefault(itemId))) + return; + if (_isBackwardsNavigated) + { + _isBackwardsNavigated = false; + _history.RemoveAt(_history.LastIndexOf(_history[_history.Count - 2])); + _history.RemoveAt(_history.LastIndexOf(_history[_history.Count - 1])); + } + + _history.Add(itemId); + + PerformNavigation(itemId, dataContext); } - public void NavigateToById(int id, object? dataContext = null) + #region PerformNavigation + + private void PerformNavigation(int itemId, object? dataContext) { + if (_navigationItems.ElementAtOrDefault(itemId) is not { } item) + return; + + if (_navigationItems.ElementAtOrDefault(_previousItemId) is {} previousItem) + previousItem.IsActive = false; + + item.IsActive = true; + Current = item; + _previousItemId = itemId; + + if (_pageService is not null) + { + NavigateByService(item); + return; + } + + if (item.Cache) + { + NavigateWithCache(itemId, item, dataContext); + return; + } + + NavigateWithoutCache(item, dataContext); + } + + private void NavigateByService(INavigationItem item) + { + var instance = _pageService!.GetPage(item.PageType); + Guard.IsNotNull(instance, "Page instance"); + + _frame.Navigate(instance); + } + + private void NavigateWithCache(int itemId, INavigationItem item, object? dataContext) + { + + } + + private void NavigateWithoutCache(INavigationItem item, object? dataContext) + { + + } + + #endregion + + private int GetItemId(Func prediction) + { + int selectedIndex = -1; + + for (int i = 0; i < _navigationItems.Length; i++) + { + if (!prediction.Invoke(_navigationItems[i])) continue; + + selectedIndex = i; + break; + } + return selectedIndex; } } From 449eb02a235eafe5f040fb2859b6ba285fae1645 Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Mon, 1 Aug 2022 19:56:02 +0600 Subject: [PATCH 03/28] fixed items --- src/Wpf.Ui/Controls/Navigation/NavigationBase.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs index 2ebbaa772..ae3ce1471 100644 --- a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs +++ b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs @@ -37,14 +37,14 @@ public abstract class NavigationBase : System.Windows.Controls.Control, INavigat /// public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(nameof(Items), typeof(ObservableCollection), typeof(NavigationBase), - new PropertyMetadata(new ObservableCollection())); + new PropertyMetadata(null)); /// /// Property for . /// public static readonly DependencyProperty FooterProperty = DependencyProperty.Register(nameof(Footer), typeof(ObservableCollection), typeof(NavigationBase), - new PropertyMetadata(new ObservableCollection())); + new PropertyMetadata(null)); /// /// Property for . @@ -239,6 +239,9 @@ static NavigationBase() /// protected NavigationBase() { + Items = new ObservableCollection(); + Footer = new ObservableCollection(); + // Let the NavigationItem children be able to get me. NavigationParent = this; @@ -300,9 +303,7 @@ protected virtual void OnUnloaded(object sender, RoutedEventArgs e) _navigationManager.Dispose(); foreach (var item in _items) - { item.Click -= OnNavigationItemClicked; - } } /// From e85c86810619f69c910dcf22741d6b614c31bb41 Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Tue, 2 Aug 2022 08:02:40 +0600 Subject: [PATCH 04/28] added navigation stack --- src/Wpf.Ui.Demo/Views/Container.xaml.cs | 7 +- .../Common/RoutedNavigationEventArgs.cs | 7 +- src/Wpf.Ui/Controls/Breadcrumb.cs | 10 +-- src/Wpf.Ui/Controls/Interfaces/INavigation.cs | 19 +---- .../Controls/Navigation/NavigationBase.cs | 23 +----- .../Services/Internal/NavigationManager.cs | 82 ++++++++++++++----- 6 files changed, 81 insertions(+), 67 deletions(-) diff --git a/src/Wpf.Ui.Demo/Views/Container.xaml.cs b/src/Wpf.Ui.Demo/Views/Container.xaml.cs index 1ef25e160..8c0d0f51c 100644 --- a/src/Wpf.Ui.Demo/Views/Container.xaml.cs +++ b/src/Wpf.Ui.Demo/Views/Container.xaml.cs @@ -4,6 +4,7 @@ // All Rights Reserved. using System; +using System.Linq; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; @@ -161,14 +162,16 @@ private void TrayMenuItem_OnClick(object sender, RoutedEventArgs e) private void RootNavigation_OnNavigated(INavigation sender, RoutedNavigationEventArgs e) { - System.Diagnostics.Debug.WriteLine($"DEBUG | WPF UI Navigated to: {sender?.Current ?? null}", "Wpf.Ui.Demo"); + var current = e.NavigationStack.ElementAt(0); + + System.Diagnostics.Debug.WriteLine($"DEBUG | WPF UI Navigated to: {current}", "Wpf.Ui.Demo"); // This funky solution allows us to impose a negative // margin for Frame only for the Dashboard page, thanks // to which the banner will cover the entire page nicely. RootFrame.Margin = new Thickness( left: 0, - top: sender?.Current?.PageTag == "dashboard" ? -69 : 0, + top: current.PageTag == "dashboard" ? -69 : 0, right: 0, bottom: 0); } diff --git a/src/Wpf.Ui/Common/RoutedNavigationEventArgs.cs b/src/Wpf.Ui/Common/RoutedNavigationEventArgs.cs index ae124110c..a6987e081 100644 --- a/src/Wpf.Ui/Common/RoutedNavigationEventArgs.cs +++ b/src/Wpf.Ui/Common/RoutedNavigationEventArgs.cs @@ -3,6 +3,7 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. +using System.Collections.Generic; using System.Windows; using Wpf.Ui.Controls.Interfaces; @@ -16,7 +17,7 @@ public class RoutedNavigationEventArgs : RoutedEventArgs /// /// Currently displayed page. /// - public INavigationItem CurrentPage { get; set; } + public IEnumerable NavigationStack { get; set; } /// /// Constructor for . @@ -24,9 +25,9 @@ public class RoutedNavigationEventArgs : RoutedEventArgs /// The new value that the SourceProperty is being set to. /// The new value that the Property is being set to. /// Currently displayed page. - public RoutedNavigationEventArgs(RoutedEvent routedEvent, object source, INavigationItem currentPage) : base( + public RoutedNavigationEventArgs(RoutedEvent routedEvent, object source, IEnumerable navigationStack) : base( routedEvent, source) { - CurrentPage = currentPage; + NavigationStack = navigationStack; } } diff --git a/src/Wpf.Ui/Controls/Breadcrumb.cs b/src/Wpf.Ui/Controls/Breadcrumb.cs index cb0b089ef..db65c6361 100644 --- a/src/Wpf.Ui/Controls/Breadcrumb.cs +++ b/src/Wpf.Ui/Controls/Breadcrumb.cs @@ -4,6 +4,7 @@ // All Rights Reserved. using System; +using System.Linq; using System.Windows; using Wpf.Ui.Common; using Wpf.Ui.Controls.Interfaces; @@ -53,13 +54,12 @@ protected virtual void OnNavigated(INavigation sender, RoutedNavigationEventArgs #endif //TODO: Navigate with previous levels + //TMP + var firstItem = e.NavigationStack.ElementAt(0); - if (Navigation?.Current is not INavigationItem item) - return; - - var pageName = item.Content as string; + var pageName = firstItem.Content as string; - if (String.IsNullOrEmpty(pageName)) + if (string.IsNullOrEmpty(pageName)) return; Current = pageName; diff --git a/src/Wpf.Ui/Controls/Interfaces/INavigation.cs b/src/Wpf.Ui/Controls/Interfaces/INavigation.cs index 24598007c..71f12a507 100644 --- a/src/Wpf.Ui/Controls/Interfaces/INavigation.cs +++ b/src/Wpf.Ui/Controls/Interfaces/INavigation.cs @@ -5,6 +5,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Windows.Controls; @@ -36,9 +37,9 @@ public interface INavigation bool CanGoBack { get; } /// - /// Currently used item like . + /// TODO /// - INavigationItem Current { get; } + IEnumerable NavigationStack { get; } /// /// Gets or sets the in which the will be loaded after navigation. @@ -49,13 +50,11 @@ public interface INavigation /// /// Gets or sets the list of that will be displayed on the navigation. /// - [Bindable(true)] ObservableCollection Items { get; set; } /// /// Gets or sets the list of which will be displayed at the bottom of the navigation and will not be scrolled. /// - [Bindable(true)] ObservableCollection Footer { get; set; } /// @@ -69,18 +68,6 @@ public interface INavigation [Category("Behavior")] event RoutedNavigationEvent Navigated; - /// - /// Gets or sets the that will be triggered during forward navigation. - /// - [Category("Behavior")] - event RoutedNavigationEvent NavigatedForward; - - /// - /// Gets or sets the that will be triggered during backward navigation. - /// - [Category("Behavior")] - event RoutedNavigationEvent NavigatedBackward; - /// /// Gets or sets a value deciding how long the effect of the transition between the pages should take. /// diff --git a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs index ae3ce1471..d9af2bffa 100644 --- a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs +++ b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs @@ -217,8 +217,7 @@ public event RoutedNavigationEvent NavigatedBackward /// public bool CanGoBack => _navigationManager.CanGoBack; - /// - public INavigationItem Current => _navigationManager.Current; + public IEnumerable NavigationStack => _navigationManager.NavigationStack; /// /// Static constructor overriding default properties. @@ -356,25 +355,7 @@ static void MoveFocus(FrameworkElement element, FocusNavigationDirection directi /// protected virtual void OnNavigated() { - var newEvent = new RoutedNavigationEventArgs(NavigatedEvent, this, Current); - RaiseEvent(newEvent); - } - - /// - /// This virtual method is called during forward navigation and it raises the . - /// - protected virtual void OnNavigatedForward() - { - var newEvent = new RoutedNavigationEventArgs(NavigatedForwardEvent, this, Current); - RaiseEvent(newEvent); - } - - /// - /// This virtual method is called during backward navigation and it raises the . - /// - protected virtual void OnNavigatedBackward() - { - var newEvent = new RoutedNavigationEventArgs(NavigatedBackwardEvent, this, Current); + var newEvent = new RoutedNavigationEventArgs(NavigatedEvent, this, NavigationStack); RaiseEvent(newEvent); } diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index 1edd9ae4e..a52588bda 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -16,14 +16,13 @@ internal sealed class NavigationManager : IDisposable private readonly INavigationItem[] _navigationItems; private readonly FrameworkElement[] _instances; private readonly List _history = new(); - private readonly List _navigationStack = new(); private readonly IPageService? _pageService; private bool _isBackwardsNavigated; - private int _previousItemId = -1; + private bool _addToNavigationStack; public bool CanGoBack => _history.Count > 1; - public INavigationItem Current { get; private set; } = null!; + public readonly List NavigationStack = new(); public NavigationManager(Frame frame, IPageService? pageService, INavigationItem[] navigationItems) { @@ -49,6 +48,10 @@ public void NavigateTo(string tag, object? dataContext = null) return; } + _addToNavigationStack = tag.Contains("//"); + if (_addToNavigationStack) + tag = tag.Replace("//", string.Empty).Trim(); + var itemId = GetItemId(item => item.PageTag == tag); NavigateInternal(itemId, dataContext); } @@ -76,9 +79,22 @@ private void NavigateBack() private void NavigateInternal(int itemId, object? dataContext) { - if (ReferenceEquals(Current, _navigationItems.ElementAtOrDefault(itemId))) + if (_navigationItems.ElementAtOrDefault(itemId) is not { } item) return; + switch (NavigationStack.Count) + { + case > 0 when NavigationStack[NavigationStack.Count -1] == item: + return; + case 0: + NavigationStack.Add(item); + break; + } + + AddToNavigationStack(item, false); + + item.IsActive = true; + if (_isBackwardsNavigated) { _isBackwardsNavigated = false; @@ -88,36 +104,50 @@ private void NavigateInternal(int itemId, object? dataContext) _history.Add(itemId); - PerformNavigation(itemId, dataContext); + PerformNavigation((itemId, item), dataContext); } - #region PerformNavigation - - private void PerformNavigation(int itemId, object? dataContext) + private void AddToNavigationStack(INavigationItem item, bool itemIsNotVisible) { - if (_navigationItems.ElementAtOrDefault(itemId) is not { } item) - return; + if (_addToNavigationStack && !NavigationStack.Contains(item)) + NavigationStack.Add(item); - if (_navigationItems.ElementAtOrDefault(_previousItemId) is {} previousItem) - previousItem.IsActive = false; + if (itemIsNotVisible || !_addToNavigationStack) + { + NavigationStack[0].IsActive = false; + NavigationStack[0] = item; - item.IsActive = true; - Current = item; - _previousItemId = itemId; + ClearNavigationStack(1); + } + + var navigationStackCount = NavigationStack.Count; + if (navigationStackCount > 1) + { + //var navItem = NavigationStack[NavigationStack.Count - 2]; + + var index = NavigationStack.IndexOf(item); + if (index < navigationStackCount - 1) + ClearNavigationStack(++index); + } + } + + #region PerformNavigation + private void PerformNavigation((int itemId, INavigationItem item) itemData, object? dataContext) + { if (_pageService is not null) { - NavigateByService(item); + NavigateByService(itemData.item); return; } - if (item.Cache) + if (itemData.item.Cache) { - NavigateWithCache(itemId, item, dataContext); + NavigateWithCache(itemData, dataContext); return; } - NavigateWithoutCache(item, dataContext); + NavigateWithoutCache(itemData.item, dataContext); } private void NavigateByService(INavigationItem item) @@ -128,7 +158,7 @@ private void NavigateByService(INavigationItem item) _frame.Navigate(instance); } - private void NavigateWithCache(int itemId, INavigationItem item, object? dataContext) + private void NavigateWithCache((int itemId, INavigationItem item) itemData, object? dataContext) { } @@ -140,6 +170,18 @@ private void NavigateWithoutCache(INavigationItem item, object? dataContext) #endregion + private void ClearNavigationStack(int navigationStackItemIndex) + { + var navigationStackCount = NavigationStack.Count; + List buffer = new(navigationStackCount - navigationStackItemIndex); + + for (int i = navigationStackItemIndex; i <= navigationStackCount - 1; i++) + buffer.Add(NavigationStack[i]); + + foreach (var item in buffer) + NavigationStack.Remove(item); + } + private int GetItemId(Func prediction) { int selectedIndex = -1; From 8fa6a608df65d4ff7a9478f716c85f6e426e2751 Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Tue, 2 Aug 2022 08:22:57 +0600 Subject: [PATCH 05/28] a small optimization of the ClearNavigationStack method --- src/Wpf.Ui/Services/Internal/NavigationManager.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index a52588bda..708107c19 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Windows; @@ -17,6 +18,7 @@ internal sealed class NavigationManager : IDisposable private readonly FrameworkElement[] _instances; private readonly List _history = new(); private readonly IPageService? _pageService; + private readonly ArrayPool _arrayPool = ArrayPool.Create(); private bool _isBackwardsNavigated; private bool _addToNavigationStack; @@ -173,13 +175,19 @@ private void NavigateWithoutCache(INavigationItem item, object? dataContext) private void ClearNavigationStack(int navigationStackItemIndex) { var navigationStackCount = NavigationStack.Count; - List buffer = new(navigationStackCount - navigationStackItemIndex); + var buffer = _arrayPool.Rent(navigationStackCount - navigationStackItemIndex); - for (int i = navigationStackItemIndex; i <= navigationStackCount - 1; i++) - buffer.Add(NavigationStack[i]); + int i = 0; + for (int j = navigationStackItemIndex; j <= navigationStackCount - 1; j++) + { + buffer[i] = NavigationStack[j]; + i++; + } foreach (var item in buffer) NavigationStack.Remove(item); + + _arrayPool.Return(buffer, true); } private int GetItemId(Func prediction) From ca342a3692915e46f9b87ac2a51a18729db8e7e2 Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Tue, 2 Aug 2022 09:18:07 +0600 Subject: [PATCH 06/28] added IsHidden property to the INavigationItem --- src/Wpf.Ui.Demo/App.xaml.cs | 2 + src/Wpf.Ui.Demo/Views/Container.xaml | 8 ++- src/Wpf.Ui.Demo/Views/Container.xaml.cs | 25 ++++----- src/Wpf.Ui/Controls/Interfaces/INavigation.cs | 5 ++ .../Controls/Interfaces/INavigationItem.cs | 5 ++ .../Controls/Navigation/NavigationBase.cs | 54 +++++++++++++------ src/Wpf.Ui/Controls/NavigationItem.cs | 3 ++ .../Services/Internal/NavigationManager.cs | 13 +++-- 8 files changed, 78 insertions(+), 37 deletions(-) diff --git a/src/Wpf.Ui.Demo/App.xaml.cs b/src/Wpf.Ui.Demo/App.xaml.cs index 0fba0c658..9a23ac79d 100644 --- a/src/Wpf.Ui.Demo/App.xaml.cs +++ b/src/Wpf.Ui.Demo/App.xaml.cs @@ -94,6 +94,8 @@ public partial class App services.AddScoped(); services.AddScoped(); + services.AddScoped(); + // Test windows services.AddTransient(); services.AddTransient(); diff --git a/src/Wpf.Ui.Demo/Views/Container.xaml b/src/Wpf.Ui.Demo/Views/Container.xaml index aaf32c924..0df798af1 100644 --- a/src/Wpf.Ui.Demo/Views/Container.xaml +++ b/src/Wpf.Ui.Demo/Views/Container.xaml @@ -98,6 +98,12 @@ Content="Theme" Icon="DarkTheme24" /> + + + @@ -198,8 +204,8 @@ ForceShutdown="False" Icon="pack://application:,,,/Resources/wpfui.png" MinimizeToTray="False" - ShowHelp="False" ShowClose="True" + ShowHelp="False" ShowMaximize="True" ShowMinimize="True" UseSnapLayout="True"> diff --git a/src/Wpf.Ui.Demo/Views/Container.xaml.cs b/src/Wpf.Ui.Demo/Views/Container.xaml.cs index 8c0d0f51c..2ed59a27b 100644 --- a/src/Wpf.Ui.Demo/Views/Container.xaml.cs +++ b/src/Wpf.Ui.Demo/Views/Container.xaml.cs @@ -115,7 +115,7 @@ public void CloseWindow() #endregion INavigationWindow methods - private void InvokeSplashScreen() + private async void InvokeSplashScreen() { if (_initialized) return; @@ -127,24 +127,17 @@ private void InvokeSplashScreen() _taskBarService.SetState(this, TaskBarProgressState.Indeterminate); - Task.Run(async () => - { - // Remember to always include Delays and Sleeps in - // your applications to be able to charge the client for optimizations later. - await Task.Delay(4000); + // Remember to always include Delays and Sleeps in + // your applications to be able to charge the client for optimizations later. + await Task.Delay(4000); - await Dispatcher.InvokeAsync(() => - { - RootWelcomeGrid.Visibility = Visibility.Hidden; - RootMainGrid.Visibility = Visibility.Visible; + RootWelcomeGrid.Visibility = Visibility.Hidden; + RootMainGrid.Visibility = Visibility.Visible; - NavigateTo(typeof(Pages.Dashboard)); + NavigateTo(typeof(Pages.Dashboard)); + NavigateTo("//test"); - _taskBarService.SetState(this, TaskBarProgressState.None); - }); - - return true; - }); + _taskBarService.SetState(this, TaskBarProgressState.None); } private void NavigationButtonTheme_OnClick(object sender, RoutedEventArgs e) diff --git a/src/Wpf.Ui/Controls/Interfaces/INavigation.cs b/src/Wpf.Ui/Controls/Interfaces/INavigation.cs index 71f12a507..14bd4ad3a 100644 --- a/src/Wpf.Ui/Controls/Interfaces/INavigation.cs +++ b/src/Wpf.Ui/Controls/Interfaces/INavigation.cs @@ -57,6 +57,11 @@ public interface INavigation /// ObservableCollection Footer { get; set; } + /// + /// TODO + /// + List HiddenItems { get; set; } + /// /// Specifies dimension of children stacking. /// diff --git a/src/Wpf.Ui/Controls/Interfaces/INavigationItem.cs b/src/Wpf.Ui/Controls/Interfaces/INavigationItem.cs index 216171173..c571d82ce 100644 --- a/src/Wpf.Ui/Controls/Interfaces/INavigationItem.cs +++ b/src/Wpf.Ui/Controls/Interfaces/INavigationItem.cs @@ -55,4 +55,9 @@ public interface INavigationItem /// [Category("Behavior")] event RoutedEventHandler Click; + + /// + /// TODO + /// + internal bool IsHidden { get; set; } } diff --git a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs index d9af2bffa..47951d42a 100644 --- a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs +++ b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs @@ -46,6 +46,13 @@ public abstract class NavigationBase : System.Windows.Controls.Control, INavigat typeof(ObservableCollection), typeof(NavigationBase), new PropertyMetadata(null)); + /// + /// Property for . + /// + public static readonly DependencyProperty HiddenItemsProperty = DependencyProperty.Register(nameof(HiddenItems), + typeof(List), typeof(NavigationBase), + new PropertyMetadata(null)); + /// /// Property for . /// @@ -107,17 +114,24 @@ public abstract class NavigationBase : System.Windows.Controls.Control, INavigat /// public ObservableCollection Items { - get => (GetValue(ItemsProperty) as ObservableCollection)!; + get => (ObservableCollection) GetValue(ItemsProperty); set => SetValue(ItemsProperty, value); } /// public ObservableCollection Footer { - get => (GetValue(FooterProperty) as ObservableCollection)!; + get => (ObservableCollection) GetValue(FooterProperty); set => SetValue(FooterProperty, value); } + /// + public List HiddenItems + { + get => (List) GetValue(HiddenItemsProperty); + set => SetValue(HiddenItemsProperty, value); + } + /// [Obsolete("Work in progress.")] public Orientation Orientation @@ -240,6 +254,7 @@ protected NavigationBase() { Items = new ObservableCollection(); Footer = new ObservableCollection(); + HiddenItems = new List(); // Let the NavigationItem children be able to get me. NavigationParent = this; @@ -284,7 +299,7 @@ protected virtual void OnLoaded(object sender, RoutedEventArgs e) Guard.IsNotNull(Frame, nameof(Frame)); - _items = MergeItems(Items, Footer); + _items = MergeItems(); _frameManager = new FrameManager(Frame, TransitionDuration, TransitionType); _navigationManager = new NavigationManager(Frame, _pageService, _items); @@ -400,27 +415,34 @@ private static void OnTransitionTypeChanged(DependencyObject d, DependencyProper return (NavigationBase?)navigationItem.GetValue(NavigationParentProperty); } - private INavigationItem[] MergeItems(IReadOnlyList items, IReadOnlyList footer) + private INavigationItem[] MergeItems() { - var overallCount = items.Count + footer.Count; - List overallItems = new List(); + var overallCount = Items.Count + Footer.Count + HiddenItems.Count; + INavigationItem[] buffer = new INavigationItem[overallCount - 1]; + int i = 0; + + AddToBufferList(Items); + AddToBufferList(Footer); + AddToBufferList(HiddenItems, item => item.IsHidden = true); + + return buffer; - foreach (var addedItem in items) + void AddToBufferList(IEnumerable list, Action? action = null) { - if (addedItem is not INavigationItem item) continue; + foreach (var addedItem in list) + { + if (addedItem is not INavigationItem item) continue; - item.Click += OnNavigationItemClicked; - overallItems.Add(item); + action?.Invoke(item); + AddToBuffer(item); + } } - foreach (var addedItem in footer) + void AddToBuffer(INavigationItem item) { - if (addedItem is not INavigationItem item) continue; - item.Click += OnNavigationItemClicked; - overallItems.Add(item); + buffer[i] = item; + i++; } - - return overallItems.ToArray(); } } diff --git a/src/Wpf.Ui/Controls/NavigationItem.cs b/src/Wpf.Ui/Controls/NavigationItem.cs index 496a57364..95ab1cbe3 100644 --- a/src/Wpf.Ui/Controls/NavigationItem.cs +++ b/src/Wpf.Ui/Controls/NavigationItem.cs @@ -237,6 +237,9 @@ protected virtual Uri BaseUri set => SetValue(BaseUriHelper.BaseUriProperty, value); } + /// + bool INavigationItem.IsHidden { get; set; } + /// protected override void OnContentChanged(object oldContent, object newContent) { diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index 708107c19..0ea72e745 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -55,6 +55,9 @@ public void NavigateTo(string tag, object? dataContext = null) tag = tag.Replace("//", string.Empty).Trim(); var itemId = GetItemId(item => item.PageTag == tag); + if (itemId < 0) + ThrowHelper.ThrowArgumentException($"Item with: {tag} tag not found"); + NavigateInternal(itemId, dataContext); } @@ -93,7 +96,7 @@ private void NavigateInternal(int itemId, object? dataContext) break; } - AddToNavigationStack(item, false); + AddToNavigationStack(item); item.IsActive = true; @@ -109,12 +112,12 @@ private void NavigateInternal(int itemId, object? dataContext) PerformNavigation((itemId, item), dataContext); } - private void AddToNavigationStack(INavigationItem item, bool itemIsNotVisible) + private void AddToNavigationStack(INavigationItem item) { if (_addToNavigationStack && !NavigationStack.Contains(item)) NavigationStack.Add(item); - if (itemIsNotVisible || !_addToNavigationStack) + if (!item.IsHidden || !_addToNavigationStack) { NavigationStack[0].IsActive = false; NavigationStack[0] = item; @@ -125,7 +128,9 @@ private void AddToNavigationStack(INavigationItem item, bool itemIsNotVisible) var navigationStackCount = NavigationStack.Count; if (navigationStackCount > 1) { - //var navItem = NavigationStack[NavigationStack.Count - 2]; + var navItem = NavigationStack[NavigationStack.Count - 2]; + if (navItem.IsHidden) + navItem.IsActive = false; var index = NavigationStack.IndexOf(item); if (index < navigationStackCount - 1) From 98d70ad83c61e3acc3badf985a634cb92fcedb7f Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Tue, 2 Aug 2022 16:18:26 +0600 Subject: [PATCH 07/28] implemented dynamic breadcrumb --- src/Wpf.Ui.Demo/App.xaml.cs | 1 + src/Wpf.Ui.Demo/Views/Container.xaml | 6 +- src/Wpf.Ui.Demo/Views/Container.xaml.cs | 1 + src/Wpf.Ui/Controls/Breadcrumb.cs | 80 ++++++++------- src/Wpf.Ui/Controls/BreadcrumbItem.cs | 43 ++++++++ src/Wpf.Ui/Styles/Controls/Breadcrumb.xaml | 58 ++--------- .../Styles/Controls/BreadcrumbItem.xaml | 97 +++++++++++++++++++ src/Wpf.Ui/Styles/Wpf.Ui.xaml | 1 + 8 files changed, 198 insertions(+), 89 deletions(-) create mode 100644 src/Wpf.Ui/Controls/BreadcrumbItem.cs create mode 100644 src/Wpf.Ui/Styles/Controls/BreadcrumbItem.xaml diff --git a/src/Wpf.Ui.Demo/App.xaml.cs b/src/Wpf.Ui.Demo/App.xaml.cs index 9a23ac79d..a4ad04789 100644 --- a/src/Wpf.Ui.Demo/App.xaml.cs +++ b/src/Wpf.Ui.Demo/App.xaml.cs @@ -95,6 +95,7 @@ public partial class App services.AddScoped(); services.AddScoped(); + services.AddScoped(); // Test windows services.AddTransient(); diff --git a/src/Wpf.Ui.Demo/Views/Container.xaml b/src/Wpf.Ui.Demo/Views/Container.xaml index 0df798af1..7a8c2cd76 100644 --- a/src/Wpf.Ui.Demo/Views/Container.xaml +++ b/src/Wpf.Ui.Demo/Views/Container.xaml @@ -99,10 +99,8 @@ Icon="DarkTheme24" /> - + + diff --git a/src/Wpf.Ui.Demo/Views/Container.xaml.cs b/src/Wpf.Ui.Demo/Views/Container.xaml.cs index 2ed59a27b..125ecce23 100644 --- a/src/Wpf.Ui.Demo/Views/Container.xaml.cs +++ b/src/Wpf.Ui.Demo/Views/Container.xaml.cs @@ -136,6 +136,7 @@ private async void InvokeSplashScreen() NavigateTo(typeof(Pages.Dashboard)); NavigateTo("//test"); + NavigateTo("//test2"); _taskBarService.SetState(this, TaskBarProgressState.None); } diff --git a/src/Wpf.Ui/Controls/Breadcrumb.cs b/src/Wpf.Ui/Controls/Breadcrumb.cs index db65c6361..42f3e9339 100644 --- a/src/Wpf.Ui/Controls/Breadcrumb.cs +++ b/src/Wpf.Ui/Controls/Breadcrumb.cs @@ -3,9 +3,10 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. -using System; -using System.Linq; +using System.Collections.ObjectModel; using System.Windows; +using System.Windows.Input; +using Microsoft.Toolkit.Diagnostics; using Wpf.Ui.Common; using Wpf.Ui.Controls.Interfaces; @@ -16,27 +17,16 @@ namespace Wpf.Ui.Controls; /// public class Breadcrumb : System.Windows.Controls.Control { - /// - /// Property for . - /// - public static readonly DependencyProperty CurrentProperty = DependencyProperty.Register(nameof(Current), - typeof(string), typeof(Breadcrumb), new PropertyMetadata(String.Empty)); - /// /// Property for . /// public static readonly DependencyProperty NavigationProperty = DependencyProperty.Register(nameof(Navigation), typeof(INavigation), typeof(Breadcrumb), - new PropertyMetadata(null, OnNavigationChanged)); + new PropertyMetadata(null)); + + public static readonly DependencyProperty BreadcrumbItemsProperty = DependencyProperty.Register(nameof(BreadcrumbItems), + typeof(ObservableCollection), typeof(Breadcrumb), new PropertyMetadata(null)); - /// - /// based on which displays the titles. - /// - public string Current - { - get => (string)GetValue(CurrentProperty); - set => SetValue(CurrentProperty, value); - } /// /// based on which displays the titles. @@ -47,34 +37,56 @@ public INavigation Navigation set => SetValue(NavigationProperty, value); } - protected virtual void OnNavigated(INavigation sender, RoutedNavigationEventArgs e) + public ObservableCollection BreadcrumbItems { -#if DEBUG - System.Diagnostics.Debug.WriteLine($"INFO | {typeof(Breadcrumb)} builded, current nav: {Navigation.GetType()}", "Wpf.Ui.Breadcrumb"); -#endif + get => (ObservableCollection)GetValue(BreadcrumbItemsProperty); + private set => SetValue(BreadcrumbItemsProperty, value); + } - //TODO: Navigate with previous levels - //TMP - var firstItem = e.NavigationStack.ElementAt(0); + private readonly ICommand _onClickCommand; - var pageName = firstItem.Content as string; + public Breadcrumb() + { + BreadcrumbItems = new ObservableCollection(); + _onClickCommand = new RelayCommand(OnClick); - if (string.IsNullOrEmpty(pageName)) - return; + Loaded += (sender, args) => + { + Guard.IsNotNull(Navigation, nameof(Navigation)); + Navigation.Navigated += OnNavigated; + }; - Current = pageName; + Unloaded += (sender, args) => + { + Navigation.Navigated -= OnNavigated; + }; } - protected virtual void OnNavigationChanged() + protected virtual void OnNavigated(INavigation sender, RoutedNavigationEventArgs e) { - Navigation.Navigated += OnNavigated; +#if DEBUG + System.Diagnostics.Debug.WriteLine($"INFO | {typeof(Breadcrumb)} builded, current nav: {Navigation.GetType()}", "Wpf.Ui.Breadcrumb"); +#endif + + //TODO This event needs some kind of optimization + BreadcrumbItems.Clear(); + foreach (var navigationItem in e.NavigationStack) + { + BreadcrumbItems.Add(new BreadcrumbItem() + { + Text = navigationItem.Content as string ?? string.Empty, + PageTag = navigationItem.PageTag, + OnClickCommand = _onClickCommand + }); + } + + BreadcrumbItems[BreadcrumbItems.Count - 1].IsActive = true; } - private static void OnNavigationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private void OnClick(object obj) { - if (d is not Breadcrumb breadcrumb) - return; + var pageTag = (string)obj; - breadcrumb.OnNavigationChanged(); + Navigation.NavigateTo(pageTag); } } diff --git a/src/Wpf.Ui/Controls/BreadcrumbItem.cs b/src/Wpf.Ui/Controls/BreadcrumbItem.cs new file mode 100644 index 000000000..3ef634d90 --- /dev/null +++ b/src/Wpf.Ui/Controls/BreadcrumbItem.cs @@ -0,0 +1,43 @@ +using System.Windows; +using System.Windows.Input; + +namespace Wpf.Ui.Controls; + +public class BreadcrumbItem : System.Windows.Controls.Control +{ + public static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), + typeof(string), typeof(BreadcrumbItem), new PropertyMetadata(string.Empty)); + + public static readonly DependencyProperty PageTagProperty = DependencyProperty.Register(nameof(PageTag), + typeof(string), typeof(BreadcrumbItem), new PropertyMetadata(string.Empty)); + + public static readonly DependencyProperty OnClickCommandProperty = DependencyProperty.Register(nameof(OnClickCommand), + typeof(ICommand), typeof(BreadcrumbItem), new PropertyMetadata(null)); + + public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register(nameof(IsActive), + typeof(bool), typeof(BreadcrumbItem), new PropertyMetadata(false)); + + public string Text + { + get => (string)GetValue(TextProperty); + set => SetValue(TextProperty, value); + } + + public string PageTag + { + get => (string)GetValue(PageTagProperty); + set => SetValue(PageTagProperty, value); + } + + public ICommand OnClickCommand + { + get => (ICommand)GetValue(OnClickCommandProperty); + set => SetValue(OnClickCommandProperty, value); + } + + public bool IsActive + { + get => (bool)GetValue(IsActiveProperty); + set => SetValue(IsActiveProperty, value); + } +} diff --git a/src/Wpf.Ui/Styles/Controls/Breadcrumb.xaml b/src/Wpf.Ui/Styles/Controls/Breadcrumb.xaml index df9936ef6..57d4846b3 100644 --- a/src/Wpf.Ui/Styles/Controls/Breadcrumb.xaml +++ b/src/Wpf.Ui/Styles/Controls/Breadcrumb.xaml @@ -10,8 +10,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="clr-namespace:Wpf.Ui.Controls"> - + + + + \ No newline at end of file diff --git a/src/Wpf.Ui/Styles/Wpf.Ui.xaml b/src/Wpf.Ui/Styles/Wpf.Ui.xaml index f1258ed10..81c6fd434 100644 --- a/src/Wpf.Ui/Styles/Wpf.Ui.xaml +++ b/src/Wpf.Ui/Styles/Wpf.Ui.xaml @@ -33,6 +33,7 @@ + From a78569cb05b692e561d33ff2a1322d2d5c9574bf Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Tue, 2 Aug 2022 16:25:36 +0600 Subject: [PATCH 08/28] navigation with previous levels works --- src/Wpf.Ui/Services/Internal/NavigationManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index 0ea72e745..b684dac36 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -117,7 +117,7 @@ private void AddToNavigationStack(INavigationItem item) if (_addToNavigationStack && !NavigationStack.Contains(item)) NavigationStack.Add(item); - if (!item.IsHidden || !_addToNavigationStack) + if (!item.IsHidden && !_addToNavigationStack) { NavigationStack[0].IsActive = false; NavigationStack[0] = item; From f1148cf9f2ff5cdac5009fa2ff0829eaf72dfe46 Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Tue, 2 Aug 2022 16:55:03 +0600 Subject: [PATCH 09/28] small breadcrumb optimization --- src/Wpf.Ui/Controls/Breadcrumb.cs | 40 ++++++++++++++----- src/Wpf.Ui/Controls/BreadcrumbItem.cs | 8 ++++ src/Wpf.Ui/Controls/Interfaces/INavigation.cs | 2 +- .../Controls/Navigation/NavigationBase.cs | 2 +- .../Services/Internal/NavigationManager.cs | 3 +- 5 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/Wpf.Ui/Controls/Breadcrumb.cs b/src/Wpf.Ui/Controls/Breadcrumb.cs index 42f3e9339..aefc8557f 100644 --- a/src/Wpf.Ui/Controls/Breadcrumb.cs +++ b/src/Wpf.Ui/Controls/Breadcrumb.cs @@ -4,6 +4,7 @@ // All Rights Reserved. using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.Windows; using System.Windows.Input; using Microsoft.Toolkit.Diagnostics; @@ -50,15 +51,18 @@ public Breadcrumb() BreadcrumbItems = new ObservableCollection(); _onClickCommand = new RelayCommand(OnClick); - Loaded += (sender, args) => + Loaded += (_, _) => { Guard.IsNotNull(Navigation, nameof(Navigation)); + Navigation.Navigated += OnNavigated; + Navigation.NavigationStack.CollectionChanged += NavigationStackOnCollectionChanged; }; - Unloaded += (sender, args) => + Unloaded += (_, _) => { Navigation.Navigated -= OnNavigated; + Navigation.NavigationStack.CollectionChanged -= NavigationStackOnCollectionChanged; }; } @@ -67,19 +71,35 @@ protected virtual void OnNavigated(INavigation sender, RoutedNavigationEventArgs #if DEBUG System.Diagnostics.Debug.WriteLine($"INFO | {typeof(Breadcrumb)} builded, current nav: {Navigation.GetType()}", "Wpf.Ui.Breadcrumb"); #endif + } - //TODO This event needs some kind of optimization - BreadcrumbItems.Clear(); - foreach (var navigationItem in e.NavigationStack) + private void NavigationStackOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) { - BreadcrumbItems.Add(new BreadcrumbItem() + case NotifyCollectionChangedAction.Add: { - Text = navigationItem.Content as string ?? string.Empty, - PageTag = navigationItem.PageTag, - OnClickCommand = _onClickCommand - }); + foreach (NavigationItem item in e.NewItems!) + { + BreadcrumbItems.Add(BreadcrumbItem.Create(item, _onClickCommand)); + } + + break; + } + case NotifyCollectionChangedAction.Remove: + BreadcrumbItems.RemoveAt(e.OldStartingIndex); + break; + case NotifyCollectionChangedAction.Replace: + var replaceItem = (INavigationItem) e.NewItems![0]; + BreadcrumbItems[0] = BreadcrumbItem.Create(replaceItem, _onClickCommand); + break; + default: + return; } + if (BreadcrumbItems.Count > 1) + BreadcrumbItems[BreadcrumbItems.Count - 2].IsActive = false; + BreadcrumbItems[BreadcrumbItems.Count - 1].IsActive = true; } diff --git a/src/Wpf.Ui/Controls/BreadcrumbItem.cs b/src/Wpf.Ui/Controls/BreadcrumbItem.cs index 3ef634d90..48a5cf507 100644 --- a/src/Wpf.Ui/Controls/BreadcrumbItem.cs +++ b/src/Wpf.Ui/Controls/BreadcrumbItem.cs @@ -1,5 +1,6 @@ using System.Windows; using System.Windows.Input; +using Wpf.Ui.Controls.Interfaces; namespace Wpf.Ui.Controls; @@ -40,4 +41,11 @@ public bool IsActive get => (bool)GetValue(IsActiveProperty); set => SetValue(IsActiveProperty, value); } + + public static BreadcrumbItem Create(INavigationItem item, ICommand onClickCommand) => new BreadcrumbItem() + { + Text = item.Content as string ?? string.Empty, + PageTag = item.PageTag, + OnClickCommand = onClickCommand + }; } diff --git a/src/Wpf.Ui/Controls/Interfaces/INavigation.cs b/src/Wpf.Ui/Controls/Interfaces/INavigation.cs index 14bd4ad3a..09ffd1b8c 100644 --- a/src/Wpf.Ui/Controls/Interfaces/INavigation.cs +++ b/src/Wpf.Ui/Controls/Interfaces/INavigation.cs @@ -39,7 +39,7 @@ public interface INavigation /// /// TODO /// - IEnumerable NavigationStack { get; } + ObservableCollection NavigationStack { get; } /// /// Gets or sets the in which the will be loaded after navigation. diff --git a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs index 47951d42a..1f142f8e8 100644 --- a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs +++ b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs @@ -231,7 +231,7 @@ public event RoutedNavigationEvent NavigatedBackward /// public bool CanGoBack => _navigationManager.CanGoBack; - public IEnumerable NavigationStack => _navigationManager.NavigationStack; + public ObservableCollection NavigationStack => _navigationManager.NavigationStack; /// /// Static constructor overriding default properties. diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index b684dac36..51cb731fb 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -2,6 +2,7 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Windows; using System.Windows.Controls; @@ -24,7 +25,7 @@ internal sealed class NavigationManager : IDisposable private bool _addToNavigationStack; public bool CanGoBack => _history.Count > 1; - public readonly List NavigationStack = new(); + public readonly ObservableCollection NavigationStack = new(); public NavigationManager(Frame frame, IPageService? pageService, INavigationItem[] navigationItems) { From e8b4b48f430736f0eb3975bd23f642612bcca0ff Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Tue, 2 Aug 2022 17:23:27 +0600 Subject: [PATCH 10/28] transition can be changed dynamically --- src/Wpf.Ui/Controls/Navigation/NavigationBase.cs | 9 +++++++-- src/Wpf.Ui/Services/Internal/FrameManager.cs | 13 +++++++------ src/Wpf.Ui/Services/Internal/NavigationManager.cs | 3 +++ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs index 1f142f8e8..ab027be21 100644 --- a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs +++ b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs @@ -29,6 +29,7 @@ public abstract class NavigationBase : System.Windows.Controls.Control, INavigat private NavigationManager _navigationManager = null!; private IPageService? _pageService; private INavigationItem[] _items = null!; + private bool _loaded; #region DependencyProperties @@ -309,6 +310,8 @@ protected virtual void OnLoaded(object sender, RoutedEventArgs e) _navigationManager.NavigateTo(SelectedPageIndex); OnNavigated(); } + + _loaded = true; } protected virtual void OnUnloaded(object sender, RoutedEventArgs e) @@ -393,7 +396,8 @@ private static void OnTransitionDurationChanged(DependencyObject d, DependencyPr if (d is not NavigationBase navigation) return; - //navigation._navigationService.TransitionDuration = (int)e.NewValue; + if (navigation._loaded) + navigation._frameManager.TransitionDuration = (int)e.NewValue; } private static void OnTransitionTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) @@ -401,7 +405,8 @@ private static void OnTransitionTypeChanged(DependencyObject d, DependencyProper if (d is not NavigationBase navigation) return; - //navigation._navigationService.TransitionType = (Animations.TransitionType)e.NewValue; + if (navigation._loaded) + navigation._frameManager.TransitionType = (Animations.TransitionType)e.NewValue; } /// diff --git a/src/Wpf.Ui/Services/Internal/FrameManager.cs b/src/Wpf.Ui/Services/Internal/FrameManager.cs index 179d9def7..d2fce91e0 100644 --- a/src/Wpf.Ui/Services/Internal/FrameManager.cs +++ b/src/Wpf.Ui/Services/Internal/FrameManager.cs @@ -11,14 +11,15 @@ namespace Wpf.Ui.Services.Internal; internal sealed class FrameManager : IDisposable { private readonly Frame _frame; - private readonly int _transitionDuration; - private readonly TransitionType _transitionType; + + public int TransitionDuration { get; set; } + public TransitionType TransitionType { get; set; } public FrameManager(Frame frame, int transitionDuration, TransitionType transitionType) { _frame = frame; - _transitionDuration = transitionDuration; - _transitionType = transitionType; + TransitionDuration = transitionDuration; + TransitionType = transitionType; _frame.NavigationUIVisibility = NavigationUIVisibility.Hidden; @@ -41,8 +42,8 @@ private void OnFrameNavigated(object sender, NavigationEventArgs e) { _frame.NavigationService.RemoveBackEntry(); - if (_transitionDuration > 0 && e.Content != null) - Transitions.ApplyTransition(e.Content, _transitionType, _transitionDuration); + if (TransitionDuration > 0 && e.Content != null) + Transitions.ApplyTransition(e.Content, TransitionType, TransitionDuration); //TODO /*// Finally, the navigation took place internally, diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index 51cb731fb..534aac170 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -65,6 +65,9 @@ public void NavigateTo(string tag, object? dataContext = null) public void NavigateTo(Type type, object? dataContext = null) { var itemId = GetItemId(serviceItem => serviceItem.PageType == type); + if (itemId < 0) + ThrowHelper.ThrowArgumentException($"Item with: {type} type not found"); + NavigateInternal(itemId, dataContext); } From b7bd7993fada9d087acb34de0be5e314bc1786fc Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Tue, 2 Aug 2022 17:29:36 +0600 Subject: [PATCH 11/28] fixed designer --- src/Wpf.Ui/Controls/Breadcrumb.cs | 4 ++++ src/Wpf.Ui/Controls/Navigation/NavigationBase.cs | 7 +++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Wpf.Ui/Controls/Breadcrumb.cs b/src/Wpf.Ui/Controls/Breadcrumb.cs index aefc8557f..12653684a 100644 --- a/src/Wpf.Ui/Controls/Breadcrumb.cs +++ b/src/Wpf.Ui/Controls/Breadcrumb.cs @@ -5,6 +5,7 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; +using System.ComponentModel; using System.Windows; using System.Windows.Input; using Microsoft.Toolkit.Diagnostics; @@ -51,6 +52,9 @@ public Breadcrumb() BreadcrumbItems = new ObservableCollection(); _onClickCommand = new RelayCommand(OnClick); + if (DesignerProperties.GetIsInDesignMode(this)) + return; + Loaded += (_, _) => { Guard.IsNotNull(Navigation, nameof(Navigation)); diff --git a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs index ab027be21..43cfdb68a 100644 --- a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs +++ b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs @@ -260,7 +260,9 @@ protected NavigationBase() // Let the NavigationItem children be able to get me. NavigationParent = this; - // Loaded does not have override + if (DesignerProperties.GetIsInDesignMode(this)) + return; + Loaded += OnLoaded; Unloaded += OnUnloaded; } @@ -295,9 +297,6 @@ public void NavigateTo(Type type, object? dataContext = null) /// protected virtual void OnLoaded(object sender, RoutedEventArgs e) { - if (DesignerProperties.GetIsInDesignMode(this)) - return; - Guard.IsNotNull(Frame, nameof(Frame)); _items = MergeItems(); From e101be1dde97a8f5d3a2050f537d507d52a82a50 Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Tue, 2 Aug 2022 18:57:59 +0600 Subject: [PATCH 12/28] implemented navigation without di --- src/Wpf.Ui.Demo/Views/Container.xaml.cs | 2 +- src/Wpf.Ui.SimpleDemo/MainWindow.xaml | 20 ++++-- .../Common/RoutedNavigationEventArgs.cs | 9 +-- .../Controls/Interfaces/INavigationItem.cs | 5 +- .../Controls/Navigation/NavigationBase.cs | 31 ++++------ .../Services/Internal/NavigationManager.cs | 62 +++++++++++++++++-- 6 files changed, 90 insertions(+), 39 deletions(-) diff --git a/src/Wpf.Ui.Demo/Views/Container.xaml.cs b/src/Wpf.Ui.Demo/Views/Container.xaml.cs index 125ecce23..92430ad2a 100644 --- a/src/Wpf.Ui.Demo/Views/Container.xaml.cs +++ b/src/Wpf.Ui.Demo/Views/Container.xaml.cs @@ -156,7 +156,7 @@ private void TrayMenuItem_OnClick(object sender, RoutedEventArgs e) private void RootNavigation_OnNavigated(INavigation sender, RoutedNavigationEventArgs e) { - var current = e.NavigationStack.ElementAt(0); + var current = sender.NavigationStack[sender.NavigationStack.Count - 1]; System.Diagnostics.Debug.WriteLine($"DEBUG | WPF UI Navigated to: {current}", "Wpf.Ui.Demo"); diff --git a/src/Wpf.Ui.SimpleDemo/MainWindow.xaml b/src/Wpf.Ui.SimpleDemo/MainWindow.xaml index 9e4f5fe78..2a82b0442 100644 --- a/src/Wpf.Ui.SimpleDemo/MainWindow.xaml +++ b/src/Wpf.Ui.SimpleDemo/MainWindow.xaml @@ -29,12 +29,24 @@ Icon="Home24" PageTag="home" PageType="{x:Type local:DashboardPage}" /> - - + + - - + + public class RoutedNavigationEventArgs : RoutedEventArgs { - /// - /// Currently displayed page. - /// - public IEnumerable NavigationStack { get; set; } - /// /// Constructor for . /// /// The new value that the SourceProperty is being set to. /// The new value that the Property is being set to. /// Currently displayed page. - public RoutedNavigationEventArgs(RoutedEvent routedEvent, object source, IEnumerable navigationStack) : base( + public RoutedNavigationEventArgs(RoutedEvent routedEvent, object source) : base( routedEvent, source) { - NavigationStack = navigationStack; + } } diff --git a/src/Wpf.Ui/Controls/Interfaces/INavigationItem.cs b/src/Wpf.Ui/Controls/Interfaces/INavigationItem.cs index c571d82ce..e9ab5fe1b 100644 --- a/src/Wpf.Ui/Controls/Interfaces/INavigationItem.cs +++ b/src/Wpf.Ui/Controls/Interfaces/INavigationItem.cs @@ -3,6 +3,7 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. +#nullable enable using System; using System.ComponentModel; using System.Windows; @@ -43,12 +44,12 @@ public interface INavigationItem /// /// A inherited from that defines page of the item. /// - Type PageType { get; set; } + Type? PageType { get; set; } /// /// Absolute path to the XAML template based on and . /// - Uri AbsolutePageSource { get; } + Uri? AbsolutePageSource { get; } /// /// Add / Remove ClickEvent handler diff --git a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs index 43cfdb68a..8dfaed087 100644 --- a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs +++ b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs @@ -372,7 +372,7 @@ static void MoveFocus(FrameworkElement element, FocusNavigationDirection directi /// protected virtual void OnNavigated() { - var newEvent = new RoutedNavigationEventArgs(NavigatedEvent, this, NavigationStack); + var newEvent = new RoutedNavigationEventArgs(NavigatedEvent, this); RaiseEvent(newEvent); } @@ -384,9 +384,6 @@ protected virtual void OnNavigationItemClicked(object sender, RoutedEventArgs e) if (sender is not INavigationItem navigationItem) return; - if (navigationItem.AbsolutePageSource == null && navigationItem.PageType == null) - return; - NavigateTo(navigationItem.PageTag); } @@ -421,32 +418,26 @@ private static void OnTransitionTypeChanged(DependencyObject d, DependencyProper private INavigationItem[] MergeItems() { - var overallCount = Items.Count + Footer.Count + HiddenItems.Count; - INavigationItem[] buffer = new INavigationItem[overallCount - 1]; - int i = 0; + List buffer = new List(Items.Count); - AddToBufferList(Items); - AddToBufferList(Footer); - AddToBufferList(HiddenItems, item => item.IsHidden = true); + AddToBuffer(Items); + AddToBuffer(Footer); + AddToBuffer(HiddenItems, item => item.IsHidden = true); - return buffer; + return buffer.ToArray(); - void AddToBufferList(IEnumerable list, Action? action = null) + void AddToBuffer(IEnumerable list, Action? action = null) { foreach (var addedItem in list) { if (addedItem is not INavigationItem item) continue; + if (item.PageType is not null) + item.Click += OnNavigationItemClicked; + action?.Invoke(item); - AddToBuffer(item); + buffer.Add(item); } } - - void AddToBuffer(INavigationItem item) - { - item.Click += OnNavigationItemClicked; - buffer[i] = item; - i++; - } } } diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index 534aac170..6c6aa6b7e 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -16,7 +16,7 @@ internal sealed class NavigationManager : IDisposable { private readonly Frame _frame; private readonly INavigationItem[] _navigationItems; - private readonly FrameworkElement[] _instances; + private readonly FrameworkElement?[] _instances; private readonly List _history = new(); private readonly IPageService? _pageService; private readonly ArrayPool _arrayPool = ArrayPool.Create(); @@ -76,6 +76,8 @@ public void NavigateTo(int id, object? dataContext = null) NavigateInternal(id, dataContext); } + #region NavigationInternal + private void NavigateBack() { if (_history.Count <= 1) @@ -142,13 +144,15 @@ private void AddToNavigationStack(INavigationItem item) } } + #endregion + #region PerformNavigation private void PerformNavigation((int itemId, INavigationItem item) itemData, object? dataContext) { if (_pageService is not null) { - NavigateByService(itemData.item); + NavigateByService(itemData); return; } @@ -161,9 +165,17 @@ private void PerformNavigation((int itemId, INavigationItem item) itemData, obje NavigateWithoutCache(itemData.item, dataContext); } - private void NavigateByService(INavigationItem item) + private void NavigateByService((int itemId, INavigationItem item) itemData) { - var instance = _pageService!.GetPage(item.PageType); + if (itemData.item.PageType is null) + return; + + if (_instances[itemData.itemId] is not null) + { + //TODO + } + + var instance = _pageService!.GetPage(itemData.item.PageType); Guard.IsNotNull(instance, "Page instance"); _frame.Navigate(instance); @@ -171,16 +183,54 @@ private void NavigateByService(INavigationItem item) private void NavigateWithCache((int itemId, INavigationItem item) itemData, object? dataContext) { + if (_instances[itemData.itemId] is null) + { + _instances[itemData.itemId] = NavigateWithoutCache(itemData.item, dataContext); + return; + } + + var instance = _instances[itemData.itemId]!; + + if (dataContext is not null) + instance.DataContext = dataContext; + _frame.Navigate(instance); + +#if DEBUG + System.Diagnostics.Debug.WriteLine( + $"DEBUG | {itemData.item.PageTag} navigated internally, with cache by it's instance."); +#endif } - private void NavigateWithoutCache(INavigationItem item, object? dataContext) + private FrameworkElement? NavigateWithoutCache(INavigationItem item, object? dataContext) { + FrameworkElement? instance = null; + + if (item.PageType is not null) + { + instance = NavigationServiceActivator.CreateInstance(item.PageType, dataContext); + _frame.Navigate(instance); + } + + if (item.AbsolutePageSource is not null) + { + _frame.Navigate(item.AbsolutePageSource); + } + +#if DEBUG + string navigationType = item.PageType is not null ? "type" : "source"; + + System.Diagnostics.Debug.WriteLine( + $"DEBUG | {item.PageTag} navigated internally, without cache by it's {navigationType}."); +#endif + return instance; } #endregion + #region PrivateMethods + private void ClearNavigationStack(int navigationStackItemIndex) { var navigationStackCount = NavigationStack.Count; @@ -213,4 +263,6 @@ private int GetItemId(Func prediction) return selectedIndex; } + + #endregion } From fe6d8a1e312251ec1e306480f9a481361ece1e2b Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Tue, 2 Aug 2022 18:58:51 +0600 Subject: [PATCH 13/28] breadcrumb loading fix --- src/Wpf.Ui/Controls/Breadcrumb.cs | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/Wpf.Ui/Controls/Breadcrumb.cs b/src/Wpf.Ui/Controls/Breadcrumb.cs index 12653684a..0d4cd7fce 100644 --- a/src/Wpf.Ui/Controls/Breadcrumb.cs +++ b/src/Wpf.Ui/Controls/Breadcrumb.cs @@ -58,36 +58,31 @@ public Breadcrumb() Loaded += (_, _) => { Guard.IsNotNull(Navigation, nameof(Navigation)); - - Navigation.Navigated += OnNavigated; Navigation.NavigationStack.CollectionChanged += NavigationStackOnCollectionChanged; + + if (Navigation.NavigationStack.Count <= 0) + return; + + foreach (var item in Navigation.NavigationStack) + BreadcrumbItems.Add( BreadcrumbItem.Create(item, _onClickCommand)); + + BreadcrumbItems[BreadcrumbItems.Count - 1].IsActive = true; }; Unloaded += (_, _) => { - Navigation.Navigated -= OnNavigated; Navigation.NavigationStack.CollectionChanged -= NavigationStackOnCollectionChanged; }; } - protected virtual void OnNavigated(INavigation sender, RoutedNavigationEventArgs e) - { -#if DEBUG - System.Diagnostics.Debug.WriteLine($"INFO | {typeof(Breadcrumb)} builded, current nav: {Navigation.GetType()}", "Wpf.Ui.Breadcrumb"); -#endif - } - private void NavigationStackOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: { - foreach (NavigationItem item in e.NewItems!) - { - BreadcrumbItems.Add(BreadcrumbItem.Create(item, _onClickCommand)); - } - + var newItem = (INavigationItem) e.NewItems![0]; + BreadcrumbItems.Add(BreadcrumbItem.Create(newItem, _onClickCommand)); break; } case NotifyCollectionChangedAction.Remove: @@ -95,7 +90,9 @@ private void NavigationStackOnCollectionChanged(object sender, NotifyCollectionC break; case NotifyCollectionChangedAction.Replace: var replaceItem = (INavigationItem) e.NewItems![0]; - BreadcrumbItems[0] = BreadcrumbItem.Create(replaceItem, _onClickCommand); + var breadcrumbItem = BreadcrumbItem.Create(replaceItem, _onClickCommand); + + BreadcrumbItems[0] = breadcrumbItem; break; default: return; @@ -105,6 +102,7 @@ private void NavigationStackOnCollectionChanged(object sender, NotifyCollectionC BreadcrumbItems[BreadcrumbItems.Count - 2].IsActive = false; BreadcrumbItems[BreadcrumbItems.Count - 1].IsActive = true; + } private void OnClick(object obj) From 5c7acfe456e9e0e4b204329a39c732233e6a94d4 Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Tue, 2 Aug 2022 19:13:58 +0600 Subject: [PATCH 14/28] implemented ClearCache --- src/Wpf.Ui/Controls/Interfaces/INavigation.cs | 5 +++++ src/Wpf.Ui/Controls/Navigation/NavigationBase.cs | 7 +++---- .../Services/Internal/NavigationManager.cs | 16 ++++++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/Wpf.Ui/Controls/Interfaces/INavigation.cs b/src/Wpf.Ui/Controls/Interfaces/INavigation.cs index 09ffd1b8c..1eeb093aa 100644 --- a/src/Wpf.Ui/Controls/Interfaces/INavigation.cs +++ b/src/Wpf.Ui/Controls/Interfaces/INavigation.cs @@ -91,6 +91,11 @@ public interface INavigation /// void SetIPageService(IPageService pageService); + /// + /// + /// + void Preload(); + /// /// Clears all initialized instances of the pages. /// diff --git a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs index 8dfaed087..67043b777 100644 --- a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs +++ b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs @@ -272,11 +272,10 @@ public void SetIPageService(IPageService pageService) _pageService = pageService; } + public void Preload() => _navigationManager.Preload(); + /// - public void ClearCache() - { - - } + public void ClearCache() => _navigationManager.ClearCache(); /// public void NavigateTo(string pageTag, object? dataContext = null) diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index 6c6aa6b7e..1f6e9ace7 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -41,6 +41,22 @@ public void Dispose() } + public void Preload() + { + //Why URI + //Application.LoadComponent() + + ThrowHelper.ThrowNotSupportedException("Preloading currently not supported"); + } + + public void ClearCache() + { + for (int i = 0; i < _instances.Length; i++) + { + _instances[i] = null; + } + } + public void NavigateTo(string tag, object? dataContext = null) { Guard.IsNotNullOrEmpty(tag, nameof(tag)); From 0032c6e973ccbdc6f3f13a77d22716b4d4bbacd3 Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Wed, 3 Aug 2022 08:15:43 +0600 Subject: [PATCH 15/28] added breadcrumb demo --- src/Wpf.Ui.Demo/App.xaml.cs | 5 ++- .../ViewModels/BreadcrumbPagesViewModel.cs | 31 +++++++++++++++++ .../ViewModels/ButtonsViewModel.cs | 2 +- .../ViewModels/DashboardViewModel.cs | 8 ++--- src/Wpf.Ui.Demo/Views/Container.xaml | 7 ++-- src/Wpf.Ui.Demo/Views/Container.xaml.cs | 2 -- .../Views/Pages/BreadcrumbDemo/Page1.xaml | 25 ++++++++++++++ .../Views/Pages/BreadcrumbDemo/Page1.xaml.cs | 11 ++++++ .../Views/Pages/BreadcrumbDemo/Page2.xaml | 25 ++++++++++++++ .../Views/Pages/BreadcrumbDemo/Page2.xaml.cs | 11 ++++++ .../Views/Pages/BreadcrumbDemo/Page3.xaml | 25 ++++++++++++++ .../Views/Pages/BreadcrumbDemo/Page3.xaml.cs | 11 ++++++ src/Wpf.Ui.Demo/Views/Pages/Controls.xaml | 19 ++++++++++- src/Wpf.Ui.Demo/Views/Pages/Controls.xaml.cs | 15 ++++++-- .../Mvvm/Contracts/INavigationService.cs | 7 ++-- src/Wpf.Ui/Mvvm/Services/NavigationService.cs | 8 ++--- .../Services/Internal/NavigationManager.cs | 34 ++++++++++++------- 17 files changed, 213 insertions(+), 33 deletions(-) create mode 100644 src/Wpf.Ui.Demo/ViewModels/BreadcrumbPagesViewModel.cs create mode 100644 src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page1.xaml create mode 100644 src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page1.xaml.cs create mode 100644 src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page2.xaml create mode 100644 src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page2.xaml.cs create mode 100644 src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page3.xaml create mode 100644 src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page3.xaml.cs diff --git a/src/Wpf.Ui.Demo/App.xaml.cs b/src/Wpf.Ui.Demo/App.xaml.cs index a4ad04789..2af198870 100644 --- a/src/Wpf.Ui.Demo/App.xaml.cs +++ b/src/Wpf.Ui.Demo/App.xaml.cs @@ -93,9 +93,8 @@ public partial class App services.AddScoped(); services.AddScoped(); - - services.AddScoped(); - services.AddScoped(); + + services.AddScoped(); // Test windows services.AddTransient(); diff --git a/src/Wpf.Ui.Demo/ViewModels/BreadcrumbPagesViewModel.cs b/src/Wpf.Ui.Demo/ViewModels/BreadcrumbPagesViewModel.cs new file mode 100644 index 000000000..a71ff847c --- /dev/null +++ b/src/Wpf.Ui.Demo/ViewModels/BreadcrumbPagesViewModel.cs @@ -0,0 +1,31 @@ +using System.Windows.Input; +using Microsoft.Toolkit.Mvvm.Input; +using Wpf.Ui.Mvvm.Contracts; + +namespace Wpf.Ui.Demo.ViewModels; + +public class BreadcrumbPagesViewModel +{ + public RelayCommand OnClickCommand { get; } + public ICommand OnNavigateBackCommand { get; } + + private readonly INavigationService _navigationService; + + public BreadcrumbPagesViewModel(INavigationService navigationService) + { + _navigationService = navigationService; + + OnClickCommand = new RelayCommand(OnClick); + OnNavigateBackCommand = new RelayCommand(OnNavigateBack); + } + + private void OnClick(string pageTag) + { + _navigationService.NavigateTo($"//{pageTag}", this); + } + + private void OnNavigateBack() + { + _navigationService.NavigateTo(".."); + } +} diff --git a/src/Wpf.Ui.Demo/ViewModels/ButtonsViewModel.cs b/src/Wpf.Ui.Demo/ViewModels/ButtonsViewModel.cs index 877d7fb81..8435b08d3 100644 --- a/src/Wpf.Ui.Demo/ViewModels/ButtonsViewModel.cs +++ b/src/Wpf.Ui.Demo/ViewModels/ButtonsViewModel.cs @@ -30,6 +30,6 @@ public ButtonsViewModel(INavigationService navigationService) private void OnShowMore(string parameter) { - _navigationService.Navigate(typeof(Views.Pages.Input)); + _navigationService.NavigateTo("//input"); } } diff --git a/src/Wpf.Ui.Demo/ViewModels/DashboardViewModel.cs b/src/Wpf.Ui.Demo/ViewModels/DashboardViewModel.cs index 038786190..4c7c89793 100644 --- a/src/Wpf.Ui.Demo/ViewModels/DashboardViewModel.cs +++ b/src/Wpf.Ui.Demo/ViewModels/DashboardViewModel.cs @@ -47,19 +47,19 @@ private void OnNavigate(string parameter) switch (parameter) { case "navigate_to_input": - _navigationService.Navigate(typeof(Views.Pages.Input)); + _navigationService.NavigateTo(typeof(Views.Pages.Input)); return; case "navigate_to_controls": - _navigationService.Navigate(typeof(Views.Pages.Controls)); + _navigationService.NavigateTo(typeof(Views.Pages.Controls)); return; case "navigate_to_colors": - _navigationService.Navigate(typeof(Views.Pages.Colors)); + _navigationService.NavigateTo(typeof(Views.Pages.Colors)); return; case "navigate_to_icons": - _navigationService.Navigate(typeof(Views.Pages.Icons)); + _navigationService.NavigateTo(typeof(Views.Pages.Icons)); return; } } diff --git a/src/Wpf.Ui.Demo/Views/Container.xaml b/src/Wpf.Ui.Demo/Views/Container.xaml index 7a8c2cd76..c4f93ec6a 100644 --- a/src/Wpf.Ui.Demo/Views/Container.xaml +++ b/src/Wpf.Ui.Demo/Views/Container.xaml @@ -2,6 +2,7 @@ x:Class="Wpf.Ui.Demo.Views.Container" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:breadcrumbDemo="clr-namespace:Wpf.Ui.Demo.Views.Pages.BreadcrumbDemo" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:demo="clr-namespace:Wpf.Ui.Demo" xmlns:diagnostics="clr-namespace:Wpf.Ui.Demo.Views.Diagnostics" @@ -99,8 +100,10 @@ Icon="DarkTheme24" /> - - + + + + diff --git a/src/Wpf.Ui.Demo/Views/Container.xaml.cs b/src/Wpf.Ui.Demo/Views/Container.xaml.cs index 92430ad2a..0bb13e39c 100644 --- a/src/Wpf.Ui.Demo/Views/Container.xaml.cs +++ b/src/Wpf.Ui.Demo/Views/Container.xaml.cs @@ -135,8 +135,6 @@ private async void InvokeSplashScreen() RootMainGrid.Visibility = Visibility.Visible; NavigateTo(typeof(Pages.Dashboard)); - NavigateTo("//test"); - NavigateTo("//test2"); _taskBarService.SetState(this, TaskBarProgressState.None); } diff --git a/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page1.xaml b/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page1.xaml new file mode 100644 index 000000000..cce9435fe --- /dev/null +++ b/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page1.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + diff --git a/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page1.xaml.cs b/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page1.xaml.cs new file mode 100644 index 000000000..2f18ed5a9 --- /dev/null +++ b/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page1.xaml.cs @@ -0,0 +1,11 @@ +using System.Windows.Controls; + +namespace Wpf.Ui.Demo.Views.Pages.BreadcrumbDemo; + +public partial class Page1 : Page +{ + public Page1() + { + InitializeComponent(); + } +} diff --git a/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page2.xaml b/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page2.xaml new file mode 100644 index 000000000..36b3303e5 --- /dev/null +++ b/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page2.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + diff --git a/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page2.xaml.cs b/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page2.xaml.cs new file mode 100644 index 000000000..452815201 --- /dev/null +++ b/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page2.xaml.cs @@ -0,0 +1,11 @@ +using System.Windows.Controls; + +namespace Wpf.Ui.Demo.Views.Pages.BreadcrumbDemo; + +public partial class Page2 : Page +{ + public Page2() + { + InitializeComponent(); + } +} diff --git a/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page3.xaml b/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page3.xaml new file mode 100644 index 000000000..bd897d338 --- /dev/null +++ b/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page3.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + diff --git a/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page3.xaml.cs b/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page3.xaml.cs new file mode 100644 index 000000000..5c2059e40 --- /dev/null +++ b/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page3.xaml.cs @@ -0,0 +1,11 @@ +using System.Windows.Controls; + +namespace Wpf.Ui.Demo.Views.Pages.BreadcrumbDemo; + +public partial class Page3 : Page +{ + public Page3() + { + InitializeComponent(); + } +} diff --git a/src/Wpf.Ui.Demo/Views/Pages/Controls.xaml b/src/Wpf.Ui.Demo/Views/Pages/Controls.xaml index c00c43d1e..f52cba0da 100644 --- a/src/Wpf.Ui.Demo/Views/Pages/Controls.xaml +++ b/src/Wpf.Ui.Demo/Views/Pages/Controls.xaml @@ -29,6 +29,7 @@ + @@ -84,6 +85,22 @@ Text="Opens the MessageBox." /> + + + + + + + (); + _navigation.NavigateTo("//page1", viewModel); + } } diff --git a/src/Wpf.Ui/Mvvm/Contracts/INavigationService.cs b/src/Wpf.Ui/Mvvm/Contracts/INavigationService.cs index bbf3feca5..73bf29cfa 100644 --- a/src/Wpf.Ui/Mvvm/Contracts/INavigationService.cs +++ b/src/Wpf.Ui/Mvvm/Contracts/INavigationService.cs @@ -3,6 +3,7 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. +#nullable enable using System; using System.Windows.Controls; using Wpf.Ui.Controls.Interfaces; @@ -56,11 +57,13 @@ public interface INavigationService /// Lets you navigate to the selected page based on it's tag. Should be used with . /// /// Tag of the page. - void Navigate(string pageTag); + /// + void NavigateTo(string pageTag, object? dataContext = null); /// /// TODO /// /// - void Navigate(Type type); + /// + void NavigateTo(Type type, object? dataContext = null); } diff --git a/src/Wpf.Ui/Mvvm/Services/NavigationService.cs b/src/Wpf.Ui/Mvvm/Services/NavigationService.cs index 83fef0202..3aa69d58f 100644 --- a/src/Wpf.Ui/Mvvm/Services/NavigationService.cs +++ b/src/Wpf.Ui/Mvvm/Services/NavigationService.cs @@ -81,14 +81,14 @@ public void SetPageService(IPageService pageService) } /// - public void Navigate(string pageTag) + public void NavigateTo(string pageTag, object? dataContext = null) { - NavigationControl.NavigateTo(pageTag); + NavigationControl.NavigateTo(pageTag, dataContext); } /// - public void Navigate(Type type) + public void NavigateTo(Type type, object? dataContext = null) { - NavigationControl.NavigateTo(type); + NavigationControl.NavigateTo(type, dataContext); } } diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index 1f6e9ace7..150020b7c 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -120,7 +120,13 @@ private void NavigateInternal(int itemId, object? dataContext) AddToNavigationStack(item); - item.IsActive = true; + if (NavigationStack.Count > 1) + { + if (NavigationStack[NavigationStack.Count - 1].IsHidden) + item.IsActive = true; + } + else + item.IsActive = true; if (_isBackwardsNavigated) { @@ -166,11 +172,8 @@ private void AddToNavigationStack(INavigationItem item) private void PerformNavigation((int itemId, INavigationItem item) itemData, object? dataContext) { - if (_pageService is not null) - { - NavigateByService(itemData); + if (_pageService is not null && NavigateByService(itemData)) return; - } if (itemData.item.Cache) { @@ -178,23 +181,28 @@ private void PerformNavigation((int itemId, INavigationItem item) itemData, obje return; } - NavigateWithoutCache(itemData.item, dataContext); + if (NavigateWithoutCache(itemData.item, dataContext) is not null) + return; + + ThrowHelper.ThrowInvalidOperationException("failed to navigate"); } - private void NavigateByService((int itemId, INavigationItem item) itemData) + private bool NavigateByService((int itemId, INavigationItem item) itemData) { if (itemData.item.PageType is null) - return; + return false; - if (_instances[itemData.itemId] is not null) + /*if (_instances[itemData.itemId] is not null) { //TODO - } + }*/ var instance = _pageService!.GetPage(itemData.item.PageType); - Guard.IsNotNull(instance, "Page instance"); + if (instance is null) + return false; _frame.Navigate(instance); + return true; } private void NavigateWithCache((int itemId, INavigationItem item) itemData, object? dataContext) @@ -234,12 +242,14 @@ private void NavigateWithCache((int itemId, INavigationItem item) itemData, obje } #if DEBUG + if (instance is null) + return instance; + string navigationType = item.PageType is not null ? "type" : "source"; System.Diagnostics.Debug.WriteLine( $"DEBUG | {item.PageTag} navigated internally, without cache by it's {navigationType}."); #endif - return instance; } From 9a2a6bb1a199f81dcde6aa4939956686a670996f Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Wed, 3 Aug 2022 10:46:14 +0600 Subject: [PATCH 16/28] fixed navigation backwards with breadcrumb elements --- src/Wpf.Ui.Demo/Views/Container.xaml | 5 ++ .../Views/Pages/BreadcrumbDemo/Page1.xaml | 4 +- .../Views/Pages/BreadcrumbDemo/Page2.xaml | 4 +- .../Views/Pages/BreadcrumbDemo/Page3.xaml | 4 +- .../Controls/Interfaces/INavigationItem.cs | 5 ++ .../Controls/Navigation/NavigationBase.cs | 2 +- src/Wpf.Ui/Controls/NavigationItem.cs | 3 + .../Services/Internal/NavigationManager.cs | 83 ++++++++++++++----- 8 files changed, 82 insertions(+), 28 deletions(-) diff --git a/src/Wpf.Ui.Demo/Views/Container.xaml b/src/Wpf.Ui.Demo/Views/Container.xaml index c4f93ec6a..c6a971ab5 100644 --- a/src/Wpf.Ui.Demo/Views/Container.xaml +++ b/src/Wpf.Ui.Demo/Views/Container.xaml @@ -244,5 +244,10 @@ + + diff --git a/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page1.xaml b/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page1.xaml index cce9435fe..d3e8394ee 100644 --- a/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page1.xaml +++ b/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page1.xaml @@ -18,8 +18,8 @@ - + diff --git a/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page2.xaml b/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page2.xaml index 36b3303e5..583c4cc4a 100644 --- a/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page2.xaml +++ b/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page2.xaml @@ -18,8 +18,8 @@ - + diff --git a/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page3.xaml b/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page3.xaml index bd897d338..7b4089dd7 100644 --- a/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page3.xaml +++ b/src/Wpf.Ui.Demo/Views/Pages/BreadcrumbDemo/Page3.xaml @@ -18,8 +18,8 @@ - + diff --git a/src/Wpf.Ui/Controls/Interfaces/INavigationItem.cs b/src/Wpf.Ui/Controls/Interfaces/INavigationItem.cs index e9ab5fe1b..9ff4c1fa7 100644 --- a/src/Wpf.Ui/Controls/Interfaces/INavigationItem.cs +++ b/src/Wpf.Ui/Controls/Interfaces/INavigationItem.cs @@ -61,4 +61,9 @@ public interface INavigationItem /// TODO /// internal bool IsHidden { get; set; } + + /// + /// + /// + internal bool WasInBreadcrumb { get; set; } } diff --git a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs index 67043b777..e409bd1ca 100644 --- a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs +++ b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs @@ -230,7 +230,7 @@ public event RoutedNavigationEvent NavigatedBackward #endregion /// - public bool CanGoBack => _navigationManager.CanGoBack; + public bool CanGoBack => !DesignerProperties.GetIsInDesignMode(this) && _navigationManager.CanGoBack; public ObservableCollection NavigationStack => _navigationManager.NavigationStack; diff --git a/src/Wpf.Ui/Controls/NavigationItem.cs b/src/Wpf.Ui/Controls/NavigationItem.cs index 95ab1cbe3..427cd3781 100644 --- a/src/Wpf.Ui/Controls/NavigationItem.cs +++ b/src/Wpf.Ui/Controls/NavigationItem.cs @@ -240,6 +240,9 @@ protected virtual Uri BaseUri /// bool INavigationItem.IsHidden { get; set; } + /// + bool INavigationItem.WasInBreadcrumb { get; set; } + /// protected override void OnContentChanged(object oldContent, object newContent) { diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index 150020b7c..05e30701e 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -20,6 +20,7 @@ internal sealed class NavigationManager : IDisposable private readonly List _history = new(); private readonly IPageService? _pageService; private readonly ArrayPool _arrayPool = ArrayPool.Create(); + private readonly List _navigationStackHistory = new(); private bool _isBackwardsNavigated; private bool _addToNavigationStack; @@ -38,7 +39,7 @@ public NavigationManager(Frame frame, IPageService? pageService, INavigationItem public void Dispose() { - + _navigationStackHistory.Clear(); } public void Preload() @@ -119,31 +120,39 @@ private void NavigateInternal(int itemId, object? dataContext) } AddToNavigationStack(item); - - if (NavigationStack.Count > 1) - { - if (NavigationStack[NavigationStack.Count - 1].IsHidden) - item.IsActive = true; - } - else - item.IsActive = true; - - if (_isBackwardsNavigated) - { - _isBackwardsNavigated = false; - _history.RemoveAt(_history.LastIndexOf(_history[_history.Count - 2])); - _history.RemoveAt(_history.LastIndexOf(_history[_history.Count - 1])); - } - - _history.Add(itemId); + ActivateItem(item); + AddToHistory(itemId); PerformNavigation((itemId, item), dataContext); } private void AddToNavigationStack(INavigationItem item) { + if (_isBackwardsNavigated && item.WasInBreadcrumb) + { + if (_navigationStackHistory.Count > 1) + { + for (var i = 0; i < _navigationStackHistory.Count - 1; i++) + { + _addToNavigationStack = true; + + var historyItem = _navigationStackHistory[i]; + historyItem.WasInBreadcrumb = false; + AddToNavigationStack(historyItem); + } + + _navigationStackHistory.Clear(); + } + + item.WasInBreadcrumb = false; + _addToNavigationStack = true; + } + if (_addToNavigationStack && !NavigationStack.Contains(item)) + { + item.WasInBreadcrumb = true; NavigationStack.Add(item); + } if (!item.IsHidden && !_addToNavigationStack) { @@ -161,9 +170,34 @@ private void AddToNavigationStack(INavigationItem item) navItem.IsActive = false; var index = NavigationStack.IndexOf(item); - if (index < navigationStackCount - 1) + if (index < navigationStackCount - 1 && _navigationStackHistory.Count == 0) ClearNavigationStack(++index); } + + _addToNavigationStack = false; + } + + private void ActivateItem(INavigationItem item) + { + if (NavigationStack.Count > 1) + { + if (NavigationStack[NavigationStack.Count - 1].IsHidden) + item.IsActive = true; + } + else + item.IsActive = true; + } + + private void AddToHistory(int itemId) + { + if (_isBackwardsNavigated) + { + _isBackwardsNavigated = false; + _history.RemoveAt(_history.LastIndexOf(_history[_history.Count - 2])); + _history.RemoveAt(_history.LastIndexOf(_history[_history.Count - 1])); + } + + _history.Add(itemId); } #endregion @@ -260,7 +294,8 @@ private void NavigateWithCache((int itemId, INavigationItem item) itemData, obje private void ClearNavigationStack(int navigationStackItemIndex) { var navigationStackCount = NavigationStack.Count; - var buffer = _arrayPool.Rent(navigationStackCount - navigationStackItemIndex); + var length = navigationStackCount - navigationStackItemIndex; + var buffer = _arrayPool.Rent(length); int i = 0; for (int j = navigationStackItemIndex; j <= navigationStackCount - 1; j++) @@ -269,9 +304,15 @@ private void ClearNavigationStack(int navigationStackItemIndex) i++; } - foreach (var item in buffer) + for (var index = 0; index < length; index++) + { + var item = buffer[index]; NavigationStack.Remove(item); + if (length > 1) + _navigationStackHistory.Add(item); + } + _arrayPool.Return(buffer, true); } From 29df3da6b050b732fb1428796eeea4685cb675f1 Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Wed, 3 Aug 2022 13:38:39 +0600 Subject: [PATCH 17/28] the stack navigation parameter has been changed --- src/Wpf.Ui.Demo/ViewModels/BreadcrumbPagesViewModel.cs | 2 +- src/Wpf.Ui.Demo/ViewModels/ButtonsViewModel.cs | 2 +- src/Wpf.Ui.Demo/Views/Pages/Controls.xaml.cs | 2 +- src/Wpf.Ui/Services/Internal/NavigationManager.cs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Wpf.Ui.Demo/ViewModels/BreadcrumbPagesViewModel.cs b/src/Wpf.Ui.Demo/ViewModels/BreadcrumbPagesViewModel.cs index a71ff847c..80037cd82 100644 --- a/src/Wpf.Ui.Demo/ViewModels/BreadcrumbPagesViewModel.cs +++ b/src/Wpf.Ui.Demo/ViewModels/BreadcrumbPagesViewModel.cs @@ -21,7 +21,7 @@ public BreadcrumbPagesViewModel(INavigationService navigationService) private void OnClick(string pageTag) { - _navigationService.NavigateTo($"//{pageTag}", this); + _navigationService.NavigateTo($"/{pageTag}", this); } private void OnNavigateBack() diff --git a/src/Wpf.Ui.Demo/ViewModels/ButtonsViewModel.cs b/src/Wpf.Ui.Demo/ViewModels/ButtonsViewModel.cs index 8435b08d3..d792b7a92 100644 --- a/src/Wpf.Ui.Demo/ViewModels/ButtonsViewModel.cs +++ b/src/Wpf.Ui.Demo/ViewModels/ButtonsViewModel.cs @@ -30,6 +30,6 @@ public ButtonsViewModel(INavigationService navigationService) private void OnShowMore(string parameter) { - _navigationService.NavigateTo("//input"); + _navigationService.NavigateTo("/input"); } } diff --git a/src/Wpf.Ui.Demo/Views/Pages/Controls.xaml.cs b/src/Wpf.Ui.Demo/Views/Pages/Controls.xaml.cs index 7a25490b1..a1a574d82 100644 --- a/src/Wpf.Ui.Demo/Views/Pages/Controls.xaml.cs +++ b/src/Wpf.Ui.Demo/Views/Pages/Controls.xaml.cs @@ -115,6 +115,6 @@ private void MessageBox_RightButtonClick(object sender, System.Windows.RoutedEve private void OnBreadcrumbButtonClick(object sender, RoutedEventArgs e) { var viewModel = _serviceProvider.GetRequiredService(); - _navigation.NavigateTo("//page1", viewModel); + _navigation.NavigateTo("/page1", viewModel); } } diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index 05e30701e..0a305a7ad 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -68,9 +68,9 @@ public void NavigateTo(string tag, object? dataContext = null) return; } - _addToNavigationStack = tag.Contains("//"); + _addToNavigationStack = tag.Contains("/"); if (_addToNavigationStack) - tag = tag.Replace("//", string.Empty).Trim(); + tag = tag.Remove(0, 1); var itemId = GetItemId(item => item.PageTag == tag); if (itemId < 0) From bfef37e5858c51b36ee454670eaaa3e59ddbee93 Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Fri, 5 Aug 2022 09:07:53 +0600 Subject: [PATCH 18/28] unsubscribed from events --- src/Wpf.Ui/Controls/Navigation/NavigationBase.cs | 7 +++++++ src/Wpf.Ui/Services/Internal/NavigationManager.cs | 2 ++ 2 files changed, 9 insertions(+) diff --git a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs index e409bd1ca..344aa4108 100644 --- a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs +++ b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs @@ -267,11 +267,13 @@ protected NavigationBase() Unloaded += OnUnloaded; } + /// public void SetIPageService(IPageService pageService) { _pageService = pageService; } + /// public void Preload() => _navigationManager.Preload(); /// @@ -314,11 +316,16 @@ protected virtual void OnLoaded(object sender, RoutedEventArgs e) protected virtual void OnUnloaded(object sender, RoutedEventArgs e) { + Loaded -= OnLoaded; + Unloaded -= OnUnloaded; + _frameManager.Dispose(); _navigationManager.Dispose(); foreach (var item in _items) item.Click -= OnNavigationItemClicked; + + _items = null!; } /// diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index 0a305a7ad..069e4e8ff 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -39,7 +39,9 @@ public NavigationManager(Frame frame, IPageService? pageService, INavigationItem public void Dispose() { + NavigationStack.Clear(); _navigationStackHistory.Clear(); + ClearCache(); } public void Preload() From ac344a81d71baf14ffbdb0fb0faa80c0af71b29f Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Fri, 5 Aug 2022 14:40:50 +0600 Subject: [PATCH 19/28] fixed firing of navigated event when navigation is not happened --- .../Controls/Navigation/NavigationBase.cs | 8 +-- .../Services/Internal/NavigationManager.cs | 51 +++++++++++-------- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs index 344aa4108..ad5b35a99 100644 --- a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs +++ b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs @@ -282,15 +282,15 @@ public void SetIPageService(IPageService pageService) /// public void NavigateTo(string pageTag, object? dataContext = null) { - _navigationManager.NavigateTo(pageTag, dataContext); - OnNavigated(); + if (_navigationManager.NavigateTo(pageTag, dataContext)) + OnNavigated(); } /// public void NavigateTo(Type type, object? dataContext = null) { - _navigationManager.NavigateTo(type, dataContext); - OnNavigated(); + if (_navigationManager.NavigateTo(type, dataContext)) + OnNavigated(); } /// diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index 069e4e8ff..0d0491fd7 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -60,14 +60,13 @@ public void ClearCache() } } - public void NavigateTo(string tag, object? dataContext = null) + public bool NavigateTo(string tag, object? dataContext = null) { Guard.IsNotNullOrEmpty(tag, nameof(tag)); if (tag == "..") { - NavigateBack(); - return; + return NavigateBack(); } _addToNavigationStack = tag.Contains("/"); @@ -78,16 +77,16 @@ public void NavigateTo(string tag, object? dataContext = null) if (itemId < 0) ThrowHelper.ThrowArgumentException($"Item with: {tag} tag not found"); - NavigateInternal(itemId, dataContext); + return NavigateInternal(itemId, dataContext); } - public void NavigateTo(Type type, object? dataContext = null) + public bool NavigateTo(Type type, object? dataContext = null) { var itemId = GetItemId(serviceItem => serviceItem.PageType == type); if (itemId < 0) ThrowHelper.ThrowArgumentException($"Item with: {type} type not found"); - NavigateInternal(itemId, dataContext); + return NavigateInternal(itemId, dataContext); } public void NavigateTo(int id, object? dataContext = null) @@ -97,25 +96,25 @@ public void NavigateTo(int id, object? dataContext = null) #region NavigationInternal - private void NavigateBack() + private bool NavigateBack() { if (_history.Count <= 1) - return; + return false; var itemId = _history[_history.Count - 2]; _isBackwardsNavigated = true; - NavigateInternal(itemId, null); + return NavigateInternal(itemId, null); } - private void NavigateInternal(int itemId, object? dataContext) + private bool NavigateInternal(int itemId, object? dataContext) { if (_navigationItems.ElementAtOrDefault(itemId) is not { } item) - return; + return false; switch (NavigationStack.Count) { case > 0 when NavigationStack[NavigationStack.Count -1] == item: - return; + return false; case 0: NavigationStack.Add(item); break; @@ -126,6 +125,7 @@ private void NavigateInternal(int itemId, object? dataContext) AddToHistory(itemId); PerformNavigation((itemId, item), dataContext); + return true; } private void AddToNavigationStack(INavigationItem item) @@ -208,8 +208,11 @@ private void AddToHistory(int itemId) private void PerformNavigation((int itemId, INavigationItem item) itemData, object? dataContext) { - if (_pageService is not null && NavigateByService(itemData)) + if (_pageService is not null) + { + NavigateByService(itemData); return; + } if (itemData.item.Cache) { @@ -223,10 +226,9 @@ private void PerformNavigation((int itemId, INavigationItem item) itemData, obje ThrowHelper.ThrowInvalidOperationException("failed to navigate"); } - private bool NavigateByService((int itemId, INavigationItem item) itemData) + private void NavigateByService((int itemId, INavigationItem item) itemData) { - if (itemData.item.PageType is null) - return false; + Guard.IsNotNull(itemData.item.PageType, nameof(itemData.item.PageType)); /*if (_instances[itemData.itemId] is not null) { @@ -235,18 +237,25 @@ private bool NavigateByService((int itemId, INavigationItem item) itemData) var instance = _pageService!.GetPage(itemData.item.PageType); if (instance is null) - return false; + { + ThrowHelper.ThrowArgumentNullException("Failed to create instance"); + return; + } _frame.Navigate(instance); - return true; } private void NavigateWithCache((int itemId, INavigationItem item) itemData, object? dataContext) { if (_instances[itemData.itemId] is null) { - _instances[itemData.itemId] = NavigateWithoutCache(itemData.item, dataContext); - return; + if (NavigateWithoutCache(itemData.item, dataContext) is not { } element) + { + ThrowHelper.ThrowArgumentNullException("Failed to create instance"); + return; + } + + _instances[itemData.itemId] = element; } var instance = _instances[itemData.itemId]!; @@ -256,10 +265,8 @@ private void NavigateWithCache((int itemId, INavigationItem item) itemData, obje _frame.Navigate(instance); -#if DEBUG System.Diagnostics.Debug.WriteLine( $"DEBUG | {itemData.item.PageTag} navigated internally, with cache by it's instance."); -#endif } private FrameworkElement? NavigateWithoutCache(INavigationItem item, object? dataContext) From 744960da8803911d126e51fc4a28868fe89e9c6f Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Sun, 7 Aug 2022 07:44:08 +0600 Subject: [PATCH 20/28] CommunityToolkit.Diagnostics has been updated to version 8.0.0 --- src/Packages.props | 2 +- src/Wpf.Ui/Controls/Breadcrumb.cs | 2 +- src/Wpf.Ui/Controls/Navigation/NavigationBase.cs | 2 +- src/Wpf.Ui/Mvvm/Services/NavigationService.cs | 2 +- src/Wpf.Ui/Services/Internal/NavigationManager.cs | 15 +++++---------- src/Wpf.Ui/Wpf.Ui.csproj | 2 +- 6 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/Packages.props b/src/Packages.props index d3ada50a2..0b215faf7 100644 --- a/src/Packages.props +++ b/src/Packages.props @@ -7,6 +7,6 @@ - + \ No newline at end of file diff --git a/src/Wpf.Ui/Controls/Breadcrumb.cs b/src/Wpf.Ui/Controls/Breadcrumb.cs index 0d4cd7fce..c4704ac5c 100644 --- a/src/Wpf.Ui/Controls/Breadcrumb.cs +++ b/src/Wpf.Ui/Controls/Breadcrumb.cs @@ -8,7 +8,7 @@ using System.ComponentModel; using System.Windows; using System.Windows.Input; -using Microsoft.Toolkit.Diagnostics; +using CommunityToolkit.Diagnostics; using Wpf.Ui.Common; using Wpf.Ui.Controls.Interfaces; diff --git a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs index ad5b35a99..d8d93008b 100644 --- a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs +++ b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs @@ -12,7 +12,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Input; -using Microsoft.Toolkit.Diagnostics; +using CommunityToolkit.Diagnostics; using Wpf.Ui.Common; using Wpf.Ui.Controls.Interfaces; using Wpf.Ui.Mvvm.Contracts; diff --git a/src/Wpf.Ui/Mvvm/Services/NavigationService.cs b/src/Wpf.Ui/Mvvm/Services/NavigationService.cs index 3aa69d58f..0d973fddc 100644 --- a/src/Wpf.Ui/Mvvm/Services/NavigationService.cs +++ b/src/Wpf.Ui/Mvvm/Services/NavigationService.cs @@ -6,7 +6,7 @@ #nullable enable using System; using System.Windows.Controls; -using Microsoft.Toolkit.Diagnostics; +using CommunityToolkit.Diagnostics; using Wpf.Ui.Controls.Interfaces; using Wpf.Ui.Mvvm.Contracts; diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index 0d0491fd7..de80e4f1b 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Windows; using System.Windows.Controls; -using Microsoft.Toolkit.Diagnostics; +using CommunityToolkit.Diagnostics; using Wpf.Ui.Controls.Interfaces; using Wpf.Ui.Mvvm.Contracts; @@ -208,11 +208,8 @@ private void AddToHistory(int itemId) private void PerformNavigation((int itemId, INavigationItem item) itemData, object? dataContext) { - if (_pageService is not null) - { - NavigateByService(itemData); + if (_pageService is not null && NavigateByService(itemData)) return; - } if (itemData.item.Cache) { @@ -226,7 +223,7 @@ private void PerformNavigation((int itemId, INavigationItem item) itemData, obje ThrowHelper.ThrowInvalidOperationException("failed to navigate"); } - private void NavigateByService((int itemId, INavigationItem item) itemData) + private bool NavigateByService((int itemId, INavigationItem item) itemData) { Guard.IsNotNull(itemData.item.PageType, nameof(itemData.item.PageType)); @@ -237,12 +234,10 @@ private void NavigateByService((int itemId, INavigationItem item) itemData) var instance = _pageService!.GetPage(itemData.item.PageType); if (instance is null) - { - ThrowHelper.ThrowArgumentNullException("Failed to create instance"); - return; - } + return false; _frame.Navigate(instance); + return true; } private void NavigateWithCache((int itemId, INavigationItem item) itemData, object? dataContext) diff --git a/src/Wpf.Ui/Wpf.Ui.csproj b/src/Wpf.Ui/Wpf.Ui.csproj index 01cbf0cce..f86152436 100644 --- a/src/Wpf.Ui/Wpf.Ui.csproj +++ b/src/Wpf.Ui/Wpf.Ui.csproj @@ -72,7 +72,7 @@ - + From 9dfa6e1304cf0564e0c8bd781111ec4e7dfb0b64 Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Mon, 15 Aug 2022 17:39:49 +0600 Subject: [PATCH 21/28] added INavigationCancelable --- .../Interfaces/INavigationCancelable.cs | 13 ++ .../Services/Internal/NavigationManager.cs | 112 +++++++----------- 2 files changed, 57 insertions(+), 68 deletions(-) create mode 100644 src/Wpf.Ui/Common/Interfaces/INavigationCancelable.cs diff --git a/src/Wpf.Ui/Common/Interfaces/INavigationCancelable.cs b/src/Wpf.Ui/Common/Interfaces/INavigationCancelable.cs new file mode 100644 index 000000000..f94dcde23 --- /dev/null +++ b/src/Wpf.Ui/Common/Interfaces/INavigationCancelable.cs @@ -0,0 +1,13 @@ +namespace Wpf.Ui.Common.Interfaces; + +/// +/// TODO +/// +public interface INavigationCancelable +{ + /// + /// TODO + /// + /// + bool CouldNavigate(); +} diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index de80e4f1b..acf03b72a 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -7,6 +7,7 @@ using System.Windows; using System.Windows.Controls; using CommunityToolkit.Diagnostics; +using Wpf.Ui.Common.Interfaces; using Wpf.Ui.Controls.Interfaces; using Wpf.Ui.Mvvm.Contracts; @@ -111,6 +112,14 @@ private bool NavigateInternal(int itemId, object? dataContext) if (_navigationItems.ElementAtOrDefault(itemId) is not { } item) return false; + var instance = GetFrameworkElement((itemId, item), dataContext); + + if (!CheckForNavigationCanceling(item, instance)) + { + _addToNavigationStack = false; + return false; + } + switch (NavigationStack.Count) { case > 0 when NavigationStack[NavigationStack.Count -1] == item: @@ -124,7 +133,7 @@ private bool NavigateInternal(int itemId, object? dataContext) ActivateItem(item); AddToHistory(itemId); - PerformNavigation((itemId, item), dataContext); + _frame.Navigate(instance); return true; } @@ -202,99 +211,66 @@ private void AddToHistory(int itemId) _history.Add(itemId); } - #endregion - - #region PerformNavigation - - private void PerformNavigation((int itemId, INavigationItem item) itemData, object? dataContext) + private bool CheckForNavigationCanceling(INavigationItem item, FrameworkElement instance) { - if (_pageService is not null && NavigateByService(itemData)) - return; - - if (itemData.item.Cache) + INavigationCancelable? navigationCancelable = instance switch { - NavigateWithCache(itemData, dataContext); - return; - } + INavigationCancelable cancelable => cancelable, + {DataContext: INavigationCancelable dataContextNavigationCancelable} => dataContextNavigationCancelable, + _ => null + }; - if (NavigateWithoutCache(itemData.item, dataContext) is not null) - return; + if (navigationCancelable is null) + return true; - ThrowHelper.ThrowInvalidOperationException("failed to navigate"); + return navigationCancelable.CouldNavigate(); } - private bool NavigateByService((int itemId, INavigationItem item) itemData) + #endregion + + #region PrivateMethods + + private FrameworkElement GetFrameworkElement((int itemId, INavigationItem item) itemData, object? dataContext) { Guard.IsNotNull(itemData.item.PageType, nameof(itemData.item.PageType)); - /*if (_instances[itemData.itemId] is not null) - { - //TODO - }*/ + if (_pageService is not null && _pageService!.GetPage(itemData.item.PageType) is { } fromServicesElement) + return fromServicesElement; - var instance = _pageService!.GetPage(itemData.item.PageType); - if (instance is null) - return false; - _frame.Navigate(instance); - return true; - } + if (itemData.item.Cache) + return GetFrameworkElementFromCache(itemData, dataContext); - private void NavigateWithCache((int itemId, INavigationItem item) itemData, object? dataContext) - { - if (_instances[itemData.itemId] is null) + if (!itemData.item.Cache && NavigationServiceActivator.CreateInstance(itemData.item.PageType, dataContext) is { } element) { - if (NavigateWithoutCache(itemData.item, dataContext) is not { } element) - { - ThrowHelper.ThrowArgumentNullException("Failed to create instance"); - return; - } + if (dataContext is not null) + element.DataContext = dataContext; - _instances[itemData.itemId] = element; + return element; } - var instance = _instances[itemData.itemId]!; - - if (dataContext is not null) - instance.DataContext = dataContext; - - _frame.Navigate(instance); - - System.Diagnostics.Debug.WriteLine( - $"DEBUG | {itemData.item.PageTag} navigated internally, with cache by it's instance."); + ThrowHelper.ThrowArgumentException("Failed to create instance"); + return null; } - private FrameworkElement? NavigateWithoutCache(INavigationItem item, object? dataContext) + private FrameworkElement GetFrameworkElementFromCache((int itemId, INavigationItem item) itemData, object? dataContext) { - FrameworkElement? instance = null; + if (_instances[itemData.itemId] is not null) + return _instances[itemData.itemId]!; - if (item.PageType is not null) + if (NavigationServiceActivator.CreateInstance(itemData.item.PageType, dataContext) is not { } element) { - instance = NavigationServiceActivator.CreateInstance(item.PageType, dataContext); - _frame.Navigate(instance); + ThrowHelper.ThrowArgumentNullException("Failed to create instance"); + return null; } - if (item.AbsolutePageSource is not null) - { - _frame.Navigate(item.AbsolutePageSource); - } - -#if DEBUG - if (instance is null) - return instance; - - string navigationType = item.PageType is not null ? "type" : "source"; + if (dataContext is not null) + element.DataContext = dataContext; - System.Diagnostics.Debug.WriteLine( - $"DEBUG | {item.PageTag} navigated internally, without cache by it's {navigationType}."); -#endif - return instance; + _instances[itemData.itemId] = element; + return _instances[itemData.itemId]!; } - #endregion - - #region PrivateMethods - private void ClearNavigationStack(int navigationStackItemIndex) { var navigationStackCount = NavigationStack.Count; From 921e6bd5e2ecd4a98508edd86e258dd25a901f09 Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Mon, 15 Aug 2022 21:32:58 +0600 Subject: [PATCH 22/28] added parameters for RoutedNavigationEventArgs --- src/Wpf.Ui.Demo/Views/Container.xaml.cs | 7 ++--- .../Interfaces/INavigationCancelable.cs | 7 +++-- .../Common/RoutedNavigationEventArgs.cs | 22 ++++++++------ .../Controls/Navigation/NavigationBase.cs | 30 +------------------ .../Services/Internal/NavigationManager.cs | 21 ++++++++----- 5 files changed, 34 insertions(+), 53 deletions(-) diff --git a/src/Wpf.Ui.Demo/Views/Container.xaml.cs b/src/Wpf.Ui.Demo/Views/Container.xaml.cs index 0bb13e39c..aa02a4864 100644 --- a/src/Wpf.Ui.Demo/Views/Container.xaml.cs +++ b/src/Wpf.Ui.Demo/Views/Container.xaml.cs @@ -154,18 +154,15 @@ private void TrayMenuItem_OnClick(object sender, RoutedEventArgs e) private void RootNavigation_OnNavigated(INavigation sender, RoutedNavigationEventArgs e) { - var current = sender.NavigationStack[sender.NavigationStack.Count - 1]; - - System.Diagnostics.Debug.WriteLine($"DEBUG | WPF UI Navigated to: {current}", "Wpf.Ui.Demo"); + System.Diagnostics.Debug.WriteLine($"DEBUG | WPF UI Navigated to: {e.NavigatedTo}", "Wpf.Ui.Demo"); // This funky solution allows us to impose a negative // margin for Frame only for the Dashboard page, thanks // to which the banner will cover the entire page nicely. RootFrame.Margin = new Thickness( left: 0, - top: current.PageTag == "dashboard" ? -69 : 0, + top: e.NavigatedTo.PageTag == "dashboard" ? -69 : 0, right: 0, bottom: 0); } } - diff --git a/src/Wpf.Ui/Common/Interfaces/INavigationCancelable.cs b/src/Wpf.Ui/Common/Interfaces/INavigationCancelable.cs index f94dcde23..baba28d42 100644 --- a/src/Wpf.Ui/Common/Interfaces/INavigationCancelable.cs +++ b/src/Wpf.Ui/Common/Interfaces/INavigationCancelable.cs @@ -1,4 +1,7 @@ -namespace Wpf.Ui.Common.Interfaces; +#nullable enable +using Wpf.Ui.Controls.Interfaces; + +namespace Wpf.Ui.Common.Interfaces; /// /// TODO @@ -9,5 +12,5 @@ public interface INavigationCancelable /// TODO /// /// - bool CouldNavigate(); + bool CouldNavigate(INavigationItem? navigationFrom); } diff --git a/src/Wpf.Ui/Common/RoutedNavigationEventArgs.cs b/src/Wpf.Ui/Common/RoutedNavigationEventArgs.cs index 5c5988588..d93551187 100644 --- a/src/Wpf.Ui/Common/RoutedNavigationEventArgs.cs +++ b/src/Wpf.Ui/Common/RoutedNavigationEventArgs.cs @@ -3,26 +3,30 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. -using System.Collections.Generic; +#nullable enable using System.Windows; using Wpf.Ui.Controls.Interfaces; namespace Wpf.Ui.Common; /// -/// with additional . +/// /// public class RoutedNavigationEventArgs : RoutedEventArgs { + public readonly INavigationItem? NavigatedFrom; + public readonly INavigationItem NavigatedTo; + /// - /// Constructor for . + /// TODO /// - /// The new value that the SourceProperty is being set to. - /// The new value that the Property is being set to. - /// Currently displayed page. - public RoutedNavigationEventArgs(RoutedEvent routedEvent, object source) : base( - routedEvent, source) + /// + /// + /// + /// + public RoutedNavigationEventArgs(RoutedEvent routedEvent, INavigation source, INavigationItem? navigatedFrom, INavigationItem navigatedTo) : base(routedEvent, source) { - + NavigatedFrom = navigatedFrom; + NavigatedTo = navigatedTo; } } diff --git a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs index d8d93008b..9a0438158 100644 --- a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs +++ b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs @@ -199,34 +199,6 @@ public event RoutedNavigationEvent Navigated remove => RemoveHandler(NavigatedEvent, value); } - /// - /// Event triggered when navigate to the next page. - /// - public static readonly RoutedEvent NavigatedForwardEvent = - EventManager.RegisterRoutedEvent(nameof(NavigatedForward), RoutingStrategy.Bubble, - typeof(RoutedNavigationEvent), typeof(NavigationBase)); - - /// - public event RoutedNavigationEvent NavigatedForward - { - add => AddHandler(NavigatedForwardEvent, value); - remove => RemoveHandler(NavigatedForwardEvent, value); - } - - /// - /// Event triggered when navigate to the previous page. - /// - public static readonly RoutedEvent NavigatedBackwardEvent = - EventManager.RegisterRoutedEvent(nameof(NavigatedBackward), RoutingStrategy.Bubble, - typeof(RoutedNavigationEvent), typeof(NavigationBase)); - - /// - public event RoutedNavigationEvent NavigatedBackward - { - add => AddHandler(NavigatedBackwardEvent, value); - remove => RemoveHandler(NavigatedBackwardEvent, value); - } - #endregion /// @@ -378,7 +350,7 @@ static void MoveFocus(FrameworkElement element, FocusNavigationDirection directi /// protected virtual void OnNavigated() { - var newEvent = new RoutedNavigationEventArgs(NavigatedEvent, this); + var newEvent = new RoutedNavigationEventArgs(NavigatedEvent, this, _navigationManager.NavigationFrom, NavigationStack[NavigationStack.Count - 1]); RaiseEvent(newEvent); } diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index acf03b72a..61990c786 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -18,7 +18,6 @@ internal sealed class NavigationManager : IDisposable private readonly Frame _frame; private readonly INavigationItem[] _navigationItems; private readonly FrameworkElement?[] _instances; - private readonly List _history = new(); private readonly IPageService? _pageService; private readonly ArrayPool _arrayPool = ArrayPool.Create(); private readonly List _navigationStackHistory = new(); @@ -26,7 +25,9 @@ internal sealed class NavigationManager : IDisposable private bool _isBackwardsNavigated; private bool _addToNavigationStack; - public bool CanGoBack => _history.Count > 1; + public bool CanGoBack => History.Count > 1; + public INavigationItem? NavigationFrom => History.Count > 1 ? _navigationItems[History[History.Count - 2]] : null; + public readonly List History = new(); public readonly ObservableCollection NavigationStack = new(); public NavigationManager(Frame frame, IPageService? pageService, INavigationItem[] navigationItems) @@ -38,6 +39,8 @@ public NavigationManager(Frame frame, IPageService? pageService, INavigationItem _pageService = pageService; } + #region Public methods + public void Dispose() { NavigationStack.Clear(); @@ -95,14 +98,16 @@ public void NavigateTo(int id, object? dataContext = null) NavigateInternal(id, dataContext); } + #endregion + #region NavigationInternal private bool NavigateBack() { - if (_history.Count <= 1) + if (History.Count <= 1) return false; - var itemId = _history[_history.Count - 2]; + var itemId = History[History.Count - 2]; _isBackwardsNavigated = true; return NavigateInternal(itemId, null); } @@ -204,11 +209,11 @@ private void AddToHistory(int itemId) if (_isBackwardsNavigated) { _isBackwardsNavigated = false; - _history.RemoveAt(_history.LastIndexOf(_history[_history.Count - 2])); - _history.RemoveAt(_history.LastIndexOf(_history[_history.Count - 1])); + History.RemoveAt(History.LastIndexOf(History[History.Count - 2])); + History.RemoveAt(History.LastIndexOf(History[History.Count - 1])); } - _history.Add(itemId); + History.Add(itemId); } private bool CheckForNavigationCanceling(INavigationItem item, FrameworkElement instance) @@ -223,7 +228,7 @@ private bool CheckForNavigationCanceling(INavigationItem item, FrameworkElement if (navigationCancelable is null) return true; - return navigationCancelable.CouldNavigate(); + return navigationCancelable.CouldNavigate(NavigationFrom); } #endregion From 49981eae1ed152d00b7e1179c0155672631a6586 Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Mon, 15 Aug 2022 22:02:14 +0600 Subject: [PATCH 23/28] fixed my mistake --- src/Wpf.Ui/Controls/Navigation/NavigationBase.cs | 4 +++- src/Wpf.Ui/Services/Internal/NavigationManager.cs | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs index 9a0438158..db872878e 100644 --- a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs +++ b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs @@ -350,7 +350,9 @@ static void MoveFocus(FrameworkElement element, FocusNavigationDirection directi /// protected virtual void OnNavigated() { - var newEvent = new RoutedNavigationEventArgs(NavigatedEvent, this, _navigationManager.NavigationFrom, NavigationStack[NavigationStack.Count - 1]); + var navigatedFrom = _navigationManager.History.Count > 1 ? _items[_navigationManager.History.Count - 2] : null; + + var newEvent = new RoutedNavigationEventArgs(NavigatedEvent, this, navigatedFrom, NavigationStack[NavigationStack.Count - 1]); RaiseEvent(newEvent); } diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index 61990c786..571cb49af 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -26,7 +26,6 @@ internal sealed class NavigationManager : IDisposable private bool _addToNavigationStack; public bool CanGoBack => History.Count > 1; - public INavigationItem? NavigationFrom => History.Count > 1 ? _navigationItems[History[History.Count - 2]] : null; public readonly List History = new(); public readonly ObservableCollection NavigationStack = new(); @@ -119,7 +118,7 @@ private bool NavigateInternal(int itemId, object? dataContext) var instance = GetFrameworkElement((itemId, item), dataContext); - if (!CheckForNavigationCanceling(item, instance)) + if (!CheckForNavigationCanceling(instance)) { _addToNavigationStack = false; return false; @@ -216,7 +215,7 @@ private void AddToHistory(int itemId) History.Add(itemId); } - private bool CheckForNavigationCanceling(INavigationItem item, FrameworkElement instance) + private bool CheckForNavigationCanceling(FrameworkElement instance) { INavigationCancelable? navigationCancelable = instance switch { @@ -228,7 +227,8 @@ private bool CheckForNavigationCanceling(INavigationItem item, FrameworkElement if (navigationCancelable is null) return true; - return navigationCancelable.CouldNavigate(NavigationFrom); + var navigationFrom = History.Count > 0 ? _navigationItems[History[History.Count - 1]] : null; + return navigationCancelable.CouldNavigate(navigationFrom); } #endregion From 5d5e50af57542aa066d60782f7ba24c4ce91a912 Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Mon, 15 Aug 2022 22:19:46 +0600 Subject: [PATCH 24/28] added addToHistory parameter --- src/Wpf.Ui.Demo/ViewModels/BreadcrumbPagesViewModel.cs | 2 +- src/Wpf.Ui.Demo/ViewModels/ButtonsViewModel.cs | 3 +-- src/Wpf.Ui.Demo/Views/Container.xaml | 4 ++-- src/Wpf.Ui.Demo/Views/Container.xaml.cs | 4 ++-- src/Wpf.Ui.Demo/Views/Pages/Controls.xaml.cs | 2 +- .../Views/Windows/ExperimentalWindow.xaml.cs | 4 ++-- src/Wpf.Ui/Controls/Interfaces/INavigation.cs | 6 ++++-- src/Wpf.Ui/Controls/Navigation/NavigationBase.cs | 8 ++++---- src/Wpf.Ui/Mvvm/Contracts/INavigationService.cs | 6 ++++-- src/Wpf.Ui/Mvvm/Services/NavigationService.cs | 8 ++++---- src/Wpf.Ui/Services/Internal/NavigationManager.cs | 10 +++++++--- 11 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/Wpf.Ui.Demo/ViewModels/BreadcrumbPagesViewModel.cs b/src/Wpf.Ui.Demo/ViewModels/BreadcrumbPagesViewModel.cs index 80037cd82..28a139f78 100644 --- a/src/Wpf.Ui.Demo/ViewModels/BreadcrumbPagesViewModel.cs +++ b/src/Wpf.Ui.Demo/ViewModels/BreadcrumbPagesViewModel.cs @@ -21,7 +21,7 @@ public BreadcrumbPagesViewModel(INavigationService navigationService) private void OnClick(string pageTag) { - _navigationService.NavigateTo($"/{pageTag}", this); + _navigationService.NavigateTo($"/{pageTag}", true, this); } private void OnNavigateBack() diff --git a/src/Wpf.Ui.Demo/ViewModels/ButtonsViewModel.cs b/src/Wpf.Ui.Demo/ViewModels/ButtonsViewModel.cs index d792b7a92..a8cb1bd5d 100644 --- a/src/Wpf.Ui.Demo/ViewModels/ButtonsViewModel.cs +++ b/src/Wpf.Ui.Demo/ViewModels/ButtonsViewModel.cs @@ -27,9 +27,8 @@ public ButtonsViewModel(INavigationService navigationService) var currentTheme = testGetThemeService.GetSystemTheme(); } - private void OnShowMore(string parameter) { - _navigationService.NavigateTo("/input"); + _navigationService.NavigateTo("/input", false); } } diff --git a/src/Wpf.Ui.Demo/Views/Container.xaml b/src/Wpf.Ui.Demo/Views/Container.xaml index c6a971ab5..8125e9bca 100644 --- a/src/Wpf.Ui.Demo/Views/Container.xaml +++ b/src/Wpf.Ui.Demo/Views/Container.xaml @@ -245,9 +245,9 @@ - + Navigation="{Binding ElementName=RootNavigation, Mode=OneTime}" /> diff --git a/src/Wpf.Ui.Demo/Views/Container.xaml.cs b/src/Wpf.Ui.Demo/Views/Container.xaml.cs index aa02a4864..b520f7ee3 100644 --- a/src/Wpf.Ui.Demo/Views/Container.xaml.cs +++ b/src/Wpf.Ui.Demo/Views/Container.xaml.cs @@ -98,10 +98,10 @@ public INavigation GetNavigation() => RootNavigation; public void NavigateTo(Type type, object dataContext = null) - => RootNavigation.NavigateTo(type, dataContext); + => RootNavigation.NavigateTo(type, true, dataContext); public void NavigateTo(string pageTag, object dataContext = null) - => RootNavigation.NavigateTo(pageTag, dataContext); + => RootNavigation.NavigateTo(pageTag, true, dataContext); public void SetPageService(IPageService pageService) => RootNavigation.SetIPageService(pageService); diff --git a/src/Wpf.Ui.Demo/Views/Pages/Controls.xaml.cs b/src/Wpf.Ui.Demo/Views/Pages/Controls.xaml.cs index a1a574d82..f13cc8e15 100644 --- a/src/Wpf.Ui.Demo/Views/Pages/Controls.xaml.cs +++ b/src/Wpf.Ui.Demo/Views/Pages/Controls.xaml.cs @@ -115,6 +115,6 @@ private void MessageBox_RightButtonClick(object sender, System.Windows.RoutedEve private void OnBreadcrumbButtonClick(object sender, RoutedEventArgs e) { var viewModel = _serviceProvider.GetRequiredService(); - _navigation.NavigateTo("/page1", viewModel); + _navigation.NavigateTo("/page1", true, viewModel); } } diff --git a/src/Wpf.Ui.Demo/Views/Windows/ExperimentalWindow.xaml.cs b/src/Wpf.Ui.Demo/Views/Windows/ExperimentalWindow.xaml.cs index a90b5fb71..d4d4b1dcc 100644 --- a/src/Wpf.Ui.Demo/Views/Windows/ExperimentalWindow.xaml.cs +++ b/src/Wpf.Ui.Demo/Views/Windows/ExperimentalWindow.xaml.cs @@ -140,8 +140,8 @@ public void CloseWindow() => Close(); public void NavigateTo(Type type, object dataContext = null) - => RootNavigation.NavigateTo(type, dataContext); + => RootNavigation.NavigateTo(type, true, dataContext); public void NavigateTo(string pageTag, object dataContext = null) - => RootNavigation.NavigateTo(pageTag, dataContext); + => RootNavigation.NavigateTo(pageTag, true, dataContext); } diff --git a/src/Wpf.Ui/Controls/Interfaces/INavigation.cs b/src/Wpf.Ui/Controls/Interfaces/INavigation.cs index 1eeb093aa..fa4bbe74a 100644 --- a/src/Wpf.Ui/Controls/Interfaces/INavigation.cs +++ b/src/Wpf.Ui/Controls/Interfaces/INavigation.cs @@ -105,13 +105,15 @@ public interface INavigation /// TODO /// /// + /// /// - void NavigateTo(string pageTag, object? dataContext = null); + void NavigateTo(string pageTag, bool addToHistory = true, object? dataContext = null); /// /// TODO /// /// + /// /// - void NavigateTo(Type type, object? dataContext = null); + void NavigateTo(Type type, bool addToHistory = true, object? dataContext = null); } diff --git a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs index db872878e..bb9621a3c 100644 --- a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs +++ b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs @@ -252,16 +252,16 @@ public void SetIPageService(IPageService pageService) public void ClearCache() => _navigationManager.ClearCache(); /// - public void NavigateTo(string pageTag, object? dataContext = null) + public void NavigateTo(string pageTag, bool addToHistory = true, object? dataContext = null) { - if (_navigationManager.NavigateTo(pageTag, dataContext)) + if (_navigationManager.NavigateTo(pageTag, addToHistory, dataContext)) OnNavigated(); } /// - public void NavigateTo(Type type, object? dataContext = null) + public void NavigateTo(Type type, bool addToHistory = true, object? dataContext = null) { - if (_navigationManager.NavigateTo(type, dataContext)) + if (_navigationManager.NavigateTo(type, addToHistory, dataContext)) OnNavigated(); } diff --git a/src/Wpf.Ui/Mvvm/Contracts/INavigationService.cs b/src/Wpf.Ui/Mvvm/Contracts/INavigationService.cs index 73bf29cfa..23519cd3e 100644 --- a/src/Wpf.Ui/Mvvm/Contracts/INavigationService.cs +++ b/src/Wpf.Ui/Mvvm/Contracts/INavigationService.cs @@ -57,13 +57,15 @@ public interface INavigationService /// Lets you navigate to the selected page based on it's tag. Should be used with . /// /// Tag of the page. + /// /// - void NavigateTo(string pageTag, object? dataContext = null); + void NavigateTo(string pageTag, bool addToHistory = true, object? dataContext = null); /// /// TODO /// /// + /// /// - void NavigateTo(Type type, object? dataContext = null); + void NavigateTo(Type type, bool addToHistory = true, object? dataContext = null); } diff --git a/src/Wpf.Ui/Mvvm/Services/NavigationService.cs b/src/Wpf.Ui/Mvvm/Services/NavigationService.cs index 0d973fddc..83e07a2bd 100644 --- a/src/Wpf.Ui/Mvvm/Services/NavigationService.cs +++ b/src/Wpf.Ui/Mvvm/Services/NavigationService.cs @@ -81,14 +81,14 @@ public void SetPageService(IPageService pageService) } /// - public void NavigateTo(string pageTag, object? dataContext = null) + public void NavigateTo(string pageTag, bool addToHistory = true, object? dataContext = null) { - NavigationControl.NavigateTo(pageTag, dataContext); + NavigationControl.NavigateTo(pageTag, addToHistory, dataContext); } /// - public void NavigateTo(Type type, object? dataContext = null) + public void NavigateTo(Type type, bool addToHistory = true, object? dataContext = null) { - NavigationControl.NavigateTo(type, dataContext); + NavigationControl.NavigateTo(type, addToHistory, dataContext); } } diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index 571cb49af..aa77b859d 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -24,6 +24,7 @@ internal sealed class NavigationManager : IDisposable private bool _isBackwardsNavigated; private bool _addToNavigationStack; + private bool _addToHistory = true; public bool CanGoBack => History.Count > 1; public readonly List History = new(); @@ -63,7 +64,7 @@ public void ClearCache() } } - public bool NavigateTo(string tag, object? dataContext = null) + public bool NavigateTo(string tag, bool addToHistory, object? dataContext = null) { Guard.IsNotNullOrEmpty(tag, nameof(tag)); @@ -83,7 +84,7 @@ public bool NavigateTo(string tag, object? dataContext = null) return NavigateInternal(itemId, dataContext); } - public bool NavigateTo(Type type, object? dataContext = null) + public bool NavigateTo(Type type, bool addToHistory, object? dataContext = null) { var itemId = GetItemId(serviceItem => serviceItem.PageType == type); if (itemId < 0) @@ -212,7 +213,10 @@ private void AddToHistory(int itemId) History.RemoveAt(History.LastIndexOf(History[History.Count - 1])); } - History.Add(itemId); + if (_addToHistory) + History.Add(itemId); + + _addToHistory = true; } private bool CheckForNavigationCanceling(FrameworkElement instance) From a85a2480d83af27eba12723f3f8dd64fa875929b Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Mon, 15 Aug 2022 23:39:51 +0600 Subject: [PATCH 25/28] separated the logic of the navigation stack from the NavigationManager --- .../ViewModels/ButtonsViewModel.cs | 2 +- .../Controls/Navigation/NavigationBase.cs | 2 +- .../Services/Internal/NavigationManager.cs | 99 ++------------- .../Internal/NavigationStackManager.cs | 119 ++++++++++++++++++ 4 files changed, 129 insertions(+), 93 deletions(-) create mode 100644 src/Wpf.Ui/Services/Internal/NavigationStackManager.cs diff --git a/src/Wpf.Ui.Demo/ViewModels/ButtonsViewModel.cs b/src/Wpf.Ui.Demo/ViewModels/ButtonsViewModel.cs index a8cb1bd5d..f12bf72c1 100644 --- a/src/Wpf.Ui.Demo/ViewModels/ButtonsViewModel.cs +++ b/src/Wpf.Ui.Demo/ViewModels/ButtonsViewModel.cs @@ -29,6 +29,6 @@ public ButtonsViewModel(INavigationService navigationService) private void OnShowMore(string parameter) { - _navigationService.NavigateTo("/input", false); + _navigationService.NavigateTo("/input"); } } diff --git a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs index bb9621a3c..8674845d9 100644 --- a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs +++ b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs @@ -350,7 +350,7 @@ static void MoveFocus(FrameworkElement element, FocusNavigationDirection directi /// protected virtual void OnNavigated() { - var navigatedFrom = _navigationManager.History.Count > 1 ? _items[_navigationManager.History.Count - 2] : null; + var navigatedFrom = _navigationManager.History.Count > 1 ? _items[_navigationManager.History[_navigationManager.History.Count - 2]] : null; var newEvent = new RoutedNavigationEventArgs(NavigatedEvent, this, navigatedFrom, NavigationStack[NavigationStack.Count - 1]); RaiseEvent(newEvent); diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index aa77b859d..75ad1214f 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -1,6 +1,5 @@ #nullable enable using System; -using System.Buffers; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -15,12 +14,11 @@ namespace Wpf.Ui.Services.Internal; internal sealed class NavigationManager : IDisposable { + private readonly NavigationStackManager _navigationStackManager; private readonly Frame _frame; private readonly INavigationItem[] _navigationItems; private readonly FrameworkElement?[] _instances; private readonly IPageService? _pageService; - private readonly ArrayPool _arrayPool = ArrayPool.Create(); - private readonly List _navigationStackHistory = new(); private bool _isBackwardsNavigated; private bool _addToNavigationStack; @@ -28,10 +26,11 @@ internal sealed class NavigationManager : IDisposable public bool CanGoBack => History.Count > 1; public readonly List History = new(); - public readonly ObservableCollection NavigationStack = new(); + public ObservableCollection NavigationStack => _navigationStackManager.NavigationStack; public NavigationManager(Frame frame, IPageService? pageService, INavigationItem[] navigationItems) { + _navigationStackManager = new NavigationStackManager(); _instances = new FrameworkElement[navigationItems.Length]; _navigationItems = navigationItems; @@ -43,8 +42,7 @@ public NavigationManager(Frame frame, IPageService? pageService, INavigationItem public void Dispose() { - NavigationStack.Clear(); - _navigationStackHistory.Clear(); + _navigationStackManager.Dispose(); ClearCache(); } @@ -125,72 +123,16 @@ private bool NavigateInternal(int itemId, object? dataContext) return false; } - switch (NavigationStack.Count) - { - case > 0 when NavigationStack[NavigationStack.Count -1] == item: - return false; - case 0: - NavigationStack.Add(item); - break; - } + if (!_navigationStackManager.AddFirstItemAndCheckIfNavigatingToCurrentItem(item)) + return false; - AddToNavigationStack(item); + _navigationStackManager.AddToNavigationStack(item, _addToNavigationStack, _isBackwardsNavigated); ActivateItem(item); AddToHistory(itemId); _frame.Navigate(instance); - return true; - } - - private void AddToNavigationStack(INavigationItem item) - { - if (_isBackwardsNavigated && item.WasInBreadcrumb) - { - if (_navigationStackHistory.Count > 1) - { - for (var i = 0; i < _navigationStackHistory.Count - 1; i++) - { - _addToNavigationStack = true; - - var historyItem = _navigationStackHistory[i]; - historyItem.WasInBreadcrumb = false; - AddToNavigationStack(historyItem); - } - - _navigationStackHistory.Clear(); - } - - item.WasInBreadcrumb = false; - _addToNavigationStack = true; - } - - if (_addToNavigationStack && !NavigationStack.Contains(item)) - { - item.WasInBreadcrumb = true; - NavigationStack.Add(item); - } - - if (!item.IsHidden && !_addToNavigationStack) - { - NavigationStack[0].IsActive = false; - NavigationStack[0] = item; - - ClearNavigationStack(1); - } - - var navigationStackCount = NavigationStack.Count; - if (navigationStackCount > 1) - { - var navItem = NavigationStack[NavigationStack.Count - 2]; - if (navItem.IsHidden) - navItem.IsActive = false; - - var index = NavigationStack.IndexOf(item); - if (index < navigationStackCount - 1 && _navigationStackHistory.Count == 0) - ClearNavigationStack(++index); - } - _addToNavigationStack = false; + return true; } private void ActivateItem(INavigationItem item) @@ -280,31 +222,6 @@ private FrameworkElement GetFrameworkElementFromCache((int itemId, INavigationIt return _instances[itemData.itemId]!; } - private void ClearNavigationStack(int navigationStackItemIndex) - { - var navigationStackCount = NavigationStack.Count; - var length = navigationStackCount - navigationStackItemIndex; - var buffer = _arrayPool.Rent(length); - - int i = 0; - for (int j = navigationStackItemIndex; j <= navigationStackCount - 1; j++) - { - buffer[i] = NavigationStack[j]; - i++; - } - - for (var index = 0; index < length; index++) - { - var item = buffer[index]; - NavigationStack.Remove(item); - - if (length > 1) - _navigationStackHistory.Add(item); - } - - _arrayPool.Return(buffer, true); - } - private int GetItemId(Func prediction) { int selectedIndex = -1; diff --git a/src/Wpf.Ui/Services/Internal/NavigationStackManager.cs b/src/Wpf.Ui/Services/Internal/NavigationStackManager.cs new file mode 100644 index 000000000..118ca5cfc --- /dev/null +++ b/src/Wpf.Ui/Services/Internal/NavigationStackManager.cs @@ -0,0 +1,119 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Wpf.Ui.Controls.Interfaces; + +namespace Wpf.Ui.Services.Internal; + +internal sealed class NavigationStackManager : IDisposable +{ + private readonly ArrayPool _arrayPool = ArrayPool.Create(); + private readonly List _navigationStackHistory = new(); + + public readonly ObservableCollection NavigationStack = new(); + + public void Dispose() + { + NavigationStack.Clear(); + _navigationStackHistory.Clear(); + } + + public bool AddFirstItemAndCheckIfNavigatingToCurrentItem(INavigationItem item) + { + switch (NavigationStack.Count) + { + case > 0 when NavigationStack[NavigationStack.Count -1] == item: + return false; + case 0: + NavigationStack.Add(item); + return true; + } + + return true; + } + + public void AddToNavigationStack(INavigationItem item, bool addToNavigationStack, bool isBackwardsNavigated) + { + if (isBackwardsNavigated) + RecreateBreadcrumbsFromHistory(item); + + if (addToNavigationStack && !NavigationStack.Contains(item)) + { + item.WasInBreadcrumb = true; + NavigationStack.Add(item); + } + + if (!addToNavigationStack) + UpdateCurrentItem(item); + + ClearNavigationStack(item); + } + + private void UpdateCurrentItem(INavigationItem item) + { + if (item.IsHidden) + return; + + NavigationStack[0].IsActive = false; + NavigationStack[0] = item; + + ClearNavigationStack(1); + } + + private void RecreateBreadcrumbsFromHistory(INavigationItem item) + { + if (!item.WasInBreadcrumb && _navigationStackHistory.Count < 1) + return; + + for (var i = 0; i < _navigationStackHistory.Count - 1; i++) + { + var historyItem = _navigationStackHistory[i]; + AddToNavigationStack(historyItem, true, false); + historyItem.WasInBreadcrumb = false; + } + + _navigationStackHistory.Clear(); + AddToNavigationStack(item, true, false); + } + + private void ClearNavigationStack(INavigationItem item) + { + var navigationStackCount = NavigationStack.Count; + if (navigationStackCount <= 1) + return; + + var navItem = NavigationStack[NavigationStack.Count - 2]; + if (navItem.IsHidden) + navItem.IsActive = false; + + var index = NavigationStack.IndexOf(item); + if (index < navigationStackCount - 1 && _navigationStackHistory.Count == 0) + ClearNavigationStack(++index); + } + + private void ClearNavigationStack(int navigationStackItemIndex) + { + var navigationStackCount = NavigationStack.Count; + var length = navigationStackCount - navigationStackItemIndex; + var buffer = _arrayPool.Rent(length); + + int i = 0; + for (int j = navigationStackItemIndex; j <= navigationStackCount - 1; j++) + { + buffer[i] = NavigationStack[j]; + i++; + } + + for (var index = 0; index < length; index++) + { + var item = buffer[index]; + NavigationStack.Remove(item); + + if (length > 1) + _navigationStackHistory.Add(item); + } + + _arrayPool.Return(buffer, true); + } +} From 9f5131d3e117de3abe3ac76f34b102fb62ace73c Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Tue, 16 Aug 2022 01:02:43 +0600 Subject: [PATCH 26/28] implemented "complex history" in the NavigationStackManager --- src/Wpf.Ui.Demo/Views/Container.xaml | 4 +- .../Controls/Navigation/NavigationBase.cs | 15 ++--- .../Services/Internal/NavigationManager.cs | 41 ++++++------ .../Internal/NavigationStackManager.cs | 67 +++++++++++++++---- 4 files changed, 82 insertions(+), 45 deletions(-) diff --git a/src/Wpf.Ui.Demo/Views/Container.xaml b/src/Wpf.Ui.Demo/Views/Container.xaml index 8125e9bca..c6a971ab5 100644 --- a/src/Wpf.Ui.Demo/Views/Container.xaml +++ b/src/Wpf.Ui.Demo/Views/Container.xaml @@ -245,9 +245,9 @@ - + Navigation="{Binding ElementName=RootNavigation, Mode=OneTime}" />--> diff --git a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs index 8674845d9..7200dfd21 100644 --- a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs +++ b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs @@ -28,7 +28,6 @@ public abstract class NavigationBase : System.Windows.Controls.Control, INavigat private FrameManager _frameManager = null!; private NavigationManager _navigationManager = null!; private IPageService? _pageService; - private INavigationItem[] _items = null!; private bool _loaded; #region DependencyProperties @@ -272,10 +271,8 @@ protected virtual void OnLoaded(object sender, RoutedEventArgs e) { Guard.IsNotNull(Frame, nameof(Frame)); - _items = MergeItems(); - _frameManager = new FrameManager(Frame, TransitionDuration, TransitionType); - _navigationManager = new NavigationManager(Frame, _pageService, _items); + _navigationManager = new NavigationManager(Frame, _pageService, MergeItems()); if (SelectedPageIndex > -1) { @@ -291,13 +288,11 @@ protected virtual void OnUnloaded(object sender, RoutedEventArgs e) Loaded -= OnLoaded; Unloaded -= OnUnloaded; - _frameManager.Dispose(); - _navigationManager.Dispose(); - - foreach (var item in _items) + foreach (var item in _navigationManager.NavigationItems) item.Click -= OnNavigationItemClicked; - _items = null!; + _frameManager.Dispose(); + _navigationManager.Dispose(); } /// @@ -350,7 +345,7 @@ static void MoveFocus(FrameworkElement element, FocusNavigationDirection directi /// protected virtual void OnNavigated() { - var navigatedFrom = _navigationManager.History.Count > 1 ? _items[_navigationManager.History[_navigationManager.History.Count - 2]] : null; + var navigatedFrom = _navigationManager.History.Count > 1 ? _navigationManager.NavigationItems[_navigationManager.History[_navigationManager.History.Count - 2]] : null; var newEvent = new RoutedNavigationEventArgs(NavigatedEvent, this, navigatedFrom, NavigationStack[NavigationStack.Count - 1]); RaiseEvent(newEvent); diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index 75ad1214f..7ef437760 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -16,7 +16,6 @@ internal sealed class NavigationManager : IDisposable { private readonly NavigationStackManager _navigationStackManager; private readonly Frame _frame; - private readonly INavigationItem[] _navigationItems; private readonly FrameworkElement?[] _instances; private readonly IPageService? _pageService; @@ -27,15 +26,17 @@ internal sealed class NavigationManager : IDisposable public bool CanGoBack => History.Count > 1; public readonly List History = new(); public ObservableCollection NavigationStack => _navigationStackManager.NavigationStack; + public readonly INavigationItem[] NavigationItems; public NavigationManager(Frame frame, IPageService? pageService, INavigationItem[] navigationItems) { - _navigationStackManager = new NavigationStackManager(); _instances = new FrameworkElement[navigationItems.Length]; - _navigationItems = navigationItems; + NavigationItems = navigationItems; _frame = frame; _pageService = pageService; + + _navigationStackManager = new NavigationStackManager(this); } #region Public methods @@ -96,6 +97,21 @@ public void NavigateTo(int id, object? dataContext = null) NavigateInternal(id, dataContext); } + public int GetItemId(Func prediction) + { + int selectedIndex = -1; + + for (int i = 0; i < NavigationItems.Length; i++) + { + if (!prediction.Invoke(NavigationItems[i])) continue; + + selectedIndex = i; + break; + } + + return selectedIndex; + } + #endregion #region NavigationInternal @@ -112,7 +128,7 @@ private bool NavigateBack() private bool NavigateInternal(int itemId, object? dataContext) { - if (_navigationItems.ElementAtOrDefault(itemId) is not { } item) + if (NavigationItems.ElementAtOrDefault(itemId) is not { } item) return false; var instance = GetFrameworkElement((itemId, item), dataContext); @@ -173,7 +189,7 @@ private bool CheckForNavigationCanceling(FrameworkElement instance) if (navigationCancelable is null) return true; - var navigationFrom = History.Count > 0 ? _navigationItems[History[History.Count - 1]] : null; + var navigationFrom = History.Count > 0 ? NavigationItems[History[History.Count - 1]] : null; return navigationCancelable.CouldNavigate(navigationFrom); } @@ -222,20 +238,5 @@ private FrameworkElement GetFrameworkElementFromCache((int itemId, INavigationIt return _instances[itemData.itemId]!; } - private int GetItemId(Func prediction) - { - int selectedIndex = -1; - - for (int i = 0; i < _navigationItems.Length; i++) - { - if (!prediction.Invoke(_navigationItems[i])) continue; - - selectedIndex = i; - break; - } - - return selectedIndex; - } - #endregion } diff --git a/src/Wpf.Ui/Services/Internal/NavigationStackManager.cs b/src/Wpf.Ui/Services/Internal/NavigationStackManager.cs index 118ca5cfc..b906001e6 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationStackManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationStackManager.cs @@ -8,15 +8,20 @@ namespace Wpf.Ui.Services.Internal; internal sealed class NavigationStackManager : IDisposable { + private readonly NavigationManager _navigationManager; private readonly ArrayPool _arrayPool = ArrayPool.Create(); - private readonly List _navigationStackHistory = new(); - + private readonly Dictionary _complexHistory = new(); public readonly ObservableCollection NavigationStack = new(); + public NavigationStackManager(NavigationManager navigationManager) + { + _navigationManager = navigationManager; + } + public void Dispose() { NavigationStack.Clear(); - _navigationStackHistory.Clear(); + _complexHistory.Clear(); } public bool AddFirstItemAndCheckIfNavigatingToCurrentItem(INavigationItem item) @@ -52,9 +57,12 @@ public void AddToNavigationStack(INavigationItem item, bool addToNavigationStack private void UpdateCurrentItem(INavigationItem item) { - if (item.IsHidden) + if (item.IsHidden || NavigationStack.Contains(item)) return; + if (NavigationStack.Count > 1) + AddToHistory(item); + NavigationStack[0].IsActive = false; NavigationStack[0] = item; @@ -63,17 +71,29 @@ private void UpdateCurrentItem(INavigationItem item) private void RecreateBreadcrumbsFromHistory(INavigationItem item) { - if (!item.WasInBreadcrumb && _navigationStackHistory.Count < 1) + if (!item.WasInBreadcrumb && !_complexHistory.ContainsKey(item)) return; - for (var i = 0; i < _navigationStackHistory.Count - 1; i++) + var history = _complexHistory[item]; + + var startIndex = 0; + + var index = _navigationManager.GetItemId(navigationItem => navigationItem == history[0]); + if (index > 0 && !history[0].IsHidden) + { + startIndex = 1; + NavigationStack[0].IsActive = false; + NavigationStack[0] = history[0]; + } + + for (var i = startIndex; i < history.Length; i++) { - var historyItem = _navigationStackHistory[i]; + var historyItem = history[i]; AddToNavigationStack(historyItem, true, false); historyItem.WasInBreadcrumb = false; } - _navigationStackHistory.Clear(); + _complexHistory.Remove(item); AddToNavigationStack(item, true, false); } @@ -88,8 +108,12 @@ private void ClearNavigationStack(INavigationItem item) navItem.IsActive = false; var index = NavigationStack.IndexOf(item); - if (index < navigationStackCount - 1 && _navigationStackHistory.Count == 0) - ClearNavigationStack(++index); + if (index >= navigationStackCount - 1 || _complexHistory.ContainsKey(item)) + return; + + AddToHistory(item); + + ClearNavigationStack(++index); } private void ClearNavigationStack(int navigationStackItemIndex) @@ -109,11 +133,28 @@ private void ClearNavigationStack(int navigationStackItemIndex) { var item = buffer[index]; NavigationStack.Remove(item); - - if (length > 1) - _navigationStackHistory.Add(item); } _arrayPool.Return(buffer, true); } + + private void AddToHistory(INavigationItem item) + { + var lastItem = NavigationStack[NavigationStack.Count - 1]; + var startIndex = NavigationStack.IndexOf(item); + if (startIndex < 0) + startIndex = 0; + + if (_complexHistory.ContainsKey(lastItem)) + _complexHistory.Remove(lastItem); + + _complexHistory.Add(lastItem, new INavigationItem[NavigationStack.Count - 1 - startIndex]); + + int i = 0; + for (int j = startIndex; j < NavigationStack.Count - 1; j++) + { + _complexHistory[lastItem][i] = NavigationStack[j]; + i++; + } + } } From 552763406528dad2955c75e2c44252e1469d5dff Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Tue, 16 Aug 2022 08:22:30 +0600 Subject: [PATCH 27/28] fixed my mistake --- src/Wpf.Ui/Services/Internal/NavigationStackManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Wpf.Ui/Services/Internal/NavigationStackManager.cs b/src/Wpf.Ui/Services/Internal/NavigationStackManager.cs index b906001e6..a2f59c6f2 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationStackManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationStackManager.cs @@ -79,7 +79,7 @@ private void RecreateBreadcrumbsFromHistory(INavigationItem item) var startIndex = 0; var index = _navigationManager.GetItemId(navigationItem => navigationItem == history[0]); - if (index > 0 && !history[0].IsHidden) + if (index >= 0 && !history[0].IsHidden) { startIndex = 1; NavigationStack[0].IsActive = false; From 6f116cb8957c1352c5b30d8f5159af71f06d64f2 Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Tue, 16 Aug 2022 09:00:04 +0600 Subject: [PATCH 28/28] removed addToHistory parameter --- src/Wpf.Ui.Demo/ViewModels/BreadcrumbPagesViewModel.cs | 2 +- src/Wpf.Ui.Demo/Views/Container.xaml.cs | 4 ++-- src/Wpf.Ui.Demo/Views/Pages/Controls.xaml.cs | 2 +- .../Views/Windows/ExperimentalWindow.xaml.cs | 4 ++-- src/Wpf.Ui/Controls/Interfaces/INavigation.cs | 6 ++---- src/Wpf.Ui/Controls/Navigation/NavigationBase.cs | 8 ++++---- src/Wpf.Ui/Mvvm/Contracts/INavigationService.cs | 6 ++---- src/Wpf.Ui/Mvvm/Services/NavigationService.cs | 8 ++++---- src/Wpf.Ui/Services/Internal/NavigationManager.cs | 10 +++------- 9 files changed, 21 insertions(+), 29 deletions(-) diff --git a/src/Wpf.Ui.Demo/ViewModels/BreadcrumbPagesViewModel.cs b/src/Wpf.Ui.Demo/ViewModels/BreadcrumbPagesViewModel.cs index 28a139f78..80037cd82 100644 --- a/src/Wpf.Ui.Demo/ViewModels/BreadcrumbPagesViewModel.cs +++ b/src/Wpf.Ui.Demo/ViewModels/BreadcrumbPagesViewModel.cs @@ -21,7 +21,7 @@ public BreadcrumbPagesViewModel(INavigationService navigationService) private void OnClick(string pageTag) { - _navigationService.NavigateTo($"/{pageTag}", true, this); + _navigationService.NavigateTo($"/{pageTag}", this); } private void OnNavigateBack() diff --git a/src/Wpf.Ui.Demo/Views/Container.xaml.cs b/src/Wpf.Ui.Demo/Views/Container.xaml.cs index b520f7ee3..aa02a4864 100644 --- a/src/Wpf.Ui.Demo/Views/Container.xaml.cs +++ b/src/Wpf.Ui.Demo/Views/Container.xaml.cs @@ -98,10 +98,10 @@ public INavigation GetNavigation() => RootNavigation; public void NavigateTo(Type type, object dataContext = null) - => RootNavigation.NavigateTo(type, true, dataContext); + => RootNavigation.NavigateTo(type, dataContext); public void NavigateTo(string pageTag, object dataContext = null) - => RootNavigation.NavigateTo(pageTag, true, dataContext); + => RootNavigation.NavigateTo(pageTag, dataContext); public void SetPageService(IPageService pageService) => RootNavigation.SetIPageService(pageService); diff --git a/src/Wpf.Ui.Demo/Views/Pages/Controls.xaml.cs b/src/Wpf.Ui.Demo/Views/Pages/Controls.xaml.cs index f13cc8e15..a1a574d82 100644 --- a/src/Wpf.Ui.Demo/Views/Pages/Controls.xaml.cs +++ b/src/Wpf.Ui.Demo/Views/Pages/Controls.xaml.cs @@ -115,6 +115,6 @@ private void MessageBox_RightButtonClick(object sender, System.Windows.RoutedEve private void OnBreadcrumbButtonClick(object sender, RoutedEventArgs e) { var viewModel = _serviceProvider.GetRequiredService(); - _navigation.NavigateTo("/page1", true, viewModel); + _navigation.NavigateTo("/page1", viewModel); } } diff --git a/src/Wpf.Ui.Demo/Views/Windows/ExperimentalWindow.xaml.cs b/src/Wpf.Ui.Demo/Views/Windows/ExperimentalWindow.xaml.cs index d4d4b1dcc..a90b5fb71 100644 --- a/src/Wpf.Ui.Demo/Views/Windows/ExperimentalWindow.xaml.cs +++ b/src/Wpf.Ui.Demo/Views/Windows/ExperimentalWindow.xaml.cs @@ -140,8 +140,8 @@ public void CloseWindow() => Close(); public void NavigateTo(Type type, object dataContext = null) - => RootNavigation.NavigateTo(type, true, dataContext); + => RootNavigation.NavigateTo(type, dataContext); public void NavigateTo(string pageTag, object dataContext = null) - => RootNavigation.NavigateTo(pageTag, true, dataContext); + => RootNavigation.NavigateTo(pageTag, dataContext); } diff --git a/src/Wpf.Ui/Controls/Interfaces/INavigation.cs b/src/Wpf.Ui/Controls/Interfaces/INavigation.cs index fa4bbe74a..1eeb093aa 100644 --- a/src/Wpf.Ui/Controls/Interfaces/INavigation.cs +++ b/src/Wpf.Ui/Controls/Interfaces/INavigation.cs @@ -105,15 +105,13 @@ public interface INavigation /// TODO /// /// - /// /// - void NavigateTo(string pageTag, bool addToHistory = true, object? dataContext = null); + void NavigateTo(string pageTag, object? dataContext = null); /// /// TODO /// /// - /// /// - void NavigateTo(Type type, bool addToHistory = true, object? dataContext = null); + void NavigateTo(Type type, object? dataContext = null); } diff --git a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs index 7200dfd21..4a29a17e9 100644 --- a/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs +++ b/src/Wpf.Ui/Controls/Navigation/NavigationBase.cs @@ -251,16 +251,16 @@ public void SetIPageService(IPageService pageService) public void ClearCache() => _navigationManager.ClearCache(); /// - public void NavigateTo(string pageTag, bool addToHistory = true, object? dataContext = null) + public void NavigateTo(string pageTag, object? dataContext = null) { - if (_navigationManager.NavigateTo(pageTag, addToHistory, dataContext)) + if (_navigationManager.NavigateTo(pageTag, dataContext)) OnNavigated(); } /// - public void NavigateTo(Type type, bool addToHistory = true, object? dataContext = null) + public void NavigateTo(Type type, object? dataContext = null) { - if (_navigationManager.NavigateTo(type, addToHistory, dataContext)) + if (_navigationManager.NavigateTo(type, dataContext)) OnNavigated(); } diff --git a/src/Wpf.Ui/Mvvm/Contracts/INavigationService.cs b/src/Wpf.Ui/Mvvm/Contracts/INavigationService.cs index 23519cd3e..73bf29cfa 100644 --- a/src/Wpf.Ui/Mvvm/Contracts/INavigationService.cs +++ b/src/Wpf.Ui/Mvvm/Contracts/INavigationService.cs @@ -57,15 +57,13 @@ public interface INavigationService /// Lets you navigate to the selected page based on it's tag. Should be used with . /// /// Tag of the page. - /// /// - void NavigateTo(string pageTag, bool addToHistory = true, object? dataContext = null); + void NavigateTo(string pageTag, object? dataContext = null); /// /// TODO /// /// - /// /// - void NavigateTo(Type type, bool addToHistory = true, object? dataContext = null); + void NavigateTo(Type type, object? dataContext = null); } diff --git a/src/Wpf.Ui/Mvvm/Services/NavigationService.cs b/src/Wpf.Ui/Mvvm/Services/NavigationService.cs index 83e07a2bd..0d973fddc 100644 --- a/src/Wpf.Ui/Mvvm/Services/NavigationService.cs +++ b/src/Wpf.Ui/Mvvm/Services/NavigationService.cs @@ -81,14 +81,14 @@ public void SetPageService(IPageService pageService) } /// - public void NavigateTo(string pageTag, bool addToHistory = true, object? dataContext = null) + public void NavigateTo(string pageTag, object? dataContext = null) { - NavigationControl.NavigateTo(pageTag, addToHistory, dataContext); + NavigationControl.NavigateTo(pageTag, dataContext); } /// - public void NavigateTo(Type type, bool addToHistory = true, object? dataContext = null) + public void NavigateTo(Type type, object? dataContext = null) { - NavigationControl.NavigateTo(type, addToHistory, dataContext); + NavigationControl.NavigateTo(type, dataContext); } } diff --git a/src/Wpf.Ui/Services/Internal/NavigationManager.cs b/src/Wpf.Ui/Services/Internal/NavigationManager.cs index 7ef437760..78d7a3629 100644 --- a/src/Wpf.Ui/Services/Internal/NavigationManager.cs +++ b/src/Wpf.Ui/Services/Internal/NavigationManager.cs @@ -21,7 +21,6 @@ internal sealed class NavigationManager : IDisposable private bool _isBackwardsNavigated; private bool _addToNavigationStack; - private bool _addToHistory = true; public bool CanGoBack => History.Count > 1; public readonly List History = new(); @@ -63,7 +62,7 @@ public void ClearCache() } } - public bool NavigateTo(string tag, bool addToHistory, object? dataContext = null) + public bool NavigateTo(string tag, object? dataContext = null) { Guard.IsNotNullOrEmpty(tag, nameof(tag)); @@ -83,7 +82,7 @@ public bool NavigateTo(string tag, bool addToHistory, object? dataContext = null return NavigateInternal(itemId, dataContext); } - public bool NavigateTo(Type type, bool addToHistory, object? dataContext = null) + public bool NavigateTo(Type type, object? dataContext = null) { var itemId = GetItemId(serviceItem => serviceItem.PageType == type); if (itemId < 0) @@ -171,10 +170,7 @@ private void AddToHistory(int itemId) History.RemoveAt(History.LastIndexOf(History[History.Count - 1])); } - if (_addToHistory) - History.Add(itemId); - - _addToHistory = true; + History.Add(itemId); } private bool CheckForNavigationCanceling(FrameworkElement instance)