Root Page is not part of the Navigation.NavigationStack collection - c#

When my app starts, I so some logic in my AppDelegate and assign the MainPage a page based on the results of that logic.
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init ();
// .....
if(authorizationStatus == PhotoLibraryAuthorizationStatus.Authorized)
{
bokehApp.SetStartupView(serviceLocator.GetService<AlbumsPage>());
}
else
{
bokehApp.SetStartupView(serviceLocator.GetService<StartupPage>());
}
}
In my app.cs, I assign the MainPage the given view from the AppDelegate
public class App : Xamarin.Forms.Application
{
public void SetStartupView(ContentPage page)
{
this.MainPage = new NavigationPage(page);
}
}
In this instance, I am passing StartupPage into the SetStartupView(Page) method. When the user does something, I navigate to the AlbumsPage.
this.Navigation.PushAsync(new AlbumPage());
When I do this, the AlbumPage is created and navigated to; it's Navigation.NavigationStack collection only contains itself, not the Page that it just navigated from. What I want to do, is prevent a call to this.Navigation.PopAsync() navigating back to the StartupPage, which is what currently happens.
Initially I was going to just run a loop and pop the original Page off, and then remove all of the remaining pages, in this case StartupPage.
// Grab the current page, as it is about to become our new Root.
var navigatedPages = this.Navigation.NavigationStack.ToList();
Page newRootPage = navigatedPages.Last();
// Dont include the current item on the stack in the removal.
navigatedPages.Remove(newRootPage);
while(navigatedPages.Count > 0)
{
Page currentPage = navigatedPages.Last();
this.Navigation.RemovePage(currentPage);
}
However, when I look, the Navigation.NavigationStack collection only contains the AlbumsPage. Yet, calling this.Navigation.PopAsync() navigates back to the StartupPage.
What do I need to do in order to reset this navigation stack, so that popping will not navigate back to the initial page?
Update
When I navigate, I've been able to use this:
App.Current.MainPage = new NavigationPage(viewModelPage);
as suggested by #Daniel but this prevents the animation from taking place. I have also tried
await App.Current.MainPage.Navigation.PushAsync(fooPage);
App.Current.MainPage = new NavigationPage(fooPage);
When I do this, I see the new page transitioned to, but once the await call on PushAsync finishes, and the MainPage is replaced, the Page disappears and I'm left with an empty screen.
I really don't want to lose the animations during transitioning from my setup Page to the actual app.

I think what you're looking for is setting:
App.Current.MainPage
to a new NavigationPage. It replaces main page of application.

I was able to solve the issue. This mostly came down to my lack of understanding how the NavigationPage worked. It would seem that each Page is given their own Navigation stack. As I navigated between Pages and examined their NavigationStack collections, they always just had one item in them.
I then started looking at App.Current.MainPage.Navigation and discovered that it actually had the entire stack (both StartupPage and FooPage). I was able to then grab the StartupPage prior to pushing the FooPage onto the navigation stack, and then removing the StartupPage once the navigation to FooPage was completed. This essentially let me reset the root page, while keeping the transition animation between the views.
Page originalRootPage = App.Current.MainPage.Navigation.NavigationStack.Last();
await App.Current.MainPage.Navigation.PushAsync(new FooPage());
App.Current.MainPage.Navigation.RemovePage(originalRootPage);
I'll mark this as answered when the time-period has expired and i'm allowed to.

This is fix my problem.
protected override bool OnBackButtonPressed()
{
foreach (Page page in Navigation.ModalStack)
{
page.Navigation.PopModalAsync();
}
return true;
}

Related

Pre-Load pages on xamarin forms

I was searching for tricks to make my Xamarin Forms app faster, and I came across this article. Well, I have tried out some options there but I am finding it difficult to achieve the last option that says;
Pre-Load screens: This improved by a lot the performance on the app
itself what we did is load on each screen, on background, the views
and view-models that where needed next, so when pushing them onto the
navigation stack they were already loaded. This reduced by a lot the
time on transition between pages.
This seems to me as a good option and what I have done is to insert pages I maybe needing next onto the Stack before navigating to the page like so; however, I don't know if I am doing this the right way.
public ProductHomePageViewModel()
{
Application.Current.MainPage.Navigation.InsertPageBefore(new TilePage(), Application.Current.MainPage.Navigation.NavigationStack[0]);
}
But this doesn't work well when it is done on a homepage where a click of a back button should exit the app. But in this case clicking the back button navigates to the next page on the stack.
Please assist me on how to pre-load pages. Any advice is good. Thank you!
Like I said in the comments, you could use some kind of Singleton Pattern, but for pages.
I'll pust an example. Consider the following code
public partial class Page1 : ContentPage
{
private static Page1 _myinstance;
public static Page1 myinstance
{
get
{
Prepare();
return _myinstance;
}
}
public static void Prepare()
{
if (_myinstance == null)
{
_myinstance = new Page1();
}
}
public Page1()
{
InitializeComponent();
}
}
For any other Page, you could call Navigation.PushAsync(Page1.Instance) . If Page1 was instanced, it will not run the constructor again, just the first time Instance is called.
But if you want to "Prepare" it beforehand, you could call in a background method the Page1.Prepare()
Remember, the constructor will be executed only once. So the page will be like "Cached"

Delete navigation cache in UWP

In my UWP app, i constantly navigate from page1 to page 2 and again from page2 to page1 and again this navigation loop repeats on submit button in both the pages. In the application starting it's performance is good but while the page navigates further it is taking more memory and app gets slow after 15 or 20 times of navigation. I tried deleting navigation cache by decreasing it's size but it didn't help and in my research i found if navigation mode is set to enabled it reduces some memory usage. But when i keep it enabled the previous data is not wiping off. I need a solution to delete the memory of previous pages and also make my app to use less memory even after it navigates many times.
The Problem is the UWP engine does not destroy your page, even if it is no longer in the navigaton Stack.
But there is solution for it:
Do not use the NavigationCacheMode in XAML code
On every page must override OnNavigatedTo() and when the NavigationMode is New, the change the NavigationCacheMode Required
On every page must override OnNavigatingFrom() and when the NavigationMode is Back, the change the NavigationCacheMode Disabled
With this mechanism, you can achieve the following: Every page on navigation stack is Cache=Required and every page which is not on navigation stack the Cache=Disabled.
But the are some when the user press the forward the page is newly allocated so the previous state is lost.
In some cases, the Disabled Cache mode in not enough, the UWP still keep the page in memory. In this case we have to delete the cache. We can do this if we reset the current frame cache size to zero and back to original.
Here is my code in every page:
protected override void OnNavigatedTo( NavigationEventArgs navigationEvent )
{
// call the original OnNavigatedTo
base.OnNavigatedTo( navigationEvent );
// when the dialog displays then we create viewmodel and set the cache mode
if( CreatedViewModel == null || navigationEvent.NavigationMode == NavigationMode.New )
{
// set the cache mode
NavigationCacheMode = NavigationCacheMode.Required;
// create viewmodel
CreatedViewModel = CreateViewModel( navigationEvent.Parameter );
DataContext = CreatedViewModel;
CreatedViewModel.InitializeAsync().ConfigureAwait( false );
}
}
protected override void OnNavigatingFrom( NavigatingCancelEventArgs navigationEvent )
{
// call the original OnNavigatingFrom
base.OnNavigatingFrom( navigationEvent );
// when the dialog is removed from navigation stack
if( navigationEvent.NavigationMode == NavigationMode.Back )
{
// set the cache mode
NavigationCacheMode = NavigationCacheMode.Disabled;
ResetPageCache();
}
}
private void ResetPageCache()
{
int cacheSize = ((Frame)Parent).CacheSize;
((Frame)Parent).CacheSize = 0;
((Frame)Parent).CacheSize = cacheSize;
}
Some note: Is more comfortable when you create a BasePage and put this code to it, and you can derive from this BasePage in every Page.

Check if ContentPage is currently shown

Is there a way to tell if a ContentPage is currently shown?
I need code inside an event handler in the ContentPage to check whether the page is currently shown and act accordingly.
In addition to GBreen12's answer, you could also do it this way...
bool isShowingMyPage = Application.Current.MainPage is MyPage || (Application.Current.MainPage is NavigationPage navPage && navPage.CurrentPage is MyPage); //If you are using a MasterDetailPage or something else, then you would have to handle this differently
//I think the below will work for checking if any modals are showing over your page but you should definitely test it in different scenarios to make sure. I am not sure if the modal is immediately removed from ModalStack when it gets popped or not
isShowingMyPage = Application.Current.MainPage?.Navigation?.ModalStack?.LastOrDefault() == null;
You can override OnAppearing which is called anytime the page is about to be shown:
Page lifecycle events in xamarin.forms
You can listen to the NavigationPage's Pushed and Popped events, like so:
((Xamarin.Forms.NavigationPage)MyProject.App.Current.MainPage).Pushed += (s, e) =>
{
if (e.Page is ContentPage)
{
// Do what you gotta do
}
// or, for a specific page:
if (e.Page is MyProject.Views.MyCustomPage)
{
// Do what you gotta do
}
};
Of course, this will only be called when the page is pushed onto the navigation stack; If you need it to be called each time the page appears on the screen, then go with what GBreen12 or hvaughan3 said.

How to detect back button or forward button navigation in a silverlight navigation application

When a Page is navigated to in silverlight you can override this method.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
}
The NavigationEventArgs has a NavigationMode enumeration which is defined as
public enum NavigationMode
{
New = 0,
Back = 1,
Forward = 2,
Refresh = 3,
}
But calling e.NavigationMode always throws a NotImplementedException
Is there a way in silverlight to detect a page is being navigated to because the user hit the forward/back browser button.
What I am trying to achieve is some kind of state that can be preserved when the user hits the back button.
For example assume you have a customer page which is showing a list of customers in a datagrid. The user can select a customer and there is a detail view which shows all the orders for that customer. Now within an order item you can click a hyperlink link that takes you to the shipping history of the order which is a separate page. When the user hits the back button I want to go back to the customers page and automatically select the customer he was viewing. Is this possible at all ?
I also tried out the fragment navigation feature
NavigationService.Navigate(new Uri("#currentcustomerid="
+ customer.Id.ToString(), UriKind.Relative));
when the customer selection changes but this adds too many items to the history when the user clicks various customers on the customer page.
EDIT
There is also an method you can override
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
}
which is the same as handling the NavigationService.Navigating event as indicated by BugFinder's answer. In this method e.NavigationMode always returns New when when you hit the Back or Forward Button. The only time this method returns Back is when you explicitly call NavigationService.GoBack()
The
public enum NavigationMode
{
New = 0,
Back = 1,
Forward = 2,
Refresh = 3,
}
applies to the Navigating event..
if I do
_ns.Navigating += ns_Navigating;
void ns_Navigating(object sender, NavigatingCancelEventArgs e)
{
if (SecurityCheck(e.Uri.OriginalString)) return;
e.Cancel = true;
ShowError("You are not authorised to view this page");
}
I can see there that e.NavigationMode is set. You could do your test there?
I don't think there are any easy ways to do it out of the box, as far as I know.
What you are trying to achieve can be easily done using a framework I created at
http://ultimateframework.codeplex.com
What I have done is to mesh the silverlight navigation frame and prism navigation together, so you will need unity and prism and mvvm friendly.
What you want to achieve can be done using the framework in the following ways
1) Implement IsNavigationTarget and returns true --> which will keep the same instance when navigating back, therefore, keeping the selection/selected item.
2) Access the onnavigatedto's journal to track where you came from, say /item/1 was the previous stack, so you know back button has been pressed from item 1.
3) You can even implement your own back/forward/refresh within the custom control provided for achieving the same result (not in codeplex yet)
I actually use it for production code at work, and I created it, so please feel free to try it out. Note that the version on there is buggy, and I have yet to have time to release our latest build, but should you require it, I will update it for you :), just pm me.
Set a variable in the usercontrol containing the contentframe which indicates what customer is active.
Add a handler for the contentframe's Navigated event in the usercontrol. Use this to check the variable indicating what customer is active (if the variable is not null), and to select the customer.
This might be what you are looking for:
http://jakkaj.wordpress.com/2008/09/08/control-silverlight-by-using-browser-back-and-foward-buttons/

NavigationWindow history is not saving states properly

I have a NavigationWindow (window1) and a custom navigationstate.
What I currently am using to do my navigation is as such:
a function (navigate(string,bool) ) which takes the location (a URL) that I want to go to, plus a boolean which defines if I should make a Back entry (i.e. I've gone into a folder)
A seperate function which ties into my NavigationService (allowing me to go back/forth within my history)
My problem though becomes that when I navigate Back, I start overriding my history!
Here's my NavigationService_Navigating(...) (which gets called when I push the back/forth button)
void NavigationService_Navigating(object sender, NavigatingCancelEventArgs e)
{
try // If something goes wrong, just bail.
{
// If we're going backwards, we want to remember the current location.
if (e.NavigationMode == NavigationMode.Back) { e.ContentStateToSave = new GopherNavState(cLocation); }
// use our internal navigation to move to the location, but dont create a back entry.
navigate((e.TargetContentState as GopherNavState).tLocation, false);
}
catch
{ } // ...
}
the problem occurs sporatically. I'll create 3/4 entries in my back, go back and see that my history is full of the page I'm currently looking at.
I've tried everything, but I still cant get it right.
I've found the source of my heartache: the history menu. Turns out, the fact I was using the chrome from the NavigationWindow was causing my headaches.
To fix this, I've simply turned off navigation controls within the window and made my own (buttons that have the command BrowseBack and BrowseForward).

Categories