From 757763620fc5561f33bda9378e1381df23164a1a Mon Sep 17 00:00:00 2001 From: BryanRoth Date: Mon, 17 Jan 2022 09:36:07 -0500 Subject: [PATCH 01/19] Fix NullReferenceExceptions on null from DataTemplateSelector in TabbedPage.ItemTemplate Not sure what other controls this picks up but it's a start. --- Xamarin.Forms.Core/DataTemplateExtensions.cs | 2 +- Xamarin.Forms.Core/TemplatedItemsList.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Xamarin.Forms.Core/DataTemplateExtensions.cs b/Xamarin.Forms.Core/DataTemplateExtensions.cs index 34ee5941a8f..0f024649325 100644 --- a/Xamarin.Forms.Core/DataTemplateExtensions.cs +++ b/Xamarin.Forms.Core/DataTemplateExtensions.cs @@ -16,7 +16,7 @@ public static DataTemplate SelectDataTemplate(this DataTemplate self, object ite public static object CreateContent(this DataTemplate self, object item, BindableObject container) { - return self.SelectDataTemplate(item, container).CreateContent(); + return self.SelectDataTemplate(item, container)?.CreateContent(); } } } \ No newline at end of file diff --git a/Xamarin.Forms.Core/TemplatedItemsList.cs b/Xamarin.Forms.Core/TemplatedItemsList.cs index 6facef478b1..2ef7e361254 100644 --- a/Xamarin.Forms.Core/TemplatedItemsList.cs +++ b/Xamarin.Forms.Core/TemplatedItemsList.cs @@ -537,7 +537,9 @@ public DataTemplate SelectDataTemplate(object item) public TItem ActivateContent(int index, object item) { - TItem content = ItemTemplate != null ? (TItem)ItemTemplate.CreateContent(item, _itemsView) : _itemsView.CreateDefault(item); + TItem content = ItemTemplate != null ? + (TItem)ItemTemplate.CreateContent(item, _itemsView) ?? _itemsView.CreateDefault(item) : + _itemsView.CreateDefault(item); content = UpdateContent(content, index, item); From daaccec7f9d9fc5b4523493c106e5097ed07f960 Mon Sep 17 00:00:00 2001 From: BryanRoth Date: Mon, 17 Jan 2022 12:06:02 -0500 Subject: [PATCH 02/19] Add DataTemplateSelector tests for null return value. --- .../DataTemplateSelectorTests.cs | 68 ++++++++++++++++++- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/Xamarin.Forms.Core.UnitTests/DataTemplateSelectorTests.cs b/Xamarin.Forms.Core.UnitTests/DataTemplateSelectorTests.cs index 7002f6466a4..a9807e4f1ab 100644 --- a/Xamarin.Forms.Core.UnitTests/DataTemplateSelectorTests.cs +++ b/Xamarin.Forms.Core.UnitTests/DataTemplateSelectorTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.ObjectModel; using NUnit.Framework; using Xamarin.Forms; @@ -37,6 +38,22 @@ public TemplateTwo() : base(typeof(EntryCell)) } } + class TemplateThree : DataTemplate + { + public TemplateThree() : base(typeof(ContentPage)) + { + + } + } + + class TemplateFour : DataTemplate + { + public TemplateFour() : base(typeof(ContentView)) + { + + } + } + class TestDTS : DataTemplateSelector { public TestDTS() @@ -51,13 +68,35 @@ protected override DataTemplate OnSelectTemplate(object item, BindableObject con return templateOne; if (item is byte) return new TestDTS(); - return templateTwo; + if (item is string) + return templateTwo; + + return null; } readonly DataTemplate templateOne; readonly DataTemplate templateTwo; } + class TestPageDTS : DataTemplateSelector + { + public TestPageDTS() + { + templateThree = new TemplateThree(); + } + + protected override DataTemplate OnSelectTemplate(object item, BindableObject container) + { + if (item is View) + return templateThree; + + //if (item is Page) + return null; + } + + readonly DataTemplate templateThree; + } + [Test] public void Constructor() { @@ -70,17 +109,31 @@ public void ReturnsCorrectType() var dts = new TestDTS(); Assert.IsInstanceOf(dts.SelectTemplate(1d, null)); Assert.IsInstanceOf(dts.SelectTemplate("test", null)); + Assert.IsNull(dts.SelectTemplate((short)0, null)); } [Test] public void ListViewSupport() { var listView = new ListView(ListViewCachingStrategy.RecycleElement); - listView.ItemsSource = new object[] { 0d, "test" }; + listView.ItemsSource = new object[] { 0d, "test", (short)0 }; listView.ItemTemplate = new TestDTS(); Assert.IsInstanceOf(listView.TemplatedItems[0]); Assert.IsInstanceOf(listView.TemplatedItems[1]); + Assert.IsInstanceOf(listView.TemplatedItems[2]); + } + + [Test] + public void ListViewRecycleDTSupport() + { + var listView = new ListView(ListViewCachingStrategy.RecycleElementAndDataTemplate); + listView.ItemsSource = new object[] { 0d, "test", (short)0 }; + + listView.ItemTemplate = new TestDTS(); + Assert.IsInstanceOf(listView.TemplatedItems[0]); + Assert.IsInstanceOf(listView.TemplatedItems[1]); + Assert.IsInstanceOf(listView.TemplatedItems[2]); } [Test] @@ -89,6 +142,17 @@ public void NestingThrowsException() var dts = new TestDTS(); Assert.Throws(() => dts.SelectTemplate((byte)0, null)); } + + [Test] + public void TabbedPageSupport() + { + var tabbedPage = new TabbedPage(); + tabbedPage.ItemsSource = new object[] { new View(), "test" }; + + tabbedPage.ItemTemplate = new TestPageDTS(); + Assert.IsInstanceOf(tabbedPage.Children[0]); + Assert.IsInstanceOf(tabbedPage.Children[1]); + } } [TestFixture] From 18be4a0faaa0f0a49cfdb470b6f907843b95544d Mon Sep 17 00:00:00 2001 From: BryanRoth Date: Mon, 17 Jan 2022 12:50:59 -0500 Subject: [PATCH 03/19] Fix null reference on DataTemplate recycle storage. --- Xamarin.Forms.Core/DataTemplateSelector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Xamarin.Forms.Core/DataTemplateSelector.cs b/Xamarin.Forms.Core/DataTemplateSelector.cs index 7e284f7c0ff..f80d7c44cb0 100644 --- a/Xamarin.Forms.Core/DataTemplateSelector.cs +++ b/Xamarin.Forms.Core/DataTemplateSelector.cs @@ -24,7 +24,7 @@ public DataTemplate SelectTemplate(object item, BindableObject container) throw new NotSupportedException( "DataTemplateSelector.OnSelectTemplate must not return another DataTemplateSelector"); - if (recycle) + if (recycle && dataTemplate != null) { if (!dataTemplate.CanRecycle) throw new NotSupportedException( From 16c4ea53b918bd66f94956a34f730dcff3c3828b Mon Sep 17 00:00:00 2001 From: BryanRoth Date: Mon, 17 Jan 2022 12:52:52 -0500 Subject: [PATCH 04/19] Validate BindingContext property set with null Template selected. --- Xamarin.Forms.Core.UnitTests/DataTemplateSelectorTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Xamarin.Forms.Core.UnitTests/DataTemplateSelectorTests.cs b/Xamarin.Forms.Core.UnitTests/DataTemplateSelectorTests.cs index a9807e4f1ab..bb9049a655d 100644 --- a/Xamarin.Forms.Core.UnitTests/DataTemplateSelectorTests.cs +++ b/Xamarin.Forms.Core.UnitTests/DataTemplateSelectorTests.cs @@ -90,7 +90,6 @@ protected override DataTemplate OnSelectTemplate(object item, BindableObject con if (item is View) return templateThree; - //if (item is Page) return null; } @@ -152,6 +151,7 @@ public void TabbedPageSupport() tabbedPage.ItemTemplate = new TestPageDTS(); Assert.IsInstanceOf(tabbedPage.Children[0]); Assert.IsInstanceOf(tabbedPage.Children[1]); + Assert.AreEqual(tabbedPage.Children[1].BindingContext, "test"); } } From 4fe81b021581c5cd4fcf737b7852ad40b8bc8582 Mon Sep 17 00:00:00 2001 From: BryanRoth Date: Tue, 18 Jan 2022 09:04:05 -0500 Subject: [PATCH 05/19] Fix CollectionView DataTemplateSelector returns null. --- .../CollectionView/TemplatedItemViewHolder.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Xamarin.Forms.Platform.Android/CollectionView/TemplatedItemViewHolder.cs b/Xamarin.Forms.Platform.Android/CollectionView/TemplatedItemViewHolder.cs index e137a13baa2..9de21436f44 100644 --- a/Xamarin.Forms.Platform.Android/CollectionView/TemplatedItemViewHolder.cs +++ b/Xamarin.Forms.Platform.Android/CollectionView/TemplatedItemViewHolder.cs @@ -6,8 +6,16 @@ namespace Xamarin.Forms.Platform.Android { public class TemplatedItemViewHolder : SelectableViewHolder - { - readonly ItemContentView _itemContentView; + { + private readonly static DataTemplate _defaultDataTemplate = + new Lazy( () => new DataTemplate( () => + { + var l = new Label(); + l.SetBinding( Label.TextProperty, "." ); + return l; + } ) ).Value; + + readonly ItemContentView _itemContentView; readonly DataTemplate _template; DataTemplate _selectedTemplate; @@ -48,11 +56,12 @@ public void Recycle(ItemsView itemsView) public void Bind(object itemBindingContext, ItemsView itemsView, Action reportMeasure = null, Size? size = null) { - var template = _template.SelectDataTemplate(itemBindingContext, itemsView); + var template = _template.SelectDataTemplate(itemBindingContext, itemsView) + ?? _defaultDataTemplate; var templateChanging = template != _selectedTemplate; - if (templateChanging) + if (templateChanging) { // Clean up any content we're still holding on to _itemContentView.Recycle(); From 2c9325af90d5de45f52d60a2be050b3997a665fd Mon Sep 17 00:00:00 2001 From: BryanRoth Date: Tue, 18 Jan 2022 11:41:50 -0500 Subject: [PATCH 06/19] iOS, ListView with Recycle, handle DataTemplateSelector return null. --- Xamarin.Forms.Platform.MacOS/Renderers/ListViewDataSource.cs | 5 +++++ Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/ListViewDataSource.cs b/Xamarin.Forms.Platform.MacOS/Renderers/ListViewDataSource.cs index e25fc70efca..9e34b3b4d45 100644 --- a/Xamarin.Forms.Platform.MacOS/Renderers/ListViewDataSource.cs +++ b/Xamarin.Forms.Platform.MacOS/Renderers/ListViewDataSource.cs @@ -230,6 +230,11 @@ int TemplateIdForPath(NSIndexPath indexPath) var item = templatedList.ListProxy[(int)indexPath.Item]; itemTemplate = selector.SelectTemplate(item, List); + + // check again to guard against DataTemplateSelectors that return null + if (itemTemplate == null) + return DefaultItemTemplateId; + int key; if (!_templateToId.TryGetValue(itemTemplate, out key)) { diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs index 3e15443f6aa..57ad7285d20 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs @@ -1425,6 +1425,11 @@ int TemplateIdForPath(NSIndexPath indexPath) var item = templatedList.ListProxy[indexPath.Row]; itemTemplate = selector.SelectTemplate(item, List); + + // check again to guard against DataTemplateSelectors that return null + if (itemTemplate == null) + return DefaultItemTemplateId; + int key; if (!_templateToId.TryGetValue(itemTemplate, out key)) { From 8eafd9b248710b00786808509efbd030d535facd Mon Sep 17 00:00:00 2001 From: BryanRoth Date: Fri, 21 Jan 2022 07:20:51 -0500 Subject: [PATCH 07/19] CollectionView DataTemplateSelector returns null for iOS/Tizen/UAP --- .../Native/CollectionView/EmptyItemAdaptor.cs | 14 ++-- .../CollectionView/ItemTemplateAdaptor.cs | 70 +++++++++---------- .../CollectionView/ItemContentControl.cs | 16 +++-- .../CollectionView/ItemsViewRenderer.cs | 5 +- .../CollectionView/TemplateHelpers.cs | 19 +++-- .../CollectionView/TemplatedCell.cs | 8 ++- 6 files changed, 72 insertions(+), 60 deletions(-) diff --git a/Xamarin.Forms.Platform.Tizen/Native/CollectionView/EmptyItemAdaptor.cs b/Xamarin.Forms.Platform.Tizen/Native/CollectionView/EmptyItemAdaptor.cs index 62fde46fa66..2e05260ab3e 100644 --- a/Xamarin.Forms.Platform.Tizen/Native/CollectionView/EmptyItemAdaptor.cs +++ b/Xamarin.Forms.Platform.Tizen/Native/CollectionView/EmptyItemAdaptor.cs @@ -1,6 +1,7 @@ using System.Collections; using System.Collections.Generic; using ElmSharp; +using Xamarin.Forms.Internals; using ESize = ElmSharp.Size; using XLabel = Xamarin.Forms.Label; @@ -44,15 +45,10 @@ public override ESize MeasureItem(int widthConstraint, int heightConstraint) public override EvasObject CreateNativeView(int index, EvasObject parent) { - View emptyView = null; - if (ItemTemplate is DataTemplateSelector selector) - { - emptyView = selector.SelectTemplate(this[index], Element).CreateContent() as View; - } - else - { - emptyView = ItemTemplate.CreateContent() as View; - } + var itemTemplate = ItemTemplate?.SelectDataTemplate(this[index], Element) ?? + ItemDefaultTemplateAdaptor.DefaultTemplate; + + View emptyView = itemTemplate.CreateContent() as View; var header = CreateHeaderView(); var footer = CreateFooterView(); diff --git a/Xamarin.Forms.Platform.Tizen/Native/CollectionView/ItemTemplateAdaptor.cs b/Xamarin.Forms.Platform.Tizen/Native/CollectionView/ItemTemplateAdaptor.cs index 28387c30763..061bf39befa 100644 --- a/Xamarin.Forms.Platform.Tizen/Native/CollectionView/ItemTemplateAdaptor.cs +++ b/Xamarin.Forms.Platform.Tizen/Native/CollectionView/ItemTemplateAdaptor.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using ElmSharp; +using Xamarin.Forms.Internals; using ESize = ElmSharp.Size; using XLabel = Xamarin.Forms.Label; @@ -10,6 +11,24 @@ namespace Xamarin.Forms.Platform.Tizen.Native { public class ItemDefaultTemplateAdaptor : ItemTemplateAdaptor { + public readonly static DataTemplate DefaultTemplate = + new Lazy(() => new DataTemplate( + () => + { + var label = new XLabel { TextColor = Color.Black, }; + + label.SetBinding( + XLabel.TextProperty, + new Binding(".", converter: new ToTextConverter())); + + return new StackLayout + { + BackgroundColor = Color.White, + Padding = 30, + Children = { label } + }; + })).Value; + class ToTextConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) @@ -22,24 +41,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn public ItemDefaultTemplateAdaptor(ItemsView itemsView) : base(itemsView) { - ItemTemplate = new DataTemplate(() => - { - var label = new XLabel - { - TextColor = Color.Black, - }; - label.SetBinding(XLabel.TextProperty, new Binding(".", converter: new ToTextConverter())); - - return new StackLayout - { - BackgroundColor = Color.White, - Padding = 30, - Children = - { - label - } - }; - }); + ItemTemplate = DefaultTemplate; } } @@ -84,22 +86,20 @@ public override object GetViewCategory(int index) { if (ItemTemplate is DataTemplateSelector selector) { - return selector.SelectTemplate(this[index], Element); + // return the selected, or default if returns null. + return selector.SelectTemplate(this[index], Element) ?? + ItemDefaultTemplateAdaptor.DefaultTemplate; } return base.GetViewCategory(index); } public override EvasObject CreateNativeView(int index, EvasObject parent) { - View view = null; - if (ItemTemplate is DataTemplateSelector selector) - { - view = selector.SelectTemplate(this[index], Element).CreateContent() as View; - } - else - { - view = ItemTemplate.CreateContent() as View; - } + var itemTemplate = ItemTemplate?.SelectDataTemplate(this[index], Element) ?? + ItemDefaultTemplateAdaptor.DefaultTemplate; + + View view = itemTemplate.CreateContent() as View; + var renderer = Platform.GetOrCreateRenderer(view); var native = renderer.NativeView; @@ -185,15 +185,11 @@ public override ESize MeasureItem(int index, int widthConstraint, int heightCons return createdView.Measure(Forms.ConvertToScaledDP(widthConstraint), Forms.ConvertToScaledDP(heightConstraint), MeasureFlags.IncludeMargins).Request.ToPixel(); } - View view = null; - if (ItemTemplate is DataTemplateSelector selector) - { - view = selector.SelectTemplate(this[index], Element).CreateContent() as View; - } - else - { - view = ItemTemplate.CreateContent() as View; - } + var itemTemplate = ItemTemplate?.SelectDataTemplate(this[index], Element) ?? + ItemDefaultTemplateAdaptor.DefaultTemplate; + + View view = itemTemplate.CreateContent() as View; + using (var renderer = Platform.GetOrCreateRenderer(view)) { view.Parent = Element; diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs b/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs index 2824099c566..31c1b3fb30d 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs @@ -9,6 +9,13 @@ namespace Xamarin.Forms.Platform.UWP { public class ItemContentControl : ContentControl { + readonly static DataTemplate _defaultTemplate = + new Lazy(() => new DataTemplate(() => + { + var l = new Label(); + l.SetBinding(Label.TextProperty, "."); + return l; + })).Value; VisualElement _visualElement; IVisualElementRenderer _renderer; DataTemplate _currentTemplate; @@ -130,13 +137,14 @@ internal void Realize() return; } - if (_renderer?.ContainerElement == null || _currentTemplate != formsTemplate - || formsTemplate is DataTemplateSelector) + var template = formsTemplate.SelectDataTemplate(dataContext, container) ?? _defaultTemplate; + + if (_renderer?.ContainerElement == null || _currentTemplate != template) { // If the content has never been realized (i.e., this is a new instance), // or if we need to switch DataTemplates (because this instance is being recycled) // then we'll need to create the content from the template - _visualElement = formsTemplate.CreateContent(dataContext, container) as VisualElement; + _visualElement = template.CreateContent() as VisualElement; _visualElement.BindingContext = dataContext; _renderer = Platform.CreateRenderer(_visualElement); Platform.SetRenderer(_visualElement, _renderer); @@ -150,7 +158,7 @@ internal void Realize() SetNativeStateConsistent(_visualElement); // Keep track of the template in case this instance gets reused later - _currentTemplate = formsTemplate; + _currentTemplate = template; } else { diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs index cdcb5e64470..c8738bef9aa 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs @@ -438,7 +438,9 @@ protected virtual void UpdateItemsLayout() FrameworkElement RealizeEmptyViewTemplate(object bindingContext, DataTemplate emptyViewTemplate) { - if (emptyViewTemplate == null) + var template = emptyViewTemplate?.SelectDataTemplate(bindingContext, null); + + if (template == null) { return new TextBlock { @@ -448,7 +450,6 @@ FrameworkElement RealizeEmptyViewTemplate(object bindingContext, DataTemplate em }; } - var template = emptyViewTemplate.SelectDataTemplate(bindingContext, null); var view = template.CreateContent() as View; view.BindingContext = bindingContext; diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/TemplateHelpers.cs b/Xamarin.Forms.Platform.iOS/CollectionView/TemplateHelpers.cs index d17fcc65e03..69039a3e3cd 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/TemplateHelpers.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/TemplateHelpers.cs @@ -6,6 +6,14 @@ namespace Xamarin.Forms.Platform.iOS { internal static class TemplateHelpers { + public readonly static DataTemplate DefaultTemplate = + new Lazy(() => new DataTemplate(() => + { + var l = new Label(); + l.SetBinding(Label.TextProperty, "."); + return l; + })).Value; + public static IVisualElementRenderer CreateRenderer(View view) { if (view == null) @@ -24,13 +32,14 @@ public static IVisualElementRenderer CreateRenderer(View view) public static (UIView NativeView, VisualElement FormsElement) RealizeView(object view, DataTemplate viewTemplate, ItemsView itemsView) { - if (viewTemplate != null) - { - // Run this through the extension method in case it's really a DataTemplateSelector - viewTemplate = viewTemplate.SelectDataTemplate(view, itemsView); + // Run this through the extension method in case it's really a DataTemplateSelector + var itemTemplate = viewTemplate?.SelectDataTemplate(view, itemsView) ?? + TemplateHelpers.DefaultTemplate; + if (itemTemplate != null) + { // We have a template; turn it into a Forms view - var templateElement = viewTemplate.CreateContent() as View; + var templateElement = itemTemplate.CreateContent() as View; // Make sure the Visual property is available when the renderer is created PropertyPropagationExtensions.PropagatePropertyChanged(null, templateElement, itemsView); diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/TemplatedCell.cs b/Xamarin.Forms.Platform.iOS/CollectionView/TemplatedCell.cs index 4e35edb7253..fb68ca6b6f3 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/TemplatedCell.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/TemplatedCell.cs @@ -96,7 +96,8 @@ public void Bind(DataTemplate template, object bindingContext, ItemsView itemsVi var oldElement = VisualElementRenderer?.Element; // Run this through the extension method in case it's really a DataTemplateSelector - var itemTemplate = template.SelectDataTemplate(bindingContext, itemsView); + var itemTemplate = template?.SelectDataTemplate(bindingContext, itemsView) ?? + TemplateHelpers.DefaultTemplate; if (itemTemplate != CurrentTemplate) { @@ -135,6 +136,9 @@ public void Bind(DataTemplate template, object bindingContext, ItemsView itemsVi BackgroundColor = UIColor.Clear }; } + + // Keep track of the template in case this instance gets reused later + CurrentTemplate = itemTemplate; } else { @@ -155,8 +159,6 @@ public void Bind(DataTemplate template, object bindingContext, ItemsView itemsVi } } } - - CurrentTemplate = itemTemplate; } public void Unbind() From 8abeb364994dd6790717d2bbbc6d4efad187bd15 Mon Sep 17 00:00:00 2001 From: BryanRoth Date: Fri, 21 Jan 2022 10:17:07 -0500 Subject: [PATCH 08/19] Remove unused DataTemplate code in Flyout. This code is never used because the protected members are never set, thus !null test is never true, and the class has not been specialized. (Cut out and compile with no errors). --- .../Renderers/ShellFlyoutRecyclerAdapter.cs | 15 +-------------- .../Renderers/ShellTableViewSource.cs | 14 -------------- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs index c664cf953b4..de30d93683e 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs @@ -36,10 +36,6 @@ public ShellFlyoutRecyclerAdapter(IShellContext shellContext, Action se IShellController ShellController => (IShellController)Shell; - protected virtual DataTemplate DefaultItemTemplate => null; - - protected virtual DataTemplate DefaultMenuItemTemplate => null; - public override int GetItemViewType(int position) { return _listItems[position].Index; @@ -59,18 +55,9 @@ DataTemplate GetDataTemplate(int viewTypeId) } DataTemplate dataTemplate = ShellController.GetFlyoutItemDataTemplate(item.Element); - if (item.Element is IMenuItemController) - { - if (DefaultMenuItemTemplate != null && Shell.MenuItemTemplate == dataTemplate) - dataTemplate = DefaultMenuItemTemplate; - } - else - { - if (DefaultItemTemplate != null && Shell.ItemTemplate == dataTemplate) - dataTemplate = DefaultItemTemplate; - } var template = dataTemplate.SelectDataTemplate(item.Element, Shell); + return template; } diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ShellTableViewSource.cs b/Xamarin.Forms.Platform.iOS/Renderers/ShellTableViewSource.cs index 420485d4546..ebf35441110 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/ShellTableViewSource.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/ShellTableViewSource.cs @@ -43,10 +43,6 @@ public List> Groups } } - protected virtual DataTemplate DefaultItemTemplate => null; - - protected virtual DataTemplate DefaultMenuItemTemplate => null; - internal void ReSyncCache() { var newGroups = ((IShellController)_context.Shell).GenerateFlyoutGrouping(); @@ -138,16 +134,6 @@ public override UITableViewCell GetCell(UITableView tableView, NSIndexPath index var context = Groups[section][row]; DataTemplate template = ShellController.GetFlyoutItemDataTemplate(context); - if (context is IMenuItemController) - { - if (DefaultMenuItemTemplate != null && _context.Shell.MenuItemTemplate == template) - template = DefaultMenuItemTemplate; - } - else - { - if (DefaultItemTemplate != null && _context.Shell.ItemTemplate == template) - template = DefaultItemTemplate; - } var cellId = ((IDataTemplateController)template.SelectDataTemplate(context, _context.Shell)).IdString; From a722a99bb8168bdf7c26320806b3ef36297bf45f Mon Sep 17 00:00:00 2001 From: BryanRoth Date: Fri, 21 Jan 2022 10:17:30 -0500 Subject: [PATCH 09/19] Rename only --- .../CollectionView/TemplatedItemViewHolder.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Xamarin.Forms.Platform.Android/CollectionView/TemplatedItemViewHolder.cs b/Xamarin.Forms.Platform.Android/CollectionView/TemplatedItemViewHolder.cs index 9de21436f44..ae8829f0643 100644 --- a/Xamarin.Forms.Platform.Android/CollectionView/TemplatedItemViewHolder.cs +++ b/Xamarin.Forms.Platform.Android/CollectionView/TemplatedItemViewHolder.cs @@ -7,7 +7,7 @@ namespace Xamarin.Forms.Platform.Android { public class TemplatedItemViewHolder : SelectableViewHolder { - private readonly static DataTemplate _defaultDataTemplate = + private readonly static DataTemplate _defaultTemplate = new Lazy( () => new DataTemplate( () => { var l = new Label(); @@ -56,8 +56,7 @@ public void Recycle(ItemsView itemsView) public void Bind(object itemBindingContext, ItemsView itemsView, Action reportMeasure = null, Size? size = null) { - var template = _template.SelectDataTemplate(itemBindingContext, itemsView) - ?? _defaultDataTemplate; + var template = _template?.SelectDataTemplate(itemBindingContext, itemsView) ?? _defaultTemplate; var templateChanging = template != _selectedTemplate; From bb6937fb895c48ef2510833425b1c1ad905ed100 Mon Sep 17 00:00:00 2001 From: BryanRoth Date: Fri, 21 Jan 2022 14:20:18 -0500 Subject: [PATCH 10/19] Shell support for DataTemplateSelector returns null --- Xamarin.Forms.Core/Shell/Shell.cs | 19 ++++++++----------- .../Renderers/ShellFlyoutRecyclerAdapter.cs | 10 ++-------- .../Renderers/ShellSearchResultsRenderer.cs | 8 +++----- .../Renderers/ShellTableViewSource.cs | 2 +- 4 files changed, 14 insertions(+), 25 deletions(-) diff --git a/Xamarin.Forms.Core/Shell/Shell.cs b/Xamarin.Forms.Core/Shell/Shell.cs index 909cada11a3..67aed8769d4 100644 --- a/Xamarin.Forms.Core/Shell/Shell.cs +++ b/Xamarin.Forms.Core/Shell/Shell.cs @@ -255,7 +255,7 @@ internal static BindableObject GetBindableObjectWithFlyoutItemTemplate(BindableO DataTemplate IShellController.GetFlyoutItemDataTemplate(BindableObject bo) { - BindableProperty bp = null; + BindableProperty bp; string textBinding; string iconBinding; var bindableObjectWithTemplate = GetBindableObjectWithFlyoutItemTemplate(bo); @@ -273,17 +273,14 @@ DataTemplate IShellController.GetFlyoutItemDataTemplate(BindableObject bo) iconBinding = "FlyoutIcon"; } - if (bindableObjectWithTemplate.IsSet(bp)) - { - return (DataTemplate)bindableObjectWithTemplate.GetValue(bp); - } - - if (IsSet(bp)) - { - return (DataTemplate)GetValue(bp); - } + DataTemplate dataTemplate = (DataTemplate)(bindableObjectWithTemplate.IsSet(bp) ? + bindableObjectWithTemplate.GetValue(bp) : + IsSet(bp) ? + GetValue(bp) : + null); - return BaseShellItem.CreateDefaultFlyoutItemCell(textBinding, iconBinding); + return dataTemplate?.SelectDataTemplate( bo, this ) ?? + BaseShellItem.CreateDefaultFlyoutItemCell(textBinding, iconBinding); } event EventHandler IShellController.StructureChanged diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs index de30d93683e..74aef236b53 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs @@ -54,11 +54,7 @@ DataTemplate GetDataTemplate(int viewTypeId) } } - DataTemplate dataTemplate = ShellController.GetFlyoutItemDataTemplate(item.Element); - - var template = dataTemplate.SelectDataTemplate(item.Element, Shell); - - return template; + return ShellController.GetFlyoutItemDataTemplate(item.Element); } public override void OnViewRecycled(Java.Lang.Object holder) @@ -163,9 +159,7 @@ public override AView FocusSearch([GeneratedEnum] FocusSearchDirection direction public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType) { - var template = GetDataTemplate(viewType); - - var content = (View)template.CreateContent(); + var content = (View)GetDataTemplate(viewType).CreateContent(); var linearLayout = new LinearLayoutWithFocus(parent.Context) { diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ShellSearchResultsRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/ShellSearchResultsRenderer.cs index b231079b199..da91d4efb16 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/ShellSearchResultsRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/ShellSearchResultsRenderer.cs @@ -83,12 +83,10 @@ public override UITableViewCell GetCell(UITableView tableView, NSIndexPath index int row = indexPath.Row; var context = proxy[row]; - var template = SearchHandler.ItemTemplate; + var template = SearchHandler.ItemTemplate?.SelectDataTemplate(context, _context.Shell) ?? + DefaultTemplate; - if (template == null) - template = DefaultTemplate; - - var cellId = ((IDataTemplateController)template.SelectDataTemplate(context, _context.Shell)).IdString; + var cellId = ((IDataTemplateController)template).IdString; var cell = (UIContainerCell)tableView.DequeueReusableCell(cellId); diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ShellTableViewSource.cs b/Xamarin.Forms.Platform.iOS/Renderers/ShellTableViewSource.cs index ebf35441110..43eff5d1e49 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/ShellTableViewSource.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/ShellTableViewSource.cs @@ -135,7 +135,7 @@ public override UITableViewCell GetCell(UITableView tableView, NSIndexPath index DataTemplate template = ShellController.GetFlyoutItemDataTemplate(context); - var cellId = ((IDataTemplateController)template.SelectDataTemplate(context, _context.Shell)).IdString; + var cellId = ((IDataTemplateController)template).IdString; UIContainerCell cell; if (!_cells.TryGetValue(context, out cell)) From 686914d6df6b50472a3a4cc506e7e3ef7025eb77 Mon Sep 17 00:00:00 2001 From: BryanRoth Date: Fri, 21 Jan 2022 14:45:43 -0500 Subject: [PATCH 11/19] Test for null ItemTemplate and shorten null coalescing statement. --- Xamarin.Forms.Core/TemplatedItemsList.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Xamarin.Forms.Core/TemplatedItemsList.cs b/Xamarin.Forms.Core/TemplatedItemsList.cs index 2ef7e361254..daa5ed2596a 100644 --- a/Xamarin.Forms.Core/TemplatedItemsList.cs +++ b/Xamarin.Forms.Core/TemplatedItemsList.cs @@ -532,14 +532,13 @@ public int IndexOf(TItem item) [EditorBrowsable(EditorBrowsableState.Never)] public DataTemplate SelectDataTemplate(object item) { - return ItemTemplate.SelectDataTemplate(item, _itemsView); + return ItemTemplate?.SelectDataTemplate(item, _itemsView); } public TItem ActivateContent(int index, object item) { - TItem content = ItemTemplate != null ? - (TItem)ItemTemplate.CreateContent(item, _itemsView) ?? _itemsView.CreateDefault(item) : - _itemsView.CreateDefault(item); + TItem content = (TItem)ItemTemplate?.CreateContent(item, _itemsView) ?? + _itemsView.CreateDefault(item); content = UpdateContent(content, index, item); From 295b84dd7bc7bc0b3863d98bdce5e4ef11430a85 Mon Sep 17 00:00:00 2001 From: BryanRoth Date: Sun, 23 Jan 2022 21:08:40 -0500 Subject: [PATCH 12/19] Move default DataTemplate to single source and update layout to center label. --- Xamarin.Forms.Core/DataTemplateExtensions.cs | 22 +++++++++++++++++++ .../CollectionView/TemplatedItemViewHolder.cs | 11 ++-------- .../CollectionView/ItemContentControl.cs | 10 ++------- .../CollectionView/TemplateHelpers.cs | 10 +-------- .../CollectionView/TemplatedCell.cs | 2 +- 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/Xamarin.Forms.Core/DataTemplateExtensions.cs b/Xamarin.Forms.Core/DataTemplateExtensions.cs index 0f024649325..8e0104118f3 100644 --- a/Xamarin.Forms.Core/DataTemplateExtensions.cs +++ b/Xamarin.Forms.Core/DataTemplateExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.ComponentModel; namespace Xamarin.Forms.Internals @@ -19,4 +20,25 @@ public static object CreateContent(this DataTemplate self, object item, Bindable return self.SelectDataTemplate(item, container)?.CreateContent(); } } + + [EditorBrowsable(EditorBrowsableState.Never)] + public static class DataTemplateHelpers + { + public readonly static DataTemplate DefaultContentTemplate = + new Lazy( + () => new DataTemplate(CreateCoreContent)).Value; + + static View CreateCoreContent() + { + var label = new Label(); + label.SetBinding(Label.TextProperty, "."); + + return new StackLayout() + { + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center, + Children = { label } + }; + } + } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/CollectionView/TemplatedItemViewHolder.cs b/Xamarin.Forms.Platform.Android/CollectionView/TemplatedItemViewHolder.cs index ae8829f0643..65726b885e9 100644 --- a/Xamarin.Forms.Platform.Android/CollectionView/TemplatedItemViewHolder.cs +++ b/Xamarin.Forms.Platform.Android/CollectionView/TemplatedItemViewHolder.cs @@ -7,14 +7,6 @@ namespace Xamarin.Forms.Platform.Android { public class TemplatedItemViewHolder : SelectableViewHolder { - private readonly static DataTemplate _defaultTemplate = - new Lazy( () => new DataTemplate( () => - { - var l = new Label(); - l.SetBinding( Label.TextProperty, "." ); - return l; - } ) ).Value; - readonly ItemContentView _itemContentView; readonly DataTemplate _template; DataTemplate _selectedTemplate; @@ -56,7 +48,8 @@ public void Recycle(ItemsView itemsView) public void Bind(object itemBindingContext, ItemsView itemsView, Action reportMeasure = null, Size? size = null) { - var template = _template?.SelectDataTemplate(itemBindingContext, itemsView) ?? _defaultTemplate; + var template = _template?.SelectDataTemplate(itemBindingContext, itemsView) ?? + DataTemplateHelpers.DefaultContentTemplate; var templateChanging = template != _selectedTemplate; diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs b/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs index 31c1b3fb30d..8bfb6bea916 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs @@ -9,13 +9,6 @@ namespace Xamarin.Forms.Platform.UWP { public class ItemContentControl : ContentControl { - readonly static DataTemplate _defaultTemplate = - new Lazy(() => new DataTemplate(() => - { - var l = new Label(); - l.SetBinding(Label.TextProperty, "."); - return l; - })).Value; VisualElement _visualElement; IVisualElementRenderer _renderer; DataTemplate _currentTemplate; @@ -137,7 +130,8 @@ internal void Realize() return; } - var template = formsTemplate.SelectDataTemplate(dataContext, container) ?? _defaultTemplate; + var template = formsTemplate.SelectDataTemplate(dataContext, container) ?? + DataTemplateHelpers.DefaultContentTemplate; if (_renderer?.ContainerElement == null || _currentTemplate != template) { diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/TemplateHelpers.cs b/Xamarin.Forms.Platform.iOS/CollectionView/TemplateHelpers.cs index 69039a3e3cd..9177bbda20f 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/TemplateHelpers.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/TemplateHelpers.cs @@ -6,14 +6,6 @@ namespace Xamarin.Forms.Platform.iOS { internal static class TemplateHelpers { - public readonly static DataTemplate DefaultTemplate = - new Lazy(() => new DataTemplate(() => - { - var l = new Label(); - l.SetBinding(Label.TextProperty, "."); - return l; - })).Value; - public static IVisualElementRenderer CreateRenderer(View view) { if (view == null) @@ -34,7 +26,7 @@ public static (UIView NativeView, VisualElement FormsElement) RealizeView(object { // Run this through the extension method in case it's really a DataTemplateSelector var itemTemplate = viewTemplate?.SelectDataTemplate(view, itemsView) ?? - TemplateHelpers.DefaultTemplate; + DataTemplateHelpers.DefaultContentTemplate; if (itemTemplate != null) { diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/TemplatedCell.cs b/Xamarin.Forms.Platform.iOS/CollectionView/TemplatedCell.cs index fb68ca6b6f3..c6296c8e520 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/TemplatedCell.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/TemplatedCell.cs @@ -97,7 +97,7 @@ public void Bind(DataTemplate template, object bindingContext, ItemsView itemsVi // Run this through the extension method in case it's really a DataTemplateSelector var itemTemplate = template?.SelectDataTemplate(bindingContext, itemsView) ?? - TemplateHelpers.DefaultTemplate; + DataTemplateHelpers.DefaultContentTemplate; if (itemTemplate != CurrentTemplate) { From 1db1c3e85d1effd7ee7bb40d6a39681a6740d8a6 Mon Sep 17 00:00:00 2001 From: BryanRoth Date: Sun, 23 Jan 2022 21:11:44 -0500 Subject: [PATCH 13/19] Use default DataTemplate to create default Page for MultiPage implementations. --- Xamarin.Forms.Core/CarouselPage.cs | 7 ++----- Xamarin.Forms.Core/DataTemplateExtensions.cs | 10 ++++++++++ Xamarin.Forms.Core/TabbedPage.cs | 7 ++----- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Xamarin.Forms.Core/CarouselPage.cs b/Xamarin.Forms.Core/CarouselPage.cs index b7d494cb4b0..12158bfcebf 100644 --- a/Xamarin.Forms.Core/CarouselPage.cs +++ b/Xamarin.Forms.Core/CarouselPage.cs @@ -1,4 +1,5 @@ using System; +using Xamarin.Forms.Internals; using Xamarin.Forms.Platform; namespace Xamarin.Forms @@ -20,11 +21,7 @@ public CarouselPage() protected override ContentPage CreateDefault(object item) { - var page = new ContentPage(); - if (item != null) - page.Title = item.ToString(); - - return page; + return (ContentPage)DataTemplateHelpers.DefaultPageTemplate.CreateContent(); } } } \ No newline at end of file diff --git a/Xamarin.Forms.Core/DataTemplateExtensions.cs b/Xamarin.Forms.Core/DataTemplateExtensions.cs index 8e0104118f3..4b93f5fc852 100644 --- a/Xamarin.Forms.Core/DataTemplateExtensions.cs +++ b/Xamarin.Forms.Core/DataTemplateExtensions.cs @@ -28,6 +28,16 @@ public static class DataTemplateHelpers new Lazy( () => new DataTemplate(CreateCoreContent)).Value; + public readonly static DataTemplate DefaultPageTemplate = + new Lazy( + () => new DataTemplate(() => + { + var page = new ContentPage() { Content = CreateCoreContent() }; + page.SetBinding(Page.TitleProperty, "."); + + return page; + })).Value; + static View CreateCoreContent() { var label = new Label(); diff --git a/Xamarin.Forms.Core/TabbedPage.cs b/Xamarin.Forms.Core/TabbedPage.cs index 053f25de59a..1e8d9c635f7 100644 --- a/Xamarin.Forms.Core/TabbedPage.cs +++ b/Xamarin.Forms.Core/TabbedPage.cs @@ -1,4 +1,5 @@ using System; +using Xamarin.Forms.Internals; using Xamarin.Forms.Platform; namespace Xamarin.Forms @@ -49,11 +50,7 @@ public Color SelectedTabColor protected override Page CreateDefault(object item) { - var page = new Page(); - if (item != null) - page.Title = item.ToString(); - - return page; + return (Page)DataTemplateHelpers.DefaultPageTemplate.CreateContent(); } public TabbedPage() From 3d72852adfce5bd799f66e978bce993f99d54738 Mon Sep 17 00:00:00 2001 From: BryanRoth Date: Sun, 23 Jan 2022 21:50:55 -0500 Subject: [PATCH 14/19] Handle null from DataTemplateSelectors. --- Xamarin.Forms.Core/Shell/ShellContent.cs | 4 ++-- Xamarin.Forms.Core/TemplatedItemsList.cs | 7 +++++-- .../Renderers/ShellSearchViewAdapter.cs | 3 ++- .../Native/CollectionView/ItemTemplateAdaptor.cs | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Xamarin.Forms.Core/Shell/ShellContent.cs b/Xamarin.Forms.Core/Shell/ShellContent.cs index c174a0a2a43..d6b168c55ab 100644 --- a/Xamarin.Forms.Core/Shell/ShellContent.cs +++ b/Xamarin.Forms.Core/Shell/ShellContent.cs @@ -53,8 +53,8 @@ public DataTemplate ContentTemplate Page IShellContentController.GetOrCreateContent() { - var template = ContentTemplate; var content = Content; + var template = ContentTemplate?.SelectDataTemplate(content, this); Page result = null; if (template == null) @@ -64,7 +64,7 @@ Page IShellContentController.GetOrCreateContent() } else { - result = ContentCache ?? (Page)template.CreateContent(content, this); + result = ContentCache ?? (Page)template.CreateContent(); ContentCache = result; } diff --git a/Xamarin.Forms.Core/TemplatedItemsList.cs b/Xamarin.Forms.Core/TemplatedItemsList.cs index daa5ed2596a..9878a60595a 100644 --- a/Xamarin.Forms.Core/TemplatedItemsList.cs +++ b/Xamarin.Forms.Core/TemplatedItemsList.cs @@ -741,9 +741,12 @@ TemplatedItemsList InsertGrouped(object item, int index) groupProxy.BindingContext = item; - if (GroupHeaderTemplate != null) + var groupHeaderTemplate = + GroupHeaderTemplate?.SelectDataTemplate(groupProxy.ItemsSource, _itemsView); + + if (groupHeaderTemplate != null) { - groupProxy.HeaderContent = (TItem)GroupHeaderTemplate.CreateContent(groupProxy.ItemsSource, _itemsView); + groupProxy.HeaderContent = (TItem)groupHeaderTemplate.CreateContent(); groupProxy.HeaderContent.BindingContext = groupProxy.ItemsSource; //groupProxy.HeaderContent.BindingContext = groupProxy; //groupProxy.HeaderContent.SetBinding (BindingContextProperty, "ItemsSource"); diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellSearchViewAdapter.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellSearchViewAdapter.cs index 4cb5b11ff1b..aa5e2175eec 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellSearchViewAdapter.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellSearchViewAdapter.cs @@ -99,7 +99,8 @@ public override AView GetView(int position, AView convertView, ViewGroup parent) } else { - var template = _searchHandler.ItemTemplate ?? DefaultTemplate; + var template = _searchHandler.ItemTemplate?.SelectDataTemplate(item, _shellContext.Shell) ?? + DefaultTemplate; var view = (View)template.CreateContent(item, _shellContext.Shell); view.BindingContext = item; diff --git a/Xamarin.Forms.Platform.Tizen/Native/CollectionView/ItemTemplateAdaptor.cs b/Xamarin.Forms.Platform.Tizen/Native/CollectionView/ItemTemplateAdaptor.cs index 061bf39befa..43ea58ec32d 100644 --- a/Xamarin.Forms.Platform.Tizen/Native/CollectionView/ItemTemplateAdaptor.cs +++ b/Xamarin.Forms.Platform.Tizen/Native/CollectionView/ItemTemplateAdaptor.cs @@ -11,7 +11,7 @@ namespace Xamarin.Forms.Platform.Tizen.Native { public class ItemDefaultTemplateAdaptor : ItemTemplateAdaptor { - public readonly static DataTemplate DefaultTemplate = + public static readonly DataTemplate DefaultTemplate = new Lazy(() => new DataTemplate( () => { From 92b0caeff1482a0b6449ecbca9459f7eafd5525e Mon Sep 17 00:00:00 2001 From: BryanRoth Date: Sun, 23 Jan 2022 21:53:55 -0500 Subject: [PATCH 15/19] Move default DataTemplate construction into lazy singleton. --- Xamarin.Forms.Core/Shell/BaseShellItem.cs | 10 +++++++++- Xamarin.Forms.Core/Shell/Shell.cs | 13 +++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Xamarin.Forms.Core/Shell/BaseShellItem.cs b/Xamarin.Forms.Core/Shell/BaseShellItem.cs index 5cc5f313f5c..56e4a35a342 100644 --- a/Xamarin.Forms.Core/Shell/BaseShellItem.cs +++ b/Xamarin.Forms.Core/Shell/BaseShellItem.cs @@ -330,7 +330,15 @@ BindableObject NonImplicitParent } } - internal static DataTemplate CreateDefaultFlyoutItemCell(string textBinding, string iconBinding) + internal static DataTemplate MenuItemDefaultDataTemplate = + new Lazy( + () => CreateDefaultFlyoutItemCell("Text", "Icon") ).Value; + + internal static DataTemplate ItemDefaultDataTemplate = + new Lazy( + () => CreateDefaultFlyoutItemCell("Title", "FlyoutIcon")).Value; + + static DataTemplate CreateDefaultFlyoutItemCell(string textBinding, string iconBinding) { return new DataTemplate(() => { diff --git a/Xamarin.Forms.Core/Shell/Shell.cs b/Xamarin.Forms.Core/Shell/Shell.cs index 67aed8769d4..e149e237211 100644 --- a/Xamarin.Forms.Core/Shell/Shell.cs +++ b/Xamarin.Forms.Core/Shell/Shell.cs @@ -256,21 +256,19 @@ internal static BindableObject GetBindableObjectWithFlyoutItemTemplate(BindableO DataTemplate IShellController.GetFlyoutItemDataTemplate(BindableObject bo) { BindableProperty bp; - string textBinding; - string iconBinding; + DataTemplate defaultTemplate; + var bindableObjectWithTemplate = GetBindableObjectWithFlyoutItemTemplate(bo); if (bo is IMenuItemController) { bp = MenuItemTemplateProperty; - textBinding = "Text"; - iconBinding = "Icon"; + defaultTemplate = BaseShellItem.MenuItemDefaultDataTemplate; } else { bp = ItemTemplateProperty; - textBinding = "Title"; - iconBinding = "FlyoutIcon"; + defaultTemplate = BaseShellItem.ItemDefaultDataTemplate; } DataTemplate dataTemplate = (DataTemplate)(bindableObjectWithTemplate.IsSet(bp) ? @@ -279,8 +277,7 @@ DataTemplate IShellController.GetFlyoutItemDataTemplate(BindableObject bo) GetValue(bp) : null); - return dataTemplate?.SelectDataTemplate( bo, this ) ?? - BaseShellItem.CreateDefaultFlyoutItemCell(textBinding, iconBinding); + return dataTemplate?.SelectDataTemplate( bo, this ) ?? defaultTemplate; } event EventHandler IShellController.StructureChanged From 021ce6b43582edb3c27eda804c41c14129c97eb4 Mon Sep 17 00:00:00 2001 From: BryanRoth Date: Sun, 23 Jan 2022 22:30:18 -0500 Subject: [PATCH 16/19] Adjust method to always return a DataTemplate. --- .../Shell/NavigationView.cs | 128 ++++++++++-------- 1 file changed, 68 insertions(+), 60 deletions(-) diff --git a/Xamarin.Forms.Platform.Tizen/Shell/NavigationView.cs b/Xamarin.Forms.Platform.Tizen/Shell/NavigationView.cs index cbc77f9ed1b..ddfc21c96c0 100644 --- a/Xamarin.Forms.Platform.Tizen/Shell/NavigationView.cs +++ b/Xamarin.Forms.Platform.Tizen/Shell/NavigationView.cs @@ -257,19 +257,23 @@ DataTemplate GetFlyoutItemDataTemplate(BindableObject bo) { string textBinding; string iconBinding; + + DataTemplate template = null; + if (bo is IMenuItemController) { if (bo is MenuItem mi && mi.Parent != null && mi.Parent.IsSet(Shell.MenuItemTemplateProperty)) { - return Shell.GetMenuItemTemplate(mi.Parent); + template = Shell.GetMenuItemTemplate(mi.Parent); } else if (bo.IsSet(Shell.MenuItemTemplateProperty)) { - return Shell.GetMenuItemTemplate(bo); + template = Shell.GetMenuItemTemplate(bo); + } + else if (Shell.MenuItemTemplate != null) + { + template = Shell.MenuItemTemplate; } - - if (Shell.MenuItemTemplate != null) - return Shell.MenuItemTemplate; textBinding = "Text"; iconBinding = "Icon"; @@ -277,69 +281,73 @@ DataTemplate GetFlyoutItemDataTemplate(BindableObject bo) else { if (Shell.GetItemTemplate(bo) != null) - return Shell.GetItemTemplate(bo); + { + template = Shell.GetItemTemplate(bo); + } else if (Shell.ItemTemplate != null) - return Shell.ItemTemplate; + { + template = Shell.ItemTemplate; + } textBinding = "Title"; iconBinding = "FlyoutIcon"; } - return new DataTemplate(() => - { - var grid = new Grid - { - HeightRequest = this.GetFlyoutItemHeight(), - }; - - ColumnDefinitionCollection columnDefinitions = new ColumnDefinitionCollection(); - columnDefinitions.Add(new ColumnDefinition { Width = this.GetFlyoutIconColumnSize() }); - columnDefinitions.Add(new ColumnDefinition { Width = GridLength.Star }); - grid.ColumnDefinitions = columnDefinitions; - - var image = new Image - { - VerticalOptions = LayoutOptions.Center, - HorizontalOptions = LayoutOptions.Center, - HeightRequest = this.GetFlyoutIconSize(), - WidthRequest = this.GetFlyoutIconSize(), - Margin = new Thickness(this.GetFlyoutMargin(), 0, 0, 0), - }; - image.SetBinding(Image.SourceProperty, new Binding(iconBinding)); - grid.Children.Add(image); - - var label = new Label - { - FontSize = this.GetFlyoutItemFontSize(), - VerticalTextAlignment = TextAlignment.Center, - TextColor = Xamarin.Forms.Color.Black.MultiplyAlpha(0.87), - Margin = new Thickness(this.GetFlyoutMargin(), 0, 0, 0), - }; - label.SetBinding(Label.TextProperty, new Binding(textBinding)); - grid.Children.Add(label, 1, 0); - - var groups = new VisualStateGroupList(); - - var commonGroup = new VisualStateGroup(); - commonGroup.Name = "CommonStates"; - groups.Add(commonGroup); - - var normalState = new VisualState(); - normalState.Name = "Normal"; - commonGroup.States.Add(normalState); - - var selectedState = new VisualState(); - selectedState.Name = "Selected"; - selectedState.Setters.Add(new Setter + return template?.SelectDataTemplate(bo, Shell) ?? new DataTemplate( + () => { - Property = VisualElement.BackgroundColorProperty, - Value = new Color(0.95) + var grid = new Grid { HeightRequest = this.GetFlyoutItemHeight(), }; + + ColumnDefinitionCollection columnDefinitions = new ColumnDefinitionCollection(); + columnDefinitions.Add(new ColumnDefinition { Width = this.GetFlyoutIconColumnSize() }); + columnDefinitions.Add(new ColumnDefinition { Width = GridLength.Star }); + grid.ColumnDefinitions = columnDefinitions; + + var image = new Image + { + VerticalOptions = LayoutOptions.Center, + HorizontalOptions = LayoutOptions.Center, + HeightRequest = this.GetFlyoutIconSize(), + WidthRequest = this.GetFlyoutIconSize(), + Margin = new Thickness(this.GetFlyoutMargin(), 0, 0, 0), + }; + image.SetBinding(Image.SourceProperty, new Binding(iconBinding)); + grid.Children.Add(image); + + var label = new Label + { + FontSize = this.GetFlyoutItemFontSize(), + VerticalTextAlignment = TextAlignment.Center, + TextColor = Xamarin.Forms.Color.Black.MultiplyAlpha(0.87), + Margin = new Thickness(this.GetFlyoutMargin(), 0, 0, 0), + }; + label.SetBinding(Label.TextProperty, new Binding(textBinding)); + + grid.Children.Add(label, 1, 0); + + var groups = new VisualStateGroupList(); + + var commonGroup = new VisualStateGroup(); + commonGroup.Name = "CommonStates"; + groups.Add(commonGroup); + + var normalState = new VisualState(); + normalState.Name = "Normal"; + commonGroup.States.Add(normalState); + + var selectedState = new VisualState(); + selectedState.Name = "Selected"; + selectedState.Setters.Add(new Setter + { + Property = VisualElement.BackgroundColorProperty, + Value = new Color(0.95) + }); + + commonGroup.States.Add(selectedState); + VisualStateManager.SetVisualStateGroups(grid, groups); + + return grid; }); - - commonGroup.States.Add(selectedState); - VisualStateManager.SetVisualStateGroups(grid, groups); - return grid; - }); } void UpdateBackgroundImage() From 8ebc0555cd0bfe9f555d90d5ef1e98b3510ac0ed Mon Sep 17 00:00:00 2001 From: BryanRoth Date: Tue, 25 Jan 2022 10:16:19 -0500 Subject: [PATCH 17/19] Deal with BindableLayout.EmptyViewTemplate selector returning null. --- Xamarin.Forms.Core/BindableLayout.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Xamarin.Forms.Core/BindableLayout.cs b/Xamarin.Forms.Core/BindableLayout.cs index 13a1093d849..dc8290d57b3 100644 --- a/Xamarin.Forms.Core/BindableLayout.cs +++ b/Xamarin.Forms.Core/BindableLayout.cs @@ -275,9 +275,11 @@ View CreateEmptyView(object emptyView, DataTemplate dataTemplate) return null; } - if (dataTemplate != null) + var template = dataTemplate?.SelectDataTemplate(layout.BindingContext, layout); + + if (template != null) { - var view = (View)dataTemplate.CreateContent(); + var view = (View)template.CreateContent(); view.BindingContext = layout.BindingContext; return view; } From 74328da6473ea596582d4b82a388635bf7e9b753 Mon Sep 17 00:00:00 2001 From: BryanRoth Date: Wed, 26 Jan 2022 07:41:46 -0500 Subject: [PATCH 18/19] Add support for null from Template Selector I've corrected the logic so it can handle a null from a DataTemplateSelector, but I've not corrected the logic that's using the template as a parameter for template selection. --- Xamarin.Forms.Core/Shell/ShellTemplatedViewManager.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Xamarin.Forms.Core/Shell/ShellTemplatedViewManager.cs b/Xamarin.Forms.Core/Shell/ShellTemplatedViewManager.cs index cf68fb62c0f..db710280283 100644 --- a/Xamarin.Forms.Core/Shell/ShellTemplatedViewManager.cs +++ b/Xamarin.Forms.Core/Shell/ShellTemplatedViewManager.cs @@ -49,9 +49,11 @@ public static void OnViewTemplateChanged( Shell shell) { View newContentView = currentViewData as View; - if (newViewTemplate != null) + + DataTemplate template = newViewTemplate?.SelectDataTemplate(newViewTemplate, shell); + if (template != null) { - newContentView = (View)newViewTemplate.CreateContent(newViewTemplate, shell); + newContentView = (View)template.CreateContent(); } SetView(ref localViewRef, From f97dfab90ea19930bc4311fb80b62807cad040c2 Mon Sep 17 00:00:00 2001 From: BryanRoth Date: Wed, 26 Jan 2022 08:03:09 -0500 Subject: [PATCH 19/19] Add a simple can return null test. --- .../DataTemplateSelectorTests.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Xamarin.Forms.Core.UnitTests/DataTemplateSelectorTests.cs b/Xamarin.Forms.Core.UnitTests/DataTemplateSelectorTests.cs index bb9049a655d..400d2926a1e 100644 --- a/Xamarin.Forms.Core.UnitTests/DataTemplateSelectorTests.cs +++ b/Xamarin.Forms.Core.UnitTests/DataTemplateSelectorTests.cs @@ -46,11 +46,11 @@ public TemplateThree() : base(typeof(ContentPage)) } } - class TemplateFour : DataTemplate + class NullSelector : DataTemplateSelector { - public TemplateFour() : base(typeof(ContentView)) + protected override DataTemplate OnSelectTemplate(object item, BindableObject container) { - + return null; } } @@ -102,6 +102,13 @@ public void Constructor() var dts = new TestDTS(); } + [Test] + public void CanReturnNull() + { + var dts = new NullSelector(); + Assert.IsNull(dts.SelectTemplate((short)0, null)); + } + [Test] public void ReturnsCorrectType() {