I have been fighting with this for a while...
I'm using Windows Phone 8.1 Runtime (not silverlight) and I have the following code:
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
if (!ExitWithoutSave().Result) return;
this.navigationHelper.OnNavigatedFrom(e);
}
private async Task<bool> ExitWithoutSave()
{
MessageDialog dialog = new MessageDialog("There are unsaved changes, are you sure you wish to leave?", "Unsaved changes");
dialog.Commands.Clear();
dialog.Commands.Add(new Windows.UI.Popups.UICommand("Yes") { Id = 0 });
dialog.Commands.Add(new Windows.UI.Popups.UICommand("No") { Id = 1 });
dialog.DefaultCommandIndex = 0;
dialog.CancelCommandIndex = 1;
var result = await dialog.ShowAsync();
if (result.Label == "No")
{
canceled = true;
}
return canceled;
}
Basically, I want to ask the user if he wishes to leave without saving, if he says no, then I want to block this functionality.
The problem is, if there's an await during the execution of the OnNavigatedFrom, Windows phone thinks the app has broken and the UI gets blocked.
Is there any way to correctly show a message box on pressing the back button?
If not, is it possible to disable the back button entirely for this page?
Thanks,
Keran
Edit 15-11-2015:
Just wanted to bump this post. I had no luck using HardwareButton events together with Navigation Helper, MessageBoxes still don't work. I can't even cancel the back button press.
So I wanted to renew my question: What is the best way to create a confirm message box on back button press on Windows Phone 8.1 Runtime? F. e. message: "You have unsaved changes, do you wish to exit?": Yes / No.
You can use following event .
HardwareButtons.BackPressed += HardwareButtons_BackPressed;
void HardwareButtons_BackPressed(object sender, BackPressedEventArgs e)
{
}
Related
I am developing a UWP application for Windows 10. I want to show a message dialog box when user reduces the size of the window. As of now, I have tried to implement the feature using this link. My purpose is being fulfilled here except the message dialog appears when user either minimizes the window or changes its size. I have also attached the code snippet below. Thank you.
private async void CoreWindow_SizeChanged(Windows.UI.Core.CoreWindow sender, Windows.UI.Core.WindowSizeChangedEventArgs args)
{
var appView = ApplicationView.GetForCurrentView();
MessageDialog messageDialog = new MessageDialog("");
if (!appView.IsFullScreen)
{
if (!msgboxshown)
{
messageDialog = new MessageDialog("To run the application smoothly, please maximize the window.");
msgboxshown = true;
await messageDialog.ShowAsync();
msgboxshown = false;
}
}
args.Handled = true;
}
My purpose is being fulfilled here except the message dialog appears when user either minimizes the window or changes its size.
When app minimizes, it will invoke EnteredBackground event, you could detect it and show your message dialog.
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
this.EnteredBackground += App_EnteredBackground;
}
private async void App_EnteredBackground(object sender, EnteredBackgroundEventArgs e)
{
var messageDialog = new MessageDialog("To run the application smoothly, please maximize the window.");
await messageDialog.ShowAsync();
}
Update
As #Raymond Chen said, it looks os bug. So please use the above methods with caution.
Problem: I have followed different tutorials on how to implement a spinner in Android, such as this. However, when the app first opens, the toast is made immediately without the user selecting anything.
My Solution: I tried creating a boolean variable, that is it set to 'false' on start-up. The toast will show only once that boolean is set to 'true'. This solution works: no toast is made on start-up, but if the user had to select the first item (which is selected by default), then no toast is made. After the user selects a different option and thereafter re-selects the first option, then only will the toast for the first item be shown. This isn't a major issue though. My code is shown below.
In my OnCreate method, I have the following:
boolToast = false;
Spinner spinner = FindViewById<Spinner>(Resource.Id.spinner);
spinner.SetSelection(0, false);
spinner.ItemSelected += new EventHandler<AdapterView.ItemSelectedEventArgs>(Spinner_ItemSelected);
var adapter = ArrayAdapter.CreateFromResource(this, Resource.Array.array, Android.Resource.Layout.SimpleSpinnerItem);
adapter.SetDropDownViewResource(Android.Resource.Layout.SimpleSpinnerDropDownItem);
spinner.Adapter = adapter;
My spinner_ItemSelected method is as follows:
private void Spinner_ItemSelected(object sender, AdapterView.ItemSelectedEventArgs e)
{
Spinner spinner = (Spinner)sender;
string toast = string.Format("Selected item is {0}", spinner.GetItemAtPosition(e.Position));
if (boolToast == true)
{
Toast.MakeText(this, toast, ToastLength.Long).Show();
}
boolToast = true;
}
I am satisfied with it already, but wanted to know if there is maybe a more elegant or proper way of achieving this. I have seen a few other fairly similar questions, but those were mostly in Java which I am very unfamiliar with.
In one stage of my app (Android & iOS are the ones we care about) we've got three pages which take in details and then open a webView for the user to input their card details to take a payment - this can't be done in the app due to Apple's guidelines.
I need to format the navigation in a way that when the user has finished in the webView it closes and then closes the 3 previous modals to get back to the original page. I've got it all working with the Appearing event so each page just closes itself:
this.Appearing += async (s, e) =>
{
await Navigation.PopModalAsync();
};
The issue I'm now having is that when the user presses the back button on the phone, it closes all of the pages that they've been through already & back to the original. I thought about implementing a custom nav bar and disabling the back button on the hardware but this would cause the same problem with the Appearing event.
Is there any easy way to solve this?
EDIT: Relevant code;
async void OnButtonClicked(object sender, EventArgs eventArgs)
{
if (IsConnected)
{
ActivityIndicator.IsVisible = true;
var button = (Button) sender;
button .IsEnabled = false;
await Navigation.PushModalAsync(new Page());
this.Appearing += (s, e) =>
{
ActivityIndicator.IsVisible = false;
button.IsEnabled = true;
RefreshPage();
};
}
else
{
NoInternetLabel.IsVisible = true;
}
}
Use this:
YourButton.Clicked += OpenPage;
OpenPage looks like this:
async public void OpenPage(object sender, EventArgs args)
{
await Navigation.PushAsync(new PageToShow());
}
You don't have to do anything to handle the PageToShow() closing, that happens by itself when the user presses the back button.
Managed to solve this by using Actions. In each new Page() we passed up an async method to close it once the one after had completed;
var nextPage = new Page(async () =>
{
await Navigation.PopModalAsync();
_completedSuccessfully();
});
await Navigation.PushModalAsync(nextPage);
And in the new page class;
private readonly Action _completedSuccessfully;
public Page(Action completedSuccessfully)
{
_completedSuccessfully = completedSuccessfully;
}
This meant that when the webView closed it called the completedSuccessfully() action and then chained all of them to the original page.
I've got a problem with the Windows.UI.Popups.MessageDialog class. The thing is: I want to ask the user if (s) he wants to close the app, when clicking the back button (on the first Page). I've tried two approaches:
First is setting the BackPressed method async:
private async void HardwareButtons_BackPressed(object sender, Windows.Phone.UI.Input.BackPressedEventArgs e)
{
Windows.UI.Popups.MessageDialog logout = new Windows.UI.Popups.MessageDialog("Do You want to close the app?");
logout.Commands.Add(new Windows.UI.Popups.UICommand("Yes", Exit));
logout.Commands.Add(new Windows.UI.Popups.UICommand("No"));
await logout.ShowAsync();
}
Second is using the System.Threading.Tasks.Task.WaitAny(...) method:
private void HardwareButtons_BackPressed(object sender, Windows.Phone.UI.Input.BackPressedEventArgs e)
{
Windows.UI.Popups.MessageDialog logout = new Windows.UI.Popups.MessageDialog("Do You want to close the app?");
logout.Commands.Add(new Windows.UI.Popups.UICommand("Yes", Exit));
logout.Commands.Add(new Windows.UI.Popups.UICommand("No"));
System.Threading.Tasks.Task.WaitAny(logout.ShowAsync().AsTask());
}
What I get is:
First: the MessageDialog shows just for a sec, then app minimizes, switching to it, and pressing the back button, just minimizes app again, but MessageDialog is not shown).
Second: it's a deadlock (after clicking "Yes", MessageDialog closes, but the Exit() method is not called, app freezes).
First of all:
Microsoft recommends that apps not close themselves programmatically.
See here:
App lifecycle - Windows app development
That page says:
You can't include any UI in your app to enable the user to close your app, or it won't pass the Store certification process.
Although I know that there are apps in the Store that do this anyway.
Anyway, in the HardwareButtons_BackPressed event handler, e.Handled = true; is needed to prevent the app from closing. This also applies for when navigating backwards in your app.
Besides this, Frame.CanGoBack should be checked to see if the app should navigate or close.
Also, you should not need to use a task, but just await MessageDialog.ShowAsync(), although you could create a task that returns a boolean telling whether to proceed or not.
Here is an entire code example to use that handles navigating backwards in the frame stack and displays a message dialog asking whether to close the app or not when the first frame is reached:
App.xaml.cs:
using Windows.Phone.UI.Input;
using Windows.UI.Popups;
public sealed partial class App : Application
{
public App()
{
HardwareButtons.BackPressed += HardwareButtons_BackPressed;
}
private async void HardwareButtons_BackPressed(object sender, BackPressedEventArgs e)
{
Frame frame = Window.Current.Content as Frame;
if (frame == null) { return; }
e.Handled = true;
if (frame.CanGoBack) { frame.GoBack(); return; }
else
{
string content = "Do you want to close the app?";
string title = "Close Confirmation";
MessageDialog confirmDialog = new MessageDialog(content, title);
confirmDialog.Commands.Add(new UICommand("Yes"));
confirmDialog.Commands.Add(new UICommand("No"));
var confirmResult = await confirmDialog.ShowAsync();
// "No" button pressed: Keep the app open.
if (confirmResult != null && confirmResult.Label == "No") { return; }
// "Back" or "Yes" button pressed: Close the app.
if (confirmResult == null || confirmResult.Label == "Yes") { Current.Exit(); }
}
}
}
In my UWP app when i click on mobile back button app get close, so add this code in app.xaml.cs
private async void HardwareButtons_BackPressed(object sender, BackPressedEventArgs e)
{
e.Handled = true;
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame.CanGoBack && rootFrame != null)
{
rootFrame.GoBack();
}
else
{
var msg = new MessageDialog("Confirm Close! \nOr Press Refresh Button to Go Back");
var okBtn = new UICommand("OK");
var cancelBtn = new UICommand("Cancel");
msg.Commands.Add(okBtn);
msg.Commands.Add(cancelBtn);
IUICommand result = await msg.ShowAsync();
if (result != null && result.Label == "OK")
{
Application.Current.Exit();
}
}
}
and
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
/* Because of this line my app work on mobile great but when
when i debug on pc it through exception "show in image" */
HardwareButtons.BackPressed += HardwareButtons_BackPressed;
}
After Done all this code when i debug app on phone, app successfully run -
Mobile Debuging:
But when debug in pc with same code, it show this error- PC Debuging:
when i remove HardwareButtons.BackPressed += HardwareButtons_BackPressed; then issue with pc debugging resolved but in mobile debuging back button again is not work.
The reason is that HardwareButtons API is not the universal solution for handling the back button. This API is available only in the Mobile extension SDK and trying to invoke it on other SKU will cause this exception, because the type is not available.
To enable the same functionality on all systems, you will need to use the new universal back button event:
SystemNavigationManager.GetForCurrentView().BackRequested += BackButtonHandler;
This will work the same on phone, PC, tablets,Xbox One, Surface Hub and HoloLens.
On PC this button is not shown by default, so you have to display it manually or create your own. To show the Back button in window's title bar, use:
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
AppViewBackButtonVisibility.Visible;
It is recommended that you hide this button once Frame.CanGoBack is false, because in that case the button is no longer useful. You should do this after each navigation of the frame. Best place to do this is when setting up the root frame in App.xaml.cs:
Frame rootFrame = Window.Current.Content as Frame;
rootFrame.Navigated += UpdateAppViewBackButton;
Now the handler could look like this:
private void UpdateAppViewBackButton( object sender, NavigationEventArgs e )
{
Frame frame = (Frame) sender;
var systemNavigationManager = SystemNavigationManager.GetForCurrentView();
systemNavigationManager.AppViewBackButtonVisibility =
frame.CanGoBack ? AppViewBackButtonVisibility.Visible :
AppViewBackButtonVisibility.Collapsed;
}
On Application close
I have also noticed that you are using Application.Current.Exit(); to exit the app. This is however not recommended. Once the user chooses OK in the dialog, you should rather set the e.Handled = false and let the system handle the app close manually. This will ensure the app suspension will run as expected and the app will stay in memory if the system has enough resources and will then launch faster again. Application.Current.Exit() kills the application and it is not recommended for UWP apps.
One thing to remember is that on Desktop, there is currently no way to catch the user clicking the close button in the app's title bar, so your confirmation dialog will not display in that case unfortunately.