How to open custom dialog box / popup using Xamarin.Forms?

16,694

Solution 1

The general purpose of what you are trying to achieve can be accomplished by using the PushModalAsync and PopModalAsync methods of Xamarin.Forms Navigation object.

The chances are that this is good enough for what you are needing - However - this isn't truely modal. I will explain after a small code snippet:-

        StackLayout objStackLayout = new StackLayout()
        {
        };
        //
        Button cmdButton_LaunchModalPage = new Button();
        cmdButton_LaunchModalPage.Text = "Launch Modal Window";
        objStackLayout.Children.Add(cmdButton_LaunchModalPage);
        //
        cmdButton_LaunchModalPage.Clicked += (async (o2, e2) =>
        {
            ContentPage objModalPage = new ContentPage();
            objModalPage.Content = await CreatePageContent_Page2();
            //
            await Navigation.PushModalAsync(objModalPage);
            //
            // Code will get executed immediately here before the page is dismissed above.
        });
        //
        return objStackLayout;



    private async Task<StackLayout> CreatePageContent_Page2()
    {
        StackLayout objStackLayout = new StackLayout()
        {
        };
        //
        Button cmdButton_CloseModalPage = new Button();
        cmdButton_CloseModalPage.Text = "Close";
        objStackLayout.Children.Add(cmdButton_CloseModalPage);
        //
        cmdButton_CloseModalPage.Clicked += ((o2, e2) =>
        {
            this.Navigation.PopModalAsync();
        });
        //
        return objStackLayout;
    }

The problem with the above is that the

            await Navigation.PushModalAsync(objModalPage);

will immediately return after the animation.

Although you can't interact with the previous page, as we are displaying a new NavigationPage with a Close button shown - the parent Navigation Page is still executing behind the scenes in parallel.

So if you had any timers or anything executing these still would get called unless you stopped those.

You could also use the TaskCompletionSource approach as outlined in the following post also How can I await modal form dismissal using Xamarin.Forms?.

Note - that although you can now await the 2nd page displaying and then when that page is dismissed allowing code execution to continue on the next line - this is still not truely a modal form. Again timers or anything executing still will get called on the parent page.

Update 1:-

To have the content appear over the top of existing content then simply include it on the current page, however make this section of content invisible until you need it.

If you use an outer container such like a Grid that supports multiple child controls in the same cell, then you will be able to achieve what you want.

You will also want to use something like a filled Box with transparency that will cover the entire page also, to control the visible, see through section, that surrounds your inner content section.

Solution 2

If you still want to have your popup's code in its own Page you can set up some custom renderers along the following logic.

1. A ModalPage & corresponding renderer

public class ModalPage : ContentPage { }

public class ModalPageRenderer : PageRenderer {
    protected override void OnElementChanged(VisualElementChangedEventArgs e)
    {
        base.OnElementChanged(e);
        this.View.BackgroundColor = UIColor.Clear;
        this.ModalPresentationStyle = UIModalPresentationStyle.OverCurrentContext;
    }

    public override void ViewDidLayoutSubviews()
    {
        base.ViewDidLayoutSubviews();
        SetElementSize (new Size (View.Bounds.Width, View.Bounds.Height));
    } 
}

2. HostPage

public class ModalHostPage : ContentPage, IModalHost
{
    #region IModalHost implementation

    public Task DisplayPageModal(Page page)
    {
        var displayEvent = DisplayPageModalRequested;

        Task completion = null;
        if (displayEvent != null)
        {
            var eventArgs = new DisplayPageModalRequestedEventArgs(page);
            displayEvent(this, eventArgs);
            completion = eventArgs.DisplayingPageTask;
        }

        // If there is no task, just create a new completed one
        return completion ?? Task.FromResult<object>(null);
    }

    #endregion

    public event EventHandler<DisplayPageModalRequestedEventArgs> DisplayPageModalRequested;

    public sealed class DisplayPageModalRequestedEventArgs : EventArgs
    {
        public Task DisplayingPageTask { get; set;}

        public Page PageToDisplay { get; }

        public DisplayPageModalRequestedEventArgs(Page modalPage)
        {
            PageToDisplay = modalPage;
        }
    }
}

3. HostPage renderer

public class ModalHostPageRenderer: PageRenderer
{
    protected override void OnElementChanged(VisualElementChangedEventArgs e)
    {
        base.OnElementChanged(e);

        if(e.OldElement as ModalHostPage != null)
        {
            var hostPage = (ModalHostPage)e.OldElement;
            hostPage.DisplayPageModalRequested -= OnDisplayPageModalRequested;
        }

        if (e.NewElement as ModalHostPage != null)
        {
            var hostPage = (ModalHostPage)e.NewElement;
            hostPage.DisplayPageModalRequested += OnDisplayPageModalRequested;
        }
    }

    void OnDisplayPageModalRequested(object sender, ModalHostPage.DisplayPageModalRequestedEventArgs e)
    {
        e.PageToDisplay.Parent = this.Element;
        var renderer = RendererFactory.GetRenderer (e.PageToDisplay);
        e.DisplayingPageTask = this.PresentViewControllerAsync(renderer.ViewController, true);
    }
}

Then it is as simple as calling await ModalHost.DisplayPageModal(new PopUpPage()); from your host page or in this particular case from the ViewModel behind.

What Pete said about PushModalAsync / PopModalAsync still remains valid for this solution too (which in my opinion is not a disadvantage), but your popup would appear with transparent background.

The main advantage of this approach, in my opinion, is that you can have your popup XAML/code definition separate from the host page and reuse it on any other page where you wish to show that popup.

Share:
16,694
Nirav Mehta
Author by

Nirav Mehta

Web &amp; iOS Developer working since last 9 years.

Updated on July 24, 2022

Comments

  • Nirav Mehta
    Nirav Mehta almost 2 years

    I am newbie to Xamarin.Forms and stuck with a situation where I want to open up a popup box with my control details [e.g. View Employee Details] on click of parent page.

    How can I open custom dialog box / popup using Xamarin.Forms?

    Any example code will be appreciated?

    Thanks in advance!

  • Nirav Mehta
    Nirav Mehta over 9 years
    But I want to have it like a popup modal dialog box which will appear above the parent page. As per your example, it opens up a separate page and previous page is not viewable.
  • Pete
    Pete over 9 years
    I've update my answer (See Update1) now that its more clear what you are trying to achieve.
  • Nirav Mehta
    Nirav Mehta over 9 years
    Thanks @Pete, Do you have any sample code link for same?
  • Pete
    Pete over 9 years
    I don't have any link to existing code for this, but it shouldn't be too hard to achieve. If you are having difficulty I am contactable outside this website also, take a look on my Profile for details.
  • Lee Richardson
    Lee Richardson over 8 years
    If I could give you bonus points for going well above and beyond, I would. Thank you for your contribution, the github project in particular was amazing.
  • Arun Gupta
    Arun Gupta about 6 years
    @Gabor: Is it possible to implement this for android?
  • testing
    testing almost 5 years
    @Gabor: I tried your sample on iPad Simulator (iOS 12.1). I don't see a popup - it's only one page navigating to the other ...