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.
Related
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;
}
In my current design approach I am running into a case where I'm having to perform an extra Database read and Databind on controls, only to overwrite them later on in the page lifecycle. The end result to the user is the same, but behind the scenes I'm doing two unnecessary and costly operations.
The search button is on the Master page. All the display controls (gridviews/tables/labels/etc) are on the content pages. The database call and databinding must be done in the Page_Init() method (most of the controls being used must be bound here).
When the user searches for an item, I save his search input in the session to persist it as being actively viewed across all the other content pages. So that when any content page is initialized, I check to see if they are actively viewing an item, and if so display his details.
// Master Page
protected void BtnSearch_OnClick(object sender, EventArgs e)
{
MySession.Current.ItemName = TxtItem.Text.Trim();
Server.Transfer("~/default.aspx");
}
// Content Page
protected void Page_Init(object sender, EventArgs e)
{
// If they're actively viewing an item, display its info
bool HasActiveItem = string.IsNullOrEmpty(MySession.Current.ItemName) ? false : true;
if (HasActiveItem)
{
// Makes one DB call to get all info;
// Binds all that info to GridViews/tables/labels on the page
BindAllDataControls(MySession.Current.ItemName);
// Display
DisplayItemDetails();
}
}
Here's the Issue: Say the user is currently viewing an Item = Boots. This value is saved in the session. Now the user searches for Item = Shirts and clicks the search button.
When the content page loads, he checks if there's anything in the session, there is but it's Item = Boots. It performs the unnecessary database/databind calls. Then, the BtnSearch click event triggers, we load the new Item = Shirts value into the session, and start the life cycle over. This lifecycle is good to go.
How can I get rid of that extra processing? Is the approach wrong?
I've thought about performing Page.IsPostback() and Page.IsCallback() logic during the content page initialization, but there are multiple other controls that cause postbacks and callbacks in the real web application. Therefore, I don't think I can get enough info from this to make the determination to skip or not. I've thought about wrapping the entire part of the page that contains all the GridViews/tables/labels/etc. in an ajax Callback, but I don't think that's a good approach. I've thought about sending an ajax call back to the server during the BtnSearch click that sets a flag. Then during load, we read that flag to skip or not, but there's no guarentee that that ajax call will process before the search BtnClick event.
Any ideas? Or should I just eat these extra calls and be done with it? There's got to be another way.
The most simple solution I see here is to make the check of search text change direct on PageInit and not on the button call.
// Master Page
protected void BtnSearch_OnClick(object sender, EventArgs e)
{
// remove that lines
// MySession.Current.ItemName = TxtItem.Text.Trim();
// Server.Transfer("~/default.aspx");
}
// Content Page
protected void Page_Init(object sender, EventArgs e)
{
// direct add here your variable on session
var vSearchText = TxtItem.Text.Trim();
if(!string.IsNullOrEmpty(vSearchText))
MySession.Current.ItemName = vSearchText ;
// ------------ rest of your code ---------------
// If they're actively viewing an item, display its info
bool HasActiveItem = string.IsNullOrEmpty(MySession.Current.ItemName) ? false : true;
if (HasActiveItem)
{
// Makes one DB call to get all info;
// Binds all that info to GridViews/tables/labels on the page
BindAllDataControls(MySession.Current.ItemName);
// Display
DisplayItemDetails();
}
}
Now to tell you the truth, the Server.Transfer is not good idea, what I do is that I use parameter in the URL, to include the search string from the input of the user, so when the user is add something for search I create the url as:
http://www.mydomain.com?q=test
and then I read the q and use it to fill the search box and make the search. This way you also have a SEO friendly search, the user can save his search and you avoid the server transfer that have other issues.
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/
I am building a Windows 8 app (C#/XAML) and using the Frame.Navigate() property to move between pages. Each page has an AppBar icon that will refresh that data on a page, and set the DataContext property. This works, and the UI updates accordingly after the button is pressed.
The problem I'm seeing shows up when I navigate to a different page, then click the back arrow to return to the previous page.
When the OnNavigatedTo(NavigationEventArgs e) method runs (after clicking the back arrow), the e.Parameter value is and old value (before I clicked the refresh button, and the DataContext was updated).
I don't know how to update the parameter value any other way than using Frame.Navigate(typeof(PageTypeName), paramValue);, but I don't want to initiate a navigation action.
My question is two fold.
How can I persist DataContext changes so that when I return to pages, the value I've set is exposed via e.Parameter in the OnNavigatedTo(NavigationEventArgs e) method.
Is there some kind of reference metrial that explains the Navigation lifecycle in Win8 Apps?
... or should I be doing this a different way?
I found the solution.
NavigationCacheMode = Windows.UI.Xaml.Navigation.NavigationCacheMode.Required;
This tells the frame that it should always cache the old instance of my page, and not create a new one when i navigate back to it.
This way my new DataContext value is not overwritten when I return to the page.
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).