diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml b/samples/ControlCatalog/Pages/ListBoxPage.xaml index 49e9aafc4a..b1b3112e60 100644 --- a/samples/ControlCatalog/Pages/ListBoxPage.xaml +++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml @@ -10,12 +10,14 @@ HorizontalAlignment="Center" Spacing="16"> - + + + Single Multiple diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml.cs b/samples/ControlCatalog/Pages/ListBoxPage.xaml.cs index 8a67766c76..cdbf8fd2b6 100644 --- a/samples/ControlCatalog/Pages/ListBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.ObjectModel; using System.Linq; using System.Reactive; @@ -27,7 +28,7 @@ namespace ControlCatalog.Pages public PageViewModel() { - Items = new ObservableCollection(Enumerable.Range(1, 10).Select(i => GenerateItem())); + Items = new ObservableCollection(Enumerable.Range(1, 10000).Select(i => GenerateItem())); SelectedItems = new ObservableCollection(); AddItemCommand = ReactiveCommand.Create(() => Items.Add(GenerateItem())); @@ -39,16 +40,34 @@ namespace ControlCatalog.Pages Items.Remove(SelectedItems[0]); } }); + + SelectRandomItemCommand = ReactiveCommand.Create(() => + { + var random = new Random(); + + SelectedItem = Items[random.Next(Items.Count - 1)]; + }); } public ObservableCollection Items { get; } + private string _selectedItem; + + public string SelectedItem + { + get { return _selectedItem; } + set { this.RaiseAndSetIfChanged(ref _selectedItem, value); } + } + + public ObservableCollection SelectedItems { get; } public ReactiveCommand AddItemCommand { get; } public ReactiveCommand RemoveItemCommand { get; } + public ReactiveCommand SelectRandomItemCommand { get; } + public SelectionMode SelectionMode { get => _selectionMode; diff --git a/src/Avalonia.Base/Utilities/TypeUtilities.cs b/src/Avalonia.Base/Utilities/TypeUtilities.cs index 7b76457049..d85eb4cd76 100644 --- a/src/Avalonia.Base/Utilities/TypeUtilities.cs +++ b/src/Avalonia.Base/Utilities/TypeUtilities.cs @@ -13,7 +13,7 @@ namespace Avalonia.Utilities /// public static class TypeUtilities { - private static int[] Conversions = + private static readonly int[] Conversions = { 0b101111111111101, // Boolean 0b100001111111110, // Char @@ -32,7 +32,7 @@ namespace Avalonia.Utilities 0b111111111111111, // String }; - private static int[] ImplicitConversions = + private static readonly int[] ImplicitConversions = { 0b000000000000001, // Boolean 0b001110111100010, // Char @@ -51,7 +51,7 @@ namespace Avalonia.Utilities 0b100000000000000, // String }; - private static Type[] InbuiltTypes = + private static readonly Type[] InbuiltTypes = { typeof(Boolean), typeof(Char), @@ -70,7 +70,7 @@ namespace Avalonia.Utilities typeof(String), }; - private static readonly Type[] NumericTypes = new[] + private static readonly Type[] NumericTypes = { typeof(Byte), typeof(Decimal), @@ -188,8 +188,7 @@ namespace Avalonia.Utilities } } - var cast = from.GetRuntimeMethods() - .FirstOrDefault(m => (m.Name == "op_Implicit" || m.Name == "op_Explicit") && m.ReturnType == to); + var cast = FindTypeConversionOperatorMethod(from, to, OperatorType.Implicit | OperatorType.Explicit); if (cast != null) { @@ -253,8 +252,7 @@ namespace Avalonia.Utilities } } - var cast = from.GetRuntimeMethods() - .FirstOrDefault(m => m.Name == "op_Implicit" && m.ReturnType == to); + var cast = FindTypeConversionOperatorMethod(from, to, OperatorType.Implicit); if (cast != null) { @@ -335,5 +333,41 @@ namespace Avalonia.Utilities return NumericTypes.Contains(type); } } + + [Flags] + private enum OperatorType + { + Implicit = 1, + Explicit = 2 + } + + private static MethodInfo FindTypeConversionOperatorMethod(Type fromType, Type toType, OperatorType operatorType) + { + const string implicitName = "op_Implicit"; + const string explicitName = "op_Explicit"; + + bool allowImplicit = (operatorType & OperatorType.Implicit) != 0; + bool allowExplicit = (operatorType & OperatorType.Explicit) != 0; + + foreach (MethodInfo method in fromType.GetMethods()) + { + if (!method.IsSpecialName || method.ReturnType != toType) + { + continue; + } + + if (allowImplicit && method.Name == implicitName) + { + return method; + } + + if (allowExplicit && method.Name == explicitName) + { + return method; + } + } + + return null; + } } } diff --git a/src/Avalonia.Controls/ContentControl.cs b/src/Avalonia.Controls/ContentControl.cs index 16f17ae1bd..02d7890404 100644 --- a/src/Avalonia.Controls/ContentControl.cs +++ b/src/Avalonia.Controls/ContentControl.cs @@ -1,11 +1,13 @@ // 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 Avalonia.Collections; using Avalonia.Controls.Mixins; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Layout; +using Avalonia.LogicalTree; using Avalonia.Metadata; namespace Avalonia.Controls @@ -39,12 +41,9 @@ namespace Avalonia.Controls public static readonly StyledProperty VerticalContentAlignmentProperty = AvaloniaProperty.Register(nameof(VerticalContentAlignment)); - /// - /// Initializes static members of the class. - /// static ContentControl() { - ContentControlMixin.Attach(ContentProperty, x => x.LogicalChildren); + ContentProperty.Changed.AddClassHandler(x => x.ContentChanged); } /// @@ -95,20 +94,39 @@ namespace Avalonia.Controls } /// - void IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter) + IAvaloniaList IContentPresenterHost.LogicalChildren => LogicalChildren; + + /// + bool IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter) { - RegisterContentPresenter(presenter); + return RegisterContentPresenter(presenter); } /// /// Called when an is registered with the control. /// /// The presenter. - protected virtual void RegisterContentPresenter(IContentPresenter presenter) + protected virtual bool RegisterContentPresenter(IContentPresenter presenter) { if (presenter.Name == "PART_ContentPresenter") { Presenter = presenter; + return true; + } + + return false; + } + + private void ContentChanged(AvaloniaPropertyChangedEventArgs e) + { + if (e.OldValue is ILogical oldChild) + { + LogicalChildren.Remove(oldChild); + } + + if (e.NewValue is ILogical newChild) + { + LogicalChildren.Add(newChild); } } } diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 902e55bde6..0fe7291835 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -302,19 +302,6 @@ namespace Avalonia.Controls /// The details of the containers. protected virtual void OnContainersRecycled(ItemContainerEventArgs e) { - var toRemove = new List(); - - foreach (var container in e.Containers) - { - // If the item is its own container, then it will be removed from the logical tree - // when it is removed from the Items collection. - if (container?.ContainerControl != container?.Item) - { - toRemove.Add(container.ContainerControl); - } - } - - LogicalChildren.RemoveAll(toRemove); } /// diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs index f26cd47bcb..449ca18465 100644 --- a/src/Avalonia.Controls/ListBox.cs +++ b/src/Avalonia.Controls/ListBox.cs @@ -66,7 +66,11 @@ namespace Avalonia.Controls } /// - public new IList SelectedItems => base.SelectedItems; + public new IList SelectedItems + { + get => base.SelectedItems; + set => base.SelectedItems = value; + } /// /// Gets or sets the selection mode. diff --git a/src/Avalonia.Controls/Mixins/ContentControlMixin.cs b/src/Avalonia.Controls/Mixins/ContentControlMixin.cs deleted file mode 100644 index b826fb982e..0000000000 --- a/src/Avalonia.Controls/Mixins/ContentControlMixin.cs +++ /dev/null @@ -1,166 +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; -using System.Linq; -using System.Reactive.Disposables; -using System.Runtime.CompilerServices; -using Avalonia.Collections; -using Avalonia.Controls.Presenters; -using Avalonia.Controls.Primitives; -using Avalonia.Interactivity; -using Avalonia.LogicalTree; - -namespace Avalonia.Controls.Mixins -{ - /// - /// Adds content control functionality to control classes. - /// - /// - /// The adds behavior to a control which acts as a content - /// control such as and . It - /// keeps the control's logical children in sync with the content being displayed by the - /// control. - /// - public class ContentControlMixin - { - private static Lazy> subscriptions = - new Lazy>(() => - new ConditionalWeakTable()); - - /// - /// Initializes a new instance of the class. - /// - /// The control type. - /// The content property. - /// - /// Given an control of should return the control's - /// logical children collection. - /// - /// - /// The name of the content presenter in the control's template. - /// - public static void Attach( - AvaloniaProperty content, - Func> logicalChildrenSelector, - string presenterName = "PART_ContentPresenter") - where TControl : TemplatedControl - { - Contract.Requires(content != null); - Contract.Requires(logicalChildrenSelector != null); - - void ChildChanging(object s, AvaloniaPropertyChangedEventArgs e) - { - if (s is IControl sender && sender?.TemplatedParent is TControl parent) - { - UpdateLogicalChild( - sender, - logicalChildrenSelector(parent), - e.OldValue, - null); - } - } - - void TemplateApplied(object s, RoutedEventArgs ev) - { - if (s is TControl sender) - { - var e = (TemplateAppliedEventArgs)ev; - var presenter = e.NameScope.Find(presenterName) as IContentPresenter; - - if (presenter != null) - { - presenter.ApplyTemplate(); - - var logicalChildren = logicalChildrenSelector(sender); - var subscription = new CompositeDisposable(); - - presenter.ChildChanging += ChildChanging; - subscription.Add(Disposable.Create(() => presenter.ChildChanging -= ChildChanging)); - - subscription.Add(presenter - .GetPropertyChangedObservable(ContentPresenter.ChildProperty) - .Subscribe(c => UpdateLogicalChild( - sender, - logicalChildren, - null, - c.NewValue))); - - UpdateLogicalChild( - sender, - logicalChildren, - null, - presenter.GetValue(ContentPresenter.ChildProperty)); - - if (subscriptions.Value.TryGetValue(sender, out IDisposable previousSubscription)) - { - subscription = new CompositeDisposable(previousSubscription, subscription); - subscriptions.Value.Remove(sender); - } - - subscriptions.Value.Add(sender, subscription); - } - } - } - - TemplatedControl.TemplateAppliedEvent.AddClassHandler( - typeof(TControl), - TemplateApplied, - RoutingStrategies.Direct); - - content.Changed.Subscribe(e => - { - if (e.Sender is TControl sender) - { - var logicalChildren = logicalChildrenSelector(sender); - UpdateLogicalChild(sender, logicalChildren, e.OldValue, e.NewValue); - } - }); - - Control.TemplatedParentProperty.Changed.Subscribe(e => - { - if (e.Sender is TControl sender) - { - var logicalChild = logicalChildrenSelector(sender).FirstOrDefault() as IControl; - logicalChild?.SetValue(Control.TemplatedParentProperty, sender.TemplatedParent); - } - }); - - TemplatedControl.TemplateProperty.Changed.Subscribe(e => - { - if (e.Sender is TControl sender) - { - if (subscriptions.Value.TryGetValue(sender, out IDisposable subscription)) - { - subscription.Dispose(); - subscriptions.Value.Remove(sender); - } - } - }); - } - - private static void UpdateLogicalChild( - IControl control, - IAvaloniaList logicalChildren, - object oldValue, - object newValue) - { - if (oldValue != newValue) - { - if (oldValue is IControl child) - { - logicalChildren.Remove(child); - ((ISetInheritanceParent)child).SetParent(child.Parent); - } - - child = newValue as IControl; - - if (child != null && !logicalChildren.Contains(child)) - { - child.SetValue(Control.TemplatedParentProperty, control.TemplatedParent); - logicalChildren.Add(child); - } - } - } - } -} diff --git a/src/Avalonia.Controls/Notifications/ReversibleStackPanel.cs b/src/Avalonia.Controls/Notifications/ReversibleStackPanel.cs index 768d87c63e..9edf9848fd 100644 --- a/src/Avalonia.Controls/Notifications/ReversibleStackPanel.cs +++ b/src/Avalonia.Controls/Notifications/ReversibleStackPanel.cs @@ -39,6 +39,11 @@ namespace Avalonia.Controls foreach (Control child in children) { + if (!child.IsVisible) + { + continue; + } + double childWidth = child.DesiredSize.Width; double childHeight = child.DesiredSize.Height; diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index b0dfa4185e..98f925cd0c 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -377,7 +377,7 @@ namespace Avalonia.Controls.Platform if (mouse?.Type == RawPointerEventType.NonClientLeftButtonDown) { - Menu.Close(); + Menu?.Close(); } } diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index 1072b21b1b..a5374e7c5a 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -6,6 +6,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Controls.Utils; using Avalonia.Data; +using Avalonia.Input; using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Media; @@ -83,7 +84,6 @@ namespace Avalonia.Controls.Presenters private IControl _child; private bool _createdChild; - EventHandler _childChanging; private IDataTemplate _dataTemplate; private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper(); @@ -190,12 +190,10 @@ namespace Avalonia.Controls.Presenters set { SetValue(PaddingProperty, value); } } - /// - event EventHandler IContentPresenter.ChildChanging - { - add => _childChanging += value; - remove => _childChanging -= value; - } + /// + /// Gets the host content control. + /// + internal IContentPresenterHost Host { get; private set; } /// public sealed override void ApplyTemplate() @@ -222,34 +220,16 @@ namespace Avalonia.Controls.Presenters var content = Content; var oldChild = Child; var newChild = CreateChild(); + var logicalChildren = Host?.LogicalChildren ?? LogicalChildren; // Remove the old child if we're not recycling it. if (newChild != oldChild) { + if (oldChild != null) { VisualChildren.Remove(oldChild); - } - - if (oldChild?.Parent == this) - { - // If we're the child's parent then the presenter isn't in a ContentControl's - // template. - LogicalChildren.Remove(oldChild); - } - else if (TemplatedParent != null) - { - // If we're in a ContentControl's template then invoke ChildChanging to let - // ContentControlMixin handle removing the logical child. - _childChanging?.Invoke(this, new AvaloniaPropertyChangedEventArgs( - this, - ChildProperty, - oldChild, - newChild, - BindingPriority.LocalValue)); - } - else if (oldChild != null) - { + logicalChildren.Remove(oldChild); ((ISetInheritanceParent)oldChild).SetParent(oldChild.Parent); } } @@ -272,15 +252,11 @@ namespace Avalonia.Controls.Presenters else if (newChild != oldChild) { ((ISetInheritanceParent)newChild).SetParent(this); - Child = newChild; - // If we're in a ContentControl's template then the child's parent will have been - // set by ContentControlMixin in response to Child changing. If not, then we're - // standalone and should make the control our own logical child. - if (newChild.Parent == null && TemplatedParent == null) + if (!logicalChildren.Contains(newChild)) { - LogicalChildren.Add(newChild); + logicalChildren.Add(newChild); } VisualChildren.Add(newChild); @@ -459,7 +435,8 @@ namespace Avalonia.Controls.Presenters private void TemplatedParentChanged(AvaloniaPropertyChangedEventArgs e) { - (e.NewValue as IContentPresenterHost)?.RegisterContentPresenter(this); + var host = e.NewValue as IContentPresenterHost; + Host = host?.RegisterContentPresenter(this) == true ? host : null; } } } diff --git a/src/Avalonia.Controls/Presenters/IContentPresenter.cs b/src/Avalonia.Controls/Presenters/IContentPresenter.cs index 78bffec93b..31ab3a21a6 100644 --- a/src/Avalonia.Controls/Presenters/IContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/IContentPresenter.cs @@ -1,8 +1,6 @@ // 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 Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; namespace Avalonia.Controls.Presenters @@ -22,16 +20,5 @@ namespace Avalonia.Controls.Presenters /// Gets or sets the content to be displayed by the presenter. /// object Content { get; set; } - - /// - /// Raised when property is about to change. - /// - /// - /// This event should be raised after the child has been removed from the visual tree, - /// but before the property has changed. It is intended for consumption - /// by in order to update the host control's logical - /// children. - /// - event EventHandler ChildChanging; } } diff --git a/src/Avalonia.Controls/Presenters/IContentPresenterHost.cs b/src/Avalonia.Controls/Presenters/IContentPresenterHost.cs index 3aa7e625ed..4acfba2c71 100644 --- a/src/Avalonia.Controls/Presenters/IContentPresenterHost.cs +++ b/src/Avalonia.Controls/Presenters/IContentPresenterHost.cs @@ -1,6 +1,8 @@ // 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 Avalonia.Collections; +using Avalonia.LogicalTree; using Avalonia.Styling; namespace Avalonia.Controls.Presenters @@ -18,10 +20,19 @@ namespace Avalonia.Controls.Presenters /// public interface IContentPresenterHost : ITemplatedControl { + /// + /// Gets a collection describing the logical children of the host control. + /// + IAvaloniaList LogicalChildren { get; } + /// /// Registers an with a host control. /// /// The content presenter. - void RegisterContentPresenter(IContentPresenter presenter); + /// + /// True if the content presenter should add its child to the logical children of the + /// host; otherwise false. + /// + bool RegisterContentPresenter(IContentPresenter presenter); } } diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs index b8b8094582..cd14211075 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs @@ -295,11 +295,14 @@ namespace Avalonia.Controls.Presenters /// public override void ScrollIntoView(object item) { - var index = Items.IndexOf(item); - - if (index != -1) + if (Items != null) { - ScrollIntoView(index); + var index = Items.IndexOf(item); + + if (index != -1) + { + ScrollIntoView(index); + } } } diff --git a/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs b/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs index 98476c9c94..3cf50a7b80 100644 --- a/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs +++ b/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs @@ -4,6 +4,7 @@ using Avalonia.Controls.Mixins; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; +using Avalonia.LogicalTree; namespace Avalonia.Controls.Primitives { @@ -29,10 +30,7 @@ namespace Avalonia.Controls.Primitives /// static HeaderedContentControl() { - ContentControlMixin.Attach( - HeaderProperty, - x => x.LogicalChildren, - "PART_HeaderPresenter"); + ContentProperty.Changed.AddClassHandler(x => x.HeaderChanged); } /// @@ -63,13 +61,29 @@ namespace Avalonia.Controls.Primitives } /// - protected override void RegisterContentPresenter(IContentPresenter presenter) + protected override bool RegisterContentPresenter(IContentPresenter presenter) { - base.RegisterContentPresenter(presenter); + var result = base.RegisterContentPresenter(presenter); if (presenter.Name == "PART_HeaderPresenter") { HeaderPresenter = presenter; + result = true; + } + + return result; + } + + private void HeaderChanged(AvaloniaPropertyChangedEventArgs e) + { + if (e.OldValue is ILogical oldChild) + { + LogicalChildren.Remove(oldChild); + } + + if (e.NewValue is ILogical newChild) + { + LogicalChildren.Add(newChild); } } } diff --git a/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs b/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs index bda426c23b..e0eb0b005f 100644 --- a/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs @@ -1,8 +1,10 @@ // 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 Avalonia.Collections; using Avalonia.Controls.Mixins; using Avalonia.Controls.Presenters; +using Avalonia.LogicalTree; namespace Avalonia.Controls.Primitives { @@ -22,10 +24,7 @@ namespace Avalonia.Controls.Primitives /// static HeaderedItemsControl() { - ContentControlMixin.Attach( - HeaderProperty, - x => x.LogicalChildren, - "PART_HeaderPresenter"); + HeaderProperty.Changed.AddClassHandler(x => x.HeaderChanged); } /// @@ -47,20 +46,39 @@ namespace Avalonia.Controls.Primitives } /// - void IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter) + IAvaloniaList IContentPresenterHost.LogicalChildren => LogicalChildren; + + /// + bool IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter) { - RegisterContentPresenter(presenter); + return RegisterContentPresenter(presenter); } /// /// Called when an is registered with the control. /// /// The presenter. - protected virtual void RegisterContentPresenter(IContentPresenter presenter) + protected virtual bool RegisterContentPresenter(IContentPresenter presenter) { if (presenter.Name == "PART_HeaderPresenter") { HeaderPresenter = presenter; + return true; + } + + return false; + } + + private void HeaderChanged(AvaloniaPropertyChangedEventArgs e) + { + if (e.OldValue is ILogical oldChild) + { + LogicalChildren.Remove(oldChild); + } + + if (e.NewValue is ILogical newChild) + { + LogicalChildren.Add(newChild); } } } diff --git a/src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs index d59be66b2b..533b643ea6 100644 --- a/src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs @@ -1,8 +1,10 @@ // 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 Avalonia.Collections; using Avalonia.Controls.Mixins; using Avalonia.Controls.Presenters; +using Avalonia.LogicalTree; namespace Avalonia.Controls.Primitives { @@ -22,10 +24,7 @@ namespace Avalonia.Controls.Primitives /// static HeaderedSelectingItemsControl() { - ContentControlMixin.Attach( - HeaderProperty, - x => x.LogicalChildren, - "PART_HeaderPresenter"); + HeaderProperty.Changed.AddClassHandler(x => x.HeaderChanged); } /// @@ -47,20 +46,39 @@ namespace Avalonia.Controls.Primitives } /// - void IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter) + IAvaloniaList IContentPresenterHost.LogicalChildren => LogicalChildren; + + /// + bool IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter) { - RegisterContentPresenter(presenter); + return RegisterContentPresenter(presenter); } /// /// Called when an is registered with the control. /// /// The presenter. - protected virtual void RegisterContentPresenter(IContentPresenter presenter) + protected virtual bool RegisterContentPresenter(IContentPresenter presenter) { if (presenter.Name == "PART_HeaderPresenter") { HeaderPresenter = presenter; + return true; + } + + return false; + } + + private void HeaderChanged(AvaloniaPropertyChangedEventArgs e) + { + if (e.OldValue is ILogical oldChild) + { + LogicalChildren.Remove(oldChild); + } + + if (e.NewValue is ILogical newChild) + { + LogicalChildren.Add(newChild); } } } diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 44ae89fdbc..cc0c5f52be 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -112,7 +112,7 @@ namespace Avalonia.Controls.Primitives private bool _syncingSelectedItems; private int _updateCount; private int _updateSelectedIndex; - private IList _updateSelectedItems; + private object _updateSelectedItem; /// /// Initializes static members of the class. @@ -160,7 +160,7 @@ namespace Avalonia.Controls.Primitives else { _updateSelectedIndex = value; - _updateSelectedItems = null; + _updateSelectedItem = null; } } } @@ -183,7 +183,7 @@ namespace Avalonia.Controls.Primitives } else { - _updateSelectedItems = new AvaloniaList(value); + _updateSelectedItem = value; _updateSelectedIndex = int.MinValue; } } @@ -855,11 +855,6 @@ namespace Avalonia.Controls.Primitives _selectedItem = ElementAt(Items, _selectedIndex); RaisePropertyChanged(SelectedIndexProperty, -1, _selectedIndex, BindingPriority.LocalValue); RaisePropertyChanged(SelectedItemProperty, null, _selectedItem, BindingPriority.LocalValue); - - if (AutoScrollToSelectedItem) - { - ScrollIntoView(_selectedIndex); - } } } @@ -1046,6 +1041,11 @@ namespace Avalonia.Controls.Primitives removed?.Select(x => ElementAt(Items, x)).ToArray() ?? Array.Empty()); RaiseEvent(e); } + + if (AutoScrollToSelectedItem && _selectedIndex != -1) + { + ScrollIntoView(_selectedItem); + } } private void UpdateSelectedItems(Action action) @@ -1075,9 +1075,9 @@ namespace Avalonia.Controls.Primitives { SelectedIndex = _updateSelectedIndex; } - else if (_updateSelectedItems != null) + else if (_updateSelectedItem != null) { - SelectedItems = _updateSelectedItems; + SelectedItem = _updateSelectedItem; } } diff --git a/src/Avalonia.Controls/RadioButton.cs b/src/Avalonia.Controls/RadioButton.cs index a1d353f135..54afde7da2 100644 --- a/src/Avalonia.Controls/RadioButton.cs +++ b/src/Avalonia.Controls/RadioButton.cs @@ -18,7 +18,7 @@ namespace Avalonia.Controls public static readonly RadioButtonGroupManager Default = new RadioButtonGroupManager(); static readonly ConditionalWeakTable s_registeredVisualRoots = new ConditionalWeakTable(); - + readonly Dictionary>> s_registeredGroups = new Dictionary>>(); @@ -127,13 +127,11 @@ namespace Avalonia.Controls { if (!string.IsNullOrEmpty(GroupName)) { - var manager = RadioButtonGroupManager.GetOrCreateForRoot(e.Root); - if (manager != _groupManager) - { - _groupManager.Remove(this, _groupName); - _groupManager = manager; - manager.Add(this); - } + _groupManager?.Remove(this, _groupName); + + _groupManager = RadioButtonGroupManager.GetOrCreateForRoot(e.Root); + + _groupManager.Add(this); } base.OnAttachedToVisualTree(e); } @@ -141,9 +139,10 @@ namespace Avalonia.Controls protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); - if (!string.IsNullOrEmpty(GroupName) && _groupManager != null) + + if (!string.IsNullOrEmpty(GroupName)) { - _groupManager.Remove(this, _groupName); + _groupManager?.Remove(this, _groupName); } } @@ -152,9 +151,9 @@ namespace Avalonia.Controls string oldGroupName = GroupName; if (newGroupName != oldGroupName) { - if (!string.IsNullOrEmpty(oldGroupName) && _groupManager != null) + if (!string.IsNullOrEmpty(oldGroupName)) { - _groupManager.Remove(this, oldGroupName); + _groupManager?.Remove(this, oldGroupName); } _groupName = newGroupName; if (!string.IsNullOrEmpty(newGroupName)) @@ -181,7 +180,7 @@ namespace Avalonia.Controls .GetVisualChildren() .OfType() .Where(x => x != this); - + foreach (var sibling in siblings) { if (sibling.IsChecked.GetValueOrDefault()) diff --git a/src/Avalonia.Controls/StackPanel.cs b/src/Avalonia.Controls/StackPanel.cs index bd3441078d..9e087b7fd3 100644 --- a/src/Avalonia.Controls/StackPanel.cs +++ b/src/Avalonia.Controls/StackPanel.cs @@ -251,7 +251,7 @@ namespace Avalonia.Controls { var child = children[i]; - if (child == null) + if (child == null || !child.IsVisible) { continue; } if (fHorizontal) diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs index fc2c118132..50bcb034ac 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/src/Avalonia.Controls/TabControl.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System.Linq; +using Avalonia.Collections; using Avalonia.Controls.Generators; using Avalonia.Controls.Mixins; using Avalonia.Controls.Presenters; @@ -9,6 +10,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Layout; +using Avalonia.LogicalTree; using Avalonia.VisualTree; namespace Avalonia.Controls @@ -16,7 +18,7 @@ namespace Avalonia.Controls /// /// A tab control that displays a tab strip along with the content of the selected tab. /// - public class TabControl : SelectingItemsControl + public class TabControl : SelectingItemsControl, IContentPresenterHost { /// /// Defines the property. @@ -68,10 +70,6 @@ namespace Avalonia.Controls SelectionModeProperty.OverrideDefaultValue(SelectionMode.AlwaysSelected); ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); AffectsMeasure(TabStripPlacementProperty); - ContentControlMixin.Attach( - SelectedContentProperty, - x => x.LogicalChildren, - "PART_SelectedContentHost"); } /// @@ -136,7 +134,31 @@ namespace Avalonia.Controls internal ItemsPresenter ItemsPresenterPart { get; private set; } - internal ContentPresenter ContentPart { get; private set; } + internal IContentPresenter ContentPart { get; private set; } + + /// + IAvaloniaList IContentPresenterHost.LogicalChildren => LogicalChildren; + + /// + bool IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter) + { + return RegisterContentPresenter(presenter); + } + + /// + /// Called when an is registered with the control. + /// + /// The presenter. + protected virtual bool RegisterContentPresenter(IContentPresenter presenter) + { + if (presenter.Name == "PART_SelectedContentHost") + { + ContentPart = presenter; + return true; + } + + return false; + } protected override IItemContainerGenerator CreateItemContainerGenerator() { @@ -148,8 +170,6 @@ namespace Avalonia.Controls base.OnTemplateApplied(e); ItemsPresenterPart = e.NameScope.Get("PART_ItemsPresenter"); - - ContentPart = e.NameScope.Get("PART_SelectedContentHost"); } /// diff --git a/src/Avalonia.Diagnostics/Views/TreePageView.xaml.cs b/src/Avalonia.Diagnostics/Views/TreePageView.xaml.cs index ddf1473b45..1326f718de 100644 --- a/src/Avalonia.Diagnostics/Views/TreePageView.xaml.cs +++ b/src/Avalonia.Diagnostics/Views/TreePageView.xaml.cs @@ -70,7 +70,10 @@ namespace Avalonia.Diagnostics.Views private void TreeViewItemTemplateApplied(object sender, TemplateAppliedEventArgs e) { var item = (TreeViewItem)sender; - var header = item.HeaderPresenter.Child; + var headerPresenter = item.HeaderPresenter; + headerPresenter.ApplyTemplate(); + + var header = headerPresenter.Child; header.PointerEnter += AddAdorner; header.PointerLeave += RemoveAdorner; item.TemplateApplied -= TreeViewItemTemplateApplied; diff --git a/src/Avalonia.Input/AccessKeyHandler.cs b/src/Avalonia.Input/AccessKeyHandler.cs index 29768513f3..9e4b2b84e0 100644 --- a/src/Avalonia.Input/AccessKeyHandler.cs +++ b/src/Avalonia.Input/AccessKeyHandler.cs @@ -231,15 +231,6 @@ namespace Avalonia.Input } break; - - case Key.F10: - _owner.ShowAccessKeys = _showingAccessKeys = true; - if (MainMenu != null) - { - MainMenu.Open(); - e.Handled = true; - } - break; } } diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs index 4b4ab177b8..47e85416cf 100644 --- a/src/Avalonia.Input/InputElement.cs +++ b/src/Avalonia.Input/InputElement.cs @@ -565,9 +565,17 @@ namespace Avalonia.Input { IsEffectivelyEnabled = IsEnabledCore && (parent?.IsEffectivelyEnabled ?? true); - foreach (var child in this.GetVisualChildren().OfType()) + // PERF-SENSITIVE: This is called on entire hierarchy and using foreach or LINQ + // will cause extra allocations and overhead. + + var children = VisualChildren; + + // ReSharper disable once ForCanBeConvertedToForeach + for (int i = 0; i < children.Count; ++i) { - child.UpdateIsEffectivelyEnabled(this); + var child = children[i] as InputElement; + + child?.UpdateIsEffectivelyEnabled(this); } } } diff --git a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs index ef7dc33f76..03d061e04f 100644 --- a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs @@ -984,6 +984,7 @@ namespace Avalonia.Controls.UnitTests TextBox textBox = GetTextBox(control); var window = new Window {Content = control}; window.ApplyTemplate(); + window.Presenter.ApplyTemplate(); Dispatcher.UIThread.RunJobs(); test.Invoke(control, textBox); } diff --git a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs index 93355a22f2..f7332415ac 100644 --- a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs @@ -50,6 +50,7 @@ namespace Avalonia.Controls.UnitTests root.Child = target; target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); @@ -331,6 +332,45 @@ namespace Avalonia.Controls.UnitTests Assert.Null(textBlock.GetLogicalParent()); } + [Fact] + public void Should_Set_Child_LogicalParent_After_Removing_And_Adding_Back_To_Logical_Tree() + { + using (UnitTestApplication.Start(TestServices.RealStyler)) + { + var target = new ContentControl(); + var root = new TestRoot + { + Styles = + { + new Style(x => x.OfType()) + { + Setters = + { + new Setter(ContentControl.TemplateProperty, GetTemplate()), + } + } + }, + Child = target + }; + + target.Content = "Foo"; + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + + Assert.Equal(target, target.Presenter.Child.LogicalParent); + + root.Child = null; + + Assert.Null(target.Template); + + target.Content = null; + root.Child = target; + target.Content = "Bar"; + + Assert.Equal(target, target.Presenter.Child.LogicalParent); + } + } + private FuncControlTemplate GetTemplate() { return new FuncControlTemplate((parent, scope) => diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index 522afc9546..ac80fc6c7a 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -27,7 +27,9 @@ namespace Avalonia.Controls.UnitTests ContextMenu = sut }; - new Window { Content = target }.ApplyTemplate(); + var window = new Window { Content = target }; + window.ApplyTemplate(); + window.Presenter.ApplyTemplate(); int openedCount = 0; @@ -53,7 +55,9 @@ namespace Avalonia.Controls.UnitTests ContextMenu = sut }; - new Window { Content = target }.ApplyTemplate(); + var window = new Window { Content = target }; + window.ApplyTemplate(); + window.Presenter.ApplyTemplate(); sut.Open(target); @@ -86,6 +90,7 @@ namespace Avalonia.Controls.UnitTests var window = new Window {Content = target}; window.ApplyTemplate(); + window.Presenter.ApplyTemplate(); _mouse.Click(target, MouseButton.Right); @@ -115,7 +120,8 @@ namespace Avalonia.Controls.UnitTests var window = new Window {Content = target}; window.ApplyTemplate(); - + window.Presenter.ApplyTemplate(); + _mouse.Click(target, MouseButton.Right); Assert.True(sut.IsOpen); diff --git a/tests/Avalonia.Controls.UnitTests/Mixins/ContentControlMixinTests.cs b/tests/Avalonia.Controls.UnitTests/Mixins/ContentControlMixinTests.cs deleted file mode 100644 index 638443e17f..0000000000 --- a/tests/Avalonia.Controls.UnitTests/Mixins/ContentControlMixinTests.cs +++ /dev/null @@ -1,107 +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 System.Linq; -using Avalonia.Collections; -using Avalonia.Controls.Mixins; -using Avalonia.Controls.Presenters; -using Avalonia.Controls.Primitives; -using Avalonia.Controls.Templates; -using Avalonia.LogicalTree; -using Moq; -using Xunit; - -namespace Avalonia.Controls.UnitTests.Mixins -{ - public class ContentControlMixinTests - { - [Fact] - public void Multiple_Mixin_Usages_Should_Not_Throw() - { - var target = new TestControl() - { - Template = new FuncControlTemplate((_, scope) => new Panel - { - Children = - { - new ContentPresenter {Name = "Content_1_Presenter"}.RegisterInNameScope(scope), - new ContentPresenter {Name = "Content_2_Presenter"}.RegisterInNameScope(scope) - } - }) - }; - - - var ex = Record.Exception(() => target.ApplyTemplate()); - - Assert.Null(ex); - } - - [Fact] - public void Replacing_Template_Releases_Events() - { - var p1 = new ContentPresenter { Name = "Content_1_Presenter" }; - var p2 = new ContentPresenter { Name = "Content_2_Presenter" }; - var target = new TestControl - { - Template = new FuncControlTemplate((_, scope) => new Panel - { - Children = - { - p1.RegisterInNameScope(scope), - p2.RegisterInNameScope(scope) - } - }) - }; - target.ApplyTemplate(); - - Control tc; - - p1.Content = tc = new Control(); - p1.UpdateChild(); - Assert.Contains(tc, target.GetLogicalChildren()); - - p2.Content = tc = new Control(); - p2.UpdateChild(); - Assert.Contains(tc, target.GetLogicalChildren()); - - target.Template = null; - - p1.Content = tc = new Control(); - p1.UpdateChild(); - Assert.DoesNotContain(tc, target.GetLogicalChildren()); - - p2.Content = tc = new Control(); - p2.UpdateChild(); - Assert.DoesNotContain(tc, target.GetLogicalChildren()); - - } - - private class TestControl : TemplatedControl - { - public static readonly StyledProperty Content1Property = - AvaloniaProperty.Register(nameof(Content1)); - - public static readonly StyledProperty Content2Property = - AvaloniaProperty.Register(nameof(Content2)); - - static TestControl() - { - ContentControlMixin.Attach(Content1Property, x => x.LogicalChildren, "Content_1_Presenter"); - ContentControlMixin.Attach(Content2Property, x => x.LogicalChildren, "Content_2_Presenter"); - } - - public object Content1 - { - get { return GetValue(Content1Property); } - set { SetValue(Content1Property, value); } - } - - public object Content2 - { - get { return GetValue(Content2Property); } - set { SetValue(Content2Property, value); } - } - } - } -} diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs index 6ab9c345d4..6d6dfc9230 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs @@ -343,6 +343,19 @@ namespace Avalonia.Controls.UnitTests.Presenters Assert.Same(logicalParent, ((IStyledElement)child).StylingParent); } + [Fact] + public void Should_Clear_Host_When_Host_Template_Cleared() + { + var (target, host) = CreateTarget(); + + Assert.Same(host, target.Host); + + host.Template = null; + host.ApplyTemplate(); + + Assert.Null(target.Host); + } + (ContentPresenter presenter, ContentControl templatedParent) CreateTarget() { var templatedParent = new ContentControl diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs index 0ebe6833d3..f75f6fcf91 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs @@ -51,6 +51,7 @@ namespace Avalonia.Controls.UnitTests.Primitives window.Content = target; window.ApplyTemplate(); + window.Presenter.ApplyTemplate(); target.ApplyTemplate(); target.Popup.Open(); @@ -167,6 +168,7 @@ namespace Avalonia.Controls.UnitTests.Primitives window.Content = target; window.ApplyTemplate(); + window.Presenter.ApplyTemplate(); target.ApplyTemplate(); target.Popup.Open(); target.PopupContent = null; diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index bc002174ec..4e4d92afdc 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -894,6 +894,53 @@ namespace Avalonia.Controls.UnitTests.Primitives Assert.Equal("Qux", target.SelectedItem); } + [Fact] + public void AutoScrollToSelectedItem_Causes_Scroll_To_SelectedItem() + { + var items = new ObservableCollection + { + "Foo", + "Bar", + "Baz" + }; + + var target = new ListBox + { + Template = Template(), + Items = items, + }; + + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + + var raised = false; + target.AddHandler(Control.RequestBringIntoViewEvent, (s, e) => raised = true); + + target.SelectedIndex = 2; + + Assert.True(raised); + } + + [Fact] + public void Can_Set_Both_SelectedItem_And_SelectedItems_During_Initialization() + { + // Issue #2969. + var target = new ListBox(); + var selectedItems = new List(); + + target.BeginInit(); + target.Template = Template(); + target.Items = new[] { "Foo", "Bar", "Baz" }; + target.SelectedItems = selectedItems; + target.SelectedItem = "Bar"; + target.EndInit(); + + Assert.Equal("Bar", target.SelectedItem); + Assert.Equal(1, target.SelectedIndex); + Assert.Same(selectedItems, target.SelectedItems); + Assert.Equal(new[] { "Bar" }, selectedItems); + } + private FuncControlTemplate Template() { return new FuncControlTemplate((control, scope) => diff --git a/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs b/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs index db113f0569..984734d414 100644 --- a/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs @@ -332,6 +332,31 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(sizeWithTwoChildren, sizeWithThreeChildren); } + [Theory] + [InlineData(Orientation.Horizontal)] + [InlineData(Orientation.Vertical)] + public void Only_Arrange_Visible_Children(Orientation orientation) + { + + var hiddenPanel = new Panel { Width = 10, Height = 10, IsVisible = false }; + var panel = new Panel { Width = 10, Height = 10 }; + + var target = new StackPanel + { + Spacing = 40, + Orientation = orientation, + Children = + { + hiddenPanel, + panel + } + }; + + target.Measure(Size.Infinity); + target.Arrange(new Rect(target.DesiredSize)); + Assert.Equal(new Rect(0, 0, 10, 10), panel.Bounds); + } + private class TestControl : Control { public Size MeasureConstraint { get; private set; } diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index ee8d9cc62e..ddf7e7a0fa 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs @@ -183,27 +183,27 @@ namespace Avalonia.Controls.UnitTests ApplyTemplate(target); - target.ContentPart.UpdateChild(); + ((ContentPresenter)target.ContentPart).UpdateChild(); var dataContext = ((TextBlock)target.ContentPart.Child).DataContext; Assert.Equal(items[0], dataContext); target.SelectedIndex = 1; - target.ContentPart.UpdateChild(); + ((ContentPresenter)target.ContentPart).UpdateChild(); dataContext = ((Button)target.ContentPart.Child).DataContext; Assert.Equal(items[1], dataContext); target.SelectedIndex = 2; - target.ContentPart.UpdateChild(); + ((ContentPresenter)target.ContentPart).UpdateChild(); dataContext = ((TextBlock)target.ContentPart.Child).DataContext; Assert.Equal("Base", dataContext); target.SelectedIndex = 3; - target.ContentPart.UpdateChild(); + ((ContentPresenter)target.ContentPart).UpdateChild(); dataContext = ((TextBlock)target.ContentPart.Child).DataContext; Assert.Equal("Qux", dataContext); target.SelectedIndex = 4; - target.ContentPart.UpdateChild(); + ((ContentPresenter)target.ContentPart).UpdateChild(); dataContext = target.ContentPart.DataContext; Assert.Equal("Base", dataContext); } @@ -279,7 +279,7 @@ namespace Avalonia.Controls.UnitTests }; ApplyTemplate(target); - target.ContentPart.UpdateChild(); + ((ContentPresenter)target.ContentPart).UpdateChild(); var content = Assert.IsType(target.ContentPart.Child); Assert.Equal("bar", content.Tag); diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index 901d780f16..645f87163a 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -202,8 +202,9 @@ namespace Avalonia.Controls.UnitTests target.Template = CreateTemplate(); target.Content = child; + target.ApplyTemplate(); - Assert.Throws(() => target.ApplyTemplate()); + Assert.Throws(() => target.Presenter.ApplyTemplate()); } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs index b1abc9ea54..77bb215ad5 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs @@ -142,6 +142,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml window.DataContext = new { Foo = "foo" }; window.ApplyTemplate(); + window.Presenter.ApplyTemplate(); Assert.Equal("foo", border.DataContext); } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests_RelativeSource.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests_RelativeSource.cs index 86b874f75c..f8678ee22e 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests_RelativeSource.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests_RelativeSource.cs @@ -73,6 +73,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml var button = window.FindControl