Browse Source

Merge pull request #2122 from donandren/prs/dropdownimprovements

DropDown improvements
pull/2126/head
Steven Kirk 8 years ago
committed by GitHub
parent
commit
2731155511
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 87
      src/Avalonia.Controls/DropDown.cs
  2. 32
      src/Avalonia.Controls/DropDownItem.cs
  3. 9
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  4. 20
      src/Avalonia.Themes.Default/DropDown.xaml
  5. 2
      src/Avalonia.Themes.Default/DropDownItem.xaml
  6. 5
      src/Avalonia.Themes.Default/FocusAdorner.xaml

87
src/Avalonia.Controls/DropDown.cs

@ -2,9 +2,12 @@
// 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.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Media;
@ -12,12 +15,17 @@ using Avalonia.VisualTree;
namespace Avalonia.Controls
{
/// <summary>
/// A drop-down list control.
/// </summary>
public class DropDown : SelectingItemsControl
{
/// <summary>
/// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
/// </summary>
private static readonly FuncTemplate<IPanel> DefaultPanel =
new FuncTemplate<IPanel>(() => new VirtualizingStackPanel());
/// <summary>
/// Defines the <see cref="IsDropDownOpen"/> property.
/// </summary>
@ -39,6 +47,12 @@ namespace Avalonia.Controls
public static readonly DirectProperty<DropDown, object> SelectionBoxItemProperty =
AvaloniaProperty.RegisterDirect<DropDown, object>(nameof(SelectionBoxItem), o => o.SelectionBoxItem);
/// <summary>
/// Defines the <see cref="VirtualizationMode"/> property.
/// </summary>
public static readonly StyledProperty<ItemVirtualizationMode> VirtualizationModeProperty =
ItemsPresenter.VirtualizationModeProperty.AddOwner<DropDown>();
private bool _isDropDownOpen;
private Popup _popup;
private object _selectionBoxItem;
@ -48,6 +62,7 @@ namespace Avalonia.Controls
/// </summary>
static DropDown()
{
ItemsPanelProperty.OverrideDefaultValue<DropDown>(DefaultPanel);
FocusableProperty.OverrideDefaultValue<DropDown>(true);
SelectedItemProperty.Changed.AddClassHandler<DropDown>(x => x.SelectedItemChanged);
KeyDownEvent.AddClassHandler<DropDown>(x => x.OnKeyDown, Interactivity.RoutingStrategies.Tunnel);
@ -80,6 +95,15 @@ namespace Avalonia.Controls
set { SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); }
}
/// <summary>
/// Gets or sets the virtualization mode for the items.
/// </summary>
public ItemVirtualizationMode VirtualizationMode
{
get { return GetValue(VirtualizationModeProperty); }
set { SetValue(VirtualizationModeProperty, value); }
}
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
@ -138,6 +162,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 => CanFocus(c));
if (firstChild != null)
{
FocusManager.Instance?.Focus(firstChild, NavigationMethod.Directional);
e.Handled = true;
}
}
}
/// <inheritdoc/>
@ -159,6 +193,7 @@ namespace Avalonia.Controls
e.Handled = true;
}
}
base.OnPointerPressed(e);
}
@ -168,28 +203,65 @@ namespace Avalonia.Controls
if (_popup != null)
{
_popup.Opened -= PopupOpened;
_popup.Closed -= PopupClosed;
}
_popup = e.NameScope.Get<Popup>("PART_Popup");
_popup.Opened += PopupOpened;
_popup.Closed += PopupClosed;
base.OnTemplateApplied(e);
}
private void PopupOpened(object sender, EventArgs e)
internal void ItemFocused(DropDownItem dropDownItem)
{
var selectedIndex = SelectedIndex;
if (IsDropDownOpen && dropDownItem.IsFocused && dropDownItem.IsArrangeValid)
{
dropDownItem.BringIntoView();
}
}
if (selectedIndex != -1)
private void PopupClosed(object sender, EventArgs e)
{
if (CanFocus(this))
{
var container = ItemContainerGenerator.ContainerFromIndex(selectedIndex);
container?.Focus();
Focus();
}
}
private void PopupOpened(object sender, EventArgs e)
{
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 && SelectedItems.Count > 0)
{
ScrollIntoView(SelectedItems[0]);
container = ItemContainerGenerator.ContainerFromIndex(selectedIndex);
}
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;
@ -219,7 +291,8 @@ namespace Avalonia.Controls
}
else
{
SelectionBoxItem = item;
var selector = MemberSelector;
SelectionBoxItem = selector != null ? selector.Select(item) : item;
}
}

32
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
{
/// <summary>
/// A selectable item in a <see cref="DropDown"/>.
/// </summary>
public class DropDownItem : ContentControl, ISelectable
public class DropDownItem : ListBoxItem
{
/// <summary>
/// Defines the <see cref="IsSelected"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsSelectedProperty =
AvaloniaProperty.Register<DropDownItem, bool>(nameof(IsSelected));
/// <summary>
/// Initializes static members of the <see cref="DropDownItem"/> class.
/// </summary>
static DropDownItem()
{
FocusableProperty.OverrideDefaultValue<DropDownItem>(true);
}
public DropDownItem()
{
this.GetObservable(DropDownItem.IsFocusedProperty).Subscribe(focused =>
{
PseudoClasses.Set(":selected", focused);
});
}
/// <summary>
/// Gets or sets the selection state of the item.
/// </summary>
public bool IsSelected
{
get { return GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
this.GetObservable(DropDownItem.IsFocusedProperty).Where(focused => focused)
.Subscribe(_ => (Parent as DropDown)?.ItemFocused(this));
}
}
}

9
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<object>().Any(v => Equals(ms.Select(v), i.Item));
MarkContainerSelected(i.ContainerControl, selected);
}
}
}

20
src/Avalonia.Themes.Default/DropDown.xaml

@ -35,15 +35,21 @@
MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}"
MaxHeight="{TemplateBinding MaxDropDownHeight}"
PlacementTarget="{TemplateBinding}"
ObeyScreenEdges="True"
StaysOpen="False">
<Border BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1">
<ScrollViewer>
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemTemplate="{TemplateBinding ItemTemplate}"
MemberSelector="{TemplateBinding MemberSelector}"/>
</ScrollViewer>
<AdornerDecorator Margin="-1 -1 0 0">
<ScrollViewer>
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
MemberSelector="{TemplateBinding MemberSelector}"
VirtualizationMode="{TemplateBinding VirtualizationMode}"
/>
</ScrollViewer>
</AdornerDecorator>
</Border>
</Popup>
</Grid>
@ -54,4 +60,4 @@
<Style Selector="DropDown:pointerover /template/ Border#border">
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}"/>
</Style>
</Styles>
</Styles>

2
src/Avalonia.Themes.Default/DropDownItem.xaml

@ -18,7 +18,7 @@
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DropDownItem:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeControlHighlightMidBrush}"/>
</Style>

5
src/Avalonia.Themes.Default/FocusAdorner.xaml

@ -3,7 +3,8 @@
<FocusAdornerTemplate>
<Rectangle Stroke="Black"
StrokeThickness="1"
StrokeDashArray="1,2"/>
StrokeDashArray="1,2"
Margin="1"/>
</FocusAdornerTemplate>
</Setter>
</Style>
</Style>

Loading…
Cancel
Save