OnOptionsItemSelected not invoked using MasterDetails page on Android

I try to implement popup which will prevent user to leave page without saving.
That means I have to handle two cases: when user uses hardware back button and back button in toolbar.
I have MasterDetails page.
As Detail I push a Details1 page wrapped by NavigationPage.
From Details1 I pused Details2 page.

Here is where problem starts.
When I click hardware back button - called OnBackPressed on activity and I can successfully handle it.
But if I use Toolbar back button - method OnOptionsItemSelected not called at all.
It's strange for me because without MasterDetails page (just push Details2 from Details1) it works as expected:
OnOptionsItemSelected invoked and I can check if this was home button.
It reproduced even with latest forms.
Am I doing something wrong? Or this is bug at Xamarin.Forms?

Best Answer

  • AlexeiMalashkevichAlexeiMalashkevich USMember
    Accepted Answer

    After looking at Xamarin.Forms sources seems like I found solution. You have to create renderer for NavigationPage and override OnAttachedToWindow.

    protected override void OnAttachedToWindow()
    {
        base.OnAttachedToWindow();
    
        Element page = Element.Parent;
        MasterDetailPage masterDetailPage = null;
        while (page != null)
        {
            if (page is MasterDetailPage)
            {
                masterDetailPage = page as MasterDetailPage;
                break;
            }
    
            page = page.Parent;
        }
    
        if (masterDetailPage == null)
        {
            return;
        }
    
        var renderer = Platform.GetRenderer(masterDetailPage) as MasterDetailPageRenderer;
        if (renderer == null)
        {
            return;
        }
    
        var drawerLayout = (DrawerLayout) renderer;
        Toolbar toolbar = null;
    
        for (int i = 0; i < ChildCount; i++)
        {
            var child = GetChildAt(i);
            toolbar = child as Toolbar;
            if (toolbar != null)
            {
                break;
            }
        }
    
        toolbar?.SetNavigationOnClickListener(new MenuClickListener(Element, drawerLayout));
    }
    
    private class MenuClickListener : Java.Lang.Object, IOnClickListener
    {
        readonly NavigationPage navigationPage;
        private DrawerLayout layout;
    
        public MenuClickListener(NavigationPage navigationPage, DrawerLayout layout)
        {
            this.navigationPage = navigationPage;
            this.layout = layout;
        }
    
        public void OnClick(View v)
        {
            var page = navigationPage.CurrentPage as BasePage;
            if (navigationPage.Navigation.NavigationStack.Count <= 1)
            {
                layout.OpenDrawer((int) GravityFlags.Left);
            }
    
            if (page != null)
            {
                if (page.OnNavigationBackButtonPressed())
                {
                    navigationPage?.PopAsync();
                }
            }
            else
            {
                navigationPage?.PopAsync();
            }
        }
    }
    

    BasePage - is inerhited from ContentPage and has method OnNavigationBackButtonPressed where you can handle special logic.

Answers

  • AlexeiMalashkevichAlexeiMalashkevich USMember
    Accepted Answer

    After looking at Xamarin.Forms sources seems like I found solution. You have to create renderer for NavigationPage and override OnAttachedToWindow.

    protected override void OnAttachedToWindow()
    {
        base.OnAttachedToWindow();
    
        Element page = Element.Parent;
        MasterDetailPage masterDetailPage = null;
        while (page != null)
        {
            if (page is MasterDetailPage)
            {
                masterDetailPage = page as MasterDetailPage;
                break;
            }
    
            page = page.Parent;
        }
    
        if (masterDetailPage == null)
        {
            return;
        }
    
        var renderer = Platform.GetRenderer(masterDetailPage) as MasterDetailPageRenderer;
        if (renderer == null)
        {
            return;
        }
    
        var drawerLayout = (DrawerLayout) renderer;
        Toolbar toolbar = null;
    
        for (int i = 0; i < ChildCount; i++)
        {
            var child = GetChildAt(i);
            toolbar = child as Toolbar;
            if (toolbar != null)
            {
                break;
            }
        }
    
        toolbar?.SetNavigationOnClickListener(new MenuClickListener(Element, drawerLayout));
    }
    
    private class MenuClickListener : Java.Lang.Object, IOnClickListener
    {
        readonly NavigationPage navigationPage;
        private DrawerLayout layout;
    
        public MenuClickListener(NavigationPage navigationPage, DrawerLayout layout)
        {
            this.navigationPage = navigationPage;
            this.layout = layout;
        }
    
        public void OnClick(View v)
        {
            var page = navigationPage.CurrentPage as BasePage;
            if (navigationPage.Navigation.NavigationStack.Count <= 1)
            {
                layout.OpenDrawer((int) GravityFlags.Left);
            }
    
            if (page != null)
            {
                if (page.OnNavigationBackButtonPressed())
                {
                    navigationPage?.PopAsync();
                }
            }
            else
            {
                navigationPage?.PopAsync();
            }
        }
    }
    

    BasePage - is inerhited from ContentPage and has method OnNavigationBackButtonPressed where you can handle special logic.

  • MilenMarinovMilenMarinov BGMember ✭✭

    Works like charm! Thanks!

Sign In or Register to comment.