From dbf4037a31f411acf6809a77027757fa9b677a81 Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Thu, 18 Apr 2019 09:25:31 -0600 Subject: [PATCH] [Shell] Propagate Page bindings to TitleView and Shell Binding to Flyout (#5934) fixes #5650 fixes #5501 * propagate bindingcontext * - add exception message and fix poorly named xaml file * add ui test automation * - fix unit test to represent new code * - changed from ui test to unit test * - propagate visual, parent, bc to titleview * - style fixes --- .../TestPages/TestPages.cs | 24 ++++ ...rin.Forms.Controls.Issues.Shared.projitems | 4 +- ...hellContent.xaml => ShellContentTest.xaml} | 2 +- ...ntent.xaml.cs => ShellContentTest.xaml.cs} | 8 +- .../Xamarin.Forms.Controls.csproj | 3 + Xamarin.Forms.Core.UnitTests/ShellTests.cs | 110 +++++++++++++++++- Xamarin.Forms.Core/BindableObject.cs | 3 + Xamarin.Forms.Core/Cells/Cell.cs | 3 + Xamarin.Forms.Core/Element.cs | 4 + Xamarin.Forms.Core/Shell/BaseShellItem.cs | 3 + Xamarin.Forms.Core/Shell/IShellController.cs | 2 - Xamarin.Forms.Core/Shell/Shell.cs | 20 ++-- Xamarin.Forms.Core/Shell/ShellContent.cs | 3 +- .../Renderers/ShellItemRenderer.cs | 3 + .../Renderers/ShellToolbarTracker.cs | 2 - 15 files changed, 168 insertions(+), 26 deletions(-) rename Xamarin.Forms.Controls/{ShellContent.xaml => ShellContentTest.xaml} (94%) rename Xamarin.Forms.Controls/{ShellContent.xaml.cs => ShellContentTest.xaml.cs} (93%) diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/TestPages.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/TestPages.cs index 607e60f44..b1bc3e74b 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/TestPages.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/TestPages.cs @@ -581,6 +581,30 @@ namespace Xamarin.Forms.Controls #endif } + public ContentPage CreateContentPage() + { + ContentPage page = new ContentPage(); + ShellItem item = new ShellItem() + { + Items = + { + new ShellSection() + { + Items = + { + new ShellContent() + { + Content = page + } + } + } + } + }; + + Items.Add(item); + return page; + + } #if UITEST [SetUp] public void Setup() diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index 6173e4ed3..83084d5df 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -34,7 +34,7 @@ - + A11yTabIndex.xaml Code @@ -1158,7 +1158,7 @@ MSBuild:UpdateDesignTimeXaml - + Designer MSBuild:Compile diff --git a/Xamarin.Forms.Controls/ShellContent.xaml b/Xamarin.Forms.Controls/ShellContentTest.xaml similarity index 94% rename from Xamarin.Forms.Controls/ShellContent.xaml rename to Xamarin.Forms.Controls/ShellContentTest.xaml index e5fb11007..8fd4eccc6 100644 --- a/Xamarin.Forms.Controls/ShellContent.xaml +++ b/Xamarin.Forms.Controls/ShellContentTest.xaml @@ -5,7 +5,7 @@ Routing.Route="shellcontent" Shell.SetPaddingInsets="true" Shell.TabBarIsVisible="false" - x:Class="Xamarin.Forms.Controls.ShellContent"> + x:Class="Xamarin.Forms.Controls.ShellContentTest"> diff --git a/Xamarin.Forms.Controls/ShellContent.xaml.cs b/Xamarin.Forms.Controls/ShellContentTest.xaml.cs similarity index 93% rename from Xamarin.Forms.Controls/ShellContent.xaml.cs rename to Xamarin.Forms.Controls/ShellContentTest.xaml.cs index 002eb86e4..204fa2d9d 100644 --- a/Xamarin.Forms.Controls/ShellContent.xaml.cs +++ b/Xamarin.Forms.Controls/ShellContentTest.xaml.cs @@ -13,7 +13,7 @@ namespace Xamarin.Forms.Controls [Preserve] [QueryProperty("Text", "welcome")] [XamlCompilation(XamlCompilationOptions.Compile)] - public partial class ShellContent : ContentPage + public partial class ShellContentTest : ContentPage { private class MySearchHandler : SearchHandler { @@ -51,7 +51,7 @@ namespace Xamarin.Forms.Controls private string _text; - public ShellContent() + public ShellContentTest() { InitializeComponent(); @@ -89,7 +89,7 @@ namespace Xamarin.Forms.Controls private void InsertClicked(object sender, EventArgs e) { - Navigation.InsertPageBefore(new ShellContent(), this); + Navigation.InsertPageBefore(new ShellContentTest(), this); } private void ToggleClicked(object sender, EventArgs e) @@ -122,7 +122,7 @@ namespace Xamarin.Forms.Controls private async void PushClicked(object sender, EventArgs e) { - await Navigation.PushAsync(new ShellContent() + await Navigation.PushAsync(new ShellContentTest() { Text = Text + "1" }); diff --git a/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj b/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj index 93ad41c5d..df98d49b9 100644 --- a/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj +++ b/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj @@ -50,6 +50,9 @@ OnPlatformExample.xaml + + ShellContentTest.xaml + MSBuild:UpdateDesignTimeXaml diff --git a/Xamarin.Forms.Core.UnitTests/ShellTests.cs b/Xamarin.Forms.Core.UnitTests/ShellTests.cs index 84cfcb15e..495647fc8 100644 --- a/Xamarin.Forms.Core.UnitTests/ShellTests.cs +++ b/Xamarin.Forms.Core.UnitTests/ShellTests.cs @@ -303,6 +303,9 @@ namespace Xamarin.Forms.Core.UnitTests var label = new Label(); + var viewModel = new Object(); + shell.BindingContext = viewModel; + shell.FlyoutHeader = label; Assert.AreEqual(((IShellController)shell).FlyoutHeader, label); @@ -315,7 +318,7 @@ namespace Xamarin.Forms.Core.UnitTests }); Assert.AreEqual(((IShellController)shell).FlyoutHeader, label2); - Assert.AreEqual(((IShellController)shell).FlyoutHeader.BindingContext, label); + Assert.AreEqual(((IShellController)shell).FlyoutHeader.BindingContext, viewModel); shell.FlyoutHeaderTemplate = null; @@ -370,5 +373,110 @@ namespace Xamarin.Forms.Core.UnitTests shell.GoToAsync("//rootlevelcontent1"); Assert.AreEqual(shell.CurrentItem, item1); } + + [Test] + public async Task TitleViewBindingContext() + { + Shell shell = new Shell(); + ContentPage page = new ContentPage(); + shell.Items.Add(CreateShellItem(page)); + page.BindingContext = new { Text = "Binding" }; + + // setup title view + StackLayout layout = new StackLayout() { BackgroundColor = Color.White }; + Label label = new Label(); + label.SetBinding(Label.TextProperty, "Text"); + layout.Children.Add(label); + Shell.SetTitleView(page, layout); + + Assert.AreEqual("Binding", label.Text); + page.BindingContext = new { Text = "Binding Changed" }; + Assert.AreEqual("Binding Changed", label.Text); + } + + [Test] + public async Task VisualPropagationPageLevel() + { + Shell shell = new Shell(); + ContentPage page = new ContentPage(); + shell.Items.Add(CreateShellItem(page)); + + // setup title view + StackLayout titleView = new StackLayout() { BackgroundColor = Color.White }; + Button button = new Button(); + titleView.Children.Add(button); + Shell.SetTitleView(page, titleView); + IVisualController visualController = button as IVisualController; + + + Assert.AreEqual(page, titleView.Parent); + + Assert.AreEqual(VisualMarker.Default, ((IVisualController)button).EffectiveVisual); + page.Visual = VisualMarker.Material; + Assert.AreEqual(VisualMarker.Material, ((IVisualController)button).EffectiveVisual); + } + + [Test] + public async Task VisualPropagationShellLevel() + { + Shell shell = new Shell(); + ContentPage page = new ContentPage(); + shell.Items.Add(CreateShellItem(page)); + + // setup title view + StackLayout titleView = new StackLayout() { BackgroundColor = Color.White }; + Button button = new Button(); + titleView.Children.Add(button); + Shell.SetTitleView(page, titleView); + IVisualController visualController = button as IVisualController; + + + Assert.AreEqual(page, titleView.Parent); + Assert.AreEqual(VisualMarker.Default, ((IVisualController)button).EffectiveVisual); + shell.Visual = VisualMarker.Material; + Assert.AreEqual(VisualMarker.Material, ((IVisualController)button).EffectiveVisual); + } + + [Test] + public async Task FlyoutViewVisualPropagation() + { + Shell shell = new Shell(); + ContentPage page = new ContentPage(); + shell.Items.Add(CreateShellItem(page)); + + + // setup title view + StackLayout flyoutView = new StackLayout() { BackgroundColor = Color.White }; + Button button = new Button(); + flyoutView.Children.Add(button); + shell.SetValue(Shell.FlyoutHeaderProperty, flyoutView); + + IVisualController visualController = button as IVisualController; + Assert.AreEqual(VisualMarker.Default, visualController.EffectiveVisual); + shell.Visual = VisualMarker.Material; + Assert.AreEqual(VisualMarker.Material, visualController.EffectiveVisual); + } + + [Test] + public async Task FlyoutViewBindingContext() + { + Shell shell = new Shell(); + ContentPage page = new ContentPage(); + shell.Items.Add(CreateShellItem(page)); + shell.BindingContext = new { Text = "Binding" }; + + // setup title view + StackLayout flyoutView = new StackLayout() { BackgroundColor = Color.White }; + Label label = new Label(); + label.SetBinding(Label.TextProperty, "Text"); + flyoutView.Children.Add(label); + shell.SetValue(Shell.FlyoutHeaderProperty, flyoutView); + + Assert.AreEqual("Binding", label.Text); + shell.BindingContext = new { Text = "Binding Changed" }; + Assert.AreEqual("Binding Changed", label.Text); + shell.SetValue(Shell.FlyoutHeaderProperty, new ContentView()); + Assert.AreEqual(null, flyoutView.BindingContext); + } } } diff --git a/Xamarin.Forms.Core/BindableObject.cs b/Xamarin.Forms.Core/BindableObject.cs index fdf386362..34164647c 100644 --- a/Xamarin.Forms.Core/BindableObject.cs +++ b/Xamarin.Forms.Core/BindableObject.cs @@ -202,6 +202,9 @@ namespace Xamarin.Forms if (Shell.GetSearchHandler(this) is SearchHandler searchHandler) SetInheritedBindingContext(searchHandler, BindingContext); + + if (Shell.GetTitleView(this) is View titleView) + SetInheritedBindingContext(titleView, BindingContext); } protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) diff --git a/Xamarin.Forms.Core/Cells/Cell.cs b/Xamarin.Forms.Core/Cells/Cell.cs index e5e4f20df..c8898d6cc 100644 --- a/Xamarin.Forms.Core/Cells/Cell.cs +++ b/Xamarin.Forms.Core/Cells/Cell.cs @@ -49,6 +49,9 @@ namespace Xamarin.Forms get { return _effectiveVisual; } set { + if (value == _effectiveVisual) + return; + _effectiveVisual = value; OnPropertyChanged(VisualElement.VisualProperty.PropertyName); } diff --git a/Xamarin.Forms.Core/Element.cs b/Xamarin.Forms.Core/Element.cs index c7274f2ac..19ad039ba 100644 --- a/Xamarin.Forms.Core/Element.cs +++ b/Xamarin.Forms.Core/Element.cs @@ -338,6 +338,10 @@ namespace Xamarin.Forms { base.OnPropertyChanged(propertyName); + IPropertyPropagationController titleView = Shell.GetTitleView(this) ?? NavigationPage.GetTitleView(this); + if(titleView != null) + PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, new[] { titleView }); + if (_effects == null || _effects.Count == 0) return; diff --git a/Xamarin.Forms.Core/Shell/BaseShellItem.cs b/Xamarin.Forms.Core/Shell/BaseShellItem.cs index 8311d83f5..da705fd14 100644 --- a/Xamarin.Forms.Core/Shell/BaseShellItem.cs +++ b/Xamarin.Forms.Core/Shell/BaseShellItem.cs @@ -67,6 +67,9 @@ namespace Xamarin.Forms get { return _effectiveVisual; } set { + if (value == _effectiveVisual) + return; + _effectiveVisual = value; OnPropertyChanged(VisualElement.VisualProperty.PropertyName); } diff --git a/Xamarin.Forms.Core/Shell/IShellController.cs b/Xamarin.Forms.Core/Shell/IShellController.cs index 9d62c0578..fcf8e0a9a 100644 --- a/Xamarin.Forms.Core/Shell/IShellController.cs +++ b/Xamarin.Forms.Core/Shell/IShellController.cs @@ -16,8 +16,6 @@ namespace Xamarin.Forms public interface IShellController : IPageController { - event EventHandler HeaderChanged; - event EventHandler StructureChanged; View FlyoutHeader { get; } diff --git a/Xamarin.Forms.Core/Shell/Shell.cs b/Xamarin.Forms.Core/Shell/Shell.cs index 5d5f99ac8..bae08e606 100644 --- a/Xamarin.Forms.Core/Shell/Shell.cs +++ b/Xamarin.Forms.Core/Shell/Shell.cs @@ -187,19 +187,12 @@ namespace Xamarin.Forms List<(IAppearanceObserver Observer, Element Pivot)> _appearanceObservers = new List<(IAppearanceObserver Observer, Element Pivot)>(); List _flyoutBehaviorObservers = new List(); - event EventHandler IShellController.HeaderChanged - { - add { _headerChanged += value; } - remove { _headerChanged -= value; } - } - event EventHandler IShellController.StructureChanged { add { _structureChanged += value; } remove { _structureChanged -= value; } } - event EventHandler _headerChanged; event EventHandler _structureChanged; View IShellController.FlyoutHeader => FlyoutHeaderView; @@ -708,10 +701,16 @@ namespace Xamarin.Forms _flyoutHeaderView = value; if (_flyoutHeaderView != null) OnChildAdded(_flyoutHeaderView); - _headerChanged?.Invoke(this, EventArgs.Empty); } } + protected override void OnBindingContextChanged() + { + base.OnBindingContextChanged(); + if (FlyoutHeaderView != null) + SetInheritedBindingContext(FlyoutHeaderView, BindingContext); + } + List> IShellController.GenerateFlyoutGrouping() { // The idea here is to create grouping such that the Flyout would @@ -1033,10 +1032,6 @@ namespace Xamarin.Forms else FlyoutHeaderView = null; } - else - { - FlyoutHeaderView.BindingContext = newVal; - } } void OnFlyoutHeaderTemplateChanged(DataTemplate oldValue, DataTemplate newValue) @@ -1051,7 +1046,6 @@ namespace Xamarin.Forms else { var newHeaderView = (View)newValue.CreateContent(FlyoutHeader, this); - newHeaderView.BindingContext = FlyoutHeader; FlyoutHeaderView = newHeaderView; } } diff --git a/Xamarin.Forms.Core/Shell/ShellContent.cs b/Xamarin.Forms.Core/Shell/ShellContent.cs index 799f208df..8403efe10 100644 --- a/Xamarin.Forms.Core/Shell/ShellContent.cs +++ b/Xamarin.Forms.Core/Shell/ShellContent.cs @@ -89,7 +89,8 @@ namespace Xamarin.Forms internal override ReadOnlyCollection LogicalChildrenInternal => _logicalChildrenReadOnly ?? (_logicalChildrenReadOnly = new ReadOnlyCollection(_logicalChildren)); - Page ContentCache { + Page ContentCache + { get { return _contentCache; } set { diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellItemRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellItemRenderer.cs index 09452a522..e6840f730 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellItemRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellItemRenderer.cs @@ -64,6 +64,9 @@ namespace Xamarin.Forms.Platform.Android _bottomView.SetBackgroundColor(Color.White.ToAndroid()); _bottomView.SetOnNavigationItemSelectedListener(this); + if(ShellItem == null) + throw new ArgumentException("Active Shell Item not set. Have you added any Shell Items to your Shell?", nameof(ShellItem)); + HookEvents(ShellItem); SetupMenu(); diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarTracker.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarTracker.cs index c7a5611c3..3d80d5d78 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarTracker.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarTracker.cs @@ -386,8 +386,6 @@ namespace Xamarin.Forms.Platform.Android } else { - // FIXME - titleView.Parent = _shellContext.Shell; _titleViewContainer = new ContainerView(context, titleView); _titleViewContainer.MatchHeight = _titleViewContainer.MatchWidth = true; _titleViewContainer.LayoutParameters = new Toolbar.LayoutParams(LP.MatchParent, LP.MatchParent)