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.
Related
I want to ask the user whether they are sure that they wanna move back or not and only move back if they select "Yes", for that I am using a message dialog, but the problem is when I press the back button it moves back and after that the dialog pops up asking whether I want to move back or not.
I checked with break point as soon as messageDialog.ShowAsync executes the page moves back.
Code
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
SystemNavigationManager.GetForCurrentView().BackRequested += SampleConditionalNavigation_BackRequested;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Collapsed;
SystemNavigationManager.GetForCurrentView().BackRequested -= SampleConditionalNavigation_BackRequested;
}
private async void SampleConditionalNavigation_BackRequested(object sender, BackRequestedEventArgs e)
{
e.Handled = true; // I have also tried setting it to false
var messageDialog = new MessageDialog("Are you sure you want to move back?") { Title = "Confirmation" };
// Add commands and set their callbacks; both buttons use the same callback function instead of inline event handlers
messageDialog.Commands.Add(new UICommand("Yes"));
messageDialog.Commands.Add(new UICommand("No"));
// Set the command that will be invoked by default
messageDialog.DefaultCommandIndex = 0;
// Set the command to be invoked when escape is pressed
messageDialog.CancelCommandIndex = 1;
//ShowDialog
var result = await messageDialog.ShowAsync();
if (result.Label == "Yes")
{
var rootFrame = Window.Current.Content as Frame;
if (rootFrame?.CanGoBack is true)
{
rootFrame?.GoBack();
}
}
}
Update 1
even if I comment the code where dialog shows itself the page still navigates back, it seems that e.Handled = true is pretty much useless here
This code is absolutely correct and works perfectly, the issue was that my app had BackRequested event in app.xaml.cs as well and that event was executing and navigating back before this method on this page was able to execute. Removing it from app.xaml.cs fixed the issue.
I've been trying to intercept the user going back from a page in my Xamarin.Forms UWP app, in order to either block it or present them with an "Are you sure?" dialog.
I've been able to remove the navigation bar back button using this in the constructor of the ContentPage:
NavigationPage.SetHasBackButton(this, false);
However, the back button on the mouse (XButton1) still causes the page to back.
I tried disabling it using this on the page:
protected override bool OnBackButtonPressed()
{
return true;
}
This would disable the hardware back button on something like Android, but it is not called at all when hitting the mouse back button.
I've also tried playing with the PointerPressed event on the UWP MainPage:
public MainPage()
{
this.InitializeComponent();
LoadApplication(new MyApp.App());
this.PointerPressed += MainPage_PointerPressed;
}
private void MainPage_PointerPressed(object sender, PointerRoutedEventArgs e)
{
PointerPoint currentPoint = e.GetCurrentPoint(this);
if (currentPoint.PointerDevice.PointerDeviceType == PointerDeviceType.Mouse)
{
PointerPointProperties pointerProperties = currentPoint.Properties;
if (pointerProperties.IsXButton1Pressed)
{
// back button pressed
}
}
}
This method is called correctly for all mouse inputs except for the XButton1 mouse back button if the app's current page is currently in a NavigationPage - almost like Xamarin.Forms is intercepting it somewhere along the way. Outside of a navigation page it picks up the XButton1 fine, and it always picks up every other input (including XButton2).
Is there a way to intercept or disable the XButton1 back function for a Xamarin.Forms UWP app?
Found a renderer workaround that allows you to handle the back button:
using Xamarin.Forms.Platform.UWP;
using Windows.UI.Xaml.Input;
using Windows.Devices.Input;
[assembly: ExportRenderer(typeof(Xamarin.Forms.Page), typeof(MyApp.UWP.Renderers.PageCustomRenderer))]
namespace MyApp.UWP.Renderers
{
public class PageCustomRenderer : PageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Page> e)
{
base.OnElementChanged(e);
this.PointerPressed += PageCustomRenderer_PointerPressed;
}
private void PageCustomRenderer_PointerPressed(object sender, PointerRoutedEventArgs e)
{
if (e.Handled) return;
var point = e.GetCurrentPoint(Control);
if (point == null || point.PointerDevice.PointerDeviceType != PointerDeviceType.Mouse) return;
if (point.Properties.IsXButton1Pressed)
{
e.Handled = true;
if (Element != null)
{
Element.SendBackButtonPressed();
}
}
}
}
}
You can then override OnBackButtonPressed on the page as in the OP to stop it (or remove the Element.SendBackButtonPressed() from the renderer above to disable it entirely).
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;
}
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