how to determine when navigationwindow back button is pressed and trap that event to something extra. I am thinking of managing the page state.
Add a handler to either NavigationWindow.Navigating or NavigationService.Navigating. In your handler:
void NavigationService_Navigating(object sender, NavigatingCancelEventArgs e)
{
if (e.NavigationMode == NavigationMode.Back) {
e.Cancel = true;
// TODO: whatever state management you're going to do
}
}
P.s. You will need to register the navigation service. In my code it didn't work on the page constructor because the navigation service was still null. So I added Loaded="page_Loaded" to the XAML page tag and assigned it there:
bool _navigationServiceAssigned = false;
private void page_Loaded(object sender, RoutedEventArgs e)
{
if (_navigationServiceAssigned == false)
{
NavigationService.Navigating += NavigationService_Navigating;
_navigationServiceAssigned = true;
}
}
The NavigatingCancelEventArgs contains all of the information about the navigation request you'll need to manage page state.
The NavigationService provides a number of events you can subscribe to, if you want to control the navigation process:
Navigating, when the frame is about to navigate. Set Cancel to true
to stop.
Navigated, when navigation has finished but before it is
rendered
NavigationFailed, when something goes wrong
NavigationProgress, when chunks of a remote navigation call are being
downloaded.
NavigationStopped, when the StopLoading method is called
or a new Navigate request is made during downloading
LoadCompleted, when the page has been rendered
Related
I have a language-learning app with the following basic page navigation structure:
Start - App title screen
Languages - List of languages
Lessons - List of lessons for selected language
Activities - Page with activities to work on for selected lesson
The first three pages each have a button that navigates to the next page with a call similar to:
private void ButtonClick(object sender, RoutedEventArgs e) => Frame.Navigate(typeof(SomePage));
On the Activities page, after a user submits his last correct answer with a button press, the app navigates back like this:
private async void SubmitAnswer_Click(object sender, RoutedEventArgs e)
{
...
if (answerCorrect && allActivitiesComplete)
{
Frame.GoBack();
return;
}
...
}
This works; I am returned to the Lessons page. If I hover over Frame with IntelliSense, I see the BackStack property has a count of 3, one for each of the previous pages.
However, I also want to display the software back button. To do this I have the following code:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
...
var nav = SystemNavigationManager.GetForCurrentView();
nav.AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
...
}
Good, now the button shows in the top left in desktop-mode. But pressing it does nothing. So I update with the following:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
...
var nav = SystemNavigationManager.GetForCurrentView();
nav.AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
nav.BackRequested += (x, y) =>
{
Frame.GoBack();
};
...
}
This fails with:
Error HRESULT E_FAIL has been returned from a call to a COM component.
Frame.BackStack shows a count of 0 and Frame.CanGoBack is false. Why is this code flow inconsistent with the button press code flow?
You should ideally wire up the BackRequested event globally, not in the scope of a specific page. This is because if you add an event handler OnNavigatedTo, you will add another each time the page is navigated. This means that clicking the back button will navigate back several times. Also it will keep all the pages that attached the handler in memory, which is a serious memory leak. At the very least you should switch from using a lamda to an event handler method and unsubscribe it in OnNavigatedFrom.
To implement app title bar back button properly you should do this:
Observe the Frame Navigated method, to show/hide the back button as appropriate based on the CanGoBack property
In the BackRequested handler check for CanGoBack to make sure the navigation is possible
An example of the implementation for a blank UWP app would be as follows:
Add the following methods to your App.xaml.cs:
private void SetupAppBarBackButton()
{
_rootFrame.Navigated += RootFrame_Navigated;
SystemNavigationManager.GetForCurrentView().BackRequested += App_BackRequested;
}
private void App_BackRequested(object sender, BackRequestedEventArgs e)
{
if (_rootFrame.CanGoBack)
{
_rootFrame.GoBack();
}
}
private void RootFrame_Navigated(object sender, NavigationEventArgs e)
{
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
_rootFrame.CanGoBack
? AppViewBackButtonVisibility.Visible
: AppViewBackButtonVisibility.Collapsed;
}
Also add a private _rootFrame field:
private Frame _rootFrame;
Finally update the OnLaunched method to store the root frame and setup the back button:
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == null)
{
...
// Place the frame in the current Window
Window.Current.Content = rootFrame;
_rootFrame = rootFrame;
SetupAppBarBackButton();
}
...
}
Please note, that if you are creating the root frame somewhere else as well (like for other activation paths), you need to store the frame and call the SetupAppBarBackButton method there as well.
try this :
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
...
Windows.UI.Core.SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
Windows.UI.Core.SystemNavigationManager.GetForCurrentView().BackRequested += (s,a) =>
{
if (Frame.CanGoBack)
{
Frame.GoBack();
a.Handled = true;
}
}
...
}
I want to check if the current page that the user is on/viewing is a specific page - MainPage. If the user is on MainPage, I want to ensure pressing the back button does not navigate to the previous page but exit the application instead. Is there anyway to check what is the page the user is on currently?
Or is there any better way to achieve what I want to achieve: to ensure pressing the back button on MainPage does not navigate to previous page but exit the application instead?
Have a look at NavigationEventArgs.SourcePageType in the navigation methods. It will return the type of the current page.
NOTE
Until there is entry in the backstack the back button will navigate to that entry (page). If the backstack is empty pressing backbutton will close the app. In my apps when the users presses the home button (navigating to mainpage), I always remove everything from the backstack except for the MainPage, and this ensures that pressing back button on MainPage will always exit.
EDIT
Code that I use to go back to MainPage (The pro for this solution is that this way the navigation events of the pages in the backstack won't be called, because they are removed. If they weren't removed their navigation events would be called while traversing through the backstack, which could cause unexpected behavior.):
private void goHome() {
var bs = Frame.BackStack.Where(b => b.SourcePageType.Name == "MainPage").FirstOrDefault();
if (bs != null)
{
Frame.BackStack.Clear();
Frame.BackStack.Add(bs);
}
this.Frame.GoBack();
}
You can use this code for that:
Put this in App.xaml.cs at end of OnLaunched method,
// Register a handler for BackRequested events and set the
// visibility of the Back button
SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested;
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
rootFrame.CanGoBack ?
AppViewBackButtonVisibility.Visible :
AppViewBackButtonVisibility.Collapsed;
And write this method,
private void OnBackRequested(object sender, BackRequestedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame.CanGoBack)
{
e.Handled = true;
rootFrame.GoBack();
}
//you can check for this here rootFrame.BackStack[rootFrame.BackStack.Count-1].SourcePageType.Name
}
Also you can check for pages and control the visibility of back button in Desktop apps like this,
private void OnNavigated(object sender, NavigationEventArgs e)
{
// Each time a navigation event occurs, update the Back button's visibility
Frame rootFrame = (Frame)sender;
if (rootFrame.BackStack != null && rootFrame.BackStack.Count == 1)
{
// take care in page names
if (rootFrame.BackStack[0].SourcePageType.Name == "MainPage"
|| rootFrame.BackStack[0].SourcePageType.Name == "AnyOtherPage")
{
rootFrame.BackStack.RemoveAt(0);
}
}
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
((Frame)sender).CanGoBack ?
AppViewBackButtonVisibility.Visible :
AppViewBackButtonVisibility.Collapsed;
}
A bit stuck here. I have a splitview app that has the event for backward navigation when the backkey is pressed residing in Appx.cs.
I want to define a different action in one of the pages navigated inside the splitviews content page (for example, when a certain element is visible to dismiss the element) however the app always follows the event set in appx.cs, and ignores the event in the page that is loaded in the content frame. Here is the code in appx.cs:
protected async override void OnLaunched(LaunchActivatedEventArgs e)
{
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (Window.Current.Content == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
_rootFrame = new Frame();
_rootFrame.NavigationFailed += OnNavigationFailed;
_rootFrame.Navigated += OnNavigated;
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
//TODO: Load state from previously suspended application
}
// Place the frame in the current Window
Window.Current.Content = new MainPage(_rootFrame);
// Register a handler for BackRequested events and set the
// visibility of the Back button
SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested;
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
_rootFrame.CanGoBack ?
AppViewBackButtonVisibility.Visible :
AppViewBackButtonVisibility.Collapsed;
}
}
private void OnBackRequested(object sender, BackRequestedEventArgs e)
{
if (_rootFrame != null && _rootFrame.CanGoBack)
{
e.Handled = true;
_rootFrame.GoBack();
}
}
And here is the code in one of the pages that is loaded into the splitviews content pane:
public CalcOutputPage()
{
this.InitializeComponent();
// add page specific handling of back navigation when entering this page
SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested;
}
// page specific back button navigation
private void OnBackRequested(object sender, BackRequestedEventArgs e)
{
if (MobileStackPanel.Visibility == Visibility.Visible)
{
MobileStackPanel.Visibility = Visibility.Collapsed;
e.Handled = true;
}
else
{
e.Handled = false;
}
}
But this isn't working. It works if its the first page loaded in the navigation stack, but at all other times, the app follows the navigation instructions in appx.cs
So to clarify:
the OnBackRequested method in Appx.cs is handling the back navigation in my app.
I have a page (lets call it PageTwo.xaml) open in the splitview content pane.
in PageTwo.xaml, I want to have an event for when OnBackRequested is pressed
I cant at the moment, as the one in Appx.cs is the one that is always called
i would like to know if there is a way to have the OnBackRequested in the PageTwo.xaml.cs page fire, instead of the OnBackRequested in the Appx.cs Page.
Any help would be appreciated. I'm losing my mind trying to get this working :P
Event handlers are triggered in the same order they have been added, so no luck on this side. One alternative is to make your own event, wrapping the original one. In App.xaml.cs:
public event EventHandler<BackRequestedEventArgs> BackRequested;
private void OnBackRequested(object sender, BackRequestedEventArgs e)
{
// Raise child event
var eventHandler = this.BackRequested;
if (eventHandler != null)
{
eventHandler(sender, e);
}
if (!e.Handled)
{
if (_rootFrame != null && _rootFrame.CanGoBack)
{
e.Handled = true;
_rootFrame.GoBack();
}
}
}
Then in your page, subscribe to the child event instead of the main one:
((App)(App.Current)).BackRequested += OnBackRequested;
Make sure to unsubscribe to the event when leaving the page, or you may end up with a memory leak.
I know how to override back button inside a page:
protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
//Do your work here
base.OnBackKeyPress(e);
}
But the problem: I use Inneractive ad service. when I call InneractiveAd.DisplayAd() it shows a new page of its own which doesn't support back button. when it navigate to this page its uri is like this:
/Inneractive.Ad;component/InneractiveFullScreenPage.xaml
The question: is it possible to override back button of that page to navigate back when user presses back?
In HandleBackKeyPress you usually intercept BackKeyPress event, so you would set e.Cancel = true to prevent standard BackKey navigation. To simulate standard navigation you would do something like:
void HookUpBackKeyPress(PhoneApplicationPage page)
{
page.BackKeyPress += HandleBackKeyPress;
}
void HandleBackKeyPress(object sender, CancelEventArgs e)
{
page.NavigationService.GoBack();
}
The problem is to get the reference to the navigated page. The following code will work only after you completely Navigated to the new Page and it might not work when immediately after NavigationService.Navigate(..)
var frame = (PhoneApplicationFrame)Application.Current.RootVisual;
page = (PhoneApplicationPage)frame.Content;
I am writting this method to track if changes occured on a page so the user can trigger a reload of a dependent system. So this as you can see triggers when the user is trying to navigate away from the page. If the e.Cancel is not there the behavior seems fine the async web-service call happen as expected but I am not sure what is really happening in the back.
The reload button click method triggers a chain of event that usually update the display but since the user has navigated away from the page the components are no longer visible. Can this cause problems to the application? Should I be forcing the user to remain on the same page just to prevent possible callback problems?
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
base.OnNavigatingFrom(e);
if (hasDataBeenModified)
{
if (System.Windows.Browser.HtmlPage.Window.Confirm("You have not reloaded the policies\nDo you want to do it now?"))
{
//e.Cancel = true;
ReloadButton_Click(null, null);
}
}
}
Instead of using OnNavigatingFrom, use OnBackKeyPress
protected override void OnBackKeyPress(CancelEventArgs e)
{
if (hasDataBeenModified)
{
if (System.Windows.Browser.HtmlPage.Window.Confirm("You have not reloaded the policies\nDo you want to do it now?"))
{
e.Cancel = true;
ReloadButton_Click(null, null);
}
}
}