Jaime Rodriguez On Windows Phone, Windows Presentation Foundation, Silverlight and Windows 7
If you have a Windows Phone application that hosts the WebBrowser control and allows or encourages the users to navigate within the WebBrowser control’s space, you are going to run into the 5.2.4.b certification requirement:
“Pressing the Back button must return the application to the previous page”
This requirement is easy to understand for PhoneApplicationPages; but for HTML, I am still undecided on how intuitive it would be to a user that is navigating inside a WebBrowser control. Most of the apps I have been involved with that use the WebBrowser control, do not encourage navigation ( they show small HTML inline) or show HTML it in a popup that only has a close button on the top right, so I have never worried about integration between WebBrowser and back button. It was always a “Back” closes the transient popup. That said, I saw a news reader app last week that had a full screen WebBrowser, and a back and forward button in the appbar, this felt weird ( to see a back button in the appbar) I thought the hardware back button should integrate with this “perceived” navigation stack . This got me thinking about 1) how hard it is to code and 2) how error prone it would be. Yesterday, I quickly threw in an HTML page that I felt would cover most linking scenarios. It has regular navigations (A HREF) , shortened URLs (that lead to redirects), navigates using script, and has fragment navigations. I then created a project with WebBrowser to integrate with back button.
Here is my one-hour exploration: 1) Listen to WebBrowser.Navigated event; keep track of the pages that have been visited.
Stack<Uri> history= new Stack<Uri>(); Uri current = null; private void WebBrowser_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e) { Uri previous = null; if (history.Count > 0) previous = history.Peek(); // This assumption is NOT always right. // if the page had a forward reference that creates a loop (e.g. A->B->A ), // we would not detect it, we assume it is an A -> B -> back () if (e.Uri == previous) { history.Pop(); } else { if (current != null) history.Push(current); } current = e.Uri; } 2) Listen to OnBackKeyPress on the page. If the WebBrowser has a navigation stack, cancel the backkeypress and navigate within the webbrowser control’s stack.
protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e) { base.OnBackKeyPress(e); if (!isPerformingCloseOperation) { if (history.Count > 0) { Uri destination = history.Peek(); webBrowser.Navigate(destination); // What about using script and going history.back? // you can do it, but // I rather use that to keep ‘track’ consistently with our stack e.Cancel = true; } } }
As you can see, code is trivial, but it has an issue I did not solve. I can’t tell the difference between:
I tried letting the browser do the work, and peeking at history.length (you can see the code in the sample, commented out), but that does not help since the sequence #1 above leads to a length of 2, instead of a length of 0. Since that does not solve it (and I don’t see a different way, is this good enough? I think it is, because:
Unless I missed another major gotcha, I think this does nicely. It is intuitive to the user in most typical navigations. It lets them out of the browser page (history.length was getting stuck since it never decreased to 0). I also asked internally to the Microsoft folks building apps, and the ones that integrate back button and webbrowser navigation are using the same pattern, and have not had any complaints from users.
To conclude, here is my personal advise/summary:
Sample code is here.
Happy Windows Phone coding!
If you want to keep up with Windows Phone on a more frequent basis, subscribe to my Windows Phone question of the day RSS feed.
Thanks for that Jamie. That was on my list of things to look into but thankfully you've beaten me to it :-)
Thanks for Sharing.
Thanks for sharing Jaime. I was getting tired of my on-line hosted help "backing" back to the app instead of the previous page. Although my case is specific, I modified your code a little so that I could return to my "Index" page (from link on all other pages) and intentionally lose the history in-between. It also "appears" to fix the round-robin you talked about. If you know of any use cases, where this screws up, I'd appreciate if you leave a message here in your Comments. Thanks.
private List<Uri> _history = new List<Uri>();
protected override void OnBackKeyPress(CancelEventArgs e)
{
base.OnBackKeyPress(e);
if (_history.Count > 1)
// Let web_Navigated clean up.
web.Navigate(_history[1]);
e.Cancel = true;
}
private void web_Navigated(object sender, NavigationEventArgs e)
int indx = _history.IndexOf(e.Uri);
if (indx >= 0)
// Remove all up to i.
for (int i = 0; i < indx; i++)
_history.RemoveAt(0);
else
_history.Insert(0, e.Uri);