How to deal with custom dialogs/popups?
This article explains how to integrate Popup control from the CommunityToolkit.Maui
Imperative approach
You can show a Popup using code like this:
public static class PopupManager
{
class PopupVisualContainer : ContentView
{
public void Unmount()
{
//this is only required if you want to attach some code when the popup is closed
base.OnUnmount();
}
}
static Popup? _sheet;
static PopupVisualContainer? _popupVisualContainer;
private static ITemplateHost? _templateHost;
public static async Task ShowPopupAsync(this MauiControls.Page? page,
Func<VisualNode> contentRender,
Action<PopupOptions>? configureOptionsAction = null,
CancellationToken cancellationToken = default)
{
if (page == null)
{
return;
}
if (_sheet != null)
{
return;
}
_popupVisualContainer = new PopupVisualContainer()
{
contentRender()
};
_templateHost = TemplateHost.Create(_popupVisualContainer);
_sheet = new Popup
{
Content = (MauiControls.View)_templateHost.NativeElement!
};
PopupOptions? options = null;
if (configureOptionsAction != null)
{
options = new PopupOptions();
configureOptionsAction(options);
}
await page.ShowPopupAsync((MauiControls.View)_templateHost.NativeElement!, options, cancellationToken);
_sheet = null;
_templateHost = null;
}
}And, in the following example, we use it to create Alert and Confirmation dialogs:
public static class Popups
{
public static async Task Alert(MauiControls.Page? page, string title, string message, string cancel = "OK")
{
if (page == null)
{
return;
}
await page.ShowPopupAsync(() =>
{
return
Component.VStack(
Component.Label(title),
Component.Label(message),
Component.Button(cancel)
.OnClicked(() => page.ClosePopupAsync())
)
;
},
configureOptionsAction: options =>
{
options.CanBeDismissedByTappingOutsideOfPopup = false;
});
}
public static async Task<bool> Confirm(MauiControls.Page? page, string title, string message, string accept = "Yes", string cancel = "No")
{
if (page == null)
{
return false;
}
bool result = false;
await page.ShowPopupAsync(() =>
{
return
Component.VStack(
Component.Label(title),
Component.Label(message),
Component.HStack(AppTheme.SpacingNormal,
Component.Button(cancel)
.OnClicked(() => page.ClosePopupAsync()),
Component.Button(accept)
.HEnd()
.OnClicked(() =>
{
result = true;
page.ClosePopupAsync();
})
)
.HEnd()
.Margin(0, AppTheme.BaseSize, 0, 0)
)
;
},
configureOptionsAction: options =>
{
options.CanBeDismissedByTappingOutsideOfPopup = false;
});
return result;
}
}
Declarative approach
This solution is for the CommunityToolkit.Maui Popup:
[Scaffold(typeof(CommunityToolkit.Maui.Views.Popup))]
partial class Popup
{
protected override void OnAddChild(VisualNode widget, MauiControls.BindableObject childNativeControl)
{
if (childNativeControl is MauiControls.View content)
{
Validate.EnsureNotNull(NativeControl);
NativeControl.Content = content;
}
base.OnAddChild(widget, childNativeControl);
}
protected override void OnRemoveChild(VisualNode widget, MauiControls.BindableObject childNativeControl)
{
Validate.EnsureNotNull(NativeControl);
if (childNativeControl is MauiControls.View content &&
NativeControl.Content == content)
{
NativeControl.Content = null;
}
base.OnRemoveChild(widget, childNativeControl);
}
}
class PopupHost : Component
{
private CommunityToolkit.Maui.Views.Popup? _popup;
private bool _isShown;
private Action<object?>? _onCloseAction;
private readonly Action<CommunityToolkit.Maui.Views.Popup?>? _nativePopupCreateAction;
public PopupHost(Action<CommunityToolkit.Maui.Views.Popup?>? nativePopupCreateAction = null)
{
_nativePopupCreateAction = nativePopupCreateAction;
}
public PopupHost IsShown(bool isShown)
{
_isShown = isShown;
return this;
}
public PopupHost OnClosed(Action<object?> action)
{
_onCloseAction = action;
return this;
}
protected override void OnMounted()
{
InitializePopup();
base.OnMounted();
}
protected override void OnPropsChanged()
{
InitializePopup();
base.OnPropsChanged();
}
void InitializePopup()
{
if (_isShown && MauiControls.Application.Current != null)
{
MauiControls.Application.Current?.Dispatcher.Dispatch(() =>
{
if (ContainerPage == null ||
_popup == null)
{
return;
}
ContainerPage.ShowPopup(_popup);
});
}
}
public override VisualNode Render()
{
var children = Children();
return _isShown ?
new Popup(r =>
{
_popup = r;
_nativePopupCreateAction?.Invoke(r);
})
{
children[0]
}
.OnClosed(OnClosed)
: null!;
}
void OnClosed(object? sender, PopupClosedEventArgs args)
{
_onCloseAction?.Invoke(args.Result);
}
}and this is how you can use it in your components:
class ShowPopupTestPage : Component<ShowPopupTestPageState>
{
private CommunityToolkit.Maui.Views.Popup? _popup;
public override VisualNode Render()
{
return new ContentPage()
{
new Grid
{
new Button(State.Result == null ? "Show popup" : $"Result: {State.Result.GetValueOrDefault()}")
.HCenter()
.VCenter()
.OnClicked(ShowPopup),
new PopupHost(r => _popup = r)
{
new VStack(spacing: 10)
{
new Label("Hi!"),
new HStack(spacing: 10)
{
new Button("OK", ()=> _popup?.Close(true)),
new Button("Cancel", ()=> _popup?.Close(false)),
}
}
}
.IsShown(State.IsShown)
.OnClosed(result => SetState(s =>
{
s.IsShown = false;
s.Result = (bool?)result;
}))
}
};
}
private void ShowPopup()
{
SetState(s => s.IsShown = true);
}
}
PreviousDo we need to add states to create simple animations such as ScaleTo, FadeTo, etc on tap?NextHow to create a Menu/ContextMenu?
Last updated
Was this helpful?