From 1312626aed977f131c1007ba7fa5b73a373e543a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 13 May 2019 17:32:38 +0200 Subject: [PATCH 01/13] Added failing test for #2512. --- .../Xaml/DataTemplateTests.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs index ce51e7ad72..61155c3c46 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs @@ -38,6 +38,37 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml } } + [Fact] + public void DataTemplate_Can_Contain_Named_UserControl() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + + + + + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var itemsControl = window.FindControl("itemsControl"); + + window.DataContext = new[] { "item1", "item2" }; + + window.ApplyTemplate(); + itemsControl.ApplyTemplate(); + itemsControl.Presenter.ApplyTemplate(); + + Assert.Equal(2, itemsControl.Presenter.Panel.Children.Count); + } + } + [Fact] public void Can_Set_DataContext_In_DataTemplate() { From 208361b8faa597273d853d8661119fa0ee39043c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 13 May 2019 17:18:51 +0200 Subject: [PATCH 02/13] Don't register controls with parent namescope. This reverts the changes in #843 because they were causing problems, as described by #2512. This should however not cause #829 to reappear because since #843 was merged we moved to Portable.Xaml and we're now registering controls with the root namescope in the XAML engine: https://github.com/AvaloniaUI/Avalonia/blob/b4577a1631755b391f3768e00264ac86c4300507/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs#L67 Fixes #2512 --- .../Primitives/TemplatedControl.cs | 2 +- src/Avalonia.Styling/StyledElement.cs | 17 -------------- .../Xaml/BasicTests.cs | 16 +++++++++++++ .../StyledElementTests.cs | 7 ++---- .../StyledElementTests_NameScope.cs | 23 ------------------- 5 files changed, 19 insertions(+), 46 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index ba4c5027d0..32e220b789 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -357,7 +357,7 @@ namespace Avalonia.Controls.Primitives if (control.TemplatedParent == this) { - foreach (IControl child in control.GetVisualChildren()) + foreach (IControl child in control.GetLogicalChildren()) { RegisterNames(child, nameScope); } diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index d314a8d44e..ae2cec5561 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/src/Avalonia.Styling/StyledElement.cs @@ -677,23 +677,6 @@ namespace Avalonia if (Name != null) { _nameScope?.Register(Name, this); - - var visualParent = Parent as StyledElement; - - if (this is INameScope && visualParent != null) - { - // If we have e.g. a named UserControl in a window then we want that control - // to be findable by name from the Window, so register with both name scopes. - // This differs from WPF's behavior in that XAML manually registers controls - // with name scopes based on the XAML file in which the name attribute appears, - // but we're trying to avoid XAML magic in Avalonia in order to made code- - // created UIs easy. This will cause problems if a UserControl declares a name - // in its XAML and that control is included multiple times in a parent control - // (as the name will be duplicated), however at the moment I'm fine with saying - // "don't do that". - var parentNameScope = NameScope.FindNameScope(visualParent); - parentNameScope?.Register(Name, this); - } } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index 2e67541c1f..743fc82f29 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -197,6 +197,22 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal("Foo", button.Content); } + [Fact] + public void Named_UserControl_Is_Added_To_Parent_NameScope() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + +"; + + var control = AvaloniaXamlLoader.Parse(xaml); + + Assert.NotNull(control.FindControl("foo")); + } + } + [Fact] public void Direct_Content_In_ItemsControl_Is_Operational() { diff --git a/tests/Avalonia.Styling.UnitTests/StyledElementTests.cs b/tests/Avalonia.Styling.UnitTests/StyledElementTests.cs index 4970addd81..7fdd70799f 100644 --- a/tests/Avalonia.Styling.UnitTests/StyledElementTests.cs +++ b/tests/Avalonia.Styling.UnitTests/StyledElementTests.cs @@ -273,13 +273,10 @@ namespace Avalonia.Styling.UnitTests var root = new TestRoot(); var child = new Border(); - ((ISupportInitialize)child).BeginInit(); + child.BeginInit(); root.Child = child; child.Name = "foo"; - Assert.Null(root.FindControl("foo")); - ((ISupportInitialize)child).EndInit(); - - Assert.Same(root.FindControl("foo"), child); + child.EndInit(); } } diff --git a/tests/Avalonia.Styling.UnitTests/StyledElementTests_NameScope.cs b/tests/Avalonia.Styling.UnitTests/StyledElementTests_NameScope.cs index 47c540f44a..47c34dfd38 100644 --- a/tests/Avalonia.Styling.UnitTests/StyledElementTests_NameScope.cs +++ b/tests/Avalonia.Styling.UnitTests/StyledElementTests_NameScope.cs @@ -1,11 +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.Presenters; -using Avalonia.Controls.Templates; -using Avalonia.Rendering; -using Avalonia.Styling; using Avalonia.UnitTests; using Xunit; @@ -70,23 +65,5 @@ namespace Avalonia.Controls.UnitTests Assert.Null(NameScope.GetNameScope((StyledElement)root.Presenter).Find("foo")); } - - [Fact] - public void Control_That_Is_NameScope_Should_Register_With_Parent_NameScope() - { - UserControl userControl; - var root = new TestTemplatedRoot - { - Content = userControl = new UserControl - { - Name = "foo", - } - }; - - root.ApplyTemplate(); - - Assert.Same(userControl, root.FindControl("foo")); - Assert.Same(userControl, userControl.FindControl("foo")); - } } } From 20eddbe6c872fabf50dd751372170d1ee7f3dbd6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 15 May 2019 14:09:09 +0200 Subject: [PATCH 03/13] Added failing test for #2518 --- .../Rendering/DeferredRendererTests.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs index 8c103360d4..f094d9c78d 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs @@ -325,6 +325,52 @@ namespace Avalonia.Visuals.UnitTests.Rendering context.Verify(x => x.DrawImage(borderLayer, 0.5, It.IsAny(), It.IsAny(), BitmapInterpolationMode.Default)); } + [Fact] + public void Can_Dirty_Control_In_SceneInvalidated() + { + Border border1; + Border border2; + var root = new TestRoot + { + Width = 100, + Height = 100, + Child = new StackPanel + { + Children = + { + (border1 = new Border + { + Background = Brushes.Red, + Child = new Canvas(), + }), + (border2 = new Border + { + Background = Brushes.Red, + Child = new Canvas(), + }), + } + } + }; + + root.Measure(Size.Infinity); + root.Arrange(new Rect(root.DesiredSize)); + + var target = CreateTargetAndRunFrame(root); + var invalidated = false; + + target.SceneInvalidated += (s, e) => + { + invalidated = true; + target.AddDirty(border2); + }; + + target.AddDirty(border1); + target.Paint(new Rect(root.DesiredSize)); + + Assert.True(invalidated); + Assert.True(((IRenderLoopTask)target).NeedsUpdate); + } + private DeferredRenderer CreateTargetAndRunFrame( TestRoot root, Mock timer = null, From 2661e939b1d548fcb4116d025c4f7e87f4684a83 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 15 May 2019 14:10:45 +0200 Subject: [PATCH 04/13] Reset dirty rects before calling SceneInvalidated. When cleared after calling `SceneInvalidated`, any control invalidated during `SceneInvalidated` was be lost. Fixes #2518. --- src/Avalonia.Visuals/Rendering/DeferredRenderer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 5d1c66f872..c83a8436b4 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -528,6 +528,8 @@ namespace Avalonia.Rendering oldScene?.Dispose(); } + _dirty.Clear(); + if (SceneInvalidated != null) { var rect = new Rect(); @@ -540,10 +542,9 @@ namespace Avalonia.Rendering } } + System.Diagnostics.Debug.WriteLine("Invalidated " + rect); SceneInvalidated(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect)); } - - _dirty.Clear(); } else { From c37f2b2fbcd631574140cbe0eb2022fd34f03948 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 15 May 2019 14:31:25 +0200 Subject: [PATCH 05/13] Added failing test for #2522. --- .../Primitives/SelectingItemsControlTests.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 2df925301f..037c16e231 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -341,6 +341,33 @@ namespace Avalonia.Controls.UnitTests.Primitives Assert.Equal(-1, target.SelectedIndex); } + [Fact] + public void Moving_Selected_Item_Should_Update_Selection() + { + var items = new AvaloniaList + { + new Item(), + new Item(), + }; + + var target = new SelectingItemsControl + { + Items = items, + Template = Template(), + }; + + target.ApplyTemplate(); + target.SelectedIndex = 0; + + Assert.Equal(items[0], target.SelectedItem); + Assert.Equal(0, target.SelectedIndex); + + items.Move(0, 1); + + Assert.Equal(items[1], target.SelectedItem); + Assert.Equal(1, target.SelectedIndex); + } + [Fact] public void Resetting_Items_Collection_Should_Clear_Selection() { From ba7cec18e37627fe9eb948f3c3870fce447ae13e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 15 May 2019 14:31:44 +0200 Subject: [PATCH 06/13] Handle move in SelectingItemsControl. Fixes #2522. --- src/Avalonia.Controls/Primitives/SelectingItemsControl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 280c3ad93a..23d2dc06af 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -380,6 +380,7 @@ namespace Avalonia.Controls.Primitives } break; + case NotifyCollectionChangedAction.Move: case NotifyCollectionChangedAction.Reset: SelectedIndex = IndexOf(Items, SelectedItem); break; From 9d99cf699a1b53b814879d4dff1cf0b0805fd4b4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 16 May 2019 09:28:14 +0200 Subject: [PATCH 07/13] Remove test that is no longer true. `UserControl`s should no longer be added to parent namescope. --- .../Xaml/BasicTests.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index 7bd659b65f..359d2521e0 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -208,22 +208,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal("Foo", button.Content); } - [Fact] - public void Named_UserControl_Is_Added_To_Parent_NameScope() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - var xaml = @" - - -"; - - var control = AvaloniaXamlLoader.Parse(xaml); - - Assert.NotNull(control.FindControl("foo")); - } - } - [Fact] public void Direct_Content_In_ItemsControl_Is_Operational() { From 67ea597d82e14a6e9feeeb83d6d34fbda15b215a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 22 May 2019 12:14:37 +0300 Subject: [PATCH 08/13] Properly pass root object instance to templates, fixes #2147 #2527 --- .../XamlIl/Runtime/XamlIlRuntimeHelpers.cs | 18 +++++-- .../Avalonia.Markup.Xaml/XamlIl/xamlil.github | 2 +- .../Xaml/XamlIlTests.cs | 53 +++++++++++++++++++ 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs index f91e221ac0..70b7fe6aec 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using Avalonia.Controls; using Avalonia.Data; +using Portable.Xaml; using Portable.Xaml.Markup; // ReSharper disable UnusedMember.Global // ReSharper disable UnusedParameter.Global @@ -17,19 +18,24 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime { var resourceNodes = provider.GetService().Parents .OfType().ToList(); - - return sp => builder(new DeferredParentServiceProvider(sp, resourceNodes)); + var rootObject = provider.GetService().RootObject; + return sp => builder(new DeferredParentServiceProvider(sp, resourceNodes, rootObject)); } - class DeferredParentServiceProvider : IAvaloniaXamlIlParentStackProvider, IServiceProvider + class DeferredParentServiceProvider : + IAvaloniaXamlIlParentStackProvider, + IServiceProvider, + IRootObjectProvider { private readonly IServiceProvider _parentProvider; private readonly List _parentResourceNodes; - public DeferredParentServiceProvider(IServiceProvider parentProvider, List parentResourceNodes) + public DeferredParentServiceProvider(IServiceProvider parentProvider, List parentResourceNodes, + object rootObject) { _parentProvider = parentProvider; _parentResourceNodes = parentResourceNodes; + RootObject = rootObject; } public IEnumerable Parents => GetParents(); @@ -46,8 +52,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime { if (serviceType == typeof(IAvaloniaXamlIlParentStackProvider)) return this; + if (serviceType == typeof(IRootObjectProvider)) + return this; return _parentProvider?.GetService(serviceType); } + + public object RootObject { get; } } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github index 3b3c1f93a5..50920ece52 160000 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github @@ -1 +1 @@ -Subproject commit 3b3c1f93a566080d417b9782f9cc4ea67cd62344 +Subproject commit 50920ece52647b19760f65b417940da125101365 diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs index a584027768..175479e3ff 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs @@ -5,8 +5,12 @@ using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; using Avalonia.Controls; +using Avalonia.Controls.Presenters; using Avalonia.Data.Converters; +using Avalonia.Input; +using Avalonia.Interactivity; using Avalonia.Media; +using Avalonia.Threading; using Avalonia.UnitTests; using Avalonia.VisualTree; using JetBrains.Annotations; @@ -117,6 +121,55 @@ namespace Avalonia.Markup.Xaml.UnitTests Assert.Equal(Brushes.Red.Color, ((ISolidColorBrush)canvas.Background).Color); } } + + [Fact] + public void Event_Handlers_Should_Work_For_Templates() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var w =new XamlIlBugTestsEventHandlerCodeBehind(); + w.ApplyTemplate(); + w.Show(); + + Dispatcher.UIThread.RunJobs(); + var itemsPresenter = ((ItemsControl)w.Content).GetVisualChildren().FirstOrDefault(); + var item = itemsPresenter + .GetVisualChildren().First() + .GetVisualChildren().First() + .GetVisualChildren().First(); + ((Control)item).RaiseEvent(new PointerPressedEventArgs {ClickCount = 20}); + Assert.Equal(20, w.Args.ClickCount); + } + } + } + + public class XamlIlBugTestsEventHandlerCodeBehind : Window + { + public PointerPressedEventArgs Args; + public void HandlePointerPressed(object sender, PointerPressedEventArgs args) + { + Args = args; + } + + public XamlIlBugTestsEventHandlerCodeBehind() + { + new AvaloniaXamlLoader().Load(@" + + + + +