Skip a page when the back button is pressed, WP7 - c#

In the current released version of WP7 you have no programmatic control over the page stack.
I start at A, go to B, and then C. B is the data entry page for new items, so coming back from C I want to logically land at A. Currently I listen for navigation on B that comes from C and force another back onto A. However, the event doesn't happen soon enough to stop the page from displaying on-screen.
A -> B -> C
C -> A
A is the top level list page. B is the new item page. C is the item detail page.
This only occurs on one form so far so my workaround is to override OnNavigatedTo in the page I want to skip, and call "go back" programmatically. However, this has the undesired effect of briefly showing the page and then immediately navigating off of it.
Is there a workable way to stop the flicker?
Should my workaround instead be to take full control of page navigation, including go backs? This will leave the page stack is a strange state, but that would be hidden from the user if I control all navigation.
I know there is a new feature in Mango to pop a page from the page stack programmatically, but I'm curious to know if there is a solution to the problem in the released version.
Another motivation for this is I don't know what version will be best to target, the latest, or the one that is just enough for the app. For the time being I'm sticking with the live version.

You should read this blog post on solving circular navigation issues and you can download this recipe code which demonstrates non-linear navigation.

I have stopped the flickering by making the root frame transparent for the duration of the skip. This example isn't straight from my code.
Firstly on the page you wish to skip, override OnNavigatedTo and test to see where you have come from (this is where my code gets specific, I keep track of where I am):
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// If I've come from page C, go back again.
NavigationService.GoBack();
}
Secondly, in the main App.xaml.cs register an event handler for Navigating (I put it in public App() constructor):
RootFrame.Navigating += RootFrame_Navigating;
Finally, flesh them out to hide the frame and show it again for the duration of the skip:
private bool _skipped;
private void RootFrame_Navigated(object sender, NavigationEventArgs e)
{
RootFrame.Opacity = 100;
RootFrame.Navigated -= RootFrame_Navigated;
}
private void RootFrame_Navigating(object sender, NavigatingCancelEventArgs e)
{
if (_skipped)
{
_skipped = false;
RootFrame.Navigated += RootFrame_Navigated;
}
if (e.NavigationMode == NavigationMode.Back &&
e.Uri.OriginalString.Contains("ThePage.xaml"))
{
RootFrame.Opacity = 0;
_skipped = true;
}
}
The code to determine when to make the page transparent is also different in my actual code, but I've added an implementation to the answer for illustration. This is almost identical to the code in the recipe linked in another answer, but doesn't involve needing to use a third party DLL.
I provided my own answer because I've seen the sources provided in the other answers before, but never paid attention to the code for hiding the root frame. I am not using the Non-Linear Navigation Service, just the code fragment for frame transparency (I don't need it to detect circular navigation as I'm well aware of the design choices I make in the app and can spot them myself :-)
This suffices as a workaround in the (currently) one case I have where I need to skip a page that doesn't make sense when going back. I'd like to think when Mango comes out I will be best placed targeting the latest version, so this code will soon be defunct.
Sources:
App Hub code recipe for Non-Linear Navigation
Non-Linear Navigation Service Blog Post

There is no way to do it before Mango, except with the flickering way you already know.
In Mango, you can use NavigationService.RemoveBackEntry.
And it's more a question of a bad application design, than anything else. You shouldn't require a immediate page for anything.
Your workaround should be to design a application structure that doesn't require any immediate pages.

Related

How to limit focus to a Control in WPF

I Have a Page Containing some controls, this page will be loaded into a ContentControl in a Window.
Now, How can i limit the focus cycle in my Page? I don't want to pass the focus to out of Page after pressing TAB in last item of my page.
As a brief, How Can I determine the next focus control and change it
It's always difficult to fully understand what a question author wants when they don't bother to provide you with more than a few hastily typed lines. However, as far as I understand your problem, it seems that you have some problem with focusing in your WPF Application.
The first thing to do is to direct you to the Focus Overview page on MSDN, where you can find out about the different focus types used in WPF. In particular, please pay attention to the Navigating Focus Programmatically section which discusses the TraversalRequest Class that can help developers to move focus programmatically.
You should also pay careful attention to the KeyboardNavigation section that discusses the KeyboardNavigation Class. This class contains some properties that enable you to define how the Tab key works in various scenarios, so this may be what you're after. It is used like this (from the last linked page on MSDN):
KeyboardNavigation.SetTabNavigation(navigationMenu, KeyboardNavigationMode.Cycle);
For future reference, you will get quicker and more accurate answers if you provide clear questions that include all of your requirements at the time of posting.
Have you tried looking at the FocusLost event - you might possibly be able to just refocus the page control with an event handler.
private void Page_LostFocus(object sender, System.Windows.RoutedEventArgs e)
{
((UIElement)sender).Focus();
}

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/

Maintain Control focus across post backs using PostBackOptions.TrackFocus

Maintaining focus across post backs is an apparently difficult task. Searching Google, you will find a ton of people that desire the same thing, but all hook it up differently, and mostly, custom-ly. I would like to avoid a custom implementation, especially if there's a way it's supported by .NET. Only after some very deep searching, did I come across PostBackOptions.TrackFocus, mentioned quietly in another stack overflow post. According to MSDN:
Gets or sets a value indicating whether the postback event should return the page to the current scroll position and return focus to the current control."
Holy crap, this is supported by .NET 4? AWESOME. But we have a ton of custom controls, how does .NET know how to set the focus on a control? I have no idea. Looking a the MSDN documentation for System.Web.UI.Control, there's an interesting method:
public virtual void Focus()
"Use the Focus method to set the initial focus of the Web page to the
control. The page will be opened in the browser with the control
selected."
Alright, clearly overridable. But what is the recommended method of doing so? It returns void. No examples. Unable to find any examples of people overriding this method in their implementations. However, after overriding it and doing nothing more than throwing an exception, it becomes evident that this is not how ASP.NET gets focus on a control that had focus before the post back: it never gets called.
After a ton of debugging using Firebug, I have found that enabling PostBackOptions.TrackFocus works! Sometimes. It is apparent that the focus of a control is only maintained when the control calls the __doPostBack JavaScript method. Other controls that launch a PostBack (when pressing enter inside the control), call WebForm_OnSubmit(), which doesn't update the ASP hidden field __LASTFOCUS. __doPostBack calls WebForm_OnSubmit() after setting the hidden fields.
This is where I'm currently stuck. It's looks as if I need to get everything to call __doPostBack, no matter what. There's very, very little documentation on the use of TrackFocus. So does anyone have any tips from here?
I've been maintaining focus accross postbacks using the method in this article:
(ie: store focus in __LASTFOCUS hidden field on field enter event clientside for all controls)
http://www.codeproject.com/KB/aspnet/MainatinFocusASPNET.aspx
If you've gotten as far as having __LASTFOCUS show up on the page, this should get you most of the rest of the way...
Note: It'd be nice to find a way to keep the extra javascript from bloating __VIEWSTATE for example.
It was working pretty well for me until I figured out that some of my pages included the hidden __LASTFOCUS field and some of my pages didn't. (That's what prompted me to search around and find your question) Now I'm just trying to figure out how to make sure __LASTFOCUS always shows up on every page I want to keep track of focus on... (Looks like I'll have to open a separate question about it)
Here is what I just did. Assuming you have a handler in your code behind that takes care of the event and has a signature like this:
protected void myEventHandler(object sender, EventArgs e)
You can use this line of code to restore focus back to the sending object:
ScriptManager.RegisterStartupScript((WebControl) sender, sender.GetType(), "RestoreFocusMethod", "document.getElementById(\"" + ((WebControl) sender).ClientID + "\").focus();", true);
just using the Focus() method of the sending control will reposition the page (if you are scrolled down a bit), but this works beautifully. And if you have specific handlers for your control, you can just use the control itself rather than casting the sender to a WebControl, like this:
protected void CityListDropDown_SelectedIndexChanged(object sender, EventArgs e)
{
...
ScriptManager.RegisterStartupScript(CityListDropDown, CityListDropDown.GetType(), "CityDropDownRefocus", "document.getElementById(\"" + CityListDropDown.ClientID + "\").focus();", true);
}

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