Shell
The Bulletcode.UI package extends the Shell mechanism provided by .NET MAUI, making it possible to use dependency injection, and display modal dialogs as overlays within the main window.
In order to use the Shell extensions, call the AddNavigationProvider() extension method of the service provider. It returns an INavigationProviderBuilder interface, which has the following extension methods:
AddPage()— registers a specific page, which must inheritBasePageAddDialog()— registers a specific dialog, which must inheritBaseDialogRegisterPagesAndDialogs()— registers all pages and dialogs defined in the specified assembly, i.e. all non-abstract classes which inheritBasePageandBaseDialog
For example:
services.AddNavigationProvider()
.RegisterPagesAndDialogs( typeof( MauiProgram ).Assembly );
Navigation provider
The INavigationProvider service should can used to navigate between pages and to display modal dialogs. It contains the following methods:
NavigateToAsync()— Navigate to the specified page. This method should be used instead of directly calling theGoToAsync()method ofMauiShell. It allows passing the navigation intent to pages (see below). It also correctly handles navigating to parent pages (for example, if you are onMain/Accountand navigate toMain, this navigates to../Maininstead). The page must be registered usingAddPage()orRegisterPagesAndDialogs().NavigateBackAsync()— navigates to the parent page. Equivalent toNavigateToAsync( ".." ).ShowDialogAsync()— shows the specified modal dialog. The dialog must be registered usingAddDialog()orRegisterPagesAndDialogs(). This method doesn’t return until the dialog is closed. Multiple dialogs can be displayed simultaneously, but only the topmost dialog is active at a given time and can receive mouse and keyboard input.CloseDialogAsync()— closes the topmost modal dialog. This method should be called by the dialog’s view model. An optional result (trueorfalse) can be passed to this method; it is then returned by the correspondingShowDialogAsync()method. The dialog is destroyed when it’s closed.ShowBusyAsync()— displays a modal busy indicator and executes the specified asynchronous function. The application remains responsive while the function is being executed, but mouse and keyboard input are blocked. This method doesn’t return until the function callback finishes executing. If the callback function returns a value, it is passed as the result of this method.
An intent object can be passed as the last parameter of the NavigateToAsync() and NavigateBackAsync() methods. The target page’s view model can implement the IIntentAware interface with the correct type in order to receive the intent object, for example:
public class ExamplePageViewModel : BasePageViewModel, IIntentAware<ExampleIntent>
{
public void OnIntent( ExampleIntent intent )
{
// do something with the passed object
}
}
Additional options can be passed as the second parameter of the ShowDialogAsync() method. The dialog’s view model can receive those options using the IDialogOptions interface with the correct type, for example:
public class ExampleDialogViewModel : BaseDialogViewModel
{
private readonly ExampleDialogOptions _options;
public ExampleDialogViewModel( IDialogOptions<ExampleDialogOptions> options )
{
_options = options.Value;
}
}
Pages and dialogs
The BasePage class, which should be inherited by every page, defines the following properties in addition to the properties inherited from the MAUI ContentPage class:
ReturnCommand— a command which is executed when the Enter key is pressedEscapeCommand— a command which is executed when the Escape key is pressed
If a page inherits the template version of the BasePage class, the parameter of the template specifies the type of the view model which is automatically created and assigned as the binding context of the page. For example:
public class ExamplePage : BasePage<ExamplePageViewModel>
{
public ExamplePage()
{
InitializeComponent();
}
}
The corresponding XAML file should also specify the base class and its type argument, for example:
<shell:BasePage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:shell="clr-namespace:Bulletcode.UI.Shell;assembly=Bulletcode.UI"
xmlns:vm="clr-namespace:App.UI.ViewModels"
x:Class="App.UI.Views.ExamplePage"
x:TypeArguments="vm:ExamplePageViewModel"
x:DataType="vm:ExamplePageViewModel"
>
<!-- TODO -->
</shell:BasePage>
In order to register a page as a dynamic route, it should have the Route attribute:
[Route( "Dynamic" )]
public class DynamicPage : BasePage<DynamicPageViewModel>
{
public DynamicPage()
{
InitializeComponent();
}
}
The BaseDialog class, which should be inherited by every dialog, defines the following properties:
Title— the title displayed in the dialog’s headerShowCloseButton— specifies if a close button is displayed in the dialog’s header (the default value istrue)ReturnCommand— a command which is executed when the Enter key is pressedEscapeCommand— a command which is executed when the Escape key is pressed
The dialog should specify either the MaximumWidthRequest — in that case it automatically shrinks when the containing window is too narrow — or the WidthRequest — in that case, a horizontal scrollbar appears when the window is too narrow.
If a dialog inherits the template version of BaseDialog, the parameter of the template specifies the type of the view model used for the dialog.
The dialog class should have the Dialog attribute which specifies the name of the dialog:
[Dialog( "About" )]
public class AboutDialog : BaseDialog<AboutDialogViewModel>
{
public AboutDialog()
{
InitializeComponent();
}
}
The pages, dialogs, and their associated view models are created using the service provider. It’s recommended that the dependencies are added to view models, and not directly to the page or dialog classes. The view models are created within the same scope as the corresponding page or dialog, and they are automatically destroyed when the page or dialog is destroyed.
The view model of a page should inherit BasePageViewModel. It extends the BaseViewModel class with the following methods:
Validate()— callsValidateAllProperties()and returnstrueif there are no validation or type conversion errorsOnNavigatedTo— called when the page is navigated toOnNavigatedFrom— called when the page is navigated from
In addition, the page view model can also implement one or more IIntentAware interfaces as described above.
The view model of a dialog should inherit BaseDialogViewModel. It extends the BaseViewModel class with the following methods:
Validate()— callsValidateAllProperties()and returnstrueif there are no validation or type conversion errorsOnShowing— called before the dialog is shownOnClosed— called after the dialog is closed
See View Models for more information about implementing view models.
Static routes
If a page is used as a static route, it should be registered in AppShell.xaml using the shell:PageTemplate markup extension, for example:
<ShellContent Route="Start" ContentTemplate="{shell:PageTemplate v:StartPage}"/>
This ensures that the page is created together with the view model and all its dependencies.