456 lines
14 KiB
C#
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);
|
|
}
|
|
}
|