From fab5c1c74f7d296cc9d6693a716808bb2391a59f Mon Sep 17 00:00:00 2001 From: xiaoy312 Date: Fri, 12 May 2023 15:45:51 -0400 Subject: [PATCH] fix(ListView): unnecessary SelectTemplate call on collection reset --- .../Given_ListViewBase.cs | 60 +++++++++++++++++-- .../Controls/ItemsControl/ItemsControl.cs | 6 +- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ListViewBase.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ListViewBase.cs index 0d419dcd6988..83f29b887e9e 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ListViewBase.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ListViewBase.cs @@ -40,6 +40,12 @@ public partial class Given_ListViewBase // resources { private ResourceDictionary _testsResources; + [TestInitialize] + public void Init() + { + _testsResources = new TestsResources(); + } + private Style BasicContainerStyle => _testsResources["BasicListViewContainerStyle"] as Style; private Style ContainerMarginStyle => _testsResources["ListViewContainerMarginStyle"] as Style; @@ -88,12 +94,6 @@ public partial class Given_ListViewBase // resources #endif public partial class Given_ListViewBase // test cases { - [TestInitialize] - public void Init() - { - _testsResources = new TestsResources(); - } - [TestMethod] [RunsOnUIThread] public void ValidSelectionChange() @@ -2803,6 +2803,41 @@ bool IsVisible(object x) => x is UIElement uie : false; #endif } + + [TestMethod] + public async Task When_ItemsSource_INCC_Reset() + { + // note: In order to repro #12059 (extra SelectTemplate call) & SelectTemplateCore called with incorrect item (the `source` is passed as item), + // we need a binding that inherits source from DataContext, and not: new Binding { Source = source }, or direct assignment. + Action onSelectTemplateHook = null; + + var source = new ObservableCollection(new[] { new object() }); + var sut = new ListView + { + ItemTemplateSelector = new LambdaDataTemplateSelector(x => + { + onSelectTemplateHook?.Invoke(x); + return TextBlockItemTemplate; + }), + }; + sut.DataContext = source; + sut.SetBinding(ItemsControl.ItemsSourceProperty, new Binding()); + + WindowHelper.WindowContent = sut; + await WindowHelper.WaitForLoaded(sut); + await WindowHelper.WaitForIdle(); + + // Clearing the source SHOULD NOT cause the ItemTemplateSelector to be re-evaluated, + // especially so when we are leaving the source empty. + var wasSelectTemplateCalled = false; + onSelectTemplateHook = x => wasSelectTemplateCalled = true; + source.Clear(); + + if (wasSelectTemplateCalled) + { + Assert.Fail("DataTemplateSelector.SelectTemplateCore is invoked during an INCC reset."); + } + } } public partial class Given_ListViewBase // data class, data-context, view-model, template-selector @@ -3035,6 +3070,19 @@ public string Display set => SetAndRaiseIfChanged(ref _display, value); } } + + public class LambdaDataTemplateSelector : DataTemplateSelector + { + private readonly Func _impl; + + public LambdaDataTemplateSelector(Func impl) + { + this._impl = impl; + } + + protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) => SelectTemplateCore(item); + protected override DataTemplate SelectTemplateCore(object item) => _impl(item); + } } public partial class Given_ListViewBase // helpers diff --git a/src/Uno.UI/UI/Xaml/Controls/ItemsControl/ItemsControl.cs b/src/Uno.UI/UI/Xaml/Controls/ItemsControl/ItemsControl.cs index f0364bf773d5..39982032b3a8 100644 --- a/src/Uno.UI/UI/Xaml/Controls/ItemsControl/ItemsControl.cs +++ b/src/Uno.UI/UI/Xaml/Controls/ItemsControl/ItemsControl.cs @@ -1171,8 +1171,6 @@ internal void CleanUpContainer(global::Windows.UI.Xaml.DependencyObject element) } else if (element is ContentControl contentControl) { - contentControl.ClearValue(DataContextProperty); - if (!isOwnContainer) { static void ClearPropertyWhenNoExpression(ContentControl target, DependencyProperty property) @@ -1201,6 +1199,10 @@ static void ClearPropertyWhenNoExpression(ContentControl target, DependencyPrope ClearPropertyWhenNoExpression(contentControl, ContentControl.ContentTemplateSelectorProperty); } } + + // We are clearing the DataContext last. Because if there is a binding set on any of the above properties, Content(Template(Selector)?)?, + // clearing the DC can cause the data-bound property to be unnecessarily re-evaluated with an inherited DC from the visual parent. + contentControl.ClearValue(DataContextProperty); } }