Persist DataContext changes after back button in Win8 Metro app - c#

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.

Related

How to reload page when navigating

I am developing WPF MUI application.I navigate to another page using button onclick and print some text on page1.xaml . after i navigate using another button to print another text on page1.xaml .but i could not do that.my out put was not new text.it is early details only.can't reload page1.xaml .when navigating I pass the parameter and according to parameter print deference text on same page.can anyone help me?
this is my navigation code
var frame = NavigationHelper.FindFrame(null, this);
frame.Source = new Uri("../Content/Sale/SaleInvoice/Nested/saleNested.xaml", UriKind.Relative);
Have the page databound to a ViewModel. Once you want to refresh just create a new Viewmodel. It would make sense to have the execution of the reloading being done in a backgroudworker so your UI stays responsive. This is esspecially usefull if you are refreshing some resource from a webService or some other online source.
The Data bind to the page at the initial time to reload the page I'm using this method
On xaml page just add Loaded Property:
<UserControl x:Class="ModernUINavigationApp1.Pages.Page"
...
Loaded="OnLoad" >
Then Add event handler in code behind to make the page do whatever you want when it's loaded
private void OnLoad(object sender, RoutedEventArgs e)
{
}
Hope this help :D
Make sure your ViewModel implements INotifyPropertyChanged. If your Page1.xaml's DataContext is set to the ViewModel, and your XAML uses bindings properly, any change to the ViewModel will be reflected in the UI. You won't need to refresh anything. Just update the property in the ViewModel object.
If you update your question with example XAML and C#, I can be of more help.

Implementing Tab-like Navigation model in Windows Phone 8 — How?

I am currently trying to implement a navigation scheme that closely resembles that of the Internet Explorer app on Windows Phone 8.
The IE app can have multiple tabs that the user can switch between. Each of these tabs has its own history. Hitting the Back Button on the phone takes you to the previous page in that tab's Navigation history (Not the PhoneApplicationFrame.BackStack). If there are no previous pages, the back button takes you to the previous opened tab or, if none, exits the app.
Why this is troubling me
Application.RootVisual can only be set once. So you can't have two PhoneApplicationFrames, each with its own BackStack, to swap RootVisual between the two.
You cannot traverse the BackStack (it is a Stack, after all). Can only call GoBack(). Calling GoForward() will throw an Exception.
PhoneApplicationFrame.GoBack() removes entries from the BackStack which can only be added again through the PhoneApplicationFrame.Navigate(...) method. So, manipulating the BackStack is a no-go.
Bright Ideas
Keep a Dictionary<enum, List<string>> which is updated with each call to a custom NavigationService.Navigate(tabTypeEnum, uriString, params). This will keep the Navigation history for each tabType, allowing us to possibly Navigate through the current Tab's history when the BackKeyPress event is handled. Bad thing is, calling Navigate(...) to go to previous pages (instead of GoBack) will add to the BackStack. So requires maintenance that hurts my brain right now.
Create a custom NavigationAwareTabPage : PhoneApplicationPage, which keeps track of its own navigation history and fakes navigation by animating a transition when its Content is changed. The only time we call a true Navigate is when we switch from one tab to another. (I think this is what the IE app does.) And the BackKeyPress would have to look like below.
This:
void RootFrame_BackKeyPress(object sender, CancelEventArgs e)
{
var rootFrame = sender as PhoneApplicationFrame;
if (rootFrame.CanGoBack)
{
// Get the NavigationAwarePage
var navAwarePage = rootFrame.Content as NavigationAwareTabPage;
if(navAwarePage.CanGoBack())
{
// This method "navigates" to the next page
// by changing the navAwarePage.Content
navAwarePage.GoBackToPreviousPage();
e.Cancel = true;
}
}
}
Has anyone been down this road?
All the magic of how ReactiveUI overrides the Back button is here:
https://github.com/reactiveui/ReactiveUI/blob/master/ReactiveUI.Mobile/WP8AutoSuspendApplication.cs#L91
The way that this works in ReactiveUI is that there is a content control named RoutedViewHost that is listening to the Back being signaled (you can do whatever you want in response to the hardware Back button and cancel the default action). ReactiveUI maintains its own ViewModel-based back stack and manipulates that instead of using WP8s, and you never call WP8s navigation methods.
This effectively means that, from WP8's perspective, there is only ever one page in the entire application. WP8 really wants to create that page itself, and it's specified in WMAppManifest.xml.
Don't try to participate in WP8's Frame system, it really wants to work its own way and you won't be able to convince it otherwise.
One last important thing, if you're at the bottom of your back stack, you must allow the default Back action to happen (i.e. what WP8 wanted to do, take you out of the app). Otherwise you'll probably fail Certification and you're Doing It Wrong™.

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/

Hide elements in a web page whilst using a web-browser control for WP7

I am currently working on an app for WP7 for my university, and need a temporary solution to a problem. Now this solution is, that I will be loading a webpage using the web browser control for WP7. For example: http://m.iastate.edu/laundry/
Now as you see on the webpage, there are certain elements I want to hide, for example the back button. For now, what I have done to handle the back button is something like this:
private void webBrowser1_Navigating(object sender, NavigatingEventArgs e)
{
// Handle loading animations
// Handle what happens when the "back" button is pressed
Uri home = new Uri("http://m.iastate.edu/");
// The the current loading address is home
// Cancel the navigation, and go back to the
// apps home page.
if (e.Uri.Equals(home))
{
e.Cancel = true;
NavigationService.Navigate(new Uri("/MainPage.xaml", UriKind.Relative));
}
}
Now that works beautifully, except for the part that there is a back button on the hardware.
So my second option is to completely hide the back button ONLY on that page, and not its children. So not on http://m.iastate.edu/laundry/l/0
I am still debating on just parsing the data and displaying it in my own style, but I'm not sure if that's completely needed seeing how the data needs constant internet service and is already in a well-put format. Plus, I feel like that would be a waste of resources? Throw in your opinions on that too :)
Thanks!
You should inject a script in the page with InvokeScript.
Here is the kind of Javascript code you need to remove the back button:
// get the first child element of the header
var backButton = document.getElementsByTagName("header")[0].firstChild;
// check if it looks like a back button
if(backButton && backButton.innerText == "Back") {
// it looks like a back button, remove it
document.getElementsByTagName("header")[0].removeChild[backButton];
}
Call this script with InvokeScript:
webBrowser1.InvokeScript("eval", "(function() { "+ script +"}()");
Warning: IsScriptEnabled must be set to true on the web control
If the removal of the back button depends of the page, just test the navigating URI in C# and inject the script if neeeded.

What determines whether NavigationCommands.BrowseBack calls the page constructor?

I have two pages with similar logic in them. Load the page, click some buttons that will show/hide other buttons, continue to next page. When I hit the next page, if I click the back button I am returned to the previous page.
The difference is that one page (FirstPage) will have the constructor called when I click the back button, which has a call to reset the defaults. The other page (SecondPage) doesn't get the constructor called and I'm not sure why.
public FirstPage()
{
InitializeComponent();
DisplayStuff();
}
FirstPage has KeepAlive set to False.
public SecondPage(object arg1, object arg2)
{
InitializeComponent();
DisplayStuff(arg1, arg2);
}
This page also has KeepAlive set to False. These two pages don't inherit from anything and there is nothing that overrides any of the properties. The only difference I can see is the empty constructor, so I tried giving SecondPage an empty constructor and still no luck.
I'm relatively new to WPF (I work on it for an hour or two every 6 months), so what am I missing?
Here is the back button in case it is relevant.
<Button Command="{x:Static NavigationCommands.BrowseBack}" />
Edit: When I click the back button, SecondPage doesn't keep its state. It just loads an empty page because DisplayStuff hasn't been called yet.
Navigation Code:
NavigateTo(new SecondPage(arg1, arg2));
protected void NavigateTo(Page page)
{
NavigationService.Navigate(page);
}
I created a similar sample application and had similar behaviour. What I figured out that when you go back to a page the constructor is not called unless the page is the first page in the journal
Read this section in Navigation in WPF:
When the page Page is navigated back to, using the journal, the following steps take place:
The Page (the top journal entry on the back stack) is instantiated.
The Page is refreshed with the state that was stored with the journal entry for the Page.
The Page is navigated back to.
Good luck!
After reading Paul Stovell's article on WPF navigation, the way I want to display stuff is not going to work.
When navigating, if you click "Back", WPF can't possibly know what values to pass to the constructor; therefore it must keep the page alive. Here's the trace output:
Since WPF can't call the constructor, it won't. It'll just keep the page alive.
He goes on to mention that KeepAlive doesn't work if you're not navigating via URI, and Loaded and Unloaded are called each time, so I can just move all my logic there and I won't need the constructor to be called on the back navigation.

Categories