/* 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; /// /// Represents the container for an item in a NavigationView control. When needed, it can be used as a normal button /// with a action. /// [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), typeof(NavigationViewItem), new PropertyMetadata(null)); /// /// Identifies the dependency property. /// internal static readonly DependencyPropertyKey HasMenuItemsPropertyKey = DependencyProperty.RegisterReadOnly( nameof(HasMenuItems), typeof(bool), typeof(NavigationViewItem), new PropertyMetadata(false)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty HasMenuItemsProperty = HasMenuItemsPropertyKey.DependencyProperty; /// /// Identifies the dependency property. /// public static readonly DependencyProperty IconProperty = DependencyProperty.Register( nameof(Icon), typeof(IconElement), typeof(NavigationViewItem), new PropertyMetadata(null, null, IconElement.Coerce)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty InfoBadgeProperty = DependencyProperty.Register( nameof(InfoBadge), typeof(InfoBadge), typeof(NavigationViewItem), new PropertyMetadata(null)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register( nameof(IsActive), typeof(bool), typeof(NavigationViewItem), new PropertyMetadata(false)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register( nameof(IsExpanded), typeof(bool), typeof(NavigationViewItem), new PropertyMetadata(false)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty IsPaneOpenProperty = DependencyProperty.Register( nameof(IsPaneOpen), typeof(bool), typeof(NavigationViewItem), new PropertyMetadata(false)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty MenuItemsProperty = MenuItemsPropertyKey.DependencyProperty; /// /// Identifies the dependency property. /// public static readonly DependencyProperty MenuItemsSourceProperty = DependencyProperty.Register( nameof(MenuItemsSource), typeof(object), typeof(NavigationViewItem), new PropertyMetadata(null, OnMenuItemsSourceChanged)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty NavigationCacheModeProperty = DependencyProperty.Register( nameof(NavigationCacheMode), typeof(NavigationCacheMode), typeof(NavigationViewItem), new FrameworkPropertyMetadata(NavigationCacheMode.Disabled)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty TargetPageTagProperty = DependencyProperty.Register( nameof(TargetPageTag), typeof(string), typeof(NavigationViewItem), new PropertyMetadata(string.Empty)); /// /// Identifies the dependency property. /// 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(); 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()) { 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); } } /// 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); } /// 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); } } /// /// Is called when mouse is clicked down. /// 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; } /// /// Correctly activates /// 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; } } /// /// Correctly deactivates /// 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; } } /// public override void OnApplyTemplate() { base.OnApplyTemplate(); if (GetTemplateChild(TemplateElementChevronGrid) is Grid chevronGrid) { ChevronGrid = chevronGrid; } } /// /// Gets a value indicating whether MenuItems.Count > 0 /// [Browsable(false)] [ReadOnly(true)] public bool HasMenuItems { get => (bool)GetValue(HasMenuItemsProperty); } /// [Bindable(true)] [Category("Appearance")] public IconElement? Icon { get => (IconElement?)GetValue(IconProperty); set => SetValue(IconProperty, value); } /// public string Id { get; } public InfoBadge? InfoBadge { get => (InfoBadge?)GetValue(InfoBadgeProperty); set => SetValue(InfoBadgeProperty, value); } /// [Browsable(false)] [ReadOnly(true)] public bool IsActive { get => (bool)GetValue(IsActiveProperty); set => SetValue(IsActiveProperty, value); } /// [Browsable(false)] [ReadOnly(true)] public bool IsExpanded { get => (bool)GetValue(IsExpandedProperty); set => SetValue(IsExpandedProperty, value); } /// public bool IsMenuElement { get; set; } [Browsable(false)] [ReadOnly(true)] public bool IsPaneOpen { get => (bool)GetValue(IsPaneOpenProperty); set => SetValue(IsPaneOpenProperty, value); } /// public IList MenuItems => (ObservableCollection)GetValue(MenuItemsProperty); /// [Bindable(true)] public object? MenuItemsSource { get => GetValue(MenuItemsSourceProperty); set { if (value is null) { ClearValue(MenuItemsSourceProperty); } else { SetValue(MenuItemsSourceProperty, value); } } } /// public NavigationCacheMode NavigationCacheMode { get => (NavigationCacheMode)GetValue(NavigationCacheModeProperty); set => SetValue(NavigationCacheModeProperty, value); } /// public INavigationViewItem? NavigationViewItemParent { get; set; } /// public string TargetPageTag { get => (string)GetValue(TargetPageTagProperty); set => SetValue(TargetPageTagProperty, value); } /// public Type? TargetPageType { get => (Type?)GetValue(TargetPageTypeProperty); set => SetValue(TargetPageTypeProperty, value); } }