-
Notifications
You must be signed in to change notification settings - Fork 2
dialog page base
All dialogs created with Shield MVVM MUST inherit from DialogPageBase<>. Each dialog is tied directly with its View Model in order to support showing a dialog via the NavigationService. This means that the XAML markup must also define the model it is linked to due to automatic code generation of the backing class that contains all of the controls. Thus, all dialogs created will have a general structure of the following XAML:
<base:DialogPageBase
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:base="clr-namespace:CoreBTS.Maui.ShieldMVVM.Pages;assembly=CoreBTS.Maui.ShieldMVVM"
x:Class="MauiSample.Features.Main.Dialog.DialogPage"
xmlns:vm="clr-namespace:MauiSample.Features.Main.Dialog"
x:TypeArguments="vm:DialogPageViewModel"
x:DataType="vm:DialogPageViewModel"
>
</base:DialogPageBase>
xmlns, xmlns:x, and xmls:base would always be the same, regardless of the page created. x:Class is the page that was just created. xmlmns:vm defines the location where the ViewModel the page inherits from is located. x:TypeArguments are used to fill in the generic part of DialogPageBase<> with the View Model this page is tied to. Finally, while not necessary due to the bindings now defined in the code-behind, x:DataType defines what View Model the dialog's Intellisense should use when filling in data for {Binding} in the XAML file.
Feel free to change base/vm prefixes to whatever standard is normally used.
See ContentPageBase<> for a summary.
The following is a small example of what a code-behind file would look like, utilizing Shield MVVM's style.
public partial class MainPage : DialogPageBase<MainPageViewModel>
{
public MainPage(MainPageViewModel viewModel) : base(viewModel)
{
}
protected override void SetupBindings()
{
Binder.WithControl(CounterBtn)
.For(c => c.BindText(), vm => vm.ButtonText)
.Once(c => c.BindClick(), vm => vm.ClickCommand);
Binder.WithControl(SomeLabel)
.Once(c => c.BindText(), vm => "Can be hard-coded if .Once");
Binder.WithControl(NumberCounter)
.For(c => c.BindText(), vm => vm.Counter, c => c.ConvertToString());
Binder.WithControl(SecondaryLabel)
.For(c => c.BindText(), vm => vm.Secondary.MyLabel);
Binder.WithControl(AboutPageButton)
.Once(c => c.BindClick(), vm => vm.AboutPageCommand);
Binder.WithControl(AboutAlternatePageButton)
.Once(c => c.BindClick(), vm => vm.AboutAlternatePageCommand);
Binder.WithControl(Dialog1)
.Once(c => c.BindClick(), vm => vm.Dialog1Command);
Binder.WithControl(Dialog2)
.Once(c => c.BindClick(), vm => vm.Dialog2Command);
}
}
The base class must be DialogPageBase<> with the VM you defined in the XAML. The View Model will be IoC injected into the Page and that View Model will be passed to the base class where all the setup logic will run for the developer. After that, the only method a developer needs to properly fill in is the SetupBindings method.
See ContentPageBase<> for a summary.
DialogPageBase<> inherits from MAUI Community Toolkit's Popup control. Thus, a developer can directly access any methods or behaviors directly.
In order to avoid tons of boiler plate code in the constructor, InitializeComponent is automatically called via reflection. Since this is a one-time call for the entire page's lifecycle, this doesn't really add any overhead.
Normally, the default for users to dismiss a popup by clicking outside is set to true, but this is now set to false by default for Shield MVVVM. If this needs to be true, you can set this property to true in the dialog's constructor.
In order to support the advanced scenario of allowing View Model inheritance, a small sacrifice had to be made when it came to using dialogs and IoC. If a developer asks the IoC container for a dialog, it will only new up the dialog with the View Model that is defined in its constructor... Even though the NavigationService knows the developer wanted a different View Model that inherited from it. To get around this problem, the NavigationService will ask the IoC container for the ViewModel and then use reflection to get the constructor of the dialog, building the parameters by asking the IoC container directly per parameter and give the constructor the specific View Model instead of the one the dialog would have produced. This works great and there aren't any issues with it. However, there is one limitation with this. That means if a dialog is registered by the developer in the IoC container that "news" it up in a specific way, that will be ignored. Only parameters that can be obtained from the IoC container will work. That being said, there shouldn't really be any reason why any needed parameter into a dialog constructor couldn't be registered properly. For everything else, the data should be sent via a TParameter via the NavigationService call.