Merge pull request #701 from irihitech/navmenu
NavMenu: SelectionChanging Event
This commit is contained in:
@@ -189,6 +189,16 @@ public class NavMenu : ItemsControl, ICustomKeyboardNavigation
|
||||
add => AddHandler(SelectionChangedEvent, value);
|
||||
remove => RemoveHandler(SelectionChangedEvent, value);
|
||||
}
|
||||
|
||||
|
||||
public static readonly RoutedEvent<SelectionChangingEventArgs> SelectionChangingEvent =
|
||||
RoutedEvent.Register<NavMenu, SelectionChangingEventArgs>(nameof(SelectionChanging), RoutingStrategies.Bubble);
|
||||
|
||||
public event EventHandler<SelectionChangingEventArgs> SelectionChanging
|
||||
{
|
||||
add => AddHandler(SelectionChangingEvent, value);
|
||||
remove => RemoveHandler(SelectionChangingEvent, value);
|
||||
}
|
||||
|
||||
protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
|
||||
{
|
||||
@@ -486,4 +496,28 @@ public class NavMenu : ItemsControl, ICustomKeyboardNavigation
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal bool CanChangeSelection(NavMenuItem item)
|
||||
{
|
||||
object? newSelection = null;
|
||||
if (item.DataContext is not null && item.DataContext != DataContext)
|
||||
newSelection = item.DataContext;
|
||||
else
|
||||
newSelection = item;
|
||||
var args = new SelectionChangingEventArgs(
|
||||
SelectionChangingEvent,
|
||||
new[] { SelectedItem },
|
||||
new[] { newSelection })
|
||||
{
|
||||
Source = this,
|
||||
};
|
||||
RaiseEvent(args);
|
||||
var result = args.CanSelect;
|
||||
if (result == false)
|
||||
{
|
||||
var container = GetContainerForItem(SelectedItem);
|
||||
container?.Focus(NavigationMethod.Directional);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -381,6 +381,10 @@ public class NavMenuItem : HeaderedItemsControl
|
||||
|
||||
internal void SelectItem(NavMenuItem item)
|
||||
{
|
||||
if (item == this && RootMenu?.CanChangeSelection(item) != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
SetCurrentValue(IsSelectedProperty, item == this);
|
||||
SetCurrentValue(IsHighlightedProperty, true);
|
||||
|
||||
|
||||
24
src/Ursa/Controls/NavMenu/SelectionChangingEventArgs.cs
Normal file
24
src/Ursa/Controls/NavMenu/SelectionChangingEventArgs.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Collections;
|
||||
using Avalonia.Interactivity;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
public class SelectionChangingEventArgs: RoutedEventArgs
|
||||
{
|
||||
/// <summary>Gets the items that were added to the selection.</summary>
|
||||
public IList NewItems { get; }
|
||||
|
||||
/// <summary>Gets the items that were removed from the selection.</summary>
|
||||
public IList OldItems { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the selection can be changed. If set to <c>false</c>, the selection will not change.
|
||||
/// </summary>
|
||||
public bool CanSelect { get; set; } = true;
|
||||
|
||||
public SelectionChangingEventArgs(RoutedEvent routedEvent, IList oldItems, IList newItems): base(routedEvent)
|
||||
{
|
||||
OldItems = oldItems;
|
||||
NewItems = newItems;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Headless;
|
||||
using Avalonia.Headless.XUnit;
|
||||
using Avalonia.LogicalTree;
|
||||
using Ursa.Controls;
|
||||
|
||||
namespace HeadlessTest.Ursa.Controls.NavMenuTests.CanSelectTests;
|
||||
|
||||
public class Test
|
||||
{
|
||||
[AvaloniaFact]
|
||||
public void CanSelect_Blocks_Selection_Change_Inline_Xaml()
|
||||
{
|
||||
Window window = new Window
|
||||
{
|
||||
Width = 400,
|
||||
Height = 400,
|
||||
};
|
||||
|
||||
var view = new TestView1();
|
||||
|
||||
window.Content = view;
|
||||
|
||||
window.Show();
|
||||
|
||||
var menu = view.FindControl<NavMenu>("Menu");
|
||||
var item1 = view.FindControl<NavMenuItem>("MenuItem1");
|
||||
var item2 = view.FindControl<NavMenuItem>("MenuItem2");
|
||||
var item3 = view.FindControl<NavMenuItem>("MenuItem3");
|
||||
|
||||
Assert.NotNull(menu);
|
||||
Assert.NotNull(item1);
|
||||
Assert.NotNull(item2);
|
||||
Assert.NotNull(item3);
|
||||
|
||||
var point1 = item1.TranslatePoint(new Point(0, 0), window);
|
||||
var point2 = item2.TranslatePoint(new Point(0, 0), window);
|
||||
var point3 = item3.TranslatePoint(new Point(0, 0), window);
|
||||
Assert.NotNull(point1);
|
||||
Assert.NotNull(point2);
|
||||
Assert.NotNull(point3);
|
||||
|
||||
window.MouseDown(new Point(point1.Value.X+10, point1.Value.Y+10), Avalonia.Input.MouseButton.Left);
|
||||
window.MouseUp(new Point(point1.Value.X+10, point1.Value.Y+10), Avalonia.Input.MouseButton.Left);
|
||||
Assert.Equal(item1, menu.SelectedItem);
|
||||
|
||||
window.MouseDown(new Point(point2.Value.X+10, point2.Value.Y+10), Avalonia.Input.MouseButton.Left);
|
||||
window.MouseUp(new Point(point2.Value.X+10, point2.Value.Y+10), Avalonia.Input.MouseButton.Left);
|
||||
Assert.Equal(item1, menu.SelectedItem); // Should not change selection due to CanSelect being false
|
||||
|
||||
window.MouseDown(new Point(point3.Value.X+10, point3.Value.Y+10), Avalonia.Input.MouseButton.Left);
|
||||
window.MouseUp(new Point(point3.Value.X+10, point3.Value.Y+10), Avalonia.Input.MouseButton.Left);
|
||||
Assert.Equal(item3, menu.SelectedItem); // Should change selection to item3
|
||||
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void CanSelect_Blocks_Selection_Change_Inline_Code()
|
||||
{
|
||||
Window window = new Window
|
||||
{
|
||||
Width = 400,
|
||||
Height = 400,
|
||||
};
|
||||
|
||||
var view = new TestView2();
|
||||
|
||||
window.Content = view;
|
||||
|
||||
window.Show();
|
||||
|
||||
var menu = view.FindControl<NavMenu>("Menu");
|
||||
var items = menu.GetLogicalDescendants().OfType<NavMenuItem>().ToList();
|
||||
var item1 = items[0];
|
||||
var item2 = items[1];
|
||||
var item3 = items[2];
|
||||
|
||||
Assert.NotNull(menu);
|
||||
Assert.NotNull(item1);
|
||||
Assert.NotNull(item2);
|
||||
Assert.NotNull(item3);
|
||||
|
||||
var point1 = item1.TranslatePoint(new Point(0, 0), window);
|
||||
var point2 = item2.TranslatePoint(new Point(0, 0), window);
|
||||
var point3 = item3.TranslatePoint(new Point(0, 0), window);
|
||||
Assert.NotNull(point1);
|
||||
Assert.NotNull(point2);
|
||||
Assert.NotNull(point3);
|
||||
|
||||
window.MouseDown(new Point(point1.Value.X+10, point1.Value.Y+10), Avalonia.Input.MouseButton.Left);
|
||||
window.MouseUp(new Point(point1.Value.X+10, point1.Value.Y+10), Avalonia.Input.MouseButton.Left);
|
||||
Assert.Equal(item1.DataContext, menu.SelectedItem);
|
||||
|
||||
window.MouseDown(new Point(point2.Value.X+10, point2.Value.Y+10), Avalonia.Input.MouseButton.Left);
|
||||
window.MouseUp(new Point(point2.Value.X+10, point2.Value.Y+10), Avalonia.Input.MouseButton.Left);
|
||||
Assert.Equal(item1.DataContext, menu.SelectedItem); // Should not change selection due to CanSelect being false
|
||||
|
||||
window.MouseDown(new Point(point3.Value.X+10, point3.Value.Y+10), Avalonia.Input.MouseButton.Left);
|
||||
window.MouseUp(new Point(point3.Value.X+10, point3.Value.Y+10), Avalonia.Input.MouseButton.Left);
|
||||
Assert.Equal(item3.DataContext, menu.SelectedItem); // Should change selection to item3
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:u="https://irihi.tech/ursa"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="HeadlessTest.Ursa.Controls.NavMenuTests.CanSelectTests.TestView1">
|
||||
<u:NavMenu Name="Menu" ExpandWidth="400" IsHorizontalCollapsed="False" SelectionChanging="Menu_OnSelectionChanging">
|
||||
<u:NavMenuItem Name="MenuItem1" Header="Item 1" />
|
||||
<u:NavMenuItem Name="MenuItem2" Header="Item 2" />
|
||||
<u:NavMenuItem Name="MenuItem3" Header="Item 3" />
|
||||
</u:NavMenu>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,23 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Ursa.Controls;
|
||||
|
||||
namespace HeadlessTest.Ursa.Controls.NavMenuTests.CanSelectTests;
|
||||
|
||||
public partial class TestView1 : UserControl
|
||||
{
|
||||
public TestView1()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void Menu_OnSelectionChanging(object? sender, SelectionChangingEventArgs e)
|
||||
{
|
||||
var newItem = e.NewItems;
|
||||
if (newItem is [NavMenuItem { Name: "MenuItem2" }])
|
||||
{
|
||||
e.CanSelect = false; // Prevent selection change for MenuItem2
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:u="https://irihi.tech/ursa"
|
||||
xmlns:canSelectTests="clr-namespace:HeadlessTest.Ursa.Controls.NavMenuTests.CanSelectTests"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:DataType="canSelectTests:TestView2ViewModel"
|
||||
x:CompileBindings="True"
|
||||
x:Class="HeadlessTest.Ursa.Controls.NavMenuTests.CanSelectTests.TestView2">
|
||||
<u:NavMenu Name="Menu" ItemsSource="{Binding MenuItems}" SelectionChanging="NavMenu_OnSelectionChanging"/>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Ursa.Controls;
|
||||
|
||||
namespace HeadlessTest.Ursa.Controls.NavMenuTests.CanSelectTests;
|
||||
|
||||
public partial class TestView2 : UserControl
|
||||
{
|
||||
public TestView2()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.DataContext = new TestView2ViewModel();
|
||||
}
|
||||
|
||||
private void NavMenu_OnSelectionChanging(object? sender, SelectionChangingEventArgs e)
|
||||
{
|
||||
if (e.NewItems is [MenuItemViewModel item])
|
||||
{
|
||||
if (item.Text.Contains("2"))
|
||||
{
|
||||
e.CanSelect = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class TestView2ViewModel
|
||||
{
|
||||
public ObservableCollection<MenuItemViewModel> MenuItems { get; } = new()
|
||||
{
|
||||
new MenuItemViewModel { Text = "Menu Item 1" },
|
||||
new MenuItemViewModel { Text = "Menu Item 2" },
|
||||
new MenuItemViewModel { Text = "Menu Item 3" }
|
||||
};
|
||||
}
|
||||
|
||||
public partial class MenuItemViewModel: ObservableObject
|
||||
{
|
||||
[ObservableProperty] private string? _text;
|
||||
}
|
||||
Reference in New Issue
Block a user