Johnathon Sullinger
Johnathon Sullinger

Reputation: 7414

Windows Phone back button not being handled

I have registered a handler with the HardwareButtons.BackPressed event, performed some logic and then set the handled property in the args to true if it applies. The handler runs through without any issue, and the Handled property gets set. The phone still navigates back outside of the app. Am I misunderstanding how to use the event?

Page

public sealed partial class FirstRunPage : VisualStateAwarePage
{
    public FirstRunPage()
    {
        this.InitializeComponent();
#if WINDOWS_PHONE_APP
        HardwareButtons.BackPressed += (sender, args) =>
            {
                bool isHandled = false;
                Action handledCallback = () => isHandled = true;
                var state = new Dictionary<string, object> { { "Callback", handledCallback } };
                ((INavigationAware)this.DataContext).OnNavigatedTo("Back", NavigationMode.Back, state);
                args.Handled = isHandled;
            };
#endif
    }

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        base.OnNavigatedTo(e);
    }
}

View model.

    public override void OnNavigatedTo(object navigationParameter, NavigationMode navigationMode, Dictionary<string, object> viewModelState)
    {
        if (navigationParameter == null || !navigationParameter.ToString().Equals("Back"))
        {
            return;
        }

        if (!viewModelState.ContainsKey("Callback"))
        {
            return;
        }

        var callback = (Action)viewModelState["Callback"];

        // If the user is new, then we set it to false and invoke our callback.
        if (this.IsNewUser)
        {
            this.IsNewUser = false;
            callback();
        }
        else
        {
            return;
        }
    }

Update

I have modified my FirstRunPage to subscribe and unsubscribe as recommended by @Martin but it still closes the app.

public sealed partial class FirstRunPage : VisualStateAwarePage
{
    public FirstRunPage()
    {
        this.InitializeComponent();
    }

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        base.OnNavigatedTo(e);
#if WINDOWS_PHONE_APP
        HardwareButtons.BackPressed += HardwareBack_OnPressed;
#endif
    }

    protected override void OnNavigatedFrom(NavigationEventArgs e)
    {
        base.OnNavigatedFrom(e);
#if WINDOWS_PHONE_APP
        HardwareButtons.BackPressed -= HardwareBack_OnPressed;
#endif
    }

    private void HardwareBack_OnPressed(object sender, BackPressedEventArgs e)
    {
        Action handledCallback = () => e.Handled = true;
        var state = new Dictionary<string, object> { { "Callback", handledCallback } };

        ((INavigationAware)this.DataContext).OnNavigatedTo("Back", NavigationMode.Back, state);
    }        
}

Upvotes: 1

Views: 663

Answers (2)

Johnathon Sullinger
Johnathon Sullinger

Reputation: 7414

With the help of @yasen I was able to get this resolved. The issue stems from the fact that the Prism library has your App.xaml.cs inherit from MvvmAppbase, which intercepts the BackPressed event.

To resolve this, I overrode the MvvmAppBase OnHardwareButtonsBackPressed and added a bit of logic to handle it. My view model and view both implement a new interface called INavigateBackwards and they're used like this:

View Model

    public bool CanNavigateBack()
    {
        // If the new user is true, then we can't navigate backwards.
        // There isn't any navigation stack, so the app will die.
        bool canNavigate = !this.IsNewUser;

        // Disable the new user mode.
        this.IsNewUser = false;

        // Return so that the view can return to it's sign-in state.
        return canNavigate;
    }

View

public sealed partial class FirstRunPage : VisualStateAwarePage, INavigateBackwards
{
    private INavigateBackwards ViewModel
    {
        get
        {
            return (INavigateBackwards)this.DataContext;
        }
    }

    public FirstRunPage()
    {
        this.InitializeComponent();
    }

    public bool CanNavigateBack()
    {
        return ViewModel.CanNavigateBack();
    }
}

Then in the MvvmAppBase subclass, I determine if I need to handle the navigation or not.

MvvmAppBase child

    protected override void OnHardwareButtonsBackPressed(object sender, BackPressedEventArgs e)
    {
        var page = (Page)((Frame)Window.Current.Content).Content;
        if (page is INavigateBackwards)
        {
            var navigatingPage = (INavigateBackwards)page;
            if (!navigatingPage.CanNavigateBack())
            {
                e.Handled = true;
                return;
            }
        }

        base.OnHardwareButtonsBackPressed(sender, e);
    }

This allows my single view to have multiple states and the user to navigate back from one state to the previous without navigating to an entirely new view.

Upvotes: 3

Martin Konopka
Martin Konopka

Reputation: 391

The reason why your application closes is that the same handler is called more than just once. First handler sets the Handled property to true, but any other subsequent call for the same event fire sets it back to false.

To illustrate it, try this:

public sealed partial class FirstRunPage : VisualStateAwarePage
{
    public FirstRunPage()
    {
       // ...
       InitializeComponent();
       HardwareButtons.BackPressed += HardwareButtons_BackPressed;   
       HardwareButtons.BackPressed += HardwareButtons_BackPressed; 
    }

    void HardwareButtons_BackPressed(object sender, BackPressedEventArgs e)
    {
        bool isHandled = false;
        Action handledCallback = () => isHandled = true;
        var state = new Dictionary<string, object> { { "Callback", handledCallback } };
        ((INavigationAware)this.DataContext).OnNavigatedTo("Back", NavigationMode.Back, state);
        args.Handled = isHandled;
    };
}

And set breakpoint to last line of the handler code.

To avoid it, assign your handler in the OnNavigatedTo method of your FirstRunPage, and unregister the handler in OnNavigatedFrom.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
    HardwareButtons.BackPressed += HardwareButtons_BackPressed;   
}

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    base.OnNavigatedFrom(e);
    HardwareButtons.BackPressed -= HardwareButtons_BackPressed;   
}

Upvotes: 0

Related Questions