using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Threading.Tasks; using Xamarin.Forms.Internals; using Xamarin.Forms.Platform; namespace Xamarin.Forms { [RenderWith(typeof(_NavigationPageRenderer))] public class NavigationPage : Page, IPageContainer, INavigationPageController, IElementConfiguration { public static readonly BindableProperty BackButtonTitleProperty = BindableProperty.CreateAttached("BackButtonTitle", typeof(string), typeof(Page), null); public static readonly BindableProperty HasNavigationBarProperty = BindableProperty.CreateAttached("HasNavigationBar", typeof(bool), typeof(Page), true); public static readonly BindableProperty HasBackButtonProperty = BindableProperty.CreateAttached("HasBackButton", typeof(bool), typeof(NavigationPage), true); [Obsolete("TintProperty is obsolete as of version 1.2.0. Please use BarBackgroundColorProperty and BarTextColorProperty to change NavigationPage bar color properties.")] public static readonly BindableProperty TintProperty = BindableProperty.Create("Tint", typeof(Color), typeof(NavigationPage), Color.Default); public static readonly BindableProperty BarBackgroundColorProperty = BindableProperty.Create("BarBackgroundColor", typeof(Color), typeof(NavigationPage), Color.Default); public static readonly BindableProperty BarTextColorProperty = BindableProperty.Create("BarTextColor", typeof(Color), typeof(NavigationPage), Color.Default); public static readonly BindableProperty TitleIconProperty = BindableProperty.CreateAttached("TitleIcon", typeof(FileImageSource), typeof(NavigationPage), default(FileImageSource)); static readonly BindablePropertyKey CurrentPagePropertyKey = BindableProperty.CreateReadOnly("CurrentPage", typeof(Page), typeof(NavigationPage), null); public static readonly BindableProperty CurrentPageProperty = CurrentPagePropertyKey.BindableProperty; static readonly BindablePropertyKey RootPagePropertyKey = BindableProperty.CreateReadOnly(nameof(RootPage), typeof(Page), typeof(NavigationPage), null); public static readonly BindableProperty RootPageProperty = RootPagePropertyKey.BindableProperty; public NavigationPage() { _platformConfigurationRegistry = new Lazy>(() => new PlatformConfigurationRegistry(this)); Navigation = new NavigationImpl(this); } public NavigationPage(Page root) : this() { PushPage(root); } public Color BarBackgroundColor { get { return (Color)GetValue(BarBackgroundColorProperty); } set { SetValue(BarBackgroundColorProperty, value); } } public Color BarTextColor { get { return (Color)GetValue(BarTextColorProperty); } set { SetValue(BarTextColorProperty, value); } } [Obsolete("Tint is obsolete as of version 1.2.0. Please use BarBackgroundColor and BarTextColor to change NavigationPage bar color properties.")] public Color Tint { get { return (Color)GetValue(TintProperty); } set { SetValue(TintProperty, value); } } internal Task CurrentNavigationTask { get; set; } [EditorBrowsable(EditorBrowsableState.Never)] public Page Peek(int depth) { if (depth < 0) { return null; } if (InternalChildren.Count <= depth) { return null; } return (Page)InternalChildren[InternalChildren.Count - depth - 1]; } [EditorBrowsable(EditorBrowsableState.Never)] public IEnumerable Pages => InternalChildren.Cast(); [EditorBrowsable(EditorBrowsableState.Never)] public int StackDepth { get { return InternalChildren.Count; } } public Page CurrentPage { get { return (Page)GetValue(CurrentPageProperty); } private set { SetValue(CurrentPagePropertyKey, value); } } public Page RootPage { get { return (Page)GetValue(RootPageProperty); } private set { SetValue(RootPagePropertyKey, value); } } public static string GetBackButtonTitle(BindableObject page) { return (string)page.GetValue(BackButtonTitleProperty); } public static bool GetHasBackButton(Page page) { if (page == null) throw new ArgumentNullException("page"); return (bool)page.GetValue(HasBackButtonProperty); } public static bool GetHasNavigationBar(BindableObject page) { return (bool)page.GetValue(HasNavigationBarProperty); } public static FileImageSource GetTitleIcon(BindableObject bindable) { return (FileImageSource)bindable.GetValue(TitleIconProperty); } public Task PopAsync() { return PopAsync(true); } public async Task PopAsync(bool animated) { if (CurrentNavigationTask != null && !CurrentNavigationTask.IsCompleted) { var tcs = new TaskCompletionSource(); Task oldTask = CurrentNavigationTask; CurrentNavigationTask = tcs.Task; await oldTask; Page page = await PopAsyncInner(animated, false); tcs.SetResult(true); return page; } Task result = PopAsyncInner(animated, false); CurrentNavigationTask = result; return await result; } public event EventHandler Popped; public event EventHandler PoppedToRoot; public Task PopToRootAsync() { return PopToRootAsync(true); } public async Task PopToRootAsync(bool animated) { if (CurrentNavigationTask != null && !CurrentNavigationTask.IsCompleted) { var tcs = new TaskCompletionSource(); Task oldTask = CurrentNavigationTask; CurrentNavigationTask = tcs.Task; await oldTask; await PopToRootAsyncInner(animated); tcs.SetResult(true); return; } Task result = PopToRootAsyncInner(animated); CurrentNavigationTask = result; await result; } public Task PushAsync(Page page) { return PushAsync(page, true); } public async Task PushAsync(Page page, bool animated) { if (CurrentNavigationTask != null && !CurrentNavigationTask.IsCompleted) { var tcs = new TaskCompletionSource(); Task oldTask = CurrentNavigationTask; CurrentNavigationTask = tcs.Task; await oldTask; await PushAsyncInner(page, animated); tcs.SetResult(true); return; } CurrentNavigationTask = PushAsyncInner(page, animated); await CurrentNavigationTask; } public event EventHandler Pushed; public static void SetBackButtonTitle(BindableObject page, string value) { page.SetValue(BackButtonTitleProperty, value); } public static void SetHasBackButton(Page page, bool value) { if (page == null) throw new ArgumentNullException("page"); page.SetValue(HasBackButtonProperty, value); } public static void SetHasNavigationBar(BindableObject page, bool value) { page.SetValue(HasNavigationBarProperty, value); } public static void SetTitleIcon(BindableObject bindable, FileImageSource value) { bindable.SetValue(TitleIconProperty, value); } protected override bool OnBackButtonPressed() { if (CurrentPage.SendBackButtonPressed()) return true; if (StackDepth > 1) { SafePop(); return true; } return base.OnBackButtonPressed(); } [EditorBrowsable(EditorBrowsableState.Never)] public event EventHandler InsertPageBeforeRequested; [EditorBrowsable(EditorBrowsableState.Never)] public async Task PopAsyncInner(bool animated, bool fast) { if (StackDepth == 1) { return null; } var page = (Page)InternalChildren.Last(); var args = new NavigationRequestedEventArgs(page, animated); var removed = true; EventHandler requestPop = PopRequested; if (requestPop != null) { requestPop(this, args); if (args.Task != null && !fast) removed = await args.Task; } if (!removed && !fast) return CurrentPage; InternalChildren.Remove(page); CurrentPage = (Page)InternalChildren.Last(); if (Popped != null) Popped(this, args); return page; } [EditorBrowsable(EditorBrowsableState.Never)] public event EventHandler PopRequested; [EditorBrowsable(EditorBrowsableState.Never)] public event EventHandler PopToRootRequested; [EditorBrowsable(EditorBrowsableState.Never)] public event EventHandler PushRequested; [EditorBrowsable(EditorBrowsableState.Never)] public event EventHandler RemovePageRequested; void InsertPageBefore(Page page, Page before) { if (page == null) throw new ArgumentNullException($"{nameof(page)} cannot be null."); if (before == null) throw new ArgumentNullException($"{nameof(before)} cannot be null."); if (!InternalChildren.Contains(before)) throw new ArgumentException($"{nameof(before)} must be a child of the NavigationPage", nameof(before)); if (InternalChildren.Contains(page)) throw new ArgumentException("Cannot insert page which is already in the navigation stack"); EventHandler handler = InsertPageBeforeRequested; handler?.Invoke(this, new NavigationRequestedEventArgs(page, before, false)); int index = InternalChildren.IndexOf(before); InternalChildren.Insert(index, page); if (index == 0) RootPage = page; // Shouldn't be required? if (Width > 0 && Height > 0) ForceLayout(); } async Task PopToRootAsyncInner(bool animated) { if (StackDepth == 1) return; Element[] childrenToRemove = InternalChildren.Skip(1).ToArray(); foreach (Element child in childrenToRemove) InternalChildren.Remove(child); CurrentPage = RootPage; var args = new NavigationRequestedEventArgs(RootPage, animated); EventHandler requestPopToRoot = PopToRootRequested; if (requestPopToRoot != null) { requestPopToRoot(this, args); if (args.Task != null) await args.Task; } PoppedToRoot?.Invoke(this, new PoppedToRootEventArgs(RootPage, childrenToRemove.OfType().ToList())); } async Task PushAsyncInner(Page page, bool animated) { if (InternalChildren.Contains(page)) return; PushPage(page); var args = new NavigationRequestedEventArgs(page, animated); EventHandler requestPush = PushRequested; if (requestPush != null) { requestPush(this, args); if (args.Task != null) await args.Task; } Pushed?.Invoke(this, args); } void PushPage(Page page) { InternalChildren.Add(page); if (InternalChildren.Count == 1) RootPage = page; CurrentPage = page; } void RemovePage(Page page) { if (page == null) throw new ArgumentNullException($"{nameof(page)} cannot be null."); if (page == CurrentPage && CurrentPage == RootPage) throw new InvalidOperationException("Cannot remove root page when it is also the currently displayed page."); if (page == CurrentPage) { Log.Warning("NavigationPage", "RemovePage called for CurrentPage object. This can result in undesired behavior, consider calling PopAsync instead."); PopAsync(); return; } if (!InternalChildren.Contains(page)) throw new ArgumentException("Page to remove must be contained on this Navigation Page"); EventHandler handler = RemovePageRequested; handler?.Invoke(this, new NavigationRequestedEventArgs(page, true)); InternalChildren.Remove(page); if (RootPage == page) RootPage = (Page)InternalChildren.First(); } void SafePop() { PopAsync(true).ContinueWith(t => { if (t.IsFaulted) throw t.Exception; }); } class NavigationImpl : NavigationProxy { readonly Lazy> _castingList; public NavigationImpl(NavigationPage owner) { Owner = owner; _castingList = new Lazy>(() => new ReadOnlyCastingList(Owner.InternalChildren)); } NavigationPage Owner { get; } protected override IReadOnlyList GetNavigationStack() { return _castingList.Value; } protected override void OnInsertPageBefore(Page page, Page before) { Owner.InsertPageBefore(page, before); } protected override Task OnPopAsync(bool animated) { return Owner.PopAsync(animated); } protected override Task OnPopToRootAsync(bool animated) { return Owner.PopToRootAsync(animated); } protected override Task OnPushAsync(Page root, bool animated) { return Owner.PushAsync(root, animated); } protected override void OnRemovePage(Page page) { Owner.RemovePage(page); } } readonly Lazy> _platformConfigurationRegistry; public new IPlatformElementConfiguration On() where T : IConfigPlatform { return _platformConfigurationRegistry.Value.On(); } } }