Browse Source

Merge branch 'master' into perf-setandraise

pull/2362/head
Dariusz Komosiński 8 years ago
committed by GitHub
parent
commit
c965df68d2
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. 1
      samples/ControlCatalog/Pages/DialogsPage.xaml
  8. 26
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  9. 42
      samples/ControlCatalog/Pages/DropDownPage.xaml
  10. 12
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  11. 12
      samples/ControlCatalog/Pages/TabControlPage.xaml
  12. 10
      samples/VirtualizationDemo/MainWindow.xaml
  13. 69
      src/Avalonia.Controls/Button.cs
  14. 373
      src/Avalonia.Controls/ComboBox.cs
  15. 10
      src/Avalonia.Controls/ComboBoxItem.cs
  16. 4
      src/Avalonia.Controls/ControlExtensions.cs
  17. 375
      src/Avalonia.Controls/DropDown.cs
  18. 35
      src/Avalonia.Controls/MenuItem.cs
  19. 4
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  20. 3
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  21. 4
      src/Avalonia.Themes.Default/ComboBox.xaml
  22. 12
      src/Avalonia.Themes.Default/ComboBoxItem.xaml
  23. 4
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  24. 12
      src/Avalonia.X11/NativeDialogs/Gtk.cs
  25. 25
      src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs
  26. 5
      src/Avalonia.X11/X11Platform.cs
  27. 12
      src/Avalonia.X11/X11Window.cs
  28. 46
      tests/Avalonia.Controls.UnitTests/ButtonTests.cs
  29. 18
      tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
  30. 54
      tests/Avalonia.Controls.UnitTests/MenuItemTests.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;
}
}
}

1
samples/ControlCatalog/Pages/DialogsPage.xaml

@ -2,6 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.DialogsPage">
<StackPanel Orientation="Vertical" Spacing="4" Margin="4">
<CheckBox Name="UseFilters">Use filters</CheckBox>
<Button Name="OpenFile">Open File</Button>
<Button Name="SaveFile">Save File</Button>
<Button Name="SelectFolder">Select Folder</Button>

26
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -1,3 +1,4 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
#pragma warning disable 4014
@ -9,18 +10,39 @@ namespace ControlCatalog.Pages
public DialogsPage()
{
this.InitializeComponent();
List<FileDialogFilter> GetFilters()
{
if (this.FindControl<CheckBox>("UseFilters").IsChecked != true)
return null;
return new List<FileDialogFilter>
{
new FileDialogFilter
{
Name = "Text files (.txt)", Extensions = new List<string> {"txt"}
},
new FileDialogFilter
{
Name = "All files",
Extensions = new List<string> {"*"}
}
};
}
this.FindControl<Button>("OpenFile").Click += delegate
{
new OpenFileDialog()
{
Title = "Open file"
Title = "Open file",
Filters = GetFilters()
}.ShowAsync(GetWindow());
};
this.FindControl<Button>("SaveFile").Click += delegate
{
new SaveFileDialog()
{
Title = "Save file"
Title = "Save file",
Filters = GetFilters()
}.ShowAsync(GetWindow());
};
this.FindControl<Button>("SelectFolder").Click += delegate

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>

69
src/Avalonia.Controls/Button.cs

@ -7,6 +7,7 @@ using System.Windows.Input;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.VisualTree;
namespace Avalonia.Controls
@ -160,6 +161,40 @@ namespace Avalonia.Controls
}
}
/// <inheritdoc/>
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
if (IsDefault)
{
if (e.Root is IInputElement inputElement)
{
StopListeningForDefault(inputElement);
}
}
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
if (Command != null)
{
Command.CanExecuteChanged += CanExecuteChanged;
}
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnDetachedFromLogicalTree(e);
if (Command != null)
{
Command.CanExecuteChanged -= CanExecuteChanged;
}
}
/// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e)
{
@ -195,20 +230,6 @@ namespace Avalonia.Controls
}
}
/// <inheritdoc/>
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
if (IsDefault)
{
if (e.Root is IInputElement inputElement)
{
StopListeningForDefault(inputElement);
}
}
}
/// <summary>
/// Invokes the <see cref="Click"/> event.
/// </summary>
@ -281,17 +302,17 @@ namespace Avalonia.Controls
{
if (e.Sender is Button button)
{
var oldCommand = e.OldValue as ICommand;
var newCommand = e.NewValue as ICommand;
if (oldCommand != null)
{
oldCommand.CanExecuteChanged -= button.CanExecuteChanged;
}
if (newCommand != null)
if (((ILogical)button).IsAttachedToLogicalTree)
{
newCommand.CanExecuteChanged += button.CanExecuteChanged;
if (e.OldValue is ICommand oldCommand)
{
oldCommand.CanExecuteChanged -= button.CanExecuteChanged;
}
if (e.NewValue is ICommand newCommand)
{
newCommand.CanExecuteChanged += button.CanExecuteChanged;
}
}
button.CanExecuteChanged(button, EventArgs.Empty);

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);
}
}

35
src/Avalonia.Controls/MenuItem.cs

@ -286,6 +286,26 @@ namespace Avalonia.Controls
return new MenuItemContainerGenerator(this);
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
if (Command != null)
{
Command.CanExecuteChanged += CanExecuteChanged;
}
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnDetachedFromLogicalTree(e);
if (Command != null)
{
Command.CanExecuteChanged -= CanExecuteChanged;
}
}
/// <summary>
/// Called when the <see cref="MenuItem"/> is clicked.
/// </summary>
@ -399,14 +419,17 @@ namespace Avalonia.Controls
{
if (e.Sender is MenuItem menuItem)
{
if (e.OldValue is ICommand oldCommand)
if (((ILogical)menuItem).IsAttachedToLogicalTree)
{
oldCommand.CanExecuteChanged -= menuItem.CanExecuteChanged;
}
if (e.OldValue is ICommand oldCommand)
{
oldCommand.CanExecuteChanged -= menuItem.CanExecuteChanged;
}
if (e.NewValue is ICommand newCommand)
{
newCommand.CanExecuteChanged += menuItem.CanExecuteChanged;
if (e.NewValue is ICommand newCommand)
{
newCommand.CanExecuteChanged += menuItem.CanExecuteChanged;
}
}
menuItem.CanExecuteChanged(menuItem, EventArgs.Empty);

4
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -91,12 +91,12 @@ namespace Avalonia.Controls.Primitives
if (screenX > screen.Bounds.Width)
{
Position = Position.WithX(Position.X - screenX - bounds.Width);
Position = Position.WithX(Position.X - (screenX - screen.Bounds.Width));
}
if (screenY > screen.Bounds.Height)
{
Position = Position.WithY(Position.Y - screenY - bounds.Height);
Position = Position.WithY(Position.Y - (screenY - screen.Bounds.Height));
}
}
}

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"/>

12
src/Avalonia.X11/NativeDialogs/Gtk.cs

@ -190,6 +190,18 @@ namespace Avalonia.X11.NativeDialogs
[DllImport(GtkName)]
public static extern void gtk_file_chooser_set_filename(IntPtr chooser, Utf8Buffer file);
[DllImport(GtkName)]
public static extern IntPtr gtk_file_filter_new();
[DllImport(GtkName)]
public static extern IntPtr gtk_file_filter_set_name(IntPtr filter, Utf8Buffer name);
[DllImport(GtkName)]
public static extern IntPtr gtk_file_filter_add_pattern(IntPtr filter, Utf8Buffer pattern);
[DllImport(GtkName)]
public static extern IntPtr gtk_file_chooser_add_filter(IntPtr chooser, IntPtr filter);
[DllImport(GtkName)]
public static extern void gtk_widget_realize(IntPtr gtkWidget);

25
src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs

@ -16,7 +16,7 @@ namespace Avalonia.X11.NativeDialogs
{
private Task<bool> _initialized;
private unsafe Task<string[]> ShowDialog(string title, IWindowImpl parent, GtkFileChooserAction action,
bool multiSelect, string initialFileName)
bool multiSelect, string initialFileName, IEnumerable<FileDialogFilter> filters)
{
IntPtr dlg;
using (var name = new Utf8Buffer(title))
@ -35,6 +35,20 @@ namespace Avalonia.X11.NativeDialogs
foreach (var d in disposables) d.Dispose();
disposables.Clear();
}
if(filters != null)
foreach (var f in filters)
{
var filter = gtk_file_filter_new();
using (var b = new Utf8Buffer(f.Name))
gtk_file_filter_set_name(filter, b);
foreach (var e in f.Extensions)
using (var b = new Utf8Buffer("*." + e))
gtk_file_filter_add_pattern(filter, b);
gtk_file_chooser_add_filter(dlg, filter);
}
disposables = new List<IDisposable>
{
@ -68,7 +82,10 @@ namespace Avalonia.X11.NativeDialogs
return false;
})
};
using (var open = new Utf8Buffer("Open"))
using (var open = new Utf8Buffer(
action == GtkFileChooserAction.Save ? "Save"
: action == GtkFileChooserAction.SelectFolder ? "Select"
: "Open"))
gtk_dialog_add_button(dlg, open, GtkResponseType.Accept);
using (var open = new Utf8Buffer("Cancel"))
gtk_dialog_add_button(dlg, open, GtkResponseType.Cancel);
@ -87,7 +104,7 @@ namespace Avalonia.X11.NativeDialogs
dialog is OpenFileDialog ? GtkFileChooserAction.Open : GtkFileChooserAction.Save,
(dialog as OpenFileDialog)?.AllowMultiple ?? false,
Path.Combine(string.IsNullOrEmpty(dialog.InitialDirectory) ? "" : dialog.InitialDirectory,
string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName)));
string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName), dialog.Filters));
}
public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent)
@ -96,7 +113,7 @@ namespace Avalonia.X11.NativeDialogs
return await await RunOnGlibThread(async () =>
{
var res = await ShowDialog(dialog.Title, parent,
GtkFileChooserAction.SelectFolder, false, dialog.InitialDirectory);
GtkFileChooserAction.SelectFolder, false, dialog.InitialDirectory, null);
return res?.FirstOrDefault();
});
}

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);

46
tests/Avalonia.Controls.UnitTests/ButtonTests.cs

@ -5,6 +5,7 @@ using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
using Xunit;
@ -21,6 +22,7 @@ namespace Avalonia.Controls.UnitTests
{
Command = command,
};
var root = new TestRoot { Child = target };
Assert.False(target.IsEnabled);
command.IsEnabled = true;
@ -215,6 +217,39 @@ namespace Avalonia.Controls.UnitTests
Assert.True(clicked);
}
[Fact]
public void Button_Does_Not_Subscribe_To_Command_CanExecuteChanged_Until_Added_To_Logical_Tree()
{
var command = new TestCommand(true);
var target = new Button
{
Command = command,
};
Assert.Equal(0, command.SubscriptionCount);
}
[Fact]
public void Button_Subscribes_To_Command_CanExecuteChanged_When_Added_To_Logical_Tree()
{
var command = new TestCommand(true);
var target = new Button { Command = command };
var root = new TestRoot { Child = target };
Assert.Equal(1, command.SubscriptionCount);
}
[Fact]
public void Button_Unsubscribes_From_Command_CanExecuteChanged_When_Removed_From_Logical_Tree()
{
var command = new TestCommand(true);
var target = new Button { Command = command };
var root = new TestRoot { Child = target };
root.Child = null;
Assert.Equal(0, command.SubscriptionCount);
}
private class TestButton : Button, IRenderRoot
{
public TestButton()
@ -298,6 +333,7 @@ namespace Avalonia.Controls.UnitTests
private class TestCommand : ICommand
{
private EventHandler _canExecuteChanged;
private bool _enabled;
public TestCommand(bool enabled)
@ -313,12 +349,18 @@ namespace Avalonia.Controls.UnitTests
if (_enabled != value)
{
_enabled = value;
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
_canExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}
public event EventHandler CanExecuteChanged;
public int SubscriptionCount { get; private set; }
public event EventHandler CanExecuteChanged
{
add { _canExecuteChanged += value; ++SubscriptionCount; }
remove { _canExecuteChanged -= value; --SubscriptionCount; }
}
public bool CanExecute(object parameter) => _enabled;

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],
}
}
}

54
tests/Avalonia.Controls.UnitTests/MenuItemTests.cs

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Input;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Controls.UnitTests
@ -22,5 +24,57 @@ namespace Avalonia.Controls.UnitTests
Assert.False(target.Focusable);
}
[Fact]
public void MenuItem_Does_Not_Subscribe_To_Command_CanExecuteChanged_Until_Added_To_Logical_Tree()
{
var command = new TestCommand();
var target = new MenuItem
{
Command = command,
};
Assert.Equal(0, command.SubscriptionCount);
}
[Fact]
public void MenuItem_Subscribes_To_Command_CanExecuteChanged_When_Added_To_Logical_Tree()
{
var command = new TestCommand();
var target = new MenuItem { Command = command };
var root = new TestRoot { Child = target };
Assert.Equal(1, command.SubscriptionCount);
}
[Fact]
public void MenuItem_Unsubscribes_From_Command_CanExecuteChanged_When_Removed_From_Logical_Tree()
{
var command = new TestCommand();
var target = new MenuItem { Command = command };
var root = new TestRoot { Child = target };
root.Child = null;
Assert.Equal(0, command.SubscriptionCount);
}
private class TestCommand : ICommand
{
private EventHandler _canExecuteChanged;
public int SubscriptionCount { get; private set; }
public event EventHandler CanExecuteChanged
{
add { _canExecuteChanged += value; ++SubscriptionCount; }
remove { _canExecuteChanged -= value; --SubscriptionCount; }
}
public bool CanExecute(object parameter) => true;
public void Execute(object parameter)
{
}
}
}
}

Loading…
Cancel
Save