优化更新代码,添加界面功能并整合
This commit is contained in:
466
WPFluent/Controls/NavigationView/NavigationView.Base.cs
Normal file
466
WPFluent/Controls/NavigationView/NavigationView.Base.cs
Normal file
@@ -0,0 +1,466 @@
|
||||
|
||||
|
||||
|
||||
/* Based on Windows UI Library https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.navigationview?view=winrt-22621 */
|
||||
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Windows.Input;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace WPFluent.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a container that enables navigation of app content. It has a header, a view for the main content, and a
|
||||
/// menu pane for navigation commands.
|
||||
/// </summary>
|
||||
public partial class NavigationView : System.Windows.Controls.Control, INavigationView
|
||||
{
|
||||
private static readonly Thickness AutoSuggestBoxMarginDefault = new(8, 8, 8, 16);
|
||||
private static readonly Thickness FrameMarginDefault = new(0, 50, 0, 0);
|
||||
private static readonly Thickness TitleBarPaneCompactMarginDefault = new(35, 0, 0, 0);
|
||||
|
||||
private static readonly Thickness TitleBarPaneOpenMarginDefault = new(35, 0, 0, 0);
|
||||
|
||||
private readonly ObservableCollection<string> _autoSuggestBoxItems = [];
|
||||
private readonly ObservableCollection<NavigationViewBreadcrumbItem> _breadcrumbBarItems = [];
|
||||
|
||||
/// <summary>
|
||||
/// Initializes static members of the <see cref="NavigationView"/> class and overrides default property metadata.
|
||||
/// </summary>
|
||||
static NavigationView()
|
||||
{
|
||||
DefaultStyleKeyProperty.OverrideMetadata(
|
||||
typeof(NavigationView),
|
||||
new FrameworkPropertyMetadata(typeof(NavigationView)));
|
||||
MarginProperty.OverrideMetadata(
|
||||
typeof(NavigationView),
|
||||
new FrameworkPropertyMetadata(new Thickness(0, 0, 0, 0)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NavigationView"/> class.
|
||||
/// </summary>
|
||||
public NavigationView()
|
||||
{
|
||||
NavigationParent = this;
|
||||
|
||||
Loaded += OnLoaded;
|
||||
Unloaded += OnUnloaded;
|
||||
SizeChanged += OnSizeChanged;
|
||||
|
||||
// Initialize MenuItems collection
|
||||
var menuItems = new ObservableCollection<object>();
|
||||
menuItems.CollectionChanged += OnMenuItems_CollectionChanged;
|
||||
SetValue(MenuItemsPropertyKey, menuItems);
|
||||
|
||||
var footerMenuItems = new ObservableCollection<object>();
|
||||
footerMenuItems.CollectionChanged += OnMenuItems_CollectionChanged;
|
||||
SetValue(FooterMenuItemsPropertyKey, footerMenuItems);
|
||||
}
|
||||
|
||||
private void AutoSuggestBoxOnQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
|
||||
{
|
||||
var suggestions = new List<string>();
|
||||
var querySplit = args.QueryText.Split(' ');
|
||||
|
||||
foreach(var item in _autoSuggestBoxItems)
|
||||
{
|
||||
bool isMatch = true;
|
||||
|
||||
foreach(string queryToken in querySplit)
|
||||
{
|
||||
if(item.IndexOf(queryToken, StringComparison.CurrentCultureIgnoreCase) < 0)
|
||||
{
|
||||
isMatch = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(isMatch)
|
||||
{
|
||||
suggestions.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
if(suggestions.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var element = suggestions.First();
|
||||
|
||||
if(NavigateToMenuItemFromAutoSuggestBox(MenuItems, element))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_ = NavigateToMenuItemFromAutoSuggestBox(FooterMenuItems, element);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigate to the page after its name is selected in <see cref="AutoSuggestBox"/>.
|
||||
/// </summary>
|
||||
private void AutoSuggestBoxOnSuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
|
||||
{
|
||||
if(sender.IsSuggestionListOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(args.SelectedItem is not string selectedSuggestBoxItem)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(NavigateToMenuItemFromAutoSuggestBox(MenuItems, selectedSuggestBoxItem))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_ = NavigateToMenuItemFromAutoSuggestBox(FooterMenuItems, selectedSuggestBoxItem);
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
private void NavigationStackOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
switch(e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
_breadcrumbBarItems.Add(new NavigationViewBreadcrumbItem((INavigationViewItem)e.NewItems![0]!));
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
_breadcrumbBarItems.RemoveAt(e.OldStartingIndex);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
_breadcrumbBarItems[0] = new NavigationViewBreadcrumbItem((INavigationViewItem)e.NewItems![0]!);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
_breadcrumbBarItems.Clear();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(e), e.Action, $"Unsupported action: {e.Action}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// TODO: Refresh
|
||||
UpdateVisualState((NavigationView)sender);
|
||||
}
|
||||
|
||||
private void UpdateAutoSuggestBoxSuggestions()
|
||||
{
|
||||
if(AutoSuggestBox == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_autoSuggestBoxItems.Clear();
|
||||
|
||||
AddItemsToAutoSuggestBoxItems();
|
||||
}
|
||||
|
||||
protected virtual void AddItemsToAutoSuggestBoxItems()
|
||||
{
|
||||
AddItemsToAutoSuggestBoxItems(MenuItems);
|
||||
AddItemsToAutoSuggestBoxItems(FooterMenuItems);
|
||||
}
|
||||
|
||||
protected virtual void AddItemsToAutoSuggestBoxItems(IEnumerable list)
|
||||
{
|
||||
foreach(NavigationViewItem singleNavigationViewItem in list.OfType<NavigationViewItem>())
|
||||
{
|
||||
if(singleNavigationViewItem is { Content: string content, TargetPageType: { } } &&
|
||||
!string.IsNullOrWhiteSpace(content))
|
||||
{
|
||||
_autoSuggestBoxItems.Add(content);
|
||||
}
|
||||
|
||||
if(singleNavigationViewItem.HasMenuItems)
|
||||
{
|
||||
AddItemsToAutoSuggestBoxItems(singleNavigationViewItem.MenuItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void AddItemsToDictionaries()
|
||||
{
|
||||
AddItemsToDictionaries(MenuItems);
|
||||
AddItemsToDictionaries(FooterMenuItems);
|
||||
}
|
||||
|
||||
protected virtual void AddItemsToDictionaries(IEnumerable list)
|
||||
{
|
||||
foreach(NavigationViewItem singleNavigationViewItem in list.OfType<NavigationViewItem>())
|
||||
{
|
||||
if(!PageIdOrTargetTagNavigationViewsDictionary.ContainsKey(singleNavigationViewItem.Id))
|
||||
{
|
||||
PageIdOrTargetTagNavigationViewsDictionary.Add(singleNavigationViewItem.Id, singleNavigationViewItem);
|
||||
}
|
||||
|
||||
if(!PageIdOrTargetTagNavigationViewsDictionary.ContainsKey(singleNavigationViewItem.TargetPageTag))
|
||||
{
|
||||
PageIdOrTargetTagNavigationViewsDictionary.Add(
|
||||
singleNavigationViewItem.TargetPageTag,
|
||||
singleNavigationViewItem);
|
||||
}
|
||||
|
||||
if(singleNavigationViewItem.TargetPageType != null &&
|
||||
!PageTypeNavigationViewsDictionary.ContainsKey(singleNavigationViewItem.TargetPageType))
|
||||
{
|
||||
PageTypeNavigationViewsDictionary.Add(singleNavigationViewItem.TargetPageType, singleNavigationViewItem);
|
||||
}
|
||||
|
||||
singleNavigationViewItem.IsMenuElement = true;
|
||||
|
||||
if(singleNavigationViewItem.HasMenuItems)
|
||||
{
|
||||
AddItemsToDictionaries(singleNavigationViewItem.MenuItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This virtual method is called when <see cref="AutoSuggestBoxSymbolButton"/> is clicked.
|
||||
/// </summary>
|
||||
protected virtual void AutoSuggestBoxSymbolButtonOnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SetCurrentValue(IsPaneOpenProperty, !IsPaneOpen);
|
||||
_ = AutoSuggestBox?.Focus();
|
||||
}
|
||||
|
||||
protected virtual void BreadcrumbBarOnItemClicked(
|
||||
Controls.BreadcrumbBar sender,
|
||||
BreadcrumbBarItemClickedEventArgs e)
|
||||
{
|
||||
var item = (NavigationViewBreadcrumbItem)e.Item;
|
||||
_ = Navigate(item.PageId);
|
||||
}
|
||||
|
||||
protected virtual void CloseNavigationViewItemMenus()
|
||||
{
|
||||
if(Journal.Count <= 0 || IsPaneOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DeactivateMenuItems(MenuItems);
|
||||
DeactivateMenuItems(FooterMenuItems);
|
||||
|
||||
INavigationViewItem currentItem = PageIdOrTargetTagNavigationViewsDictionary[Journal[^1]];
|
||||
if(currentItem.NavigationViewItemParent is null)
|
||||
{
|
||||
currentItem.Activate(this);
|
||||
return;
|
||||
}
|
||||
|
||||
currentItem.Deactivate(this);
|
||||
currentItem.NavigationViewItemParent?.Activate(this);
|
||||
}
|
||||
|
||||
protected void DeactivateMenuItems(IEnumerable list)
|
||||
{
|
||||
foreach(var item in list)
|
||||
{
|
||||
if(item is NavigationViewItem singleNavigationViewItem)
|
||||
{
|
||||
singleNavigationViewItem.Deactivate(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool NavigateToMenuItemFromAutoSuggestBox(IEnumerable list, string selectedSuggestBoxItem)
|
||||
{
|
||||
foreach(NavigationViewItem singleNavigationViewItem in list.OfType<NavigationViewItem>())
|
||||
{
|
||||
if(singleNavigationViewItem.Content is string content && content == selectedSuggestBoxItem)
|
||||
{
|
||||
_ = NavigateInternal(singleNavigationViewItem);
|
||||
singleNavigationViewItem.BringIntoView();
|
||||
_ = singleNavigationViewItem.Focus(); // TODO: Element or content?
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if(NavigateToMenuItemFromAutoSuggestBox(singleNavigationViewItem.MenuItems, selectedSuggestBoxItem))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This virtual method is called when <see cref="BackButton"/> is clicked.
|
||||
/// </summary>
|
||||
protected virtual void OnBackButtonClick(object sender, RoutedEventArgs e) { _ = GoBack(); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnInitialized(EventArgs e)
|
||||
{
|
||||
base.OnInitialized(e);
|
||||
|
||||
NavigationStack.CollectionChanged += NavigationStackOnCollectionChanged;
|
||||
|
||||
InvalidateArrange();
|
||||
InvalidateVisual();
|
||||
UpdateLayout();
|
||||
|
||||
UpdateAutoSuggestBoxSuggestions();
|
||||
|
||||
AddItemsToDictionaries();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This virtual method is called when <see cref="ItemTemplate"/> is changed.
|
||||
/// </summary>
|
||||
protected virtual void OnItemTemplateChanged() { UpdateMenuItemsTemplate(); }
|
||||
|
||||
protected override void OnMouseDown(MouseButtonEventArgs e)
|
||||
{
|
||||
// Back button
|
||||
if(e.ChangedButton is MouseButton.XButton1)
|
||||
{
|
||||
_ = GoBack();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
base.OnMouseDown(e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This virtual method is called when <see cref="PaneDisplayMode"/> is changed.
|
||||
/// </summary>
|
||||
protected virtual void OnPaneDisplayModeChanged()
|
||||
{
|
||||
switch(PaneDisplayMode)
|
||||
{
|
||||
case NavigationViewPaneDisplayMode.LeftFluent:
|
||||
SetCurrentValue(IsBackButtonVisibleProperty, NavigationViewBackButtonVisible.Collapsed);
|
||||
SetCurrentValue(IsPaneToggleVisibleProperty, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This virtual method is called when ActualWidth or ActualHeight (or both) changed.
|
||||
/// </summary>
|
||||
protected virtual void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
// TODO: Update reveal
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This virtual method is called when <see cref="ToggleButton"/> is clicked.
|
||||
/// </summary>
|
||||
protected virtual void OnToggleButtonClick(object sender, RoutedEventArgs e)
|
||||
{ SetCurrentValue(IsPaneOpenProperty, !IsPaneOpen); }
|
||||
|
||||
/// <summary>
|
||||
/// This virtual method is called when this element is detached form a loaded tree.
|
||||
/// </summary>
|
||||
protected virtual void OnUnloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Loaded -= OnLoaded;
|
||||
Unloaded -= OnUnloaded;
|
||||
SizeChanged -= OnSizeChanged;
|
||||
|
||||
NavigationStack.CollectionChanged -= NavigationStackOnCollectionChanged;
|
||||
|
||||
PageIdOrTargetTagNavigationViewsDictionary.Clear();
|
||||
PageTypeNavigationViewsDictionary.Clear();
|
||||
|
||||
ClearJournal();
|
||||
|
||||
if(AutoSuggestBox is not null)
|
||||
{
|
||||
AutoSuggestBox.SuggestionChosen -= AutoSuggestBoxOnSuggestionChosen;
|
||||
AutoSuggestBox.QuerySubmitted -= AutoSuggestBoxOnQuerySubmitted;
|
||||
}
|
||||
|
||||
if(Header is Controls.BreadcrumbBar breadcrumbBar)
|
||||
{
|
||||
breadcrumbBar.ItemClicked -= BreadcrumbBarOnItemClicked;
|
||||
}
|
||||
|
||||
if(ToggleButton is not null)
|
||||
{
|
||||
ToggleButton.Click -= OnToggleButtonClick;
|
||||
}
|
||||
|
||||
if(BackButton is not null)
|
||||
{
|
||||
BackButton.Click -= OnToggleButtonClick;
|
||||
}
|
||||
|
||||
if(AutoSuggestBoxSymbolButton is not null)
|
||||
{
|
||||
AutoSuggestBoxSymbolButton.Click -= AutoSuggestBoxSymbolButtonOnClick;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void UpdateMenuItemsTemplate()
|
||||
{
|
||||
UpdateMenuItemsTemplate(MenuItems);
|
||||
UpdateMenuItemsTemplate(FooterMenuItems);
|
||||
}
|
||||
|
||||
protected virtual void UpdateMenuItemsTemplate(IEnumerable list)
|
||||
{
|
||||
if(ItemTemplate == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach(var item in list)
|
||||
{
|
||||
if(item is NavigationViewItem singleNavigationViewItem)
|
||||
{
|
||||
singleNavigationViewItem.Template = ItemTemplate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static void UpdateVisualState(NavigationView navigationView)
|
||||
{
|
||||
// Skip display modes that don't have multiple states
|
||||
if(navigationView.PaneDisplayMode
|
||||
is NavigationViewPaneDisplayMode.LeftFluent
|
||||
or NavigationViewPaneDisplayMode.Top
|
||||
or NavigationViewPaneDisplayMode.Bottom)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_ = VisualStateManager.GoToState(navigationView, navigationView.IsPaneOpen ? "PaneOpen" : "PaneCompact", true);
|
||||
}
|
||||
|
||||
protected Dictionary<string, INavigationViewItem> PageIdOrTargetTagNavigationViewsDictionary
|
||||
{
|
||||
get;
|
||||
} =
|
||||
[];
|
||||
|
||||
protected Dictionary<Type, INavigationViewItem> PageTypeNavigationViewsDictionary { get; } = [];
|
||||
|
||||
internal void OnNavigationViewItemClick(NavigationViewItem navigationViewItem)
|
||||
{
|
||||
OnItemInvoked();
|
||||
|
||||
_ = NavigateInternal(navigationViewItem);
|
||||
}
|
||||
|
||||
internal void ToggleAllExpands()
|
||||
{
|
||||
// TODO: When shift clicked on navigationviewitem
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public INavigationViewItem? SelectedItem { get; protected set; }
|
||||
}
|
||||
Reference in New Issue
Block a user