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).
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;
}
}
...
}
Is there a way i can "suspend" a UWP app by pressing the back button.
I'm not even sure suspending is the right term but what i want to do is to close the app when the user presses the back button rather than going back to the Signup/Login page in my app.
I want the app go close but also still be shown when the user opens the recent apps menu by holding the back button.
Heres my code in the App.Xaml.Cs
private void OnBackRequested(object sender, Windows.UI.Core.BackRequestedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame.CurrentSourcePageType == typeof(Pages.Home))
{
App.Current.Exit();
}
if (rootFrame.CanGoBack)
{
e.Handled = true;
rootFrame.GoBack();
}
}
The Problem is with the App.Current.Exit();
it terminates the app where what i want to do is just close and and not go back to the MainPage.
How can I do that?
To leave your app without terminating it, just remove the App.Current.Exit(); and ignore the back event (e.Handled = false;). You will then get the default behavior which is to leave the app on mobile devices. On desktop, your app will stay on the page it is. You will then have to do anything that fit your need to save/restore your app in its appropriate state using the suspending/resuming event handlers.
void OnBackRequested(object sender, Windows.UI.Core.BackRequestedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame.CurrentSourcePageType == typeof(Pages.Home))
{
// ignore the event. We want the default system behavior
e.Handled = false;
}
else if (rootFrame.CanGoBack)
{
e.Handled = true;
rootFrame.GoBack();
}
}
I have an app and it works as expected on Windows 10. But on Windows 10 Mobile, when user presses back button, application closes. In my back requested event handler I even commented out GoBack() method but app still closes.
private void MobileNavigationService_BackRequested(object sender, BackRequestedEventArgs e)
{
e.Handled = true;
var navigationService = UnityConfiguration.Resolve<IMobileNavigationService>();
if (navigationService.CanGoBack())
{
//navigationService.GoBack();
}
}
I have a feeling that even though I set e.Handled = true it ignores it and acts like there is no handler.
Updated
As an additional information
App has only one page, Shell. it has common things like menu and title bar. It also has frame. In frame I open all the other pages. So going back, for my app, means going back in that frame, not in entire application. I want to override default behavior.
Setting up an event handler for BackRequested and to navigate back in the page stack.
Enabling and disabling the title bar back button based on the app's internal page stack.
You can get this DEMO from Microsoft in GitHub
Back Button Sample
protected override void OnNavigatedTo(NavigationEventArgs e)
{
bool optedIn = false;
if ((bool)e.Parameter)
{
optedIn = true;
}
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame.CanGoBack && optedIn)
{
// If we have pages in our in-app backstack and have opted in to showing back, do so
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
}
else
{
// Remove the UI from the title bar if there are no pages in our in-app back stack
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = 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 made a WebBrowser and it works except the back button after pressing the back button the app closes and does not go back one in history. How can I solve the problem? I found solutions in the internet but they don't seem to work.
public MainPage()
{
this.InitializeComponent();
this.webBrowser.Navigate(new Uri("http://www.google.com", UriKind.Absolute));
this.webBrowser.LoadCompleted += webBrowser_LoadCompleted;
this.webBrowser.NavigationFailed += webBrowser_NavigationFailed;
this.webBrowser.IsScriptEnabled = true;
}
protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
webBrowser.InvokeScript("eval", "history.go(-1)" );
}
P. S.:that is not the whole script but i think the rest is unneccesary if not tell me :)
P. P. S.:I'm new to Windows Phone programing.
Web browser is just a control inside the page and pressing the device back button navigates back to the previous page or exits the app if it has only one page. So, you would need to stop page navigation on back key press something like this.
protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
e.Cancel=true;
}
This prevents a backnavigation
Now rest is to go to the previous page which can be done by
webBrowser.InvokeScript("eval", "history.go(-1)" );
so the event becomes
protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
e.Cancel=true;
webBrowser.InvokeScript("eval", "history.go(-1)" );
}
Try to do:
protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
WB1.InvokeScript("eval", "history.go(-1)");
e.Cancel = true;
}
When you override OnBackKeyPress, and you don't perform e.Cancel = true; it will do your code, but will also do what normal BackButton does - NavigateBack, Exit App and so on. But you must remember to leave the User an ability to exit your App or Navigate Back, so it will be more suitable to check some conditions (e.g. your webbrowser history is not null) and then do e.Canel, otherwise Exit the App.
To exit the app when you are in the root (so you can approve the cerfitication requirements), and also go back in navigation until you are in the root, try with this
protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
if (MiniBrowser.CanGoBack){
e.Cancel = true;
MiniBrowser.InvokeScript("eval", "history.go(-1)");
}
}