Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide the ability to show popups without having to subclass Popup #1581

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1e15307
Initial musings on showing popups without having to create your own p…
bijington Nov 29, 2023
fdd7715
Remove commented code
bijington Nov 29, 2023
1f78fa7
Pass on the BindingContext of a view up to the popup
bijington Dec 4, 2023
71847fe
Merge branch 'main' into feature/sl/remove-popup-constraint-from-popu…
brminnick Feb 2, 2024
68928f1
Merge branch 'main' into feature/sl/remove-popup-constraint-from-popu…
bijington Feb 11, 2024
f7fb581
Expand IPopupService API to enable the ability to supply the configur…
bijington Feb 11, 2024
733a467
Merge branch 'feature/sl/remove-popup-constraint-from-popup-service' …
bijington Feb 11, 2024
3969115
Merge branch 'main' into feature/sl/remove-popup-constraint-from-popu…
brminnick Feb 15, 2024
b365000
Merge branch 'main' into feature/sl/remove-popup-constraint-from-popu…
bijington Feb 18, 2024
3a14894
Name the cancellation token usage
bijington Feb 20, 2024
a3ae853
Merge branch 'feature/sl/remove-popup-constraint-from-popup-service' …
bijington Feb 20, 2024
46f8182
Merge branch 'main' into feature/sl/remove-popup-constraint-from-popu…
bijington Mar 27, 2024
086b161
Merge branch 'main' into feature/sl/remove-popup-constraint-from-popu…
brminnick Mar 27, 2024
e0d6dfc
Merge branch 'main' into feature/sl/remove-popup-constraint-from-popu…
bijington Jun 5, 2024
b2810bb
Merge branch 'main' into feature/sl/remove-popup-constraint-from-popu…
bijington Jun 9, 2024
e249bbc
Merge branch 'main' into feature/sl/remove-popup-constraint-from-popu…
brminnick Jul 24, 2024
64916d3
Merge branch 'main' into feature/sl/remove-popup-constraint-from-popu…
bijington Aug 1, 2024
574b600
Merge branch 'main' into feature/sl/remove-popup-constraint-from-popu…
bijington Aug 28, 2024
7584b48
Merge branch 'main' into feature/sl/remove-popup-constraint-from-popu…
bijington Sep 3, 2024
0a9d436
Merge branch 'main' into feature/sl/remove-popup-constraint-from-popu…
bijington Sep 8, 2024
880bd32
Include DynamicallyAccessedMembers
bijington Sep 10, 2024
b701700
Merge branch 'main' into feature/sl/remove-popup-constraint-from-popu…
bijington Sep 11, 2024
8c993d3
Merge branch 'main' into feature/sl/remove-popup-constraint-from-popu…
bijington Oct 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions samples/CommunityToolkit.Maui.Sample/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ static void RegisterViewsAndViewModels(in IServiceCollection services)
services.AddTransientPopup<CsharpBindingPopup, CsharpBindingPopupViewModel>();
services.AddTransientPopup<UpdatingPopup, UpdatingPopupViewModel>();
services.AddTransientPopup<XamlBindingPopup, XamlBindingPopupViewModel>();

services.AddTransientPopupContent<PopupContentView, PopupContentViewModel>();
}

static void RegisterEssentials(in IServiceCollection services)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
<Button Text="C# Binding Popup" Command="{Binding CsharpBindingPopupCommand}" />

<Button Text="Updating Popup" Command="{Binding UpdatingPopupCommand}" />

<Button Text="Show Popup content" Command="{Binding ShowPopupContentCommand}" />
</VerticalStackLayout>
</ScrollView>
</ContentPage.Content>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,12 @@ Task OnUpdatingPopup(CancellationToken token)
onPresenting: viewModel => viewModel.PerformUpdates(10),
token);
}

[RelayCommand]
Task OnShowPopupContent(CancellationToken token)
{
return popupService.ShowPopupAsync<PopupContentViewModel>(
onPresenting: viewModel => viewModel.SetMessage("This is a dynamically set message, shown in a popup without the need to create your own Popup subclass."),
token);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using CommunityToolkit.Mvvm.ComponentModel;

namespace CommunityToolkit.Maui.Sample.ViewModels.Views;

public sealed partial class PopupContentViewModel : BaseViewModel
{
[ObservableProperty]
string message = "";

internal void SetMessage(string text) => this.Message = text;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<Grid
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CommunityToolkit.Maui.Sample.Views.Popups.PopupContentView"
xmlns:viewModels="clr-namespace:CommunityToolkit.Maui.Sample.ViewModels.Views"
x:DataType="viewModels:PopupContentViewModel">

<Label
Text="{Binding Message}"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
Margin="20" />

</Grid>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using CommunityToolkit.Maui.Sample.ViewModels.Views;

namespace CommunityToolkit.Maui.Sample.Views.Popups;

public partial class PopupContentView : Grid
{
public PopupContentView(PopupContentViewModel popupContentViewModel)
{
InitializeComponent();

BindingContext = popupContentViewModel;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,25 @@ public static IServiceCollection AddTransientPopup<TPopupView, TPopupViewModel>(
return services;
}

/// <summary>
/// Adds a <see cref="View"/> of the type specified in <typeparamref name="TPopupContentView"/> and a ViewModel
/// of the type specified in <typeparamref name="TPopupViewModel"/> to the specified
/// <see cref="IServiceCollection"/> with <see cref="ServiceLifetime.Transient"/> lifetime.
/// </summary>
/// <typeparam name="TPopupContentView">The type of the Popup to add. Constrained to <see cref="View"/></typeparam>
/// <typeparam name="TPopupViewModel">The type of the ViewModel to add. Constrained to
/// <see cref="INotifyPropertyChanged"/></typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
/// <returns>A reference to this instance after the operation has completed.</returns>
public static IServiceCollection AddTransientPopupContent<TPopupContentView, TPopupViewModel>(this IServiceCollection services)
where TPopupContentView : View
where TPopupViewModel : INotifyPropertyChanged
{
PopupService.AddTransientPopupContent<TPopupContentView, TPopupViewModel>(services);

return services;
}

/// <summary>
/// Adds a <see cref="NavigableElement"/> of the type specified in <typeparamref name="TView"/> and a ViewModel
/// of the type specified in <typeparamref name="TViewModel"/> to the specified
Expand Down
48 changes: 43 additions & 5 deletions src/CommunityToolkit.Maui/PopupService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ public PopupService()
?? throw new InvalidOperationException("Could not locate IServiceProvider");
}

internal static void AddTransientPopupContent<TPopupContentView, TPopupViewModel>(IServiceCollection services)
where TPopupContentView : View
where TPopupViewModel : INotifyPropertyChanged
{
viewModelToViewMappings.Add(typeof(TPopupViewModel), typeof(TPopupContentView));

services.AddTransient(typeof(TPopupContentView));
services.AddTransient(typeof(TPopupViewModel));
}

internal static void AddTransientPopup<TPopupView, TPopupViewModel>(IServiceCollection services)
where TPopupView : IPopup
where TPopupViewModel : INotifyPropertyChanged
Expand Down Expand Up @@ -126,24 +136,52 @@ public void ShowPopup<TViewModel>(Action<TViewModel> onPresenting) where TViewMo
/// <exception cref="InvalidOperationException"></exception>
static void ValidateBindingContext<TViewModel>(Popup popup, out TViewModel bindingContext)
{
if (popup.BindingContext is not TViewModel viewModel)
if (popup.BindingContext is TViewModel viewModel)
{
bindingContext = viewModel;
return;
}
else if (popup.Content?.BindingContext is TViewModel contentViewModel)
{
throw new InvalidOperationException($"Unexpected type has been assigned to the BindingContext of {popup.GetType().FullName}. Expected type {typeof(TViewModel).FullName} but was {popup.BindingContext?.GetType().FullName ?? "null"}");
bindingContext = contentViewModel;
return;
}

bindingContext = viewModel;
throw new InvalidOperationException($"Unexpected type has been assigned to the BindingContext of {popup.GetType().FullName}. Expected type {typeof(TViewModel).FullName} but was {popup.BindingContext?.GetType().FullName ?? "null"}");
}

Popup GetPopup(Type viewModelType)
{
var popup = serviceProvider.GetService(viewModelToViewMappings[viewModelType]) as Popup;
var view = serviceProvider.GetService(viewModelToViewMappings[viewModelType]);

if (popup is null)
if (view is null)
{
throw new InvalidOperationException(
$"Unable to resolve popup type for {viewModelType} please make sure that you have called {nameof(AddTransientPopup)}");
}

Popup popup;

if (view is View visualElement)
{
popup = new Popup
{
Content = visualElement
};

// Binding the BindingContext property up from the view to the popup so that it is nicely handled on macOS.
popup.SetBinding(Popup.BindingContextProperty, new Binding { Source = view, Path = Popup.BindingContextProperty.PropertyName });
}
else if (view is Popup viewPopup)
bijington marked this conversation as resolved.
Show resolved Hide resolved
{
popup = viewPopup;
}
else
{
throw new InvalidOperationException(
$"Invalid type of view being used to present a Popup. Expected either IPopup or View.");
}

return popup;
}
}