Browse Source

Merge branch 'master' into fixes/2390-command-leak

pull/2413/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
8305ebef97
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      samples/ControlCatalog/MainView.xaml
  2. 2
      samples/ControlCatalog/MainView.xaml.cs
  3. 18
      samples/ControlCatalog/Pages/CarouselPage.xaml
  4. 8
      samples/ControlCatalog/Pages/CarouselPage.xaml.cs
  5. 41
      samples/ControlCatalog/Pages/ComboBoxPage.xaml
  6. 10
      samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
  7. 42
      samples/ControlCatalog/Pages/DropDownPage.xaml
  8. 12
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  9. 12
      samples/ControlCatalog/Pages/TabControlPage.xaml
  10. 10
      samples/VirtualizationDemo/MainWindow.xaml
  11. 373
      src/Avalonia.Controls/ComboBox.cs
  12. 10
      src/Avalonia.Controls/ComboBoxItem.cs
  13. 4
      src/Avalonia.Controls/ControlExtensions.cs
  14. 375
      src/Avalonia.Controls/DropDown.cs
  15. 3
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  16. 4
      src/Avalonia.Themes.Default/ComboBox.xaml
  17. 12
      src/Avalonia.Themes.Default/ComboBoxItem.xaml
  18. 4
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  19. 5
      src/Avalonia.X11/X11Platform.cs
  20. 12
      src/Avalonia.X11/X11Window.cs
  21. 18
      tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs

12
samples/ControlCatalog/MainView.xaml

@ -6,10 +6,10 @@
Foreground="{DynamicResource ThemeForegroundBrush}"
FontSize="{DynamicResource FontSizeNormal}">
<Grid>
<DropDown x:Name="Themes" SelectedIndex="0" Width="100" Margin="8" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<DropDownItem>Light</DropDownItem>
<DropDownItem>Dark</DropDownItem>
</DropDown>
<ComboBox x:Name="Themes" SelectedIndex="0" Width="100" Margin="8" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<ComboBoxItem>Light</ComboBoxItem>
<ComboBoxItem>Dark</ComboBoxItem>
</ComboBox>
<TabControl Classes="sidebar" Name="Sidebar">
<TabItem Header="AutoCompleteBox"><pages:AutoCompleteBoxPage/></TabItem>
<TabItem Header="Border"><pages:BorderPage/></TabItem>
@ -19,11 +19,11 @@
<TabItem Header="Canvas"><pages:CanvasPage/></TabItem>
<TabItem Header="Carousel"><pages:CarouselPage/></TabItem>
<TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem>
<TabItem Header="ComboBox"><pages:ComboBoxPage/></TabItem>
<TabItem Header="ContextMenu"><pages:ContextMenuPage/></TabItem>
<TabItem Header="DataGrid"><pages:DataGridPage/></TabItem>
<TabItem Header="DatePicker"><pages:DatePickerPage/></TabItem>
<TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem>
<TabItem Header="DropDown"><pages:DropDownPage/></TabItem>
<TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
<TabItem Header="Image"><pages:ImagePage/></TabItem>
<TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem>
@ -40,4 +40,4 @@
<TabItem Header="Viewbox"><pages:ViewboxPage/></TabItem>
</TabControl>
</Grid>
</UserControl>
</UserControl>

2
samples/ControlCatalog/MainView.xaml.cs

@ -30,7 +30,7 @@ namespace ControlCatalog
}
var light = AvaloniaXamlLoader.Parse<StyleInclude>(@"<StyleInclude xmlns='https://github.com/avaloniaui' Source='resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default'/>");
var dark = AvaloniaXamlLoader.Parse<StyleInclude>(@"<StyleInclude xmlns='https://github.com/avaloniaui' Source='resm:Avalonia.Themes.Default.Accents.BaseDark.xaml?assembly=Avalonia.Themes.Default'/>");
var themes = this.Find<DropDown>("Themes");
var themes = this.Find<ComboBox>("Themes");
themes.SelectionChanged += (sender, e) =>
{
switch (themes.SelectedIndex)

18
samples/ControlCatalog/Pages/CarouselPage.xaml

@ -24,19 +24,19 @@
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBlock VerticalAlignment="Center">Transition</TextBlock>
<DropDown Name="transition" SelectedIndex="1" VerticalAlignment="Center">
<DropDownItem>None</DropDownItem>
<DropDownItem>Slide</DropDownItem>
<DropDownItem>Crossfade</DropDownItem>
</DropDown>
<ComboBox Name="transition" SelectedIndex="1" VerticalAlignment="Center">
<ComboBoxItem>None</ComboBoxItem>
<ComboBoxItem>Slide</ComboBoxItem>
<ComboBoxItem>Crossfade</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBlock VerticalAlignment="Center">Orientation</TextBlock>
<DropDown Name="orientation" SelectedIndex="1" VerticalAlignment="Center">
<DropDownItem>Horizontal</DropDownItem>
<DropDownItem>Vertical</DropDownItem>
</DropDown>
<ComboBox Name="orientation" SelectedIndex="1" VerticalAlignment="Center">
<ComboBoxItem>Horizontal</ComboBoxItem>
<ComboBoxItem>Vertical</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>

8
samples/ControlCatalog/Pages/CarouselPage.xaml.cs

@ -10,8 +10,8 @@ namespace ControlCatalog.Pages
private Carousel _carousel;
private Button _left;
private Button _right;
private DropDown _transition;
private DropDown _orientation;
private ComboBox _transition;
private ComboBox _orientation;
public CarouselPage()
{
@ -28,8 +28,8 @@ namespace ControlCatalog.Pages
_carousel = this.FindControl<Carousel>("carousel");
_left = this.FindControl<Button>("left");
_right = this.FindControl<Button>("right");
_transition = this.FindControl<DropDown>("transition");
_orientation = this.FindControl<DropDown>("orientation");
_transition = this.FindControl<ComboBox>("transition");
_orientation = this.FindControl<ComboBox>("orientation");
}
private void TransitionChanged(object sender, SelectionChangedEventArgs e)

41
samples/ControlCatalog/Pages/ComboBoxPage.xaml

@ -0,0 +1,41 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ComboBoxPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">ComboBox</TextBlock>
<TextBlock Classes="h2">A drop-down list.</TextBlock>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0 16 0 0" Spacing="8">
<ComboBox SelectedIndex="0">
<ComboBoxItem>Inline Items</ComboBoxItem>
<ComboBoxItem>Inline Item 2</ComboBoxItem>
<ComboBoxItem>Inline Item 3</ComboBoxItem>
<ComboBoxItem>Inline Item 4</ComboBoxItem>
</ComboBox>
<ComboBox SelectedIndex="0">
<ComboBoxItem>
<Panel>
<Rectangle Fill="{DynamicResource ThemeAccentBrush}"/>
<TextBlock Margin="8">Control Items</TextBlock>
</Panel>
</ComboBoxItem>
<ComboBoxItem>
<Ellipse Width="50" Height="50" Fill="Yellow"/>
</ComboBoxItem>
<ComboBoxItem>
<TextBox Text="TextBox"/>
</ComboBoxItem>
</ComboBox>
<ComboBox x:Name="fontComboBox" SelectedIndex="0">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" FontFamily="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</StackPanel>
</UserControl>

10
samples/ControlCatalog/Pages/DropDownPage.xaml.cs → samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs

@ -3,9 +3,9 @@ using Avalonia.Markup.Xaml;
namespace ControlCatalog.Pages
{
public class DropDownPage : UserControl
public class ComboBoxPage : UserControl
{
public DropDownPage()
public ComboBoxPage()
{
this.InitializeComponent();
}
@ -13,9 +13,9 @@ namespace ControlCatalog.Pages
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
var fontDropDown = this.Find<DropDown>("fontDropDown");
fontDropDown.Items = Avalonia.Media.FontFamily.SystemFontFamilies;
fontDropDown.SelectedIndex = 0;
var fontComboBox = this.Find<ComboBox>("fontComboBox");
fontComboBox.Items = Avalonia.Media.FontFamily.SystemFontFamilies;
fontComboBox.SelectedIndex = 0;
}
}
}

42
samples/ControlCatalog/Pages/DropDownPage.xaml

@ -1,42 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.DropDownPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">DropDown</TextBlock>
<TextBlock Classes="h2">A drop-down list.</TextBlock>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0 16 0 0" Spacing="8">
<DropDown SelectedIndex="0">
<DropDownItem>Inline Items</DropDownItem>
<DropDownItem>Inline Item 2</DropDownItem>
<DropDownItem>Inline Item 3</DropDownItem>
<DropDownItem>Inline Item 4</DropDownItem>
</DropDown>
<DropDown SelectedIndex="0">
<DropDownItem>
<Panel>
<Rectangle Fill="{DynamicResource ThemeAccentBrush}"/>
<TextBlock Margin="8">Control Items</TextBlock>
</Panel>
</DropDownItem>
<DropDownItem>
<Ellipse Width="50" Height="50" Fill="Yellow"/>
</DropDownItem>
<DropDownItem>
<TextBox Text="TextBox"/>
</DropDownItem>
</DropDown>
<DropDown x:Name="fontDropDown" SelectedIndex="0">
<DropDown.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" FontFamily="{Binding}" />
</DataTemplate>
</DropDown.ItemTemplate>
</DropDown>
</StackPanel>
</StackPanel>
</UserControl>

12
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@ -23,9 +23,9 @@
</Grid>
<Grid Grid.Row="0" Grid.Column="1" Margin="10,2,2,2" ColumnDefinitions="Auto, 120" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="2">FormatString:</TextBlock>
<DropDown Grid.Row="0" Grid.Column="1" Items="{Binding Formats}" SelectedItem="{Binding SelectedFormat}"
<ComboBox Grid.Row="0" Grid.Column="1" Items="{Binding Formats}" SelectedItem="{Binding SelectedFormat}"
VerticalAlignment="Center" Margin="2">
<DropDown.ItemTemplate>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Spacing="2">
<TextBlock Text="{Binding Name}"/>
@ -33,15 +33,15 @@
<TextBlock Text="{Binding Value}"/>
</StackPanel>
</DataTemplate>
</DropDown.ItemTemplate>
</DropDown>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="2">ButtonSpinnerLocation:</TextBlock>
<DropDown Grid.Row="1" Grid.Column="1" Items="{Binding SpinnerLocations}" SelectedItem="{Binding #upDown.ButtonSpinnerLocation}"
<ComboBox Grid.Row="1" Grid.Column="1" Items="{Binding SpinnerLocations}" SelectedItem="{Binding #upDown.ButtonSpinnerLocation}"
VerticalAlignment="Center" Margin="2"/>
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="2">CultureInfo:</TextBlock>
<DropDown Grid.Row="2" Grid.Column="1" Items="{Binding Cultures}" SelectedItem="{Binding #upDown.CultureInfo}"
<ComboBox Grid.Row="2" Grid.Column="1" Items="{Binding Cultures}" SelectedItem="{Binding #upDown.CultureInfo}"
VerticalAlignment="Center" Margin="2"/>
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="2">Watermark:</TextBlock>

12
samples/ControlCatalog/Pages/TabControlPage.xaml

@ -88,12 +88,12 @@
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock VerticalAlignment="Center">Tab Placement:</TextBlock>
<DropDown SelectedIndex="{Binding TabPlacement, Mode=TwoWay}">
<DropDownItem>Left</DropDownItem>
<DropDownItem>Bottom</DropDownItem>
<DropDownItem>Right</DropDownItem>
<DropDownItem>Top</DropDownItem>
</DropDown>
<ComboBox SelectedIndex="{Binding TabPlacement, Mode=TwoWay}">
<ComboBoxItem>Left</ComboBoxItem>
<ComboBoxItem>Bottom</ComboBoxItem>
<ComboBoxItem>Right</ComboBoxItem>
<ComboBoxItem>Top</ComboBoxItem>
</ComboBox>
</StackPanel>
</Grid>
</DockPanel>

10
samples/VirtualizationDemo/MainWindow.xaml

@ -7,9 +7,9 @@
Margin="16 0 0 0"
MinWidth="150"
Spacing="4">
<DropDown Items="{Binding VirtualizationModes}"
<ComboBox Items="{Binding VirtualizationModes}"
SelectedItem="{Binding VirtualizationMode}"/>
<DropDown Items="{Binding Orientations}"
<ComboBox Items="{Binding Orientations}"
SelectedItem="{Binding Orientation}"/>
<TextBox Watermark="Item Count"
UseFloatingWatermark="True"
@ -24,10 +24,10 @@
UseFloatingWatermark="True"
Text="{Binding #listBox.Scroll.Viewport, Mode=OneWay}"/>
<TextBlock>Horiz. ScrollBar</TextBlock>
<DropDown Items="{Binding ScrollBarVisibilities}"
<ComboBox Items="{Binding ScrollBarVisibilities}"
SelectedItem="{Binding HorizontalScrollBarVisibility}"/>
<TextBlock>Vert. ScrollBar</TextBlock>
<DropDown Items="{Binding ScrollBarVisibilities}"
<ComboBox Items="{Binding ScrollBarVisibilities}"
SelectedItem="{Binding VerticalScrollBarVisibility}"/>
<TextBox Watermark="Item to Create"
UseFloatingWatermark="True"
@ -58,4 +58,4 @@
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</Window>
</Window>

373
src/Avalonia.Controls/ComboBox.cs

@ -0,0 +1,373 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// 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;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
/// <summary>
/// A drop-down list control.
/// </summary>
public class ComboBox : 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>
public static readonly DirectProperty<ComboBox, bool> IsDropDownOpenProperty =
AvaloniaProperty.RegisterDirect<ComboBox, bool>(
nameof(IsDropDownOpen),
o => o.IsDropDownOpen,
(o, v) => o.IsDropDownOpen = v);
/// <summary>
/// Defines the <see cref="MaxDropDownHeight"/> property.
/// </summary>
public static readonly StyledProperty<double> MaxDropDownHeightProperty =
AvaloniaProperty.Register<ComboBox, double>(nameof(MaxDropDownHeight), 200);
/// <summary>
/// Defines the <see cref="SelectionBoxItem"/> property.
/// </summary>
public static readonly DirectProperty<ComboBox, object> SelectionBoxItemProperty =
AvaloniaProperty.RegisterDirect<ComboBox, object>(nameof(SelectionBoxItem), o => o.SelectionBoxItem);
/// <summary>
/// Defines the <see cref="VirtualizationMode"/> property.
/// </summary>
public static readonly StyledProperty<ItemVirtualizationMode> VirtualizationModeProperty =
ItemsPresenter.VirtualizationModeProperty.AddOwner<ComboBox>();
private bool _isDropDownOpen;
private Popup _popup;
private object _selectionBoxItem;
private IDisposable _subscriptionsOnOpen;
/// <summary>
/// Initializes static members of the <see cref="ComboBox"/> class.
/// </summary>
static ComboBox()
{
ItemsPanelProperty.OverrideDefaultValue<ComboBox>(DefaultPanel);
FocusableProperty.OverrideDefaultValue<ComboBox>(true);
SelectedItemProperty.Changed.AddClassHandler<ComboBox>(x => x.SelectedItemChanged);
KeyDownEvent.AddClassHandler<ComboBox>(x => x.OnKeyDown, Interactivity.RoutingStrategies.Tunnel);
}
/// <summary>
/// Gets or sets a value indicating whether the dropdown is currently open.
/// </summary>
public bool IsDropDownOpen
{
get { return _isDropDownOpen; }
set { SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); }
}
/// <summary>
/// Gets or sets the maximum height for the dropdown list.
/// </summary>
public double MaxDropDownHeight
{
get { return GetValue(MaxDropDownHeightProperty); }
set { SetValue(MaxDropDownHeightProperty, value); }
}
/// <summary>
/// Gets or sets the item to display as the control's content.
/// </summary>
protected object SelectionBoxItem
{
get { return _selectionBoxItem; }
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()
{
return new ItemContainerGenerator<ComboBoxItem>(
this,
ComboBoxItem.ContentProperty,
ComboBoxItem.ContentTemplateProperty);
}
/// <inheritdoc/>
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
this.UpdateSelectionBoxItem(this.SelectedItem);
}
/// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Handled)
return;
if (e.Key == Key.F4 ||
((e.Key == Key.Down || e.Key == Key.Up) && ((e.Modifiers & InputModifiers.Alt) != 0)))
{
IsDropDownOpen = !IsDropDownOpen;
e.Handled = true;
}
else if (IsDropDownOpen && e.Key == Key.Escape)
{
IsDropDownOpen = false;
e.Handled = true;
}
else if (IsDropDownOpen && e.Key == Key.Enter)
{
SelectFocusedItem();
IsDropDownOpen = false;
e.Handled = true;
}
else if (!IsDropDownOpen)
{
if (e.Key == Key.Down)
{
SelectNext();
e.Handled = true;
}
else if (e.Key == Key.Up)
{
SelectPrev();
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/>
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{
base.OnPointerWheelChanged(e);
if (!e.Handled)
{
if (!IsDropDownOpen)
{
if (IsFocused)
{
if (e.Delta.Y < 0)
SelectNext();
else
SelectPrev();
e.Handled = true;
}
}
else
{
e.Handled = true;
}
}
}
/// <inheritdoc/>
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
if (!e.Handled)
{
if (_popup?.PopupRoot != null && ((IVisual)e.Source).GetVisualRoot() == _popup?.PopupRoot)
{
if (UpdateSelectionFromEventSource(e.Source))
{
_popup?.Close();
e.Handled = true;
}
}
else
{
IsDropDownOpen = !IsDropDownOpen;
e.Handled = true;
}
}
base.OnPointerPressed(e);
}
/// <inheritdoc/>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
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);
}
internal void ItemFocused(ComboBoxItem dropDownItem)
{
if (IsDropDownOpen && dropDownItem.IsFocused && dropDownItem.IsArrangeValid)
{
dropDownItem.BringIntoView();
}
}
private void PopupClosed(object sender, EventArgs e)
{
_subscriptionsOnOpen?.Dispose();
_subscriptionsOnOpen = null;
if (CanFocus(this))
{
Focus();
}
}
private void PopupOpened(object sender, EventArgs e)
{
TryFocusSelectedItem();
_subscriptionsOnOpen?.Dispose();
_subscriptionsOnOpen = null;
var toplevel = this.GetVisualRoot() as TopLevel;
if (toplevel != null)
{
_subscriptionsOnOpen = toplevel.AddHandler(PointerWheelChangedEvent, (s, ev) =>
{
//eat wheel scroll event outside dropdown popup while it's open
if (IsDropDownOpen && (ev.Source as IVisual).GetVisualRoot() == toplevel)
{
ev.Handled = true;
}
}, Interactivity.RoutingStrategies.Tunnel);
}
}
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;
if (contentControl != null)
{
item = contentControl.Content;
}
var control = item as IControl;
if (control != null)
{
control.Measure(Size.Infinity);
SelectionBoxItem = new Rectangle
{
Width = control.DesiredSize.Width,
Height = control.DesiredSize.Height,
Fill = new VisualBrush
{
Visual = control,
Stretch = Stretch.None,
AlignmentX = AlignmentX.Left,
}
};
}
else
{
var selector = MemberSelector;
SelectionBoxItem = selector != null ? selector.Select(item) : item;
}
}
private void SelectFocusedItem()
{
foreach (ItemContainerInfo dropdownItem in ItemContainerGenerator.Containers)
{
if (dropdownItem.ContainerControl.IsFocused)
{
SelectedIndex = dropdownItem.Index;
break;
}
}
}
private void SelectNext()
{
int next = SelectedIndex + 1;
if (next >= ItemCount)
next = 0;
SelectedIndex = next;
}
private void SelectPrev()
{
int prev = SelectedIndex - 1;
if (prev < 0)
prev = ItemCount - 1;
SelectedIndex = prev;
}
}
}

10
src/Avalonia.Controls/DropDownItem.cs → src/Avalonia.Controls/ComboBoxItem.cs

@ -7,14 +7,14 @@ using System.Reactive.Linq;
namespace Avalonia.Controls
{
/// <summary>
/// A selectable item in a <see cref="DropDown"/>.
/// A selectable item in a <see cref="ComboBox"/>.
/// </summary>
public class DropDownItem : ListBoxItem
public class ComboBoxItem : ListBoxItem
{
public DropDownItem()
public ComboBoxItem()
{
this.GetObservable(DropDownItem.IsFocusedProperty).Where(focused => focused)
.Subscribe(_ => (Parent as DropDown)?.ItemFocused(this));
this.GetObservable(ComboBoxItem.IsFocusedProperty).Where(focused => focused)
.Subscribe(_ => (Parent as ComboBox)?.ItemFocused(this));
}
}
}

4
src/Avalonia.Controls/ControlExtensions.cs

@ -15,7 +15,7 @@ namespace Avalonia.Controls
public static class ControlExtensions
{
/// <summary>
/// Tries to being the control into view.
/// Tries to bring the control into view.
/// </summary>
/// <param name="control">The control.</param>
public static void BringIntoView(this IControl control)
@ -26,7 +26,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Tries to being the control into view.
/// Tries to bring the control into view.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="rect">The area of the control to being into view.</param>

375
src/Avalonia.Controls/DropDown.cs

@ -1,373 +1,28 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// 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;
using Avalonia.VisualTree;
using System;
using Avalonia.Logging;
using Avalonia.Styling;
namespace Avalonia.Controls
{
/// <summary>
/// A drop-down list control.
/// </summary>
public class DropDown : SelectingItemsControl
[Obsolete("Use ComboBox")]
public class DropDown : ComboBox, IStyleable
{
/// <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>
public static readonly DirectProperty<DropDown, bool> IsDropDownOpenProperty =
AvaloniaProperty.RegisterDirect<DropDown, bool>(
nameof(IsDropDownOpen),
o => o.IsDropDownOpen,
(o, v) => o.IsDropDownOpen = v);
/// <summary>
/// Defines the <see cref="MaxDropDownHeight"/> property.
/// </summary>
public static readonly StyledProperty<double> MaxDropDownHeightProperty =
AvaloniaProperty.Register<DropDown, double>(nameof(MaxDropDownHeight), 200);
/// <summary>
/// Defines the <see cref="SelectionBoxItem"/> property.
/// </summary>
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;
private IDisposable _subscriptionsOnOpen;
/// <summary>
/// Initializes static members of the <see cref="DropDown"/> class.
/// </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);
}
/// <summary>
/// Gets or sets a value indicating whether the dropdown is currently open.
/// </summary>
public bool IsDropDownOpen
{
get { return _isDropDownOpen; }
set { SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); }
}
/// <summary>
/// Gets or sets the maximum height for the dropdown list.
/// </summary>
public double MaxDropDownHeight
{
get { return GetValue(MaxDropDownHeightProperty); }
set { SetValue(MaxDropDownHeightProperty, value); }
}
/// <summary>
/// Gets or sets the item to display as the control's content.
/// </summary>
protected object SelectionBoxItem
{
get { return _selectionBoxItem; }
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()
{
return new ItemContainerGenerator<DropDownItem>(
this,
DropDownItem.ContentProperty,
DropDownItem.ContentTemplateProperty);
}
/// <inheritdoc/>
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
this.UpdateSelectionBoxItem(this.SelectedItem);
}
/// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Handled)
return;
if (e.Key == Key.F4 ||
((e.Key == Key.Down || e.Key == Key.Up) && ((e.Modifiers & InputModifiers.Alt) != 0)))
{
IsDropDownOpen = !IsDropDownOpen;
e.Handled = true;
}
else if (IsDropDownOpen && e.Key == Key.Escape)
{
IsDropDownOpen = false;
e.Handled = true;
}
else if (IsDropDownOpen && e.Key == Key.Enter)
{
SelectFocusedItem();
IsDropDownOpen = false;
e.Handled = true;
}
else if (!IsDropDownOpen)
{
if (e.Key == Key.Down)
{
SelectNext();
e.Handled = true;
}
else if (e.Key == Key.Up)
{
SelectPrev();
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/>
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{
base.OnPointerWheelChanged(e);
if (!e.Handled)
{
if (!IsDropDownOpen)
{
if (IsFocused)
{
if (e.Delta.Y < 0)
SelectNext();
else
SelectPrev();
e.Handled = true;
}
}
else
{
e.Handled = true;
}
}
}
/// <inheritdoc/>
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
if (!e.Handled)
{
if (_popup?.PopupRoot != null && ((IVisual)e.Source).GetVisualRoot() == _popup?.PopupRoot)
{
if (UpdateSelectionFromEventSource(e.Source))
{
_popup?.Close();
e.Handled = true;
}
}
else
{
IsDropDownOpen = !IsDropDownOpen;
e.Handled = true;
}
}
base.OnPointerPressed(e);
}
/// <inheritdoc/>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
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);
}
internal void ItemFocused(DropDownItem dropDownItem)
public DropDown()
{
if (IsDropDownOpen && dropDownItem.IsFocused && dropDownItem.IsArrangeValid)
{
dropDownItem.BringIntoView();
}
Logger.Warning(LogArea.Control, this, "DropDown is deprecated: Use ComboBox");
}
private void PopupClosed(object sender, EventArgs e)
{
_subscriptionsOnOpen?.Dispose();
_subscriptionsOnOpen = null;
if (CanFocus(this))
{
Focus();
}
}
private void PopupOpened(object sender, EventArgs e)
{
TryFocusSelectedItem();
_subscriptionsOnOpen?.Dispose();
_subscriptionsOnOpen = null;
var toplevel = this.GetVisualRoot() as TopLevel;
if (toplevel != null)
{
_subscriptionsOnOpen = toplevel.AddHandler(PointerWheelChangedEvent, (s, ev) =>
{
//eat wheel scroll event outside dropdown popup while it's open
if (IsDropDownOpen && (ev.Source as IVisual).GetVisualRoot() == toplevel)
{
ev.Handled = true;
}
}, Interactivity.RoutingStrategies.Tunnel);
}
}
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;
if (contentControl != null)
{
item = contentControl.Content;
}
var control = item as IControl;
if (control != null)
{
control.Measure(Size.Infinity);
SelectionBoxItem = new Rectangle
{
Width = control.DesiredSize.Width,
Height = control.DesiredSize.Height,
Fill = new VisualBrush
{
Visual = control,
Stretch = Stretch.None,
AlignmentX = AlignmentX.Left,
}
};
}
else
{
var selector = MemberSelector;
SelectionBoxItem = selector != null ? selector.Select(item) : item;
}
}
private void SelectFocusedItem()
{
foreach (ItemContainerInfo dropdownItem in ItemContainerGenerator.Containers)
{
if (dropdownItem.ContainerControl.IsFocused)
{
SelectedIndex = dropdownItem.Index;
break;
}
}
}
Type IStyleable.StyleKey => typeof(ComboBox);
}
private void SelectNext()
[Obsolete("Use ComboBoxItem")]
public class DropDownItem : ComboBoxItem, IStyleable
{
public DropDownItem()
{
int next = SelectedIndex + 1;
if (next >= ItemCount)
next = 0;
SelectedIndex = next;
Logger.Warning(LogArea.Control, this, "DropDownItem is deprecated: Use ComboBoxItem");
}
private void SelectPrev()
{
int prev = SelectedIndex - 1;
if (prev < 0)
prev = ItemCount - 1;
SelectedIndex = prev;
}
Type IStyleable.StyleKey => typeof(ComboBoxItem);
}
}

3
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -67,8 +67,7 @@ namespace Avalonia.Native
if (_factory.MacOptions != null)
{
var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>();
if (macOpts != null)
_factory.MacOptions.ShowInDock = macOpts.ShowInDock ? 1 : 0;
_factory.MacOptions.ShowInDock = macOpts?.ShowInDock != false ? 1 : 0;
}
AvaloniaLocator.CurrentMutable

4
src/Avalonia.Themes.Default/DropDown.xaml → src/Avalonia.Themes.Default/ComboBox.xaml

@ -1,5 +1,5 @@
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="DropDown">
<Style Selector="ComboBox">
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
<Setter Property="Padding" Value="4"/>
@ -57,7 +57,7 @@
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DropDown:pointerover /template/ Border#border">
<Style Selector="ComboBox:pointerover /template/ Border#border">
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}"/>
</Style>
</Styles>

12
src/Avalonia.Themes.Default/DropDownItem.xaml → src/Avalonia.Themes.Default/ComboBoxItem.xaml

@ -1,5 +1,5 @@
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="DropDownItem">
<Style Selector="ComboBoxItem">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
@ -19,23 +19,23 @@
</Setter>
</Style>
<Style Selector="DropDownItem:pointerover /template/ ContentPresenter">
<Style Selector="ComboBoxItem:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeControlHighlightMidBrush}"/>
</Style>
<Style Selector="DropDownItem:selected /template/ ContentPresenter">
<Style Selector="ComboBoxItem:selected /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/>
</Style>
<Style Selector="DropDownItem:selected:focus /template/ ContentPresenter">
<Style Selector="ComboBoxItem:selected:focus /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush3}"/>
</Style>
<Style Selector="DropDownItem:selected:pointerover /template/ ContentPresenter">
<Style Selector="ComboBoxItem:selected:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush3}"/>
</Style>
<Style Selector="DropDownItem:selected:focus:pointerover /template/ ContentPresenter">
<Style Selector="ComboBoxItem:selected:focus:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
</Style>
</Styles>

4
src/Avalonia.Themes.Default/DefaultTheme.xaml

@ -7,9 +7,9 @@
<StyleInclude Source="resm:Avalonia.Themes.Default.Button.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Carousel.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.CheckBox.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ComboBox.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ComboBoxItem.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ContentControl.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.DropDown.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.DropDownItem.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.GridSplitter.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ItemsControl.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ListBox.xaml?assembly=Avalonia.Themes.Default"/>

5
src/Avalonia.X11/X11Platform.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
@ -24,6 +25,7 @@ namespace Avalonia.X11
public X11Info Info { get; private set; }
public IX11Screens X11Screens { get; private set; }
public IScreenImpl Screens { get; private set; }
public X11PlatformOptions Options { get; private set; }
public void Initialize(X11PlatformOptions options)
{
XInitThreads();
@ -63,6 +65,8 @@ namespace Avalonia.X11
else
GlxGlPlatformFeature.TryInitialize(Info);
}
Options = options;
}
public IntPtr DeferredDisplay { get; set; }
@ -91,6 +95,7 @@ namespace Avalonia
{
public bool UseEGL { get; set; }
public bool UseGpu { get; set; } = true;
public string WmClass { get; set; } = Assembly.GetEntryAssembly()?.GetName()?.Name ?? "AvaloniaApplication";
}
public static class AvaloniaX11PlatformExtensions
{

12
src/Avalonia.X11/X11Window.cs

@ -128,6 +128,8 @@ namespace Avalonia.X11
XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_WINDOW_TYPE, _x11.Atoms.XA_ATOM,
32, PropertyMode.Replace, new[] {_x11.Atoms._NET_WM_WINDOW_TYPE_NORMAL}, 1);
if (platform.Options.WmClass != null)
SetWmClass(platform.Options.WmClass);
var surfaces = new List<object>
{
@ -873,6 +875,16 @@ namespace Avalonia.X11
}
}
public void SetWmClass(string wmClass)
{
var data = Encoding.ASCII.GetBytes(wmClass);
fixed (void* pdata = data)
{
XChangeProperty(_x11.Display, _handle, _x11.Atoms.XA_WM_CLASS, _x11.Atoms.XA_STRING, 8,
PropertyMode.Replace, pdata, data.Length);
}
}
public void SetMinMaxSize(Size minSize, Size maxSize)
{
_scaledMinMaxSize = (minSize, maxSize);

18
tests/Avalonia.Controls.UnitTests/DropDownTests.cs → tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs

@ -13,12 +13,12 @@ using Xunit;
namespace Avalonia.Controls.UnitTests
{
public class DropDownTests
public class ComboBoxTests
{
[Fact]
public void Clicking_On_Control_Toggles_IsDropDownOpen()
{
var target = new DropDown
var target = new ComboBox
{
Items = new[] { "Foo", "Bar" },
};
@ -42,13 +42,13 @@ namespace Avalonia.Controls.UnitTests
public void SelectionBoxItem_Is_Rectangle_With_VisualBrush_When_Selection_Is_Control()
{
var items = new[] { new Canvas() };
var target = new DropDown
var target = new ComboBox
{
Items = items,
SelectedIndex = 0,
};
var rectangle = target.GetValue(DropDown.SelectionBoxItemProperty) as Rectangle;
var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle;
Assert.NotNull(rectangle);
var brush = rectangle.Fill as VisualBrush;
@ -59,7 +59,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void SelectionBoxItem_Rectangle_Is_Removed_From_Logical_Tree()
{
var target = new DropDown
var target = new ComboBox
{
Items = new[] { new Canvas() },
SelectedIndex = 0,
@ -70,7 +70,7 @@ namespace Avalonia.Controls.UnitTests
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
var rectangle = target.GetValue(DropDown.SelectionBoxItemProperty) as Rectangle;
var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle;
Assert.True(((ILogical)target).IsAttachedToLogicalTree);
Assert.True(((ILogical)rectangle).IsAttachedToLogicalTree);
@ -84,7 +84,7 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate GetTemplate()
{
return new FuncControlTemplate<DropDown>(parent =>
return new FuncControlTemplate<ComboBox>(parent =>
{
return new Panel
{
@ -93,7 +93,7 @@ namespace Avalonia.Controls.UnitTests
{
new ContentControl
{
[!ContentControl.ContentProperty] = parent[!DropDown.SelectionBoxItemProperty],
[!ContentControl.ContentProperty] = parent[!ComboBox.SelectionBoxItemProperty],
},
new ToggleButton
{
@ -105,7 +105,7 @@ namespace Avalonia.Controls.UnitTests
Child = new ItemsPresenter
{
Name = "PART_ItemsPresenter",
[!ItemsPresenter.ItemsProperty] = parent[!DropDown.ItemsProperty],
[!ItemsPresenter.ItemsProperty] = parent[!ComboBox.ItemsProperty],
}
}
}
Loading…
Cancel
Save