From 2a77f4426ad45f7dc35a76b0673281d3dd409dd7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 19 Feb 2019 18:49:49 +0100 Subject: [PATCH 01/19] Snap dirty rects in DeferredRenderer to device pixels. Fixes #2015. --- .../Rendering/DeferredRenderer.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 3bc5e92fb4..5293e1b978 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -393,24 +393,36 @@ namespace Avalonia.Rendering } else { + var scale = scene.Scaling; + foreach (var rect in layer.Dirty) { + var snappedRect = SnapToDevicePixels(rect, scale); context.Transform = Matrix.Identity; - context.PushClip(rect); + context.PushClip(snappedRect); context.Clear(Colors.Transparent); - Render(context, node, layer.LayerRoot, rect); + Render(context, node, layer.LayerRoot, snappedRect); context.PopClip(); if (DrawDirtyRects) { - _dirtyRectsDisplay.Add(rect); + _dirtyRectsDisplay.Add(snappedRect); } } } } } } + } + private static Rect SnapToDevicePixels(Rect rect, double scale) + { + return new Rect( + Math.Floor(rect.X * scale) / scale, + Math.Floor(rect.Y * scale) / scale, + Math.Ceiling(rect.Width * scale) / scale, + Math.Ceiling(rect.Height * scale) / scale); + } private void RenderOverlay(Scene scene, IDrawingContextImpl parentContent) From e7e57d0e9136f0804f2ba57dd531f285026b169b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 4 Mar 2019 13:30:20 +0100 Subject: [PATCH 02/19] Fix text rendering where TextAlignment != Left. The renderer was using the wrong bounds for text where `TextAlignment != Left` as seen in #1625. To fix this, change `FormattedTextImpl.Size` to `Bounds`, which returns a rect that takes into account the text alignment and similarly change `FormattedText.Measure` to return a `Rect` instead of a `Size. Fixes #1625. --- .../Presenters/TextPresenter.cs | 2 +- src/Avalonia.Controls/TextBlock.cs | 2 +- src/Avalonia.Visuals/Media/FormattedText.cs | 9 +++---- .../Platform/IFormattedTextImpl.cs | 5 ++-- .../Rendering/SceneGraph/TextNode.cs | 2 +- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 2 +- src/Skia/Avalonia.Skia/FormattedTextImpl.cs | 26 ++++++++++++++----- .../Media/DrawingContextImpl.cs | 2 +- .../Media/FormattedTextImpl.cs | 12 ++++++--- .../FullLayoutTests.cs | 2 +- .../Media/FormattedTextImplTests.cs | 4 +-- 11 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index f73a335de5..00b2ed2149 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -275,7 +275,7 @@ namespace Avalonia.Controls.Presenters Typeface = new Typeface(FontFamily, FontSize, FontStyle, FontWeight), TextAlignment = TextAlignment, Constraint = availableSize, - }.Measure(); + }.Measure().Size; } } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index af7b0f835e..2ccab016f2 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -396,7 +396,7 @@ namespace Avalonia.Controls FormattedText.Constraint = Size.Infinity; } - return FormattedText.Measure(); + return FormattedText.Measure().Size; } return new Size(); diff --git a/src/Avalonia.Visuals/Media/FormattedText.cs b/src/Avalonia.Visuals/Media/FormattedText.cs index 0d456eb3b7..7abf8465a7 100644 --- a/src/Avalonia.Visuals/Media/FormattedText.cs +++ b/src/Avalonia.Visuals/Media/FormattedText.cs @@ -159,13 +159,10 @@ namespace Avalonia.Media } /// - /// Gets the size of the text, taking into account. + /// Gets the bounds of the text, taking into account. /// - /// The bounds box of the text. - public Size Measure() - { - return PlatformImpl.Size; - } + /// The bounds of the text. + public Rect Measure() => PlatformImpl.Bounds; private void Set(ref T field, T value) { diff --git a/src/Avalonia.Visuals/Platform/IFormattedTextImpl.cs b/src/Avalonia.Visuals/Platform/IFormattedTextImpl.cs index 2a4e9cde4f..2543e4f363 100644 --- a/src/Avalonia.Visuals/Platform/IFormattedTextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IFormattedTextImpl.cs @@ -1,6 +1,7 @@ // 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.Collections.Generic; using Avalonia.Media; @@ -17,9 +18,9 @@ namespace Avalonia.Platform Size Constraint { get; } /// - /// The measured size of the text. + /// The measured bounds of the text. /// - Size Size { get; } + Rect Bounds{ get; } /// /// Gets the text. diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs index c06d0d26b4..dec5c382c9 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs @@ -27,7 +27,7 @@ namespace Avalonia.Rendering.SceneGraph Point origin, IFormattedTextImpl text, IDictionary childScenes = null) - : base(new Rect(origin, text.Size), transform, null) + : base(text.Bounds, transform, null) { Transform = transform; Foreground = foreground?.ToImmutable(); diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 353354da5e..993d1eb256 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -218,7 +218,7 @@ namespace Avalonia.Skia /// public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text) { - using (var paint = CreatePaint(foreground, text.Size)) + using (var paint = CreatePaint(foreground, text.Bounds.Size)) { var textImpl = (FormattedTextImpl) text; textImpl.Draw(this, Canvas, origin.ToSKPoint(), paint, _canTextUseLcdRendering); diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs index 7e3f4aa40a..c83d5f26fb 100644 --- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs +++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs @@ -89,7 +89,7 @@ namespace Avalonia.Skia public Size Constraint => _constraint; - public Size Size => _size; + public Rect Bounds => _bounds; public IEnumerable GetLines() { @@ -135,7 +135,7 @@ namespace Avalonia.Skia }; } - bool end = point.X > _size.Width || point.Y > _lines.Sum(l => l.Height); + bool end = point.X > _bounds.Width || point.Y > _lines.Sum(l => l.Height); return new TextHitTestResult() { @@ -323,7 +323,7 @@ namespace Avalonia.Skia private Size _constraint = new Size(double.PositiveInfinity, double.PositiveInfinity); private float _lineHeight = 0; private float _lineOffset = 0; - private Size _size; + private Rect _bounds; private List _skiaLines; private static void ApplyWrapperTo(ref SKPaint current, DrawingContextImpl.PaintWrapper wrapper, @@ -639,12 +639,26 @@ namespace Avalonia.Skia if (_skiaLines.Count == 0) { _lines.Add(new FormattedTextLine(0, _lineHeight)); - _size = new Size(0, _lineHeight); + _bounds = new Rect(0, 0, 0, _lineHeight); } else { var lastLine = _skiaLines[_skiaLines.Count - 1]; - _size = new Size(maxX, lastLine.Top + lastLine.Height); + _bounds = new Rect(0, 0, maxX, lastLine.Top + lastLine.Height); + + switch (_paint.TextAlign) + { + case SKTextAlign.Center: + _bounds = new Rect(Constraint).CenterRect(_bounds); + break; + case SKTextAlign.Right: + _bounds = new Rect( + Constraint.Width - _bounds.Width, + 0, + _bounds.Width, + _bounds.Height); + break; + } } } @@ -660,7 +674,7 @@ namespace Avalonia.Skia { double width = Constraint.Width > 0 && !double.IsPositiveInfinity(Constraint.Width) ? Constraint.Width : - _size.Width; + _bounds.Width; switch (align) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 5e150ff647..6123088d7e 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -274,7 +274,7 @@ namespace Avalonia.Direct2D1.Media { var impl = (FormattedTextImpl)text; - using (var brush = CreateBrush(foreground, impl.Size)) + using (var brush = CreateBrush(foreground, impl.Bounds.Size)) using (var renderer = new AvaloniaTextRenderer(this, _deviceContext, brush.PlatformBrush)) { if (brush.PlatformBrush != null) diff --git a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs index 7164ec7c0d..b3cc4c8e0d 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs @@ -45,12 +45,12 @@ namespace Avalonia.Direct2D1.Media } } - Size = Measure(); + Bounds = Measure(); } public Size Constraint => new Size(TextLayout.MaxWidth, TextLayout.MaxHeight); - public Size Size { get; } + public Rect Bounds { get; } public string Text { get; } @@ -104,7 +104,7 @@ namespace Avalonia.Direct2D1.Media } } - private Size Measure() + private Rect Measure() { var metrics = TextLayout.Metrics; @@ -115,7 +115,11 @@ namespace Avalonia.Direct2D1.Media width = metrics.Width; } - return new Size(width, TextLayout.Metrics.Height); + return new Rect( + TextLayout.Metrics.Left, + TextLayout.Metrics.Top, + width, + TextLayout.Metrics.Height); } } } diff --git a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs index ffdb146eec..6cf38b6121 100644 --- a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs +++ b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs @@ -146,7 +146,7 @@ namespace Avalonia.Layout.UnitTests public string Text { get; } - public Size Size => new Size(); + public Rect Bounds => Rect.Empty; public void Dispose() { diff --git a/tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs b/tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs index a8f9d42c1e..353123ab2a 100644 --- a/tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs +++ b/tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs @@ -100,7 +100,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media public void Should_Measure_String_Correctly(string input, double fontSize, double expWidth, double expHeight) { var fmt = Create(input, fontSize); - var size = fmt.Size; + var size = fmt.Bounds.Size; Assert.Equal(expWidth, size.Width, 2); Assert.Equal(expHeight, size.Height, 2); @@ -265,4 +265,4 @@ namespace Avalonia.Direct2D1.RenderTests.Media } } } -} \ No newline at end of file +} From 4a910039816b97894412cc7df5c92c2acdc2274c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 4 Mar 2019 15:46:20 +0100 Subject: [PATCH 03/19] FormattedText.Measure() -> FormattedText.Bounds. Changed `FormattedText.Measure` method to a `Bounds` property. --- src/Avalonia.Controls/Presenters/TextPresenter.cs | 2 +- src/Avalonia.Controls/TextBlock.cs | 2 +- src/Avalonia.Visuals/Media/FormattedText.cs | 12 ++++++------ src/Avalonia.Visuals/Rendering/RendererBase.cs | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 00b2ed2149..fbdf885709 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -275,7 +275,7 @@ namespace Avalonia.Controls.Presenters Typeface = new Typeface(FontFamily, FontSize, FontStyle, FontWeight), TextAlignment = TextAlignment, Constraint = availableSize, - }.Measure().Size; + }.Bounds.Size; } } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 2ccab016f2..6b0c48b97b 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -396,7 +396,7 @@ namespace Avalonia.Controls FormattedText.Constraint = Size.Infinity; } - return FormattedText.Measure().Size; + return FormattedText.Bounds.Size; } return new Size(); diff --git a/src/Avalonia.Visuals/Media/FormattedText.cs b/src/Avalonia.Visuals/Media/FormattedText.cs index 7abf8465a7..e20e03e296 100644 --- a/src/Avalonia.Visuals/Media/FormattedText.cs +++ b/src/Avalonia.Visuals/Media/FormattedText.cs @@ -37,6 +37,12 @@ namespace Avalonia.Media _platform = platform; } + /// + /// Gets the bounds of the text within the . + /// + /// The bounds of the text. + public Rect Bounds => PlatformImpl.Bounds; + /// /// Gets or sets the constraint of the text. /// @@ -158,12 +164,6 @@ namespace Avalonia.Media return PlatformImpl.HitTestTextRange(index, length); } - /// - /// Gets the bounds of the text, taking into account. - /// - /// The bounds of the text. - public Rect Measure() => PlatformImpl.Bounds; - private void Set(ref T field, T value) { field = value; diff --git a/src/Avalonia.Visuals/Rendering/RendererBase.cs b/src/Avalonia.Visuals/Rendering/RendererBase.cs index ed464ec7f9..7b10fc1212 100644 --- a/src/Avalonia.Visuals/Rendering/RendererBase.cs +++ b/src/Avalonia.Visuals/Rendering/RendererBase.cs @@ -45,7 +45,7 @@ namespace Avalonia.Rendering _fpsText.Text = string.Format("FPS: {0:000}", _fps); } - var size = _fpsText.Measure(); + var size = _fpsText.Bounds.Size; var rect = new Rect(clientRect.Right - size.Width, 0, size.Width, size.Height); context.Transform = Matrix.Identity; From 85d309830a4fbe93accbfff2fc83784ac9dd7d75 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Mon, 4 Mar 2019 20:05:58 +0100 Subject: [PATCH 04/19] TreeView multiple selection support. --- .../ControlCatalog/Pages/TreeViewPage.xaml | 2 +- .../Primitives/SelectingItemsControl.cs | 2 +- src/Avalonia.Controls/TreeView.cs | 494 ++++++++++++++---- src/Avalonia.Controls/TreeViewHelper.cs | 90 ++++ .../TreeViewTests.cs | 145 ++++- 5 files changed, 639 insertions(+), 94 deletions(-) create mode 100644 src/Avalonia.Controls/TreeViewHelper.cs diff --git a/samples/ControlCatalog/Pages/TreeViewPage.xaml b/samples/ControlCatalog/Pages/TreeViewPage.xaml index f8f3cd5848..3392572cd3 100644 --- a/samples/ControlCatalog/Pages/TreeViewPage.xaml +++ b/samples/ControlCatalog/Pages/TreeViewPage.xaml @@ -9,7 +9,7 @@ Margin="0,16,0,0" HorizontalAlignment="Center" Spacing="16"> - + diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index c40ddc37ad..d21211371e 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -639,7 +639,7 @@ namespace Avalonia.Controls.Primitives /// /// The items collection. /// The desired items. - private static void SynchronizeItems(IList items, IEnumerable desired) + internal static void SynchronizeItems(IList items, IEnumerable desired) { int index = 0; diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index c574799724..d8a2eb0c06 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -2,13 +2,15 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; +using Avalonia.Collections; using Avalonia.Controls.Generators; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Interactivity; -using Avalonia.Styling; using Avalonia.Threading; using Avalonia.VisualTree; @@ -34,14 +36,24 @@ namespace Avalonia.Controls (o, v) => o.SelectedItem = v); /// - /// Defines the event. + /// Defines the property. /// - public static readonly RoutedEvent SelectedItemChangedEvent = - RoutedEvent.Register( - "SelectedItemChanged", - RoutingStrategies.Bubble); + public static readonly DirectProperty SelectedItemsProperty = + AvaloniaProperty.RegisterDirect( + nameof(SelectedItems), + o => o.SelectedItems, + (o, v) => o.SelectedItems = v); + /// + /// Defines the property. + /// + protected static readonly StyledProperty SelectionModeProperty = + AvaloniaProperty.Register( + nameof(SelectionMode)); + + private static readonly IList Empty = new object[0]; private object _selectedItem; + private IList _selectedItems; /// /// Initializes static members of the class. @@ -54,16 +66,16 @@ namespace Avalonia.Controls /// /// Occurs when the control's selection changes. /// - public event EventHandler SelectedItemChanged + public event EventHandler SelectionChanged { - add { AddHandler(SelectedItemChangedEvent, value); } - remove { RemoveHandler(SelectedItemChangedEvent, value); } + add => AddHandler(SelectingItemsControl.SelectionChangedEvent, value); + remove => RemoveHandler(SelectingItemsControl.SelectionChangedEvent, value); } /// /// Gets the for the tree view. /// - public new ITreeItemContainerGenerator ItemContainerGenerator => + public new ITreeItemContainerGenerator ItemContainerGenerator => (ITreeItemContainerGenerator)base.ItemContainerGenerator; /// @@ -71,81 +83,270 @@ namespace Avalonia.Controls /// public bool AutoScrollToSelectedItem { - get { return GetValue(AutoScrollToSelectedItemProperty); } - set { SetValue(AutoScrollToSelectedItemProperty, value); } + get => GetValue(AutoScrollToSelectedItemProperty); + set => SetValue(AutoScrollToSelectedItemProperty, value); + } + + private bool _syncingSelectedItems; + + /// + /// Gets or sets the selection mode. + /// + public SelectionMode SelectionMode + { + get => GetValue(SelectionModeProperty); + set => SetValue(SelectionModeProperty, value); } /// /// Gets or sets the selected item. /// public object SelectedItem + { + get => _selectedItem; + set + { + SetAndRaise(SelectedItemProperty, ref _selectedItem, + (object val, ref object backing, Action notifyWrapper) => + { + var old = backing; + backing = val; + + notifyWrapper(() => + RaisePropertyChanged( + SelectedItemProperty, + old, + val)); + + if (val != null) + { + if (SelectedItems.Count != 1 || SelectedItems[0] != val) + { + _syncingSelectedItems = true; + SelectSingleItem(val); + _syncingSelectedItems = false; + } + } + else if (SelectedItems.Count > 0) + { + SelectedItems.Clear(); + } + }, value); + } + } + + /// + /// Gets the selected items. + /// + protected IList SelectedItems { get { - return _selectedItem; + if (_selectedItems == null) + { + _selectedItems = new AvaloniaList(); + SubscribeToSelectedItems(); + } + + return _selectedItems; } set { - if (_selectedItem != null) + if (value?.IsFixedSize == true || value?.IsReadOnly == true) { - var container = ItemContainerGenerator.Index.ContainerFromItem(_selectedItem); - MarkContainerSelected(container, false); + throw new NotSupportedException( + "Cannot use a fixed size or read-only collection as SelectedItems."); } - var oldItem = _selectedItem; - SetAndRaise(SelectedItemProperty, ref _selectedItem, value); + UnsubscribeFromSelectedItems(); + _selectedItems = value ?? new AvaloniaList(); + SubscribeToSelectedItems(); + } + } - if (_selectedItem != null) - { - var container = ItemContainerGenerator.Index.ContainerFromItem(_selectedItem); - MarkContainerSelected(container, true); + /// + /// Subscribes to the CollectionChanged event, if any. + /// + private void SubscribeToSelectedItems() + { + if (_selectedItems is INotifyCollectionChanged incc) + { + incc.CollectionChanged += SelectedItemsCollectionChanged; + } + + SelectedItemsCollectionChanged( + _selectedItems, + new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + private void SelectSingleItem(object item) + { + SelectedItems.Clear(); + SelectedItems.Add(item); + } + + /// + /// Called when the CollectionChanged event is raised. + /// + /// The event sender. + /// The event args. + private void SelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + IList added = null; + IList removed = null; - if (AutoScrollToSelectedItem && container != null) + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + + SelectedItemsAdded(e.NewItems.Cast().ToArray()); + + if (AutoScrollToSelectedItem) { - container.BringIntoView(); + var container = (TreeViewItem)ItemContainerGenerator.Index.ContainerFromItem(e.NewItems[0]); + + container?.BringIntoView(); } - } - if (oldItem != _selectedItem) - { - // Fire the SelectionChanged event - List removed = new List(); - if (oldItem != null) + added = e.NewItems; + + break; + case NotifyCollectionChangedAction.Remove: + + if (!_syncingSelectedItems) { - removed.Add(oldItem); + if (SelectedItems.Count == 0) + { + SelectedItem = null; + } + else + { + var selectedIndex = SelectedItems.IndexOf(_selectedItem); + + if (selectedIndex == -1) + { + var old = _selectedItem; + _selectedItem = SelectedItems[0]; + + RaisePropertyChanged(SelectedItemProperty, old, _selectedItem); + } + } } - List added = new List(); - if (_selectedItem != null) + foreach (var item in e.OldItems) { - added.Add(_selectedItem); + MarkItemSelected(item, false); } - var changed = new SelectionChangedEventArgs( - SelectedItemChangedEvent, - added, - removed); - RaiseEvent(changed); - } + removed = e.OldItems; + + break; + case NotifyCollectionChangedAction.Reset: + + foreach (IControl container in ItemContainerGenerator.Index.Items) + { + MarkContainerSelected(container, false); + } + + if (SelectedItems.Count > 0) + { + SelectedItemsAdded(SelectedItems); + + added = SelectedItems; + } + else if (!_syncingSelectedItems) + { + SelectedItem = null; + } + + break; + case NotifyCollectionChangedAction.Replace: + + foreach (var item in e.OldItems) + { + MarkItemSelected(item, false); + } + + foreach (var item in e.NewItems) + { + MarkItemSelected(item, true); + } + + if (SelectedItem != SelectedItems[0] && !_syncingSelectedItems) + { + var oldItem = SelectedItem; + var item = SelectedItems[0]; + _selectedItem = item; + RaisePropertyChanged(SelectedItemProperty, oldItem, item); + } + + added = e.NewItems; + removed = e.OldItems; + + break; + } + + if (added?.Count > 0 || removed?.Count > 0) + { + var changed = new SelectionChangedEventArgs( + SelectingItemsControl.SelectionChangedEvent, + added ?? Empty, + removed ?? Empty); + RaiseEvent(changed); + } + } + + private void MarkItemSelected(object item, bool selected) + { + var container = ItemContainerGenerator.Index.ContainerFromItem(item); + + MarkContainerSelected(container, selected); + } + + private void SelectedItemsAdded(IList items) + { + if (items.Count == 0) + { + return; + } + + foreach (object item in items) + { + MarkItemSelected(item, true); + } + + if (SelectedItem == null && !_syncingSelectedItems) + { + SetAndRaise(SelectedItemProperty, ref _selectedItem, items[0]); } } - (bool handled, IInputElement next) ICustomKeyboardNavigation.GetNext(IInputElement element, NavigationDirection direction) + /// + /// Unsubscribes from the CollectionChanged event, if any. + /// + private void UnsubscribeFromSelectedItems() + { + if (_selectedItems is INotifyCollectionChanged incc) + { + incc.CollectionChanged -= SelectedItemsCollectionChanged; + } + } + + (bool handled, IInputElement next) ICustomKeyboardNavigation.GetNext(IInputElement element, + NavigationDirection direction) { if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous) { if (!this.IsVisualAncestorOf(element)) { - IControl result = _selectedItem != null ? - ItemContainerGenerator.Index.ContainerFromItem(_selectedItem) : - ItemContainerGenerator.ContainerFromIndex(0); + IControl result = _selectedItem != null + ? ItemContainerGenerator.Index.ContainerFromItem(_selectedItem) + : ItemContainerGenerator.ContainerFromIndex(0); return (true, result); } - else - { - return (true, null); - } + + return (true, null); } return (false, null); @@ -186,7 +387,7 @@ namespace Avalonia.Controls if (SelectedItem != null) { var next = GetContainerInDirection( - GetContainerFromEventSource(e.Source) as TreeViewItem, + GetContainerFromEventSource(e.Source), direction.Value, true); @@ -208,17 +409,9 @@ namespace Avalonia.Controls NavigationDirection direction, bool intoChildren) { - IItemContainerGenerator parentGenerator; + IItemContainerGenerator parentGenerator = GetParentContainerGenerator(from); - if (from?.Parent is TreeView treeView) - { - parentGenerator = treeView.ItemContainerGenerator; - } - else if (from?.Parent is TreeViewItem item) - { - parentGenerator = item.ItemContainerGenerator; - } - else + if (parentGenerator == null) { return null; } @@ -233,9 +426,9 @@ namespace Avalonia.Controls if (index > 0) { var previous = (TreeViewItem)parentGenerator.ContainerFromIndex(index - 1); - result = previous.IsExpanded ? - (TreeViewItem)previous.ItemContainerGenerator.ContainerFromIndex(previous.ItemCount - 1) : - previous; + result = previous.IsExpanded + ? (TreeViewItem)previous.ItemContainerGenerator.ContainerFromIndex(previous.ItemCount - 1) + : previous; } else { @@ -257,6 +450,7 @@ namespace Avalonia.Controls { return GetContainerInDirection(parentItem, direction, false); } + break; } @@ -293,18 +487,134 @@ namespace Avalonia.Controls { var item = ItemContainerGenerator.Index.ItemFromContainer(container); - if (item != null) + if (item == null) { - if (SelectedItem != null) + return; + } + + IControl selectedContainer = null; + + if (SelectedItem != null) + { + selectedContainer = ItemContainerGenerator.Index.ContainerFromItem(SelectedItem); + } + + var mode = SelectionMode; + var toggle = toggleModifier || (mode & SelectionMode.Toggle) != 0; + var multi = (mode & SelectionMode.Multiple) != 0; + var range = multi && selectedContainer != null && rangeModifier; + + if (!toggle && !range) + { + SelectSingleItem(item); + } + else if (multi && range) + { + SelectingItemsControl.SynchronizeItems( + SelectedItems, + GetItemsInRange(selectedContainer as TreeViewItem, container as TreeViewItem)); + } + else + { + var i = SelectedItems.IndexOf(item); + + if (i != -1) + { + SelectedItems.Remove(item); + } + else + { + if (multi) + { + SelectedItems.Add(item); + } + else + { + SelectedItem = item; + } + } + } + } + + private static IItemContainerGenerator GetParentContainerGenerator(TreeViewItem item) + { + if (item == null) + { + return null; + } + + switch (item.Parent) + { + case TreeView treeView: + return treeView.ItemContainerGenerator; + case TreeViewItem treeViewItem: + return treeViewItem.ItemContainerGenerator; + default: + return null; + } + } + + /// + /// Returns all items that belong to containers between and . + /// The range is inclusive. + /// + /// From container. + /// To container. + private List GetItemsInRange(TreeViewItem from, TreeViewItem to) + { + var items = new List(); + + if (from == null || to == null) + { + return items; + } + + TreeViewItem firstItem = TreeViewHelper.FindFirstNode(this, new TreeViewHelper.SearchInfo(from, to)); + + if (firstItem == null) + { + return items; + } + + bool wasReversed = false; + + if (firstItem == to) + { + var temp = from; + + from = to; + to = temp; + + wasReversed = true; + } + + TreeViewItem node = from; + + while (node != to) + { + var item = ItemContainerGenerator.Index.ItemFromContainer(node); + + if (item != null) { - var old = ItemContainerGenerator.Index.ContainerFromItem(SelectedItem); - MarkContainerSelected(old, false); + items.Add(item); } - SelectedItem = item; + node = GetContainerInDirection(node, NavigationDirection.Down, true); + } + + var toItem = ItemContainerGenerator.Index.ItemFromContainer(to); - MarkContainerSelected(container, true); + if (toItem != null) + { + items.Add(toItem); + } + + if (wasReversed) + { + items.Reverse(); } + + return items; } /// @@ -341,7 +651,7 @@ namespace Avalonia.Controls /// /// The control that raised the event. /// The container or null if the event did not originate in a container. - protected IControl GetContainerFromEventSource(IInteractive eventSource) + protected TreeViewItem GetContainerFromEventSource(IInteractive eventSource) { var item = ((IVisual)eventSource).GetSelfAndVisualAncestors() .OfType() @@ -349,7 +659,7 @@ namespace Avalonia.Controls if (item != null) { - if (item.ItemContainerGenerator.Index == this.ItemContainerGenerator.Index) + if (item.ItemContainerGenerator.Index == ItemContainerGenerator.Index) { return item; } @@ -367,21 +677,23 @@ namespace Avalonia.Controls { var selectedItem = SelectedItem; - if (selectedItem != null) + if (selectedItem == null) { - foreach (var container in e.Containers) - { - if (container.Item == selectedItem) - { - ((TreeViewItem)container.ContainerControl).IsSelected = true; + return; + } - if (AutoScrollToSelectedItem) - { - Dispatcher.UIThread.Post(container.ContainerControl.BringIntoView); - } + foreach (var container in e.Containers) + { + if (container.Item == selectedItem) + { + ((TreeViewItem)container.ContainerControl).IsSelected = true; - break; + if (AutoScrollToSelectedItem) + { + Dispatcher.UIThread.Post(container.ContainerControl.BringIntoView); } + + break; } } } @@ -393,18 +705,18 @@ namespace Avalonia.Controls /// Whether the control is selected private void MarkContainerSelected(IControl container, bool selected) { - if (container != null) + if (container == null) { - var selectable = container as ISelectable; + return; + } - if (selectable != null) - { - selectable.IsSelected = selected; - } - else - { - ((IPseudoClasses)container.Classes).Set(":selected", selected); - } + if (container is ISelectable selectable) + { + selectable.IsSelected = selected; + } + else + { + container.Classes.Set(":selected", selected); } } } diff --git a/src/Avalonia.Controls/TreeViewHelper.cs b/src/Avalonia.Controls/TreeViewHelper.cs new file mode 100644 index 0000000000..b2d43185cb --- /dev/null +++ b/src/Avalonia.Controls/TreeViewHelper.cs @@ -0,0 +1,90 @@ +// 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.Collections.Generic; +using Avalonia.Controls.Generators; + +namespace Avalonia.Controls +{ + /// + /// Helper for related operations. + /// + internal static class TreeViewHelper + { + /// + /// Find which node from search info is first in hierarchy. + /// + /// Search root. + /// Nodes to search for. + /// Found first node. + public static TreeViewItem FindFirstNode(TreeView treeView, in SearchInfo searchInfo) + { + return FindInContainers(treeView.ItemContainerGenerator, in searchInfo); + } + + private static TreeViewItem FindInContainers(ITreeItemContainerGenerator containerGenerator, + in SearchInfo searchInfo) + { + IEnumerable containers = containerGenerator.Containers; + + foreach (ItemContainerInfo container in containers) + { + TreeViewItem node = FindFirstNode(container.ContainerControl as TreeViewItem, in searchInfo); + + if (node != null) + { + return node; + } + } + + return null; + } + + private static TreeViewItem FindFirstNode(TreeViewItem node, in SearchInfo searchInfo) + { + if (node == null) + { + return null; + } + + TreeViewItem match = searchInfo.GetMatch(node); + + if (match != null) + { + return match; + } + + return FindInContainers(node.ItemContainerGenerator, in searchInfo); + } + + /// + /// Node search info. + /// + public readonly struct SearchInfo + { + public readonly TreeViewItem Search1; + public readonly TreeViewItem Search2; + + public SearchInfo(TreeViewItem search1, TreeViewItem search2) + { + Search1 = search1; + Search2 = search2; + } + + public TreeViewItem GetMatch(TreeViewItem candidate) + { + if (candidate == Search1) + { + return Search1; + } + + if (candidate == Search2) + { + return Search2; + } + + return null; + } + } + } +} diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 1a913865cb..4364af3a62 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -140,6 +140,149 @@ namespace Avalonia.Controls.UnitTests Assert.True(container.IsSelected); } + [Fact] + public void Clicking_WithControlModifier_Selected_Item_Should_Deselect_It() + { + var tree = CreateTestTreeData(); + var target = new TreeView + { + Template = CreateTreeViewTemplate(), + Items = tree, + }; + + var visualRoot = new TestRoot(); + visualRoot.Child = target; + + CreateNodeDataTemplate(target); + ApplyTemplates(target); + + var item = tree[0].Children[1].Children[0]; + var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item); + + Assert.NotNull(container); + + target.SelectedItem = item; + + Assert.True(container.IsSelected); + + container.RaiseEvent(new PointerPressedEventArgs + { + RoutedEvent = InputElement.PointerPressedEvent, + MouseButton = MouseButton.Left, + InputModifiers = InputModifiers.Control + }); + + Assert.Null(target.SelectedItem); + Assert.False(container.IsSelected); + } + + [Fact] + public void Clicking_WithControlModifier_Not_Selected_Item_Should_Select_It() + { + var tree = CreateTestTreeData(); + var target = new TreeView + { + Template = CreateTreeViewTemplate(), + Items = tree, + }; + + var visualRoot = new TestRoot(); + visualRoot.Child = target; + + CreateNodeDataTemplate(target); + ApplyTemplates(target); + + var item1 = tree[0].Children[1].Children[0]; + var container1 = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1); + + var item2 = tree[0].Children[1]; + var container2 = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item2); + + Assert.NotNull(container1); + Assert.NotNull(container2); + + target.SelectedItem = item1; + + Assert.True(container1.IsSelected); + + container2.RaiseEvent(new PointerPressedEventArgs + { + RoutedEvent = InputElement.PointerPressedEvent, + MouseButton = MouseButton.Left, + InputModifiers = InputModifiers.Control + }); + + Assert.Equal(item2, target.SelectedItem); + Assert.False(container1.IsSelected); + Assert.True(container2.IsSelected); + } + + [Fact] + public void Clicking_WithShiftModifier_Should_Select_Range_Of_Items() + { + var tree = CreateTestTreeData(); + var target = new TreeView + { + Template = CreateTreeViewTemplate(), + Items = tree, + SelectionMode = SelectionMode.Multiple + }; + + var visualRoot = new TestRoot(); + visualRoot.Child = target; + + CreateNodeDataTemplate(target); + ApplyTemplates(target); + + var rootNode = tree[0]; + + var from = rootNode.Children[0]; + var to = rootNode.Children.Last(); + + var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); + var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); + + void ClickContainer(IControl container, InputModifiers modifiers) + { + container.RaiseEvent(new PointerPressedEventArgs + { + RoutedEvent = InputElement.PointerPressedEvent, + MouseButton = MouseButton.Left, + InputModifiers = modifiers + }); + } + + void AssertChildrenSelected() + { + foreach (var child in rootNode.Children) + { + var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(child); + + Assert.True(container.IsSelected); + } + } + + // Top to down + ClickContainer(fromContainer, InputModifiers.None); + + Assert.True(fromContainer.IsSelected); + + ClickContainer(toContainer, InputModifiers.Shift); + + AssertChildrenSelected(); + + // Down to up + target.SelectedItem = null; + + ClickContainer(toContainer, InputModifiers.None); + + Assert.True(toContainer.IsSelected); + + ClickContainer(fromContainer, InputModifiers.Shift); + + AssertChildrenSelected(); + } + [Fact] public void Setting_SelectedItem_Should_Set_Container_Selected() { @@ -186,7 +329,7 @@ namespace Avalonia.Controls.UnitTests var item = tree[0].Children[1].Children[0]; var called = false; - target.SelectedItemChanged += (s, e) => + target.SelectionChanged += (s, e) => { Assert.Empty(e.RemovedItems); Assert.Equal(1, e.AddedItems.Count); From 2c04c95a12f6ddd9eefad73219f57d966122c3bb Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 5 Mar 2019 11:35:46 +0100 Subject: [PATCH 05/19] Expose SelectedItems property. Add more tests for edge cases failing for ListBox. --- src/Avalonia.Controls/TreeView.cs | 2 +- .../TreeViewTests.cs | 171 ++++++++++++++---- 2 files changed, 140 insertions(+), 33 deletions(-) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index d8a2eb0c06..6aa45d9dc1 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -138,7 +138,7 @@ namespace Avalonia.Controls /// /// Gets the selected items. /// - protected IList SelectedItems + public IList SelectedItems { get { diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 4364af3a62..f65ef7d08e 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -11,7 +11,6 @@ using Avalonia.Data; using Avalonia.Data.Core; using Avalonia.Input; using Avalonia.LogicalTree; -using Avalonia.Markup.Data; using Avalonia.UnitTests; using Xunit; @@ -147,7 +146,7 @@ namespace Avalonia.Controls.UnitTests var target = new TreeView { Template = CreateTreeViewTemplate(), - Items = tree, + Items = tree }; var visualRoot = new TestRoot(); @@ -183,7 +182,7 @@ namespace Avalonia.Controls.UnitTests var target = new TreeView { Template = CreateTreeViewTemplate(), - Items = tree, + Items = tree }; var visualRoot = new TestRoot(); @@ -218,7 +217,46 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void Clicking_WithShiftModifier_Should_Select_Range_Of_Items() + public void Clicking_WithControlModifier_Selected_Item_Should_Deselect_And_Remove_From_SelectedItems() + { + var tree = CreateTestTreeData(); + var target = new TreeView + { + Template = CreateTreeViewTemplate(), + Items = tree, + SelectionMode = SelectionMode.Multiple + }; + + var visualRoot = new TestRoot(); + visualRoot.Child = target; + + CreateNodeDataTemplate(target); + ApplyTemplates(target); + + var rootNode = tree[0]; + + var item1 = rootNode.Children[0]; + var item2 = rootNode.Children.Last(); + + var item1Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1); + var item2Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item2); + + TreeTestHelper.ClickContainer(item1Container, InputModifiers.Control); + Assert.True(item1Container.IsSelected); + + TreeTestHelper.ClickContainer(item2Container, InputModifiers.Control); + Assert.True(item2Container.IsSelected); + + Assert.Equal(new[] {item1, item2}, target.SelectedItems.OfType()); + + TreeTestHelper.ClickContainer(item1Container, InputModifiers.Control); + Assert.False(item1Container.IsSelected); + + Assert.DoesNotContain(item1, target.SelectedItems.OfType()); + } + + [Fact] + public void Clicking_WithShiftModifier_DownDirection_Should_Select_Range_Of_Items() { var tree = CreateTestTreeData(); var target = new TreeView @@ -242,45 +280,92 @@ namespace Avalonia.Controls.UnitTests var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); - void ClickContainer(IControl container, InputModifiers modifiers) - { - container.RaiseEvent(new PointerPressedEventArgs - { - RoutedEvent = InputElement.PointerPressedEvent, - MouseButton = MouseButton.Left, - InputModifiers = modifiers - }); - } + TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None); - void AssertChildrenSelected() + Assert.True(fromContainer.IsSelected); + + TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift); + TreeTestHelper.AssertChildrenSelected(target, rootNode); + } + + [Fact] + public void Clicking_WithShiftModifier_UpDirection_Should_Select_Range_Of_Items() + { + var tree = CreateTestTreeData(); + var target = new TreeView { - foreach (var child in rootNode.Children) - { - var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(child); + Template = CreateTreeViewTemplate(), + Items = tree, + SelectionMode = SelectionMode.Multiple + }; - Assert.True(container.IsSelected); - } - } + var visualRoot = new TestRoot(); + visualRoot.Child = target; + + CreateNodeDataTemplate(target); + ApplyTemplates(target); + + var rootNode = tree[0]; + + var from = rootNode.Children.Last(); + var to = rootNode.Children[0]; - // Top to down - ClickContainer(fromContainer, InputModifiers.None); + var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); + var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); + + TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None); Assert.True(fromContainer.IsSelected); - ClickContainer(toContainer, InputModifiers.Shift); + TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift); + TreeTestHelper.AssertChildrenSelected(target, rootNode); + } - AssertChildrenSelected(); + [Fact] + public void Clicking_First_Item_Of_SelectedItems_Should_Select_Only_It() + { + var tree = CreateTestTreeData(); + var target = new TreeView + { + Template = CreateTreeViewTemplate(), + Items = tree, + SelectionMode = SelectionMode.Multiple + }; + + var visualRoot = new TestRoot(); + visualRoot.Child = target; + + CreateNodeDataTemplate(target); + ApplyTemplates(target); - // Down to up - target.SelectedItem = null; + var rootNode = tree[0]; - ClickContainer(toContainer, InputModifiers.None); + var from = rootNode.Children.Last(); + var to = rootNode.Children[0]; - Assert.True(toContainer.IsSelected); + var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); + var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); - ClickContainer(fromContainer, InputModifiers.Shift); + TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None); - AssertChildrenSelected(); + TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift); + TreeTestHelper.AssertChildrenSelected(target, rootNode); + + TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None); + + Assert.True(fromContainer.IsSelected); + + foreach (var child in rootNode.Children) + { + if (child == from) + { + continue; + } + + var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(child); + + Assert.False(container.IsSelected); + } } [Fact] @@ -309,7 +394,6 @@ namespace Avalonia.Controls.UnitTests Assert.True(container.IsSelected); } - [Fact] public void Setting_SelectedItem_Should_Raise_SelectedItemChanged_Event() { @@ -585,7 +669,7 @@ namespace Avalonia.Controls.UnitTests new Node { Value = "Child3", - }, + } } } }; @@ -658,6 +742,29 @@ namespace Avalonia.Controls.UnitTests } } + private static class TreeTestHelper + { + public static void ClickContainer(IControl container, InputModifiers modifiers) + { + container.RaiseEvent(new PointerPressedEventArgs + { + RoutedEvent = InputElement.PointerPressedEvent, + MouseButton = MouseButton.Left, + InputModifiers = modifiers + }); + } + + public static void AssertChildrenSelected(TreeView treeView, Node rootNode) + { + foreach (var child in rootNode.Children) + { + var container = (TreeViewItem)treeView.ItemContainerGenerator.Index.ContainerFromItem(child); + + Assert.True(container.IsSelected); + } + } + } + private class Node : NotifyingBase { private IAvaloniaList _children; From 5e7a5b460c1180a83be3578ac8c96ec9790b71eb Mon Sep 17 00:00:00 2001 From: artyom Date: Thu, 7 Mar 2019 01:42:25 +0300 Subject: [PATCH 06/19] Synchronize DataContext with ReactiveUI.IViewFor.ViewModel property --- src/Avalonia.ReactiveUI/RoutedViewHost.cs | 2 ++ .../RoutedViewHostTest.cs | 14 +++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.ReactiveUI/RoutedViewHost.cs b/src/Avalonia.ReactiveUI/RoutedViewHost.cs index 726e086d9c..e364d5de0b 100644 --- a/src/Avalonia.ReactiveUI/RoutedViewHost.cs +++ b/src/Avalonia.ReactiveUI/RoutedViewHost.cs @@ -163,6 +163,8 @@ namespace Avalonia this.Log().Info($"Ready to show {view} with autowired {viewModel}."); view.ViewModel = viewModel; + if (view is IStyledElement styled) + styled.DataContext = viewModel; UpdateContent(view); } diff --git a/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs index c85b999af2..de09a1ea89 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs @@ -50,7 +50,7 @@ namespace Avalonia } [Fact] - public void RoutedViewHostShouldStayInSyncWithRoutingState() + public void RoutedViewHost_Should_Stay_In_Sync_With_RoutingState() { var screen = new ScreenViewModel(); var defaultContent = new TextBlock(); @@ -71,19 +71,25 @@ namespace Avalonia Assert.Equal(typeof(TextBlock), host.Content.GetType()); Assert.Equal(defaultContent, host.Content); + var first = new FirstRoutableViewModel(); screen.Router.Navigate - .Execute(new FirstRoutableViewModel()) + .Execute(first) .Subscribe(); Assert.NotNull(host.Content); Assert.Equal(typeof(FirstRoutableView), host.Content.GetType()); + Assert.Equal(first, ((FirstRoutableView)host.Content).DataContext); + Assert.Equal(first, ((FirstRoutableView)host.Content).ViewModel); + var second = new SecondRoutableViewModel(); screen.Router.Navigate - .Execute(new SecondRoutableViewModel()) + .Execute(second) .Subscribe(); Assert.NotNull(host.Content); Assert.Equal(typeof(SecondRoutableView), host.Content.GetType()); + Assert.Equal(second, ((SecondRoutableView)host.Content).DataContext); + Assert.Equal(second, ((SecondRoutableView)host.Content).ViewModel); screen.Router.NavigateBack .Execute(Unit.Default) @@ -91,6 +97,8 @@ namespace Avalonia Assert.NotNull(host.Content); Assert.Equal(typeof(FirstRoutableView), host.Content.GetType()); + Assert.Equal(first, ((FirstRoutableView)host.Content).DataContext); + Assert.Equal(first, ((FirstRoutableView)host.Content).ViewModel); screen.Router.NavigateBack .Execute(Unit.Default) From 0116a9ba23a9164bdb288dbc64b0bd9e5253d30b Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 7 Mar 2019 11:09:51 +0100 Subject: [PATCH 07/19] Fix TreeViewPage indent. --- samples/ControlCatalog/Pages/TreeViewPage.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/TreeViewPage.xaml b/samples/ControlCatalog/Pages/TreeViewPage.xaml index 3392572cd3..c03edb8b03 100644 --- a/samples/ControlCatalog/Pages/TreeViewPage.xaml +++ b/samples/ControlCatalog/Pages/TreeViewPage.xaml @@ -9,7 +9,7 @@ Margin="0,16,0,0" HorizontalAlignment="Center" Spacing="16"> - + From c9d8fa6a9d637370bc9e3470e4e674be80dc39b9 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 7 Mar 2019 11:13:29 +0100 Subject: [PATCH 08/19] Move SynchronizeItems about private methods. --- .../Primitives/SelectingItemsControl.cs | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index d21211371e..d68f90cc4f 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -614,26 +614,6 @@ namespace Avalonia.Controls.Primitives return false; } - /// - /// Gets a range of items from an IEnumerable. - /// - /// The items. - /// The index of the first item. - /// The index of the last item. - /// The items. - private static IEnumerable GetRange(IEnumerable items, int first, int last) - { - var list = (items as IList) ?? items.Cast().ToList(); - int step = first > last ? -1 : 1; - - for (int i = first; i != last; i += step) - { - yield return list[i]; - } - - yield return list[last]; - } - /// /// Makes a list of objects equal another. /// @@ -666,6 +646,26 @@ namespace Avalonia.Controls.Primitives } } + /// + /// Gets a range of items from an IEnumerable. + /// + /// The items. + /// The index of the first item. + /// The index of the last item. + /// The items. + private static IEnumerable GetRange(IEnumerable items, int first, int last) + { + var list = (items as IList) ?? items.Cast().ToList(); + int step = first > last ? -1 : 1; + + for (int i = first; i != last; i += step) + { + yield return list[i]; + } + + yield return list[last]; + } + /// /// Called when a container raises the . /// From 5822e60db615148be9ffb2d71bb20f1e77ce1f01 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 7 Mar 2019 11:41:42 +0100 Subject: [PATCH 09/19] Fix ternary operator formatting, add config entry to the .editorconfig. --- .editorconfig | 3 +++ src/Avalonia.Controls/TreeView.cs | 12 ++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.editorconfig b/.editorconfig index b7a03207a4..5f08d1e940 100644 --- a/.editorconfig +++ b/.editorconfig @@ -132,6 +132,9 @@ csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_between_square_brackets = false +# Wrapping preferences +csharp_wrap_before_ternary_opsigns = false + # Xaml files [*.xaml] indent_size = 4 diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 6aa45d9dc1..19e09b4339 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -340,9 +340,9 @@ namespace Avalonia.Controls { if (!this.IsVisualAncestorOf(element)) { - IControl result = _selectedItem != null - ? ItemContainerGenerator.Index.ContainerFromItem(_selectedItem) - : ItemContainerGenerator.ContainerFromIndex(0); + IControl result = _selectedItem != null ? + ItemContainerGenerator.Index.ContainerFromItem(_selectedItem) : + ItemContainerGenerator.ContainerFromIndex(0); return (true, result); } @@ -426,9 +426,9 @@ namespace Avalonia.Controls if (index > 0) { var previous = (TreeViewItem)parentGenerator.ContainerFromIndex(index - 1); - result = previous.IsExpanded - ? (TreeViewItem)previous.ItemContainerGenerator.ContainerFromIndex(previous.ItemCount - 1) - : previous; + result = previous.IsExpanded ? + (TreeViewItem)previous.ItemContainerGenerator.ContainerFromIndex(previous.ItemCount - 1) : + previous; } else { From c3480fb84033fd179d86e1dfe85b924877562658 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 7 Mar 2019 11:49:22 +0100 Subject: [PATCH 10/19] Remove TreeViewHelper and shorten the code so the extra struct is not needed. --- src/Avalonia.Controls/TreeView.cs | 50 +++++++++++++- src/Avalonia.Controls/TreeViewHelper.cs | 90 ------------------------- 2 files changed, 49 insertions(+), 91 deletions(-) delete mode 100644 src/Avalonia.Controls/TreeViewHelper.cs diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 19e09b4339..db8de49494 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -554,6 +554,54 @@ namespace Avalonia.Controls } } + /// + /// Find which node is first in hierarchy. + /// + /// Search root. + /// Nodes to find. + /// Node to find. + /// Found first node. + private static TreeViewItem FindFirstNode(TreeView treeView, TreeViewItem nodeA, TreeViewItem nodeB) + { + return FindInContainers(treeView.ItemContainerGenerator, nodeA, nodeB); + } + + private static TreeViewItem FindInContainers(ITreeItemContainerGenerator containerGenerator, + TreeViewItem nodeA, + TreeViewItem nodeB) + { + IEnumerable containers = containerGenerator.Containers; + + foreach (ItemContainerInfo container in containers) + { + TreeViewItem node = FindFirstNode(container.ContainerControl as TreeViewItem, nodeA, nodeB); + + if (node != null) + { + return node; + } + } + + return null; + } + + private static TreeViewItem FindFirstNode(TreeViewItem node, TreeViewItem nodeA, TreeViewItem nodeB) + { + if (node == null) + { + return null; + } + + TreeViewItem match = node == nodeA ? nodeA : node == nodeB ? nodeB : null; + + if (match != null) + { + return match; + } + + return FindInContainers(node.ItemContainerGenerator, nodeA, nodeB); + } + /// /// Returns all items that belong to containers between and . /// The range is inclusive. @@ -569,7 +617,7 @@ namespace Avalonia.Controls return items; } - TreeViewItem firstItem = TreeViewHelper.FindFirstNode(this, new TreeViewHelper.SearchInfo(from, to)); + TreeViewItem firstItem = FindFirstNode(this, from, to); if (firstItem == null) { diff --git a/src/Avalonia.Controls/TreeViewHelper.cs b/src/Avalonia.Controls/TreeViewHelper.cs deleted file mode 100644 index b2d43185cb..0000000000 --- a/src/Avalonia.Controls/TreeViewHelper.cs +++ /dev/null @@ -1,90 +0,0 @@ -// 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.Collections.Generic; -using Avalonia.Controls.Generators; - -namespace Avalonia.Controls -{ - /// - /// Helper for related operations. - /// - internal static class TreeViewHelper - { - /// - /// Find which node from search info is first in hierarchy. - /// - /// Search root. - /// Nodes to search for. - /// Found first node. - public static TreeViewItem FindFirstNode(TreeView treeView, in SearchInfo searchInfo) - { - return FindInContainers(treeView.ItemContainerGenerator, in searchInfo); - } - - private static TreeViewItem FindInContainers(ITreeItemContainerGenerator containerGenerator, - in SearchInfo searchInfo) - { - IEnumerable containers = containerGenerator.Containers; - - foreach (ItemContainerInfo container in containers) - { - TreeViewItem node = FindFirstNode(container.ContainerControl as TreeViewItem, in searchInfo); - - if (node != null) - { - return node; - } - } - - return null; - } - - private static TreeViewItem FindFirstNode(TreeViewItem node, in SearchInfo searchInfo) - { - if (node == null) - { - return null; - } - - TreeViewItem match = searchInfo.GetMatch(node); - - if (match != null) - { - return match; - } - - return FindInContainers(node.ItemContainerGenerator, in searchInfo); - } - - /// - /// Node search info. - /// - public readonly struct SearchInfo - { - public readonly TreeViewItem Search1; - public readonly TreeViewItem Search2; - - public SearchInfo(TreeViewItem search1, TreeViewItem search2) - { - Search1 = search1; - Search2 = search2; - } - - public TreeViewItem GetMatch(TreeViewItem candidate) - { - if (candidate == Search1) - { - return Search1; - } - - if (candidate == Search2) - { - return Search2; - } - - return null; - } - } - } -} From e3d7d477538bec919957c92498589bf189929a2e Mon Sep 17 00:00:00 2001 From: MonkAlex <3amepob@gmail.com> Date: Fri, 8 Mar 2019 18:43:54 +0400 Subject: [PATCH 11/19] #2355 Ctrl+A hotkey for SelectingItemsControl --- .../Primitives/SelectingItemsControl.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index a54bb502c0..f5dcbf9186 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -459,6 +459,23 @@ namespace Avalonia.Controls.Primitives } } + /// + /// Handles 'select all' (Ctrl+A) in . + /// + /// The key events. + protected override void OnKeyDown(KeyEventArgs e) + { + base.OnKeyDown(e); + + if (!e.Handled) + { + if (this.SelectionMode == SelectionMode.Multiple && e.Key == Key.A && e.Modifiers == InputModifiers.Control) + { + SynchronizeItems(SelectedItems, Items?.Cast()); + } + } + } + /// /// Moves the selection in the specified direction relative to the current selection. /// From e38e49e7d0fa69832b198ac375e4d152c39291d0 Mon Sep 17 00:00:00 2001 From: MonkAlex <3amepob@gmail.com> Date: Sat, 9 Mar 2019 23:40:01 +0400 Subject: [PATCH 12/19] #2355 'Select all' for SelectingItemsControl with platform hotkey configuration. --- src/Avalonia.Controls/Primitives/SelectingItemsControl.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index f5dcbf9186..e868f39fce 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -10,6 +10,7 @@ using Avalonia.Collections; using Avalonia.Controls.Generators; using Avalonia.Data; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Styling; using Avalonia.VisualTree; @@ -469,9 +470,13 @@ namespace Avalonia.Controls.Primitives if (!e.Handled) { - if (this.SelectionMode == SelectionMode.Multiple && e.Key == Key.A && e.Modifiers == InputModifiers.Control) + var keymap = AvaloniaLocator.Current.GetService(); + bool Match(List gestures) => gestures.Any(g => g.Matches(e)); + + if (this.SelectionMode == SelectionMode.Multiple && Match(keymap.SelectAll)) { SynchronizeItems(SelectedItems, Items?.Cast()); + e.Handled = true; } } } From 57dcfadf57175590313c136ed8bf8d96e0e8ff8d Mon Sep 17 00:00:00 2001 From: MonkAlex <3amepob@gmail.com> Date: Sun, 10 Mar 2019 00:40:48 +0400 Subject: [PATCH 13/19] #2355 'Select all' support TreeView too, only visible part of tree. --- src/Avalonia.Controls/TreeView.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index db8de49494..141a151bdb 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -10,6 +10,7 @@ using Avalonia.Collections; using Avalonia.Controls.Generators; using Avalonia.Controls.Primitives; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Threading; using Avalonia.VisualTree; @@ -402,6 +403,19 @@ namespace Avalonia.Controls SelectedItem = ElementAt(Items, 0); } } + + if (!e.Handled) + { + var keymap = AvaloniaLocator.Current.GetService(); + bool Match(List gestures) => gestures.Any(g => g.Matches(e)); + + if (this.SelectionMode == SelectionMode.Multiple && Match(keymap.SelectAll)) + { + var allVisibleItems = ItemContainerGenerator.Index.Items.Select(ItemContainerGenerator.Index.ItemFromContainer); + SelectingItemsControl.SynchronizeItems(SelectedItems, allVisibleItems); + e.Handled = true; + } + } } private TreeViewItem GetContainerInDirection( From 33a6ae8215ce81b45d95cd0b6254b969aabd4ee0 Mon Sep 17 00:00:00 2001 From: MonkAlex <3amepob@gmail.com> Date: Sun, 10 Mar 2019 00:55:29 +0400 Subject: [PATCH 14/19] #2355 Direct access for treeview index values when select all. --- src/Avalonia.Controls/Generators/TreeContainerIndex.cs | 5 +++++ src/Avalonia.Controls/TreeView.cs | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Generators/TreeContainerIndex.cs b/src/Avalonia.Controls/Generators/TreeContainerIndex.cs index 24b3fc1f32..0a65dd9ff1 100644 --- a/src/Avalonia.Controls/Generators/TreeContainerIndex.cs +++ b/src/Avalonia.Controls/Generators/TreeContainerIndex.cs @@ -36,6 +36,11 @@ namespace Avalonia.Controls.Generators /// public IEnumerable Items => _containerToItem.Keys; + /// + /// Gets the items of currently materialized containers. + /// + public IEnumerable Objects => _containerToItem.Values; + /// /// Adds an entry to the index. /// diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 141a151bdb..abd9bc4232 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -411,8 +411,7 @@ namespace Avalonia.Controls if (this.SelectionMode == SelectionMode.Multiple && Match(keymap.SelectAll)) { - var allVisibleItems = ItemContainerGenerator.Index.Items.Select(ItemContainerGenerator.Index.ItemFromContainer); - SelectingItemsControl.SynchronizeItems(SelectedItems, allVisibleItems); + SelectingItemsControl.SynchronizeItems(SelectedItems, ItemContainerGenerator.Index.Objects); e.Handled = true; } } From 235cc660fd2cc97fc3ac922cd8a81f8548e628fa Mon Sep 17 00:00:00 2001 From: MonkAlex <3amepob@gmail.com> Date: Sun, 10 Mar 2019 11:27:51 +0400 Subject: [PATCH 15/19] #1052 Rename TreeContainerIndex.Items to Containers --- src/Avalonia.Controls/Generators/TreeContainerIndex.cs | 2 +- src/Avalonia.Controls/TreeView.cs | 2 +- tests/Avalonia.Controls.UnitTests/TreeViewTests.cs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/Generators/TreeContainerIndex.cs b/src/Avalonia.Controls/Generators/TreeContainerIndex.cs index 0a65dd9ff1..c831b7985c 100644 --- a/src/Avalonia.Controls/Generators/TreeContainerIndex.cs +++ b/src/Avalonia.Controls/Generators/TreeContainerIndex.cs @@ -34,7 +34,7 @@ namespace Avalonia.Controls.Generators /// /// Gets the currently materialized containers. /// - public IEnumerable Items => _containerToItem.Keys; + public IEnumerable Containers => _containerToItem.Keys; /// /// Gets the items of currently materialized containers. diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index abd9bc4232..8236c11983 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -245,7 +245,7 @@ namespace Avalonia.Controls break; case NotifyCollectionChangedAction.Reset: - foreach (IControl container in ItemContainerGenerator.Index.Items) + foreach (IControl container in ItemContainerGenerator.Index.Containers) { MarkContainerSelected(container, false); } diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index f65ef7d08e..519872f9f2 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -54,7 +54,7 @@ namespace Avalonia.Controls.UnitTests ApplyTemplates(target); - var items = target.ItemContainerGenerator.Index.Items + var items = target.ItemContainerGenerator.Index.Containers .OfType() .ToList(); @@ -463,11 +463,11 @@ namespace Avalonia.Controls.UnitTests CreateNodeDataTemplate(target); ApplyTemplates(target); - Assert.Equal(5, target.ItemContainerGenerator.Index.Items.Count()); + Assert.Equal(5, target.ItemContainerGenerator.Index.Containers.Count()); tree[0].Children.RemoveAt(1); - Assert.Equal(3, target.ItemContainerGenerator.Index.Items.Count()); + Assert.Equal(3, target.ItemContainerGenerator.Index.Containers.Count()); } [Fact] From 10cb451ad004f34f740e6fae6950b1a16987befc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Komosi=C5=84ski?= Date: Sun, 10 Mar 2019 17:28:42 +0100 Subject: [PATCH 16/19] Add Equals implementation to the TransformedBounds --- .../VisualTree/TransformedBounds.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/Avalonia.Visuals/VisualTree/TransformedBounds.cs b/src/Avalonia.Visuals/VisualTree/TransformedBounds.cs index da21a416e1..39b328adc2 100644 --- a/src/Avalonia.Visuals/VisualTree/TransformedBounds.cs +++ b/src/Avalonia.Visuals/VisualTree/TransformedBounds.cs @@ -50,5 +50,41 @@ namespace Avalonia.VisualTree return Bounds.Contains(point); } } + + public bool Equals(TransformedBounds other) + { + return Bounds == other.Bounds && Clip == other.Clip && Transform == other.Transform; + } + + public override bool Equals(object obj) + { + if (obj is null) + { + return false; + } + + return obj is TransformedBounds other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = Bounds.GetHashCode(); + hashCode = (hashCode * 397) ^ Clip.GetHashCode(); + hashCode = (hashCode * 397) ^ Transform.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(TransformedBounds left, TransformedBounds right) + { + return left.Equals(right); + } + + public static bool operator !=(TransformedBounds left, TransformedBounds right) + { + return !left.Equals(right); + } } } From 1c460061cc65a9941412432cc240440dbcc0008f Mon Sep 17 00:00:00 2001 From: MonkAlex <3amepob@gmail.com> Date: Mon, 11 Mar 2019 19:26:56 +0400 Subject: [PATCH 17/19] #2355 Remove useless xmldoc. --- src/Avalonia.Controls/Primitives/SelectingItemsControl.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index e868f39fce..a64dbe0546 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -460,10 +460,6 @@ namespace Avalonia.Controls.Primitives } } - /// - /// Handles 'select all' (Ctrl+A) in . - /// - /// The key events. protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); From 1114df5d453838326d493386e2ae9aed2b077b86 Mon Sep 17 00:00:00 2001 From: MonkAlex <3amepob@gmail.com> Date: Mon, 11 Mar 2019 22:42:54 +0400 Subject: [PATCH 18/19] #2356 Small rename Objects to Items --- src/Avalonia.Controls/Generators/TreeContainerIndex.cs | 2 +- src/Avalonia.Controls/TreeView.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Generators/TreeContainerIndex.cs b/src/Avalonia.Controls/Generators/TreeContainerIndex.cs index c831b7985c..0da84008f6 100644 --- a/src/Avalonia.Controls/Generators/TreeContainerIndex.cs +++ b/src/Avalonia.Controls/Generators/TreeContainerIndex.cs @@ -39,7 +39,7 @@ namespace Avalonia.Controls.Generators /// /// Gets the items of currently materialized containers. /// - public IEnumerable Objects => _containerToItem.Values; + public IEnumerable Items => _containerToItem.Values; /// /// Adds an entry to the index. diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 8236c11983..94989254dc 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -411,7 +411,7 @@ namespace Avalonia.Controls if (this.SelectionMode == SelectionMode.Multiple && Match(keymap.SelectAll)) { - SelectingItemsControl.SynchronizeItems(SelectedItems, ItemContainerGenerator.Index.Objects); + SelectingItemsControl.SynchronizeItems(SelectedItems, ItemContainerGenerator.Index.Items); e.Handled = true; } } From f9b263997f8640ed517dfc1dde2409f5acaf9c97 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 15 Mar 2019 22:21:09 +0100 Subject: [PATCH 19/19] Don't override UserControl.StyleKey. Instead of pinning `UserControl.StyleKey` at `typeof(UserControl)`, make the `UserControl` default template selector `:is(UserControl)`. This will allow controls derived from `UserControl` to be more easily styled. --- src/Avalonia.Controls/UserControl.cs | 3 --- src/Avalonia.Themes.Default/UserControl.xaml | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/UserControl.cs b/src/Avalonia.Controls/UserControl.cs index 3f51f613a4..e42ca5e0e6 100644 --- a/src/Avalonia.Controls/UserControl.cs +++ b/src/Avalonia.Controls/UserControl.cs @@ -27,9 +27,6 @@ namespace Avalonia.Controls remove { _nameScope.Unregistered -= value; } } - /// - Type IStyleable.StyleKey => typeof(UserControl); - /// void INameScope.Register(string name, object element) { diff --git a/src/Avalonia.Themes.Default/UserControl.xaml b/src/Avalonia.Themes.Default/UserControl.xaml index 2bf5f19698..f4d0c21367 100644 --- a/src/Avalonia.Themes.Default/UserControl.xaml +++ b/src/Avalonia.Themes.Default/UserControl.xaml @@ -1,4 +1,4 @@ -