Files
ShrlAlgoToolkit/WPFluent/Controls/NavigationView/NavigationViewItem.cs
2025-04-24 20:56:44 +08:00

456 lines
14 KiB
C#

/* Based on Windows UI Library https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.navigationviewitem?view=winrt-22621 */
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Windows.Automation.Peers;
using System.Windows.Controls;
using System.Windows.Input;
// ReSharper disable once CheckNamespace
namespace WPFluent.Controls;
/// <summary>
/// Represents the container for an item in a NavigationView control. When needed, it can be used as a normal button
/// with a <see cref="System.Windows.Controls.Primitives.ButtonBase.Click"/> action.
/// </summary>
[TemplatePart(Name = TemplateElementChevronGrid, Type = typeof(Grid))]
public class NavigationViewItem : System.Windows.Controls.Primitives.ButtonBase, INavigationViewItem, IIconControl
{
protected const string TemplateElementChevronGrid = "PART_ChevronGrid";
private static readonly DependencyPropertyKey MenuItemsPropertyKey = DependencyProperty.RegisterReadOnly(
nameof(MenuItems),
typeof(ObservableCollection<object>),
typeof(NavigationViewItem),
new PropertyMetadata(null));
/// <summary>
/// Identifies the <see cref="HasMenuItems"/> dependency property.
/// </summary>
internal static readonly DependencyPropertyKey HasMenuItemsPropertyKey =
DependencyProperty.RegisterReadOnly(
nameof(HasMenuItems),
typeof(bool),
typeof(NavigationViewItem),
new PropertyMetadata(false));
/// <summary>
/// Identifies the <see cref="HasMenuItems"/> dependency property.
/// </summary>
public static readonly DependencyProperty HasMenuItemsProperty =
HasMenuItemsPropertyKey.DependencyProperty;
/// <summary>
/// Identifies the <see cref="Icon"/> dependency property.
/// </summary>
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
nameof(Icon),
typeof(IconElement),
typeof(NavigationViewItem),
new PropertyMetadata(null, null, IconElement.Coerce));
/// <summary>
/// Identifies the <see cref="InfoBadge"/> dependency property.
/// </summary>
public static readonly DependencyProperty InfoBadgeProperty = DependencyProperty.Register(
nameof(InfoBadge),
typeof(InfoBadge),
typeof(NavigationViewItem),
new PropertyMetadata(null));
/// <summary>
/// Identifies the <see cref="IsActive"/> dependency property.
/// </summary>
public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register(
nameof(IsActive),
typeof(bool),
typeof(NavigationViewItem),
new PropertyMetadata(false));
/// <summary>
/// Identifies the <see cref="IsExpanded"/> dependency property.
/// </summary>
public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register(
nameof(IsExpanded),
typeof(bool),
typeof(NavigationViewItem),
new PropertyMetadata(false));
/// <summary>
/// Identifies the <see cref="IsPaneOpen"/> dependency property.
/// </summary>
public static readonly DependencyProperty IsPaneOpenProperty = DependencyProperty.Register(
nameof(IsPaneOpen),
typeof(bool),
typeof(NavigationViewItem),
new PropertyMetadata(false));
/// <summary>
/// Identifies the <see cref="MenuItems"/> dependency property.
/// </summary>
public static readonly DependencyProperty MenuItemsProperty = MenuItemsPropertyKey.DependencyProperty;
/// <summary>
/// Identifies the <see cref="MenuItemsSource"/> dependency property.
/// </summary>
public static readonly DependencyProperty MenuItemsSourceProperty = DependencyProperty.Register(
nameof(MenuItemsSource),
typeof(object),
typeof(NavigationViewItem),
new PropertyMetadata(null, OnMenuItemsSourceChanged));
/// <summary>
/// Identifies the <see cref="NavigationCacheMode"/> dependency property.
/// </summary>
public static readonly DependencyProperty NavigationCacheModeProperty = DependencyProperty.Register(
nameof(NavigationCacheMode),
typeof(NavigationCacheMode),
typeof(NavigationViewItem),
new FrameworkPropertyMetadata(NavigationCacheMode.Disabled));
/// <summary>
/// Identifies the <see cref="TargetPageTag"/> dependency property.
/// </summary>
public static readonly DependencyProperty TargetPageTagProperty = DependencyProperty.Register(
nameof(TargetPageTag),
typeof(string),
typeof(NavigationViewItem),
new PropertyMetadata(string.Empty));
/// <summary>
/// Identifies the <see cref="TargetPageType"/> dependency property.
/// </summary>
public static readonly DependencyProperty TargetPageTypeProperty = DependencyProperty.Register(
nameof(TargetPageType),
typeof(Type),
typeof(NavigationViewItem),
new PropertyMetadata(null));
static NavigationViewItem()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(NavigationViewItem),
new FrameworkPropertyMetadata(typeof(NavigationViewItem)));
}
public NavigationViewItem()
{
Id = Guid.NewGuid().ToString("n");
Unloaded += static (sender, _) =>
{
((NavigationViewItem)sender).NavigationViewItemParent = null;
};
Loaded += (_, _) => InitializeNavigationViewEvents();
// Initialize the `Items` collection
var menuItems = new ObservableCollection<object>();
menuItems.CollectionChanged += OnMenuItems_CollectionChanged;
SetValue(MenuItemsPropertyKey, menuItems);
}
public NavigationViewItem(Type targetPageType) : this() { SetValue(TargetPageTypeProperty, targetPageType); }
public NavigationViewItem(string name, Type targetPageType) : this(targetPageType)
{ SetValue(ContentProperty, name); }
public NavigationViewItem(string name, SymbolRegular icon, Type targetPageType) : this(targetPageType)
{
SetValue(ContentProperty, name);
SetValue(IconProperty, new SymbolIcon(icon));
}
public NavigationViewItem(string name, SymbolRegular icon, Type targetPageType, IList menuItems) : this(
name,
icon,
targetPageType)
{ SetValue(MenuItemsSourceProperty, menuItems); }
private void InitializeNavigationViewEvents()
{
if (NavigationView.GetNavigationParent(this) is { } navigationView)
{
SetCurrentValue(IsPaneOpenProperty, navigationView.IsPaneOpen);
navigationView.PaneOpened += (_, _) => SetCurrentValue(IsPaneOpenProperty, true);
navigationView.PaneClosed += (_, _) => SetCurrentValue(IsPaneOpenProperty, false);
}
}
private void OnMenuItems_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
SetValue(HasMenuItemsPropertyKey, MenuItems.Count > 0);
foreach (INavigationViewItem item in MenuItems.OfType<INavigationViewItem>())
{
item.NavigationViewItemParent = this;
}
}
private static void OnMenuItemsSourceChanged(DependencyObject? d, DependencyPropertyChangedEventArgs e)
{
if (d is not NavigationViewItem navigationViewItem)
{
return;
}
navigationViewItem.MenuItems.Clear();
if (e.NewValue is IEnumerable newItemsSource and not string)
{
foreach (var item in newItemsSource)
{
navigationViewItem.MenuItems.Add(item);
}
}
else if (e.NewValue != null)
{
navigationViewItem.MenuItems.Add(e.NewValue);
}
}
/// <inheritdoc/>
protected override void OnClick()
{
if (NavigationView.GetNavigationParent(this) is not { } navigationView)
{
return;
}
if (HasMenuItems && navigationView.IsPaneOpen)
{
SetCurrentValue(IsExpandedProperty, !IsExpanded);
}
if (TargetPageType is not null)
{
navigationView.OnNavigationViewItemClick(this);
}
base.OnClick();
}
protected override AutomationPeer OnCreateAutomationPeer() { return new NavigationViewItemAutomationPeer(this); }
/// <inheritdoc/>
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
if (string.IsNullOrWhiteSpace(TargetPageTag) && Content is not null)
{
SetCurrentValue(
TargetPageTagProperty,
Content as string ?? Content.ToString()?.ToLower().Trim() ?? string.Empty);
}
}
/// <summary>
/// Is called when mouse is clicked down.
/// </summary>
protected override void OnMouseDown(MouseButtonEventArgs e)
{
if (!HasMenuItems || e.LeftButton != MouseButtonState.Pressed)
{
base.OnMouseDown(e);
return;
}
if (NavigationView.GetNavigationParent(this) is not { } navigationView)
{
return;
}
if (!navigationView.IsPaneOpen ||
navigationView.PaneDisplayMode != NavigationViewPaneDisplayMode.Left ||
ChevronGrid is null)
{
base.OnMouseDown(e);
return;
}
var mouseOverChevron = ActualWidth < e.GetPosition(this).X + ChevronGrid.ActualWidth;
if (!mouseOverChevron)
{
base.OnMouseDown(e);
return;
}
SetCurrentValue(IsExpandedProperty, !IsExpanded);
for (int i = 0; i < MenuItems.Count; i++)
{
object? menuItem = MenuItems[i];
if (menuItem is not INavigationViewItem { IsActive: true })
{
continue;
}
if (IsExpanded)
{
Deactivate(navigationView);
}
else
{
Activate(navigationView);
}
break;
}
e.Handled = true;
}
protected Grid? ChevronGrid { get; set; }
/// <summary>
/// Correctly activates
/// </summary>
public virtual void Activate(INavigationView navigationView)
{
SetCurrentValue(IsActiveProperty, true);
if (!navigationView.IsPaneOpen && NavigationViewItemParent is not null)
{
NavigationViewItemParent.Activate(navigationView);
}
if (NavigationViewItemParent is not null)
{
if (navigationView.IsPaneOpen && navigationView.PaneDisplayMode != NavigationViewPaneDisplayMode.Top)
{
NavigationViewItemParent.IsExpanded = true;
}
else
{
NavigationViewItemParent.IsExpanded = false;
}
}
if (Icon is SymbolIcon symbolIcon && navigationView.PaneDisplayMode == NavigationViewPaneDisplayMode.LeftFluent)
{
symbolIcon.Filled = true;
}
}
/// <summary>
/// Correctly deactivates
/// </summary>
public virtual void Deactivate(INavigationView navigationView)
{
SetCurrentValue(IsActiveProperty, false);
NavigationViewItemParent?.Deactivate(navigationView);
if (!navigationView.IsPaneOpen && HasMenuItems)
{
SetCurrentValue(IsExpandedProperty, false);
}
if (Icon is SymbolIcon symbolIcon && navigationView.PaneDisplayMode == NavigationViewPaneDisplayMode.LeftFluent)
{
symbolIcon.Filled = false;
}
}
/// <inheritdoc/>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (GetTemplateChild(TemplateElementChevronGrid) is Grid chevronGrid)
{
ChevronGrid = chevronGrid;
}
}
/// <summary>
/// Gets a value indicating whether MenuItems.Count > 0
/// </summary>
[Browsable(false)]
[ReadOnly(true)]
public bool HasMenuItems { get => (bool)GetValue(HasMenuItemsProperty); }
/// <inheritdoc cref="IIconControl.Icon" />
[Bindable(true)]
[Category("Appearance")]
public IconElement? Icon { get => (IconElement?)GetValue(IconProperty); set => SetValue(IconProperty, value); }
/// <inheritdoc/>
public string Id { get; }
public InfoBadge? InfoBadge
{
get => (InfoBadge?)GetValue(InfoBadgeProperty);
set => SetValue(InfoBadgeProperty, value);
}
/// <inheritdoc/>
[Browsable(false)]
[ReadOnly(true)]
public bool IsActive { get => (bool)GetValue(IsActiveProperty); set => SetValue(IsActiveProperty, value); }
/// <inheritdoc/>
[Browsable(false)]
[ReadOnly(true)]
public bool IsExpanded { get => (bool)GetValue(IsExpandedProperty); set => SetValue(IsExpandedProperty, value); }
/// <inheritdoc/>
public bool IsMenuElement { get; set; }
[Browsable(false)]
[ReadOnly(true)]
public bool IsPaneOpen { get => (bool)GetValue(IsPaneOpenProperty); set => SetValue(IsPaneOpenProperty, value); }
/// <inheritdoc/>
public IList MenuItems => (ObservableCollection<object>)GetValue(MenuItemsProperty);
/// <inheritdoc/>
[Bindable(true)]
public object? MenuItemsSource
{
get => GetValue(MenuItemsSourceProperty);
set
{
if (value is null)
{
ClearValue(MenuItemsSourceProperty);
}
else
{
SetValue(MenuItemsSourceProperty, value);
}
}
}
/// <inheritdoc/>
public NavigationCacheMode NavigationCacheMode
{
get => (NavigationCacheMode)GetValue(NavigationCacheModeProperty);
set => SetValue(NavigationCacheModeProperty, value);
}
/// <inheritdoc/>
public INavigationViewItem? NavigationViewItemParent { get; set; }
/// <inheritdoc/>
public string TargetPageTag
{
get => (string)GetValue(TargetPageTagProperty);
set => SetValue(TargetPageTagProperty, value);
}
/// <inheritdoc/>
public Type? TargetPageType
{
get => (Type?)GetValue(TargetPageTypeProperty);
set => SetValue(TargetPageTypeProperty, value);
}
}