From fa8380d92d3c17bd517827aa0ce65bac504a14f9 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 00:52:23 +0200 Subject: [PATCH 01/11] focus first item in DropDown on key up/down --- src/Avalonia.Controls/DropDown.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index 8596d06d2c..c941f92449 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Linq; using Avalonia.Controls.Generators; using Avalonia.Controls.Primitives; using Avalonia.Controls.Shapes; @@ -138,6 +139,16 @@ namespace Avalonia.Controls e.Handled = true; } } + else if (IsDropDownOpen && SelectedIndex < 0 && ItemCount > 0 && + (e.Key == Key.Up || e.Key == Key.Down)) + { + var firstChild = Presenter?.Panel?.Children.FirstOrDefault(c => c.Focusable); + if (firstChild != null) + { + firstChild.Focus(); + e.Handled = true; + } + } } /// From 16ebfb5be9672c5d4068c5d6b6ca135b6f1b84db Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 00:55:46 +0200 Subject: [PATCH 02/11] focus DropDown after DropDown popup closed, otherwise focus is unpredicatable --- src/Avalonia.Controls/DropDown.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index c941f92449..847db8a089 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -179,10 +179,22 @@ namespace Avalonia.Controls if (_popup != null) { _popup.Opened -= PopupOpened; + _popup.Closed -= PopupClosed; } _popup = e.NameScope.Get("PART_Popup"); _popup.Opened += PopupOpened; + _popup.Closed += PopupClosed; + + base.OnTemplateApplied(e); + } + + private void PopupClosed(object sender, EventArgs e) + { + if (Focusable) + { + Focus(); + } } private void PopupOpened(object sender, EventArgs e) From d26d9842566601065ae6c75d982a4ecafb76494a Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 00:58:23 +0200 Subject: [PATCH 03/11] focus dropdown item in more safe manner --- src/Avalonia.Controls/DropDown.cs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index 847db8a089..4ac2b6f822 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -170,6 +170,7 @@ namespace Avalonia.Controls e.Handled = true; } } + base.OnPointerPressed(e); } @@ -199,18 +200,26 @@ namespace Avalonia.Controls private void PopupOpened(object sender, EventArgs e) { - var selectedIndex = SelectedIndex; - - if (selectedIndex != -1) - { - var container = ItemContainerGenerator.ContainerFromIndex(selectedIndex); - container?.Focus(); - } + TryFocusSelectedItem(); } private void SelectedItemChanged(AvaloniaPropertyChangedEventArgs e) { UpdateSelectionBoxItem(e.NewValue); + TryFocusSelectedItem(); + } + + private void TryFocusSelectedItem() + { + var selectedIndex = SelectedIndex; + if (IsDropDownOpen && selectedIndex != -1) + { + var container = ItemContainerGenerator.ContainerFromIndex(selectedIndex); + if (container != null && container.Focusable) + { + container.Focus(); + } + } } private void UpdateSelectionBoxItem(object item) From a297f0086304e6c47b44655fb7cdd14420e84657 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 01:02:36 +0200 Subject: [PATCH 04/11] Scroll and focus to selected item when dropdown is shown --- src/Avalonia.Controls/DropDown.cs | 8 +++++++ src/Avalonia.Controls/DropDownItem.cs | 32 ++++----------------------- 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index 4ac2b6f822..a30cc231b7 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -190,6 +190,14 @@ namespace Avalonia.Controls base.OnTemplateApplied(e); } + internal void ItemFocused(DropDownItem dropDownItem) + { + if (IsDropDownOpen && dropDownItem.IsFocused && dropDownItem.IsArrangeValid) + { + dropDownItem.BringIntoView(); + } + } + private void PopupClosed(object sender, EventArgs e) { if (Focusable) diff --git a/src/Avalonia.Controls/DropDownItem.cs b/src/Avalonia.Controls/DropDownItem.cs index fb465e93ec..1e22ededf6 100644 --- a/src/Avalonia.Controls/DropDownItem.cs +++ b/src/Avalonia.Controls/DropDownItem.cs @@ -2,43 +2,19 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Reactive.Linq; namespace Avalonia.Controls { /// /// A selectable item in a . /// - public class DropDownItem : ContentControl, ISelectable + public class DropDownItem : ListBoxItem { - /// - /// Defines the property. - /// - public static readonly StyledProperty IsSelectedProperty = - AvaloniaProperty.Register(nameof(IsSelected)); - - /// - /// Initializes static members of the class. - /// - static DropDownItem() - { - FocusableProperty.OverrideDefaultValue(true); - } - public DropDownItem() { - this.GetObservable(DropDownItem.IsFocusedProperty).Subscribe(focused => - { - PseudoClasses.Set(":selected", focused); - }); - } - - /// - /// Gets or sets the selection state of the item. - /// - public bool IsSelected - { - get { return GetValue(IsSelectedProperty); } - set { SetValue(IsSelectedProperty, value); } + this.GetObservable(DropDownItem.IsFocusedProperty).Where(focused => focused) + .Subscribe(_ => (Parent as DropDown)?.ItemFocused(this)); } } } From c2c71105a49612058d6a601687cd4550800ea250 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 01:04:05 +0200 Subject: [PATCH 05/11] style a bit focused dropdown item, so keyboard navigation is visible --- src/Avalonia.Themes.Default/DropDownItem.xaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Themes.Default/DropDownItem.xaml b/src/Avalonia.Themes.Default/DropDownItem.xaml index f52608c0a8..a15e770bf5 100644 --- a/src/Avalonia.Themes.Default/DropDownItem.xaml +++ b/src/Avalonia.Themes.Default/DropDownItem.xaml @@ -4,6 +4,7 @@ + - + + + From b8b7de237d50f44c4925c4787e12215cf4084f41 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 01:06:05 +0200 Subject: [PATCH 06/11] fit dropdown popup in screen --- src/Avalonia.Themes.Default/DropDown.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Themes.Default/DropDown.xaml b/src/Avalonia.Themes.Default/DropDown.xaml index 451f1c2f23..4684e1c872 100644 --- a/src/Avalonia.Themes.Default/DropDown.xaml +++ b/src/Avalonia.Themes.Default/DropDown.xaml @@ -35,6 +35,7 @@ MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}" MaxHeight="{TemplateBinding MaxDropDownHeight}" PlacementTarget="{TemplateBinding}" + ObeyScreenEdges="True" StaysOpen="False"> From 59a286ba103ff673779e8d58ea95151d3d95d696 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 01:10:29 +0200 Subject: [PATCH 07/11] let dropdown support virtualization if needed --- src/Avalonia.Controls/DropDown.cs | 25 ++++++++++++++++++++++- src/Avalonia.Themes.Default/DropDown.xaml | 5 ++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index a30cc231b7..d17089c127 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -4,8 +4,10 @@ using System; using System.Linq; using Avalonia.Controls.Generators; +using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Shapes; +using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.LogicalTree; using Avalonia.Media; @@ -13,12 +15,17 @@ using Avalonia.VisualTree; namespace Avalonia.Controls { - /// /// A drop-down list control. /// public class DropDown : SelectingItemsControl { + /// + /// The default value for the property. + /// + private static readonly FuncTemplate DefaultPanel = + new FuncTemplate(() => new VirtualizingStackPanel()); + /// /// Defines the property. /// @@ -40,6 +47,12 @@ namespace Avalonia.Controls public static readonly DirectProperty SelectionBoxItemProperty = AvaloniaProperty.RegisterDirect(nameof(SelectionBoxItem), o => o.SelectionBoxItem); + /// + /// Defines the property. + /// + public static readonly StyledProperty VirtualizationModeProperty = + ItemsPresenter.VirtualizationModeProperty.AddOwner(); + private bool _isDropDownOpen; private Popup _popup; private object _selectionBoxItem; @@ -49,6 +62,7 @@ namespace Avalonia.Controls /// static DropDown() { + ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); FocusableProperty.OverrideDefaultValue(true); SelectedItemProperty.Changed.AddClassHandler(x => x.SelectedItemChanged); KeyDownEvent.AddClassHandler(x => x.OnKeyDown, Interactivity.RoutingStrategies.Tunnel); @@ -81,6 +95,15 @@ namespace Avalonia.Controls set { SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); } } + /// + /// Gets or sets the virtualization mode for the items. + /// + public ItemVirtualizationMode VirtualizationMode + { + get { return GetValue(VirtualizationModeProperty); } + set { SetValue(VirtualizationModeProperty, value); } + } + /// protected override IItemContainerGenerator CreateItemContainerGenerator() { diff --git a/src/Avalonia.Themes.Default/DropDown.xaml b/src/Avalonia.Themes.Default/DropDown.xaml index 4684e1c872..9603bba107 100644 --- a/src/Avalonia.Themes.Default/DropDown.xaml +++ b/src/Avalonia.Themes.Default/DropDown.xaml @@ -42,8 +42,11 @@ + MemberSelector="{TemplateBinding MemberSelector}" + VirtualizationMode="{TemplateBinding VirtualizationMode}" + /> From c4ab6648338e14489c27e5aef49f2f863e34c3c7 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 13:34:29 +0200 Subject: [PATCH 08/11] Use Adorner Focus styling for focused DropDownItem --- src/Avalonia.Controls/DropDown.cs | 10 ++++++---- src/Avalonia.Themes.Default/DropDown.xaml | 20 ++++++++++--------- src/Avalonia.Themes.Default/DropDownItem.xaml | 5 ----- src/Avalonia.Themes.Default/FocusAdorner.xaml | 5 +++-- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index d17089c127..2197047936 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -165,10 +165,10 @@ namespace Avalonia.Controls else if (IsDropDownOpen && SelectedIndex < 0 && ItemCount > 0 && (e.Key == Key.Up || e.Key == Key.Down)) { - var firstChild = Presenter?.Panel?.Children.FirstOrDefault(c => c.Focusable); + var firstChild = Presenter?.Panel?.Children.FirstOrDefault(c => CanFocus(c)); if (firstChild != null) { - firstChild.Focus(); + FocusManager.Instance?.Focus(firstChild, NavigationMethod.Directional); e.Handled = true; } } @@ -223,7 +223,7 @@ namespace Avalonia.Controls private void PopupClosed(object sender, EventArgs e) { - if (Focusable) + if (CanFocus(this)) { Focus(); } @@ -246,13 +246,15 @@ namespace Avalonia.Controls if (IsDropDownOpen && selectedIndex != -1) { var container = ItemContainerGenerator.ContainerFromIndex(selectedIndex); - if (container != null && container.Focusable) + if (container != null && CanFocus(container)) { container.Focus(); } } } + private bool CanFocus(IControl control) => control.Focusable && control.IsEnabledCore && control.IsVisible; + private void UpdateSelectionBoxItem(object item) { var contentControl = item as IContentControl; diff --git a/src/Avalonia.Themes.Default/DropDown.xaml b/src/Avalonia.Themes.Default/DropDown.xaml index 9603bba107..ad2be275d6 100644 --- a/src/Avalonia.Themes.Default/DropDown.xaml +++ b/src/Avalonia.Themes.Default/DropDown.xaml @@ -39,15 +39,17 @@ StaysOpen="False"> - - + + - + + @@ -58,4 +60,4 @@ - \ No newline at end of file + diff --git a/src/Avalonia.Themes.Default/DropDownItem.xaml b/src/Avalonia.Themes.Default/DropDownItem.xaml index a15e770bf5..f542a34d71 100644 --- a/src/Avalonia.Themes.Default/DropDownItem.xaml +++ b/src/Avalonia.Themes.Default/DropDownItem.xaml @@ -4,7 +4,6 @@ - - - diff --git a/src/Avalonia.Themes.Default/FocusAdorner.xaml b/src/Avalonia.Themes.Default/FocusAdorner.xaml index 573c43dc8d..2d5e369573 100644 --- a/src/Avalonia.Themes.Default/FocusAdorner.xaml +++ b/src/Avalonia.Themes.Default/FocusAdorner.xaml @@ -3,7 +3,8 @@ + StrokeDashArray="1,2" + Margin="1"/> - \ No newline at end of file + From 850cbfdbf0b251f92c7b8a37fd8996fd437ee1fd Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 13:42:57 +0200 Subject: [PATCH 09/11] Support out of the box Member Selector in DropDown --- src/Avalonia.Controls/DropDown.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index 2197047936..f7cb0aa0ef 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -284,7 +284,8 @@ namespace Avalonia.Controls } else { - SelectionBoxItem = item; + var selector = MemberSelector; + SelectionBoxItem = selector != null ? selector.Select(item) : item; } } From daadd9c674ce27da2a4496bbd62223b865a4dc68 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 14:02:57 +0200 Subject: [PATCH 10/11] support member selector when recycling Dropdown/listboxitems --- .../Primitives/SelectingItemsControl.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index ccbdc71b1d..c40ddc37ad 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -431,9 +431,12 @@ namespace Avalonia.Controls.Primitives { if (i.ContainerControl != null && i.Item != null) { - MarkContainerSelected( - i.ContainerControl, - SelectedItems.Contains(i.Item)); + var ms = MemberSelector; + bool selected = ms == null ? + SelectedItems.Contains(i.Item) : + SelectedItems.OfType().Any(v => Equals(ms.Select(v), i.Item)); + + MarkContainerSelected(i.ContainerControl, selected); } } } From cd7c5eb762aa493a1d73a0d1474ee8e0d40f3477 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 14:15:57 +0200 Subject: [PATCH 11/11] Ensure selected DropDownItem is visible when dropdown is open --- src/Avalonia.Controls/DropDown.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index f7cb0aa0ef..93b33e0589 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -246,6 +246,13 @@ namespace Avalonia.Controls if (IsDropDownOpen && selectedIndex != -1) { var container = ItemContainerGenerator.ContainerFromIndex(selectedIndex); + + if(container == null && SelectedItems.Count > 0) + { + ScrollIntoView(SelectedItems[0]); + container = ItemContainerGenerator.ContainerFromIndex(selectedIndex); + } + if (container != null && CanFocus(container)) { container.Focus();