In Part 3 we will add simple navigation to push a new page onto the stack to display details about the monkey.
We will use the built-in Shell navigation of .NET MAUI. This powerful navigation system is based on URIs. You can pass additional information while navigating query parameter such as a string, or a full object.
For example, let's say we wanted to navigate to a details page and pass in an identifier.
await Shell.Current.GoToAsync("DetailsPage?name=james");Then in our details page or view model we should define this property:
[QueryProperty(nameof(Name), "name")]
public partial class DetailsPage : ContentPage
{
string name;
public string Name
{
get => name;
set => name = value;
}
}When we navigate, the name "james" would be passed along automatically. We can also pass a full object as well using the same mechanism:
var person = new Person { Name="James" };
await Shell.Current.GoToAsync("DetailsPage", new Dictionary<string, object>
{
{ "person", person }
});Then on our page or view model we would create the property.
[QueryProperty(nameof(Person), "person")]
public partial class DetailsPage : ContentPage
{
Person person;
public Person Person
{
get => person;
set => person = value;
}
}Here, the Person is automatically serialized and deserialized for us when we navigate.
Now, let's add a click handler to the collection view and pass the monkey to the details page.
Now, let's add navigation to a second page that displays monkey details!
-
In
MonkeysViewModel.cs, create a methodasync Task GoToDetailsAsync(Monkey monkey)exposed as an[RelayCommand]:[RelayCommand] async Task GoToDetails(Monkey monkey) { if (monkey == null) return; await Shell.Current.GoToAsync(nameof(DetailsPage), true, new Dictionary<string, object> { {"Monkey", monkey } }); }
- This code checks to see if the selected item is non-null and then uses the built in Shell
NavigationAPI to push a new page with the monkey as a parameter and then deselects the item.
- This code checks to see if the selected item is non-null and then uses the built in Shell
-
In
MainPage.xamlwe can add anTapGestureRecognizerevent to theFrameof our monkey inside of theCollectionView.ItemTemplate:Before:
<CollectionView.ItemTemplate> <DataTemplate x:DataType="model:Monkey"> <Grid Padding="10"> <Frame HeightRequest="125" Style="{StaticResource CardView}"> <Grid Padding="0" ColumnDefinitions="125,*"> <Image Aspect="AspectFill" HeightRequest="125" Source="{Binding Image}" WidthRequest="125" /> <VerticalStackLayout Grid.Column="1" Padding="10"> <Label Style="{StaticResource LargeLabel}" Text="{Binding Name}" /> <Label Style="{StaticResource MediumLabel}" Text="{Binding Location}" /> </VerticalStackLayout> </Grid> </Frame> </Grid> </DataTemplate> </CollectionView.ItemTemplate>
After:
<CollectionView.ItemTemplate> <DataTemplate x:DataType="model:Monkey"> <Grid Padding="10"> <Frame HeightRequest="125" Style="{StaticResource CardView}"> <!-- Add the Gesture Recognizer--> <Frame.GestureRecognizers> <TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:MonkeysViewModel}}, Path=GoToDetailsCommand}" CommandParameter="{Binding .}"/> </Frame.GestureRecognizers> <Grid Padding="0" ColumnDefinitions="125,*"> <Image Aspect="AspectFill" HeightRequest="125" Source="{Binding Image}" WidthRequest="125" /> <VerticalStackLayout Grid.Column="1" Padding="10"> <Label Style="{StaticResource LargeLabel}" Text="{Binding Name}" /> <Label Style="{StaticResource MediumLabel}" Text="{Binding Location}" /> </VerticalStackLayout> </Grid> </Frame> </Grid> </DataTemplate> </CollectionView.ItemTemplate>
This uses a
RelativeSourcebinding, which means that it isn't binding to theMonkeyanymore in theDataTemplate, but instead it is looking up the hierarchy specifically for anAncestorTypeofMonkeysViewModel. This allows for more advanced scenarios like this.
-
Inside of our
ViewModel/MonkeyDetailsViewModel.cs, we will house our logic for assigning the monkey to the view model. Let's first create a bindable property for theMonkey:public partial class MonkeyDetailsViewModel : BaseViewModel { public MonkeyDetailsViewModel() { } [ObservableProperty] Monkey monkey; }
-
Next, we will add a
QueryPropertyto handle passing the monkey data://Add QueryProperty [QueryProperty(nameof(Monkey), "Monkey")] public partial class MonkeyDetailsViewModel : BaseViewModel { public MonkeyDetailsViewModel() { } [ObservableProperty] Monkey monkey; }
Now that we have our details page in place, we need to register it for routing. This is done in both the Shell routing system and with the .NET MAUI dependency service.
-
Open
AppShell.xaml.cscode behind and add the following code into the constructor under theInitializeComponent();invoke:Routing.RegisterRoute(nameof(DetailsPage), typeof(DetailsPage));
This will register the details page with the route of "DetailsPage", which we used earlier.
-
Open
MauiProgram.csand add both the view model and the page asTransientso a new page and view model is created each time it is navigated to:builder.Services.AddTransient<MonkeyDetailsViewModel>(); builder.Services.AddTransient<DetailsPage>();
-
Finally, we must inject the view model into our
DetailsPage. Open the code behind for the page inDetailsPage.xaml.csand change the constructor to the following:public DetailsPage(MonkeyDetailsViewModel viewModel) { InitializeComponent(); BindingContext = viewModel; }
Let's add UI to the DetailsPage. Our end goal is to get a fancy profile screen like this:
-
Let's first start by defining our DataType by defining the view model namespace and also setting the title:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MonkeyFinder.DetailsPage" xmlns:viewmodel="clr-namespace:MonkeyFinder.ViewModel" x:DataType="viewmodel:MonkeyDetailsViewModel" Title="{Binding Monkey.Name}"> <!-- Add Content Here --> </ContentPage>
-
At the core is a
ScrollView,VerticalStackLayout, andGridto layout all of the controls nicely on the screen:<ScrollView> <VerticalStackLayout> <Grid ColumnDefinitions="*,Auto,*" RowDefinitions="160, Auto"> <!-- Background and Image of Monkey --> </Grid> <!-- Details of Monkey --> </VerticalStackLayout> </ScrollView>
-
We can now fill in our
Gridwith the following code to place a box as the background color of yellow, and then our monkey image cut out in the shape of a circle:<BoxView Grid.ColumnSpan="3" Background="{StaticResource Primary}" HeightRequest="160" HorizontalOptions="FillAndExpand" /> <Frame Grid.RowSpan="2" Grid.Column="1" Margin="0,80,0,0" HeightRequest="160" WidthRequest="160" HorizontalOptions="Center" Padding="0" IsClippedToBounds="True" CornerRadius="80"> <Image Aspect="AspectFill" HeightRequest="160" HorizontalOptions="Center" VerticalOptions="Center" Source="{Binding Monkey.Image}" WidthRequest="160"/> </Frame>
-
Finally, under the
Grid, but inside of theVerticalStackLayoutwe will add details about the monkey.
<VerticalStackLayout Padding="10" Spacing="10">
<Label Style="{StaticResource MediumLabel}" Text="{Binding Monkey.Details}" />
<Label Style="{StaticResource MicroLabel}" Text="{Binding Monkey.Location, StringFormat='Location: {0}'}" />
<Label Style="{StaticResource MicroLabel}" Text="{Binding Monkey.Population, StringFormat='Population: {0}'}" />
</VerticalStackLayout>- Run the application on the desired platform and tap on a monkey to navigate!
Platform features are the next topic for us to explore. Navigate to Part 4 to begin the next module.