mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-21 16:36:27 +08:00
Optimize NavMenu and add NavMenuItemSelectedEvent
This commit is contained in:
-22
@@ -1,22 +0,0 @@
|
||||
using System.Globalization;
|
||||
using Avalonia;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace AtomUI.Controls.Converters;
|
||||
|
||||
internal class DataGridHeaderIndicatorLayoutMarginsConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is Thickness paddings)
|
||||
{
|
||||
return new Thickness(0, 0, paddings.Right, 0);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -140,15 +140,13 @@ internal class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
|
||||
// 判断当前选中的是不是自己
|
||||
if (!ReferenceEquals(LatestSelectedItem, navMenuItem))
|
||||
{
|
||||
var navMenu = Menu as NavMenu;
|
||||
if (LatestSelectedItem != null)
|
||||
{
|
||||
var ancestorInfo = HasCommonAncestor(LatestSelectedItem, navMenuItem);
|
||||
if (!ancestorInfo.Item1)
|
||||
{
|
||||
if (Menu is NavMenu navMenu)
|
||||
{
|
||||
navMenu.ClearSelection();
|
||||
}
|
||||
navMenu?.ClearSelection();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -160,6 +158,7 @@ internal class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
|
||||
}
|
||||
navMenuItem.SelectItemRecursively();
|
||||
LatestSelectedItem = navMenuItem;
|
||||
navMenu?.RaiseNavMenuItemSelected(navMenuItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,7 +218,7 @@ internal class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
|
||||
}
|
||||
|
||||
_currentPressedIsValid = false;
|
||||
if (e.InitialPressMouseButton == MouseButton.Left && item.HasSubMenu == false)
|
||||
if (e.InitialPressMouseButton == MouseButton.Left)
|
||||
{
|
||||
Click(item);
|
||||
e.Handled = true;
|
||||
@@ -264,14 +263,25 @@ internal class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
|
||||
{
|
||||
if (e.Source is ILogical control && !Menu.IsLogicalAncestorOf(control))
|
||||
{
|
||||
Menu?.Close();
|
||||
CloseAllTopLevelMenuItems();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void WindowDeactivated(object? sender, EventArgs e)
|
||||
{
|
||||
Menu?.Close();
|
||||
CloseAllTopLevelMenuItems();
|
||||
}
|
||||
|
||||
private void CloseAllTopLevelMenuItems()
|
||||
{
|
||||
if (Menu != null)
|
||||
{
|
||||
foreach (var i in Menu.SubItems)
|
||||
{
|
||||
i.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static NavMenuItem? GetMenuItem(StyledElement? item) => (NavMenuItem?)GetMenuItemCore(item);
|
||||
@@ -352,12 +362,14 @@ internal class DefaultNavMenuInteractionHandler : INavMenuInteractionHandler
|
||||
internal void Click(INavMenuItem item)
|
||||
{
|
||||
item.RaiseClick();
|
||||
var navMenu = FindNavMenu(item);
|
||||
navMenu?.RaiseNavMenuItemClick(item);
|
||||
if (!item.StaysOpenOnClick)
|
||||
if (Menu is NavMenu navMenu)
|
||||
{
|
||||
var topLevelItem = FindTopLevelMenuItem(item);
|
||||
topLevelItem?.Close();
|
||||
navMenu.RaiseNavMenuItemClick(item);
|
||||
if (!item.HasSubMenu && !item.StaysOpenOnClick)
|
||||
{
|
||||
var topLevelItem = FindTopLevelMenuItem(item);
|
||||
topLevelItem?.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
using Avalonia.Input;
|
||||
using AtomUI.Controls.Primitives;
|
||||
using Avalonia.Rendering;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
internal interface INavMenu : INavMenuElement, IInputElement
|
||||
internal interface INavMenu : INavMenuElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the currently selected submenu item.
|
||||
/// </summary>
|
||||
INavMenuItem? SelectedItem { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the menu interaction handler.
|
||||
/// </summary>
|
||||
@@ -19,4 +24,14 @@ internal interface INavMenu : INavMenuElement, IInputElement
|
||||
/// Gets the root of the visual tree, if the control is attached to a visual tree.
|
||||
/// </summary>
|
||||
IRenderRoot? VisualRoot { get; }
|
||||
|
||||
/// <summary>
|
||||
/// List of paths opened by default
|
||||
/// </summary>
|
||||
IList<TreeNodePath>? DefaultOpenPaths { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default selected path
|
||||
/// </summary>
|
||||
TreeNodePath? DefaultSelectedPath { get; set; }
|
||||
}
|
||||
@@ -5,11 +5,6 @@ namespace AtomUI.Controls;
|
||||
|
||||
public interface INavMenuElement : IInputElement, ILogical
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the currently selected submenu item.
|
||||
/// </summary>
|
||||
INavMenuItem? SelectedItem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the submenu items.
|
||||
/// </summary>
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
public interface INavMenuItem : INavMenuElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the currently selected submenu item.
|
||||
/// </summary>
|
||||
INavMenuItem? SelectedItem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value that item key.
|
||||
/// </summary>
|
||||
|
||||
@@ -9,7 +9,7 @@ using NavMenuControl = NavMenu;
|
||||
|
||||
internal class InlineNavMenuInteractionHandler : INavMenuInteractionHandler
|
||||
{
|
||||
internal INavMenu? NavMenu { get; private set; }
|
||||
internal INavMenu? Menu { get; private set; }
|
||||
|
||||
public void Attach(NavMenu navMenu) => AttachCore(navMenu);
|
||||
public void Detach(NavMenu navMenu) => DetachCore(navMenu);
|
||||
@@ -19,24 +19,24 @@ internal class InlineNavMenuInteractionHandler : INavMenuInteractionHandler
|
||||
|
||||
internal void AttachCore(INavMenu navMenu)
|
||||
{
|
||||
if (NavMenu != null)
|
||||
if (Menu != null)
|
||||
{
|
||||
throw new NotSupportedException("InlineNavMenuInteractionHandler is already attached.");
|
||||
}
|
||||
NavMenu = navMenu;
|
||||
NavMenu.PointerPressed += PointerPressed;
|
||||
NavMenu.PointerReleased += PointerReleased;
|
||||
Menu = navMenu;
|
||||
Menu.PointerPressed += PointerPressed;
|
||||
Menu.PointerReleased += PointerReleased;
|
||||
}
|
||||
|
||||
internal void DetachCore(INavMenu navMenu)
|
||||
{
|
||||
if (NavMenu != navMenu)
|
||||
if (Menu != navMenu)
|
||||
{
|
||||
throw new NotSupportedException("InlineNavMenuInteractionHandler is not attached to the navMenu.");
|
||||
}
|
||||
NavMenu.PointerPressed -= PointerPressed;
|
||||
NavMenu.PointerReleased -= PointerReleased;
|
||||
NavMenu = null;
|
||||
Menu.PointerPressed -= PointerPressed;
|
||||
Menu.PointerReleased -= PointerReleased;
|
||||
Menu = null;
|
||||
}
|
||||
|
||||
protected virtual void PointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
@@ -75,15 +75,13 @@ internal class InlineNavMenuInteractionHandler : INavMenuInteractionHandler
|
||||
// 判断当前选中的是不是自己
|
||||
if (!ReferenceEquals(LatestSelectedItem, navMenuItem))
|
||||
{
|
||||
var navMenu = Menu as NavMenu;
|
||||
if (LatestSelectedItem != null)
|
||||
{
|
||||
var ancestorInfo = HasCommonAncestor(LatestSelectedItem, navMenuItem);
|
||||
if (!ancestorInfo.Item1)
|
||||
{
|
||||
if (NavMenu is NavMenu navMenu)
|
||||
{
|
||||
navMenu.ClearSelection();
|
||||
}
|
||||
navMenu?.ClearSelection();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -96,6 +94,7 @@ internal class InlineNavMenuInteractionHandler : INavMenuInteractionHandler
|
||||
|
||||
navMenuItem.SelectItemRecursively();
|
||||
LatestSelectedItem = navMenuItem;
|
||||
navMenu?.RaiseNavMenuItemSelected(navMenuItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,7 +155,7 @@ internal class InlineNavMenuInteractionHandler : INavMenuInteractionHandler
|
||||
|
||||
_currentPressedIsValid = false;
|
||||
|
||||
if (e.InitialPressMouseButton == MouseButton.Left && !item.HasSubMenu)
|
||||
if (e.InitialPressMouseButton == MouseButton.Left)
|
||||
{
|
||||
Click(item);
|
||||
e.Handled = true;
|
||||
@@ -166,8 +165,10 @@ internal class InlineNavMenuInteractionHandler : INavMenuInteractionHandler
|
||||
internal void Click(INavMenuItem item)
|
||||
{
|
||||
item.RaiseClick();
|
||||
var navMenu = FindNavMenu(item);
|
||||
navMenu?.RaiseNavMenuItemClick(item);
|
||||
if (Menu is NavMenu navMenu)
|
||||
{
|
||||
navMenu.RaiseNavMenuItemClick(item);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Open(INavMenuItem item)
|
||||
@@ -192,21 +193,4 @@ internal class InlineNavMenuInteractionHandler : INavMenuInteractionHandler
|
||||
item = item.Parent;
|
||||
}
|
||||
}
|
||||
|
||||
private static NavMenu? FindNavMenu(INavMenuItem item)
|
||||
{
|
||||
var current = (INavMenuElement?)item;
|
||||
NavMenu? result = null;
|
||||
|
||||
while (current != null && !(current is NavMenu))
|
||||
{
|
||||
current = (current as INavMenuItem)?.Parent;
|
||||
}
|
||||
|
||||
if (current is NavMenu navMenu)
|
||||
{
|
||||
result = navMenu;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ using System.Reactive.Disposables;
|
||||
using AtomUI.Controls.Primitives;
|
||||
using AtomUI.Controls.Themes;
|
||||
using AtomUI.Data;
|
||||
using AtomUI.Exceptions;
|
||||
using AtomUI.Theme;
|
||||
using AtomUI.Theme.Data;
|
||||
using AtomUI.Theme.Styling;
|
||||
@@ -28,60 +27,62 @@ using Avalonia.VisualTree;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public class NavMenuItemClickEventArgs : RoutedEventArgs
|
||||
{
|
||||
public NavMenuItemClickEventArgs(RoutedEvent routedEvent, INavMenuItem navMenuItem)
|
||||
: base(routedEvent)
|
||||
{
|
||||
NavMenuItem = navMenuItem;
|
||||
}
|
||||
|
||||
public INavMenuItem NavMenuItem { get; }
|
||||
}
|
||||
|
||||
[PseudoClasses(
|
||||
NavMenuPseudoClass.InlineMode,
|
||||
NavMenuPseudoClass.HorizontalMode,
|
||||
NavMenuPseudoClass.VerticalMode,
|
||||
NavMenuPseudoClass.DarkStyle,
|
||||
NavMenuPseudoClass.LightStyle)]
|
||||
public class NavMenu : SelectingItemsControl,
|
||||
IFocusScope,
|
||||
public class NavMenu : ItemsControl,
|
||||
IFocusScope,
|
||||
INavMenu,
|
||||
IMotionAwareControl,
|
||||
IControlSharedTokenResourcesHost
|
||||
{
|
||||
#region 公共属性定义
|
||||
|
||||
public static readonly DirectProperty<NavMenu, IList<TreeNodePath>?> DefaultOpenPathsProperty =
|
||||
AvaloniaProperty.RegisterDirect<NavMenu, IList<TreeNodePath>?>(
|
||||
nameof(DefaultOpenPaths),
|
||||
o => o.DefaultOpenPaths,
|
||||
(o, v) => o.DefaultOpenPaths = v);
|
||||
|
||||
public static readonly DirectProperty<NavMenu, INavMenuItem?> SelectedItemProperty =
|
||||
AvaloniaProperty.RegisterDirect<NavMenu, INavMenuItem?>(
|
||||
nameof(SelectedItem),
|
||||
o => o.SelectedItem);
|
||||
|
||||
public static readonly DirectProperty<NavMenu, TreeNodePath?> DefaultSelectedPathProperty =
|
||||
AvaloniaProperty.RegisterDirect<NavMenu, TreeNodePath?>(
|
||||
nameof(DefaultSelectedPath),
|
||||
o => o.DefaultSelectedPath,
|
||||
(o, v) => o.DefaultSelectedPath = v);
|
||||
|
||||
|
||||
public static readonly DirectProperty<NavMenu, bool> IsOpenProperty =
|
||||
AvaloniaProperty.RegisterDirect<NavMenu, bool>(
|
||||
nameof(IsOpen),
|
||||
o => o.IsOpen);
|
||||
|
||||
|
||||
public static readonly StyledProperty<bool> IsMotionEnabledProperty =
|
||||
MotionAwareControlProperty.IsMotionEnabledProperty.AddOwner<NavMenu>();
|
||||
|
||||
|
||||
public static readonly StyledProperty<bool> IsAccordionModeProperty =
|
||||
AvaloniaProperty.Register<NavMenu, bool>(nameof(IsAccordionMode), false);
|
||||
|
||||
|
||||
public static readonly StyledProperty<NavMenuMode> ModeProperty =
|
||||
AvaloniaProperty.Register<NavMenu, NavMenuMode>(nameof(Mode), NavMenuMode.Horizontal);
|
||||
|
||||
public static readonly StyledProperty<bool> IsDarkStyleProperty =
|
||||
AvaloniaProperty.Register<NavMenu, bool>(nameof(IsDarkStyle), false);
|
||||
|
||||
public static readonly DirectProperty<NavMenu, IList<TreeNodePath>?> DefaultOpenPathsProperty =
|
||||
AvaloniaProperty.RegisterDirect<NavMenu, IList<TreeNodePath>?>(
|
||||
nameof(DefaultOpenPaths),
|
||||
o => o.DefaultOpenPaths,
|
||||
(o, v) => o.DefaultOpenPaths = v);
|
||||
|
||||
public INavMenuItem? _selectedItem;
|
||||
|
||||
public INavMenuItem? SelectedItem
|
||||
{
|
||||
get => _selectedItem;
|
||||
private set => SetAndRaise(SelectedItemProperty, ref _selectedItem, value);
|
||||
}
|
||||
|
||||
private IList<TreeNodePath>? _defaultOpenPaths;
|
||||
|
||||
public IList<TreeNodePath>? DefaultOpenPaths
|
||||
@@ -135,9 +136,10 @@ public class NavMenu : SelectingItemsControl,
|
||||
#region 公共事件定义
|
||||
|
||||
public static readonly RoutedEvent<NavMenuItemClickEventArgs> NavMenuItemClickEvent =
|
||||
RoutedEvent.Register<NavMenu, NavMenuItemClickEventArgs>(
|
||||
nameof(NavMenuItemClick),
|
||||
RoutingStrategies.Bubble);
|
||||
RoutedEvent.Register<NavMenu, NavMenuItemClickEventArgs>(nameof(NavMenuItemClick), RoutingStrategies.Bubble);
|
||||
|
||||
public static readonly RoutedEvent<NavMenuItemSelectedEventArgs> NavMenuItemSelectedEvent =
|
||||
RoutedEvent.Register<NavMenu, NavMenuItemSelectedEventArgs>(nameof(NavMenuItemSelected), RoutingStrategies.Bubble);
|
||||
|
||||
public static readonly RoutedEvent<RoutedEventArgs> OpenedEvent =
|
||||
RoutedEvent.Register<NavMenu, RoutedEventArgs>(nameof(Opened), RoutingStrategies.Bubble);
|
||||
@@ -151,6 +153,12 @@ public class NavMenu : SelectingItemsControl,
|
||||
remove => RemoveHandler(NavMenuItemClickEvent, value);
|
||||
}
|
||||
|
||||
public event EventHandler<NavMenuItemSelectedEventArgs>? NavMenuItemSelected
|
||||
{
|
||||
add => AddHandler(NavMenuItemSelectedEvent, value);
|
||||
remove => RemoveHandler(NavMenuItemSelectedEvent, value);
|
||||
}
|
||||
|
||||
public event EventHandler<RoutedEventArgs>? Opened
|
||||
{
|
||||
add => AddHandler(OpenedEvent, value);
|
||||
@@ -185,17 +193,6 @@ public class NavMenu : SelectingItemsControl,
|
||||
|
||||
IRenderRoot? INavMenu.VisualRoot => VisualRoot;
|
||||
|
||||
INavMenuItem? INavMenuElement.SelectedItem
|
||||
{
|
||||
get
|
||||
{
|
||||
var index = SelectedIndex;
|
||||
return (index != -1) ? (INavMenuItem?)ContainerFromIndex(index) : null;
|
||||
}
|
||||
|
||||
set => SelectedIndex = value is Control c ? IndexFromContainer(c) : -1;
|
||||
}
|
||||
|
||||
IEnumerable<INavMenuItem> INavMenuElement.SubItems => LogicalChildren.OfType<INavMenuItem>();
|
||||
|
||||
/// <summary>
|
||||
@@ -217,7 +214,6 @@ public class NavMenu : SelectingItemsControl,
|
||||
KeyboardNavigationMode.Once);
|
||||
AutomationProperties.AccessibilityViewProperty.OverrideDefaultValue<NavMenu>(AccessibilityView.Control);
|
||||
AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue<NavMenu>(AutomationControlType.Menu);
|
||||
AutoScrollToSelectedItemProperty.OverrideDefaultValue<NavMenu>(false);
|
||||
NavMenuItem.SubmenuOpenedEvent.AddClassHandler<NavMenu>((x, e) => x.OnSubmenuOpened(e));
|
||||
}
|
||||
|
||||
@@ -240,7 +236,7 @@ public class NavMenu : SelectingItemsControl,
|
||||
}
|
||||
|
||||
IsOpen = false;
|
||||
SelectedIndex = -1;
|
||||
SelectedItem = null;
|
||||
|
||||
RaiseEvent(new RoutedEventArgs
|
||||
{
|
||||
@@ -286,15 +282,6 @@ public class NavMenu : SelectingItemsControl,
|
||||
}
|
||||
IsOpen = true;
|
||||
}
|
||||
|
||||
private void ValidateSelectionMode()
|
||||
{
|
||||
if (SelectionMode.HasFlag(SelectionMode.Multiple))
|
||||
{
|
||||
throw new InvalidPropertyValueException(SelectionModeProperty.Name, SelectionMode.Multiple,
|
||||
$"The value '{SelectionMode.Multiple}' is invalid for the '{SelectionModeProperty.Name}' property in NavMenuItem.");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
@@ -331,10 +318,6 @@ public class NavMenu : SelectingItemsControl,
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (change.Property == SelectionModeProperty)
|
||||
{
|
||||
ValidateSelectionMode();
|
||||
}
|
||||
if (change.Property == IsDarkStyleProperty ||
|
||||
change.Property == ModeProperty)
|
||||
{
|
||||
@@ -660,6 +643,11 @@ public class NavMenu : SelectingItemsControl,
|
||||
RaiseEvent(new NavMenuItemClickEventArgs(NavMenuItemClickEvent, navMenuItem));
|
||||
}
|
||||
|
||||
internal void RaiseNavMenuItemSelected(INavMenuItem navMenuItem)
|
||||
{
|
||||
RaiseEvent(new NavMenuItemSelectedEventArgs(NavMenuItemSelectedEvent, navMenuItem));
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using Avalonia.Interactivity;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public class NavMenuItemClickEventArgs : RoutedEventArgs
|
||||
{
|
||||
public NavMenuItemClickEventArgs(RoutedEvent routedEvent, INavMenuItem navMenuItem)
|
||||
: base(routedEvent)
|
||||
{
|
||||
NavMenuItem = navMenuItem;
|
||||
}
|
||||
|
||||
public INavMenuItem NavMenuItem { get; }
|
||||
}
|
||||
|
||||
public class NavMenuItemSelectedEventArgs : RoutedEventArgs
|
||||
{
|
||||
public NavMenuItemSelectedEventArgs(RoutedEvent routedEvent, INavMenuItem navMenuItem)
|
||||
: base(routedEvent)
|
||||
{
|
||||
NavMenuItem = navMenuItem;
|
||||
}
|
||||
|
||||
public INavMenuItem NavMenuItem { get; }
|
||||
}
|
||||
@@ -191,12 +191,12 @@ public class NavMenuItem : HeaderedSelectingItemsControl,
|
||||
|
||||
protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute;
|
||||
|
||||
INavMenuItem? INavMenuElement.SelectedItem
|
||||
INavMenuItem? INavMenuItem.SelectedItem
|
||||
{
|
||||
get
|
||||
{
|
||||
var index = SelectedIndex;
|
||||
return (index != -1) ? (INavMenuItem?)ContainerFromIndex(index) : null;
|
||||
return index != -1 ? (INavMenuItem?)ContainerFromIndex(index) : null;
|
||||
}
|
||||
|
||||
set => SelectedIndex = value is Control c ? IndexFromContainer(c) : -1;
|
||||
|
||||
Reference in New Issue
Block a user