diff --git a/src/Avalonia.Base/Threading/DispatcherPriority.cs b/src/Avalonia.Base/Threading/DispatcherPriority.cs index ceda1c397f..a2b4b86bac 100644 --- a/src/Avalonia.Base/Threading/DispatcherPriority.cs +++ b/src/Avalonia.Base/Threading/DispatcherPriority.cs @@ -17,7 +17,7 @@ namespace Avalonia.Threading SystemIdle = 1, /// - /// The job will be processed when the application sis idle. + /// The job will be processed when the application is idle. /// ApplicationIdle = 2, diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs index a224ceaadd..d3bd45d13c 100644 --- a/src/Avalonia.Controls/TreeViewItem.cs +++ b/src/Avalonia.Controls/TreeViewItem.cs @@ -51,6 +51,7 @@ namespace Avalonia.Controls SelectableMixin.Attach(IsSelectedProperty); FocusableProperty.OverrideDefaultValue(true); ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); + ParentProperty.Changed.AddClassHandler((o, e) => o.OnParentChanged(e)); RequestBringIntoViewEvent.AddClassHandler((x, e) => x.OnRequestBringIntoView(e)); } @@ -179,5 +180,16 @@ namespace Avalonia.Controls return logical != null ? result : @default; } + + private void OnParentChanged(AvaloniaPropertyChangedEventArgs e) + { + if (!((ILogical)this).IsAttachedToLogicalTree && e.NewValue is null) + { + // If we're not attached to the logical tree, then OnDetachedFromLogicalTree isn't going to be + // called when the item is removed. This results in the item not being removed from the index, + // causing #3551. In this case, update the index when Parent is changed to null. + ItemContainerGenerator.UpdateIndex(); + } + } } } diff --git a/src/Avalonia.Input/FocusManager.cs b/src/Avalonia.Input/FocusManager.cs index bcae8a3c53..011ae6ce6b 100644 --- a/src/Avalonia.Input/FocusManager.cs +++ b/src/Avalonia.Input/FocusManager.cs @@ -53,11 +53,11 @@ namespace Avalonia.Input /// /// The control to focus. /// The method by which focus was changed. - /// Any input modifiers active at the time of focus. + /// Any key modifiers active at the time of focus. public void Focus( IInputElement control, NavigationMethod method = NavigationMethod.Unspecified, - InputModifiers modifiers = InputModifiers.None) + KeyModifiers keyModifiers = KeyModifiers.None) { if (control != null) { @@ -67,7 +67,7 @@ namespace Avalonia.Input if (scope != null) { Scope = scope; - SetFocusedElement(scope, control, method, modifiers); + SetFocusedElement(scope, control, method, keyModifiers); } } else if (Current != null) @@ -95,7 +95,7 @@ namespace Avalonia.Input /// The focus scope. /// The element to focus. May be null. /// The method by which focus was changed. - /// Any input modifiers active at the time of focus. + /// Any key modifiers active at the time of focus. /// /// If the specified scope is the current then the keyboard focus /// will change. @@ -104,7 +104,7 @@ namespace Avalonia.Input IFocusScope scope, IInputElement element, NavigationMethod method = NavigationMethod.Unspecified, - InputModifiers modifiers = InputModifiers.None) + KeyModifiers keyModifiers = KeyModifiers.None) { Contract.Requires(scope != null); @@ -123,7 +123,7 @@ namespace Avalonia.Input if (Scope == scope) { - KeyboardDevice.Instance?.SetFocusedElement(element, method, modifiers); + KeyboardDevice.Instance?.SetFocusedElement(element, method, keyModifiers); } } @@ -195,7 +195,7 @@ namespace Avalonia.Input { if (element is IInputElement inputElement && CanFocus(inputElement)) { - Instance?.Focus(inputElement, NavigationMethod.Pointer, ev.InputModifiers); + Instance?.Focus(inputElement, NavigationMethod.Pointer, ev.KeyModifiers); break; } diff --git a/src/Avalonia.Input/IFocusManager.cs b/src/Avalonia.Input/IFocusManager.cs index 84cd791ee0..9122cc428d 100644 --- a/src/Avalonia.Input/IFocusManager.cs +++ b/src/Avalonia.Input/IFocusManager.cs @@ -20,11 +20,11 @@ namespace Avalonia.Input /// /// The control to focus. /// The method by which focus was changed. - /// Any input modifiers active at the time of focus. + /// Any key modifiers active at the time of focus. void Focus( - IInputElement control, + IInputElement control, NavigationMethod method = NavigationMethod.Unspecified, - InputModifiers modifiers = InputModifiers.None); + KeyModifiers keyModifiers = KeyModifiers.None); /// /// Notifies the focus manager of a change in focus scope. diff --git a/src/Avalonia.Input/IKeyboardDevice.cs b/src/Avalonia.Input/IKeyboardDevice.cs index 2725638b9a..ba7e0484ee 100644 --- a/src/Avalonia.Input/IKeyboardDevice.cs +++ b/src/Avalonia.Input/IKeyboardDevice.cs @@ -63,6 +63,6 @@ namespace Avalonia.Input void SetFocusedElement( IInputElement element, NavigationMethod method, - InputModifiers modifiers); + KeyModifiers modifiers); } } diff --git a/src/Avalonia.Input/IKeyboardNavigationHandler.cs b/src/Avalonia.Input/IKeyboardNavigationHandler.cs index 4d0ae7e85d..88d00b3b50 100644 --- a/src/Avalonia.Input/IKeyboardNavigationHandler.cs +++ b/src/Avalonia.Input/IKeyboardNavigationHandler.cs @@ -19,10 +19,10 @@ namespace Avalonia.Input /// /// The current element. /// The direction to move. - /// Any input modifiers active at the time of focus. + /// Any key modifiers active at the time of focus. void Move( IInputElement element, NavigationDirection direction, - InputModifiers modifiers = InputModifiers.None); + KeyModifiers keyModifiers = KeyModifiers.None); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Input/KeyboardDevice.cs b/src/Avalonia.Input/KeyboardDevice.cs index 006a6b12d9..0321b0bdf3 100644 --- a/src/Avalonia.Input/KeyboardDevice.cs +++ b/src/Avalonia.Input/KeyboardDevice.cs @@ -35,7 +35,7 @@ namespace Avalonia.Input public void SetFocusedElement( IInputElement element, NavigationMethod method, - InputModifiers modifiers) + KeyModifiers keyModifiers) { if (element != FocusedElement) { @@ -53,7 +53,7 @@ namespace Avalonia.Input { RoutedEvent = InputElement.GotFocusEvent, NavigationMethod = method, - InputModifiers = modifiers, + KeyModifiers = keyModifiers, }); } } diff --git a/src/Avalonia.Input/KeyboardNavigationHandler.cs b/src/Avalonia.Input/KeyboardNavigationHandler.cs index 323a225b50..c425eeeedb 100644 --- a/src/Avalonia.Input/KeyboardNavigationHandler.cs +++ b/src/Avalonia.Input/KeyboardNavigationHandler.cs @@ -91,11 +91,11 @@ namespace Avalonia.Input /// /// The current element. /// The direction to move. - /// Any input modifiers active at the time of focus. + /// Any key modifiers active at the time of focus. public void Move( IInputElement element, NavigationDirection direction, - InputModifiers modifiers = InputModifiers.None) + KeyModifiers keyModifiers = KeyModifiers.None) { Contract.Requires(element != null); @@ -106,7 +106,7 @@ namespace Avalonia.Input var method = direction == NavigationDirection.Next || direction == NavigationDirection.Previous ? NavigationMethod.Tab : NavigationMethod.Directional; - FocusManager.Instance.Focus(next, method, modifiers); + FocusManager.Instance.Focus(next, method, keyModifiers); } } @@ -123,7 +123,7 @@ namespace Avalonia.Input { var direction = (e.KeyModifiers & KeyModifiers.Shift) == 0 ? NavigationDirection.Next : NavigationDirection.Previous; - Move(current, direction, e.Modifiers); + Move(current, direction, e.KeyModifiers); e.Handled = true; } } diff --git a/src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs b/src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs index fe626f4d38..abace92f08 100644 --- a/src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs +++ b/src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs @@ -45,7 +45,7 @@ namespace Avalonia.Win32.Embedding focused = focused.VisualParent; if (focused == _root) - KeyboardDevice.Instance.SetFocusedElement(null, NavigationMethod.Unspecified, InputModifiers.None); + KeyboardDevice.Instance.SetFocusedElement(null, NavigationMethod.Unspecified, KeyModifiers.None); } private void PlatformImpl_LostFocus() diff --git a/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs b/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs index f1123e3958..1258bb0109 100644 --- a/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs +++ b/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs @@ -44,7 +44,7 @@ namespace Avalonia.Win32.Input public void WindowActivated(Window window) { - SetFocusedElement(window, NavigationMethod.Unspecified, InputModifiers.None); + SetFocusedElement(window, NavigationMethod.Unspecified, KeyModifiers.None); } public string StringFromVirtualKey(uint virtualKey) diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 32b09e2c47..bd303a81cd 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -1002,6 +1002,35 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(1, child2Node.Presenter.Panel.Children.Count); } + [Fact] + public void Clearing_TreeView_Items_Clears_Index() + { + // Issue #3551 + var tree = CreateTestTreeData(); + var target = new TreeView + { + Template = CreateTreeViewTemplate(), + Items = tree, + }; + + var root = new TestRoot(); + root.Child = target; + + CreateNodeDataTemplate(target); + ApplyTemplates(target); + + var rootNode = tree[0]; + var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(rootNode); + + Assert.NotNull(container); + + root.Child = null; + + tree.Clear(); + + Assert.Empty(target.ItemContainerGenerator.Index.Containers); + } + private void ApplyTemplates(TreeView tree) { tree.ApplyTemplate(); diff --git a/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs b/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs index 3c8e800fca..df0a077c7f 100644 --- a/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs +++ b/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs @@ -35,7 +35,7 @@ namespace Avalonia.Input.UnitTests target.SetFocusedElement( focused.Object, NavigationMethod.Unspecified, - InputModifiers.None); + KeyModifiers.None); target.ProcessRawEvent( new RawKeyEventArgs( @@ -75,7 +75,7 @@ namespace Avalonia.Input.UnitTests target.SetFocusedElement( focused.Object, NavigationMethod.Unspecified, - InputModifiers.None); + KeyModifiers.None); target.ProcessRawEvent( new RawTextInputEventArgs(