Browse Source

Merge branch 'master' into fix/gridsplitter_minsize

pull/4420/head
Steven Kirk 6 years ago
committed by GitHub
parent
commit
0a48c44b63
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  2. 68
      samples/ControlCatalog/Pages/ListBoxPage.xaml.cs
  3. 65
      samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs
  4. 2
      src/Avalonia.Controls/ItemsControl.cs
  5. 3
      src/Avalonia.Controls/ListBox.cs
  6. 37
      src/Avalonia.Controls/Utils/SelectedItemsSync.cs
  7. 126
      tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs
  8. 14
      tests/Avalonia.Controls.UnitTests/Utils/SelectedItemsSyncTests.cs

3
samples/ControlCatalog/Pages/ListBoxPage.xaml

@ -11,8 +11,7 @@
Spacing="16">
<StackPanel Orientation="Vertical" Spacing="8">
<ListBox Items="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
SelectedItems="{Binding SelectedItems}"
Selection="{Binding Selection}"
AutoScrollToSelectedItem="True"
SelectionMode="{Binding SelectionMode}"
Width="250"

68
samples/ControlCatalog/Pages/ListBoxPage.xaml.cs

@ -1,10 +1,6 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ReactiveUI;
using ControlCatalog.ViewModels;
namespace ControlCatalog.Pages
{
@ -13,72 +9,12 @@ namespace ControlCatalog.Pages
public ListBoxPage()
{
InitializeComponent();
DataContext = new PageViewModel();
DataContext = new ListBoxPageViewModel();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private class PageViewModel : ReactiveObject
{
private int _counter;
private SelectionMode _selectionMode;
public PageViewModel()
{
Items = new ObservableCollection<string>(Enumerable.Range(1, 10000).Select(i => GenerateItem()));
SelectedItems = new ObservableCollection<string>();
AddItemCommand = ReactiveCommand.Create(() => Items.Add(GenerateItem()));
RemoveItemCommand = ReactiveCommand.Create(() =>
{
while (SelectedItems.Count > 0)
{
Items.Remove(SelectedItems[0]);
}
});
SelectRandomItemCommand = ReactiveCommand.Create(() =>
{
var random = new Random();
SelectedItem = Items[random.Next(Items.Count - 1)];
});
}
public ObservableCollection<string> Items { get; }
private string _selectedItem;
public string SelectedItem
{
get { return _selectedItem; }
set { this.RaiseAndSetIfChanged(ref _selectedItem, value); }
}
public ObservableCollection<string> SelectedItems { get; }
public ReactiveCommand<Unit, Unit> AddItemCommand { get; }
public ReactiveCommand<Unit, Unit> RemoveItemCommand { get; }
public ReactiveCommand<Unit, Unit> SelectRandomItemCommand { get; }
public SelectionMode SelectionMode
{
get => _selectionMode;
set
{
SelectedItems.Clear();
this.RaiseAndSetIfChanged(ref _selectionMode, value);
}
}
private string GenerateItem() => $"Item {_counter++.ToString()}";
}
}
}

65
samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs

@ -0,0 +1,65 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using Avalonia.Controls;
using ReactiveUI;
namespace ControlCatalog.ViewModels
{
public class ListBoxPageViewModel : ReactiveObject
{
private int _counter;
private SelectionMode _selectionMode;
public ListBoxPageViewModel()
{
Items = new ObservableCollection<string>(Enumerable.Range(1, 10000).Select(i => GenerateItem()));
Selection = new SelectionModel();
Selection.Select(1);
AddItemCommand = ReactiveCommand.Create(() => Items.Add(GenerateItem()));
RemoveItemCommand = ReactiveCommand.Create(() =>
{
while (Selection.SelectedItems.Count > 0)
{
Items.Remove((string)Selection.SelectedItems.First());
}
});
SelectRandomItemCommand = ReactiveCommand.Create(() =>
{
var random = new Random();
using (Selection.Update())
{
Selection.ClearSelection();
Selection.Select(random.Next(Items.Count - 1));
}
});
}
public ObservableCollection<string> Items { get; }
public SelectionModel Selection { get; }
public ReactiveCommand<Unit, Unit> AddItemCommand { get; }
public ReactiveCommand<Unit, Unit> RemoveItemCommand { get; }
public ReactiveCommand<Unit, Unit> SelectRandomItemCommand { get; }
public SelectionMode SelectionMode
{
get => _selectionMode;
set
{
Selection.ClearSelection();
this.RaiseAndSetIfChanged(ref _selectionMode, value);
}
}
private string GenerateItem() => $"Item {_counter++.ToString()}";
}
}

2
src/Avalonia.Controls/ItemsControl.cs

@ -295,7 +295,7 @@ namespace Avalonia.Controls
if (next != null)
{
focus.Focus(next, NavigationMethod.Directional);
focus.Focus(next, NavigationMethod.Directional, e.KeyModifiers);
e.Handled = true;
}

3
src/Avalonia.Controls/ListBox.cs

@ -136,7 +136,8 @@ namespace Avalonia.Controls
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
(e.KeyModifiers & KeyModifiers.Shift) != 0);
(e.KeyModifiers & KeyModifiers.Shift) != 0,
(e.KeyModifiers & KeyModifiers.Control) != 0);
}
}

37
src/Avalonia.Controls/Utils/SelectedItemsSync.cs

@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using Avalonia.Collections;
@ -16,6 +17,7 @@ namespace Avalonia.Controls.Utils
private IList? _items;
private bool _updatingItems;
private bool _updatingModel;
private bool _initializeOnSourceAssignment;
public SelectedItemsSync(ISelectionModel model)
{
@ -63,10 +65,18 @@ namespace Avalonia.Controls.Utils
_updatingModel = true;
_items = items;
using (Model.Update())
if (Model.Source is object)
{
Model.ClearSelection();
Add(items);
using (Model.Update())
{
Model.ClearSelection();
Add(items);
}
}
else if (!_initializeOnSourceAssignment)
{
Model.PropertyChanged += SelectionModelPropertyChanged;
_initializeOnSourceAssignment = true;
}
if (_items is INotifyCollectionChanged incc2)
@ -86,9 +96,11 @@ namespace Avalonia.Controls.Utils
if (_items != null)
{
Model.PropertyChanged -= SelectionModelPropertyChanged;
Model.SelectionChanged -= SelectionModelSelectionChanged;
Model = model;
Model.SelectionChanged += SelectionModelSelectionChanged;
_initializeOnSourceAssignment = false;
try
{
@ -175,6 +187,25 @@ namespace Avalonia.Controls.Utils
}
}
private void SelectionModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (_initializeOnSourceAssignment &&
_items != null &&
e.PropertyName == nameof(SelectionModel.Source))
{
try
{
_updatingModel = true;
Add(_items);
_initializeOnSourceAssignment = false;
}
finally
{
_updatingModel = false;
}
}
}
private void SelectionModelSelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e)
{
if (_updatingModel)

126
tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs

@ -0,0 +1,126 @@
using System.Linq;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Xunit;
namespace Avalonia.Controls.UnitTests
{
public class ListBoxTests_Multiple
{
[Fact]
public void Focusing_Item_With_Shift_And_Arrow_Key_Should_Add_To_Selection()
{
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
SelectionMode = SelectionMode.Multiple
};
ApplyTemplate(target);
target.SelectedItem = "Foo";
target.Presenter.Panel.Children[1].RaiseEvent(new GotFocusEventArgs
{
RoutedEvent = InputElement.GotFocusEvent,
NavigationMethod = NavigationMethod.Directional,
KeyModifiers = KeyModifiers.Shift
});
Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems);
}
[Fact]
public void Focusing_Item_With_Ctrl_And_Arrow_Key_Should_Add_To_Selection()
{
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
SelectionMode = SelectionMode.Multiple
};
ApplyTemplate(target);
target.SelectedItem = "Foo";
target.Presenter.Panel.Children[1].RaiseEvent(new GotFocusEventArgs
{
RoutedEvent = InputElement.GotFocusEvent,
NavigationMethod = NavigationMethod.Directional,
KeyModifiers = KeyModifiers.Control
});
Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems);
}
[Fact]
public void Focusing_Selected_Item_With_Ctrl_And_Arrow_Key_Should_Remove_From_Selection()
{
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
SelectionMode = SelectionMode.Multiple
};
ApplyTemplate(target);
target.SelectedItems.Add("Foo");
target.SelectedItems.Add("Bar");
target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs
{
RoutedEvent = InputElement.GotFocusEvent,
NavigationMethod = NavigationMethod.Directional,
KeyModifiers = KeyModifiers.Control
});
Assert.Equal(new[] { "Bar" }, target.SelectedItems);
}
private Control CreateListBoxTemplate(ITemplatedControl parent, INameScope scope)
{
return new ScrollViewer
{
Template = new FuncControlTemplate(CreateScrollViewerTemplate),
Content = new ItemsPresenter
{
Name = "PART_ItemsPresenter",
[~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty).ToBinding(),
}.RegisterInNameScope(scope)
};
}
private Control CreateScrollViewerTemplate(ITemplatedControl parent, INameScope scope)
{
return new ScrollContentPresenter
{
Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] =
parent.GetObservable(ContentControl.ContentProperty).ToBinding(),
}.RegisterInNameScope(scope);
}
private void ApplyTemplate(ListBox target)
{
// Apply the template to the ListBox itself.
target.ApplyTemplate();
// Then to its inner ScrollViewer.
var scrollViewer = (ScrollViewer)target.GetVisualChildren().Single();
scrollViewer.ApplyTemplate();
// Then make the ScrollViewer create its child.
((ContentPresenter)scrollViewer.Presenter).UpdateChild();
// Now the ItemsPresenter should be reigstered, so apply its template.
target.Presenter.ApplyTemplate();
}
}
}

14
tests/Avalonia.Controls.UnitTests/Utils/SelectedItemsSyncTests.cs

@ -208,6 +208,20 @@ namespace Avalonia.Controls.UnitTests.Utils
target.SetItems(new[] { "foo", "bar", "baz" }));
}
[Fact]
public void Selected_Items_Can_Be_Set_Before_SelectionModel_Source()
{
var model = new SelectionModel();
var target = new SelectedItemsSync(model);
var items = new AvaloniaList<string> { "foo", "bar", "baz" };
var selectedItems = new AvaloniaList<string> { "bar" };
target.SetItems(selectedItems);
model.Source = items;
Assert.Equal(new IndexPath(1), model.SelectedIndex);
}
private static SelectedItemsSync CreateTarget(
IEnumerable<string> items = null)
{

Loading…
Cancel
Save