In Part 1 of this ongoing series on developing an application in .NET MAUI, you learned the basics of XAML, XML namespaces, attributes, and elements. You created your first .NET MAUI application and ran that application on both a Windows computer and an Android emulator. And you learned how to lay out a basic data-entry page using a Grid and Stack Layouts.

In this article, you'll learn to apply styles so all pages in your application look consistent. You'll create several pages and learn how to use the built-in navigation to move from one page to another. Using a ContentView control, you'll create a header with data bindings that you can reuse on all pages in your application. Finally, you'll add a border and a scroll viewer around all your controls.

Applying Styles to Your Controls

In the XAML you wrote in the previous article Exploring .NET MAUI: Getting Started, you added attributes to many of the same types of controls to change the Margin, Padding, and Spacing properties. The problem with adding these attributes to individual controls is that if you wish to change them, you must find them all and change them one-by-one. Instead of applying attributes on each control, create a Style element to specify which attributes you wish to apply to all controls of the same type. For example, you can create a style that applies to all Label controls or to all Grid controls. Styles avoid you having to set the attributes on individual controls.

There are a few different locations you may place these Style elements. Where you place them has different ramifications as to how those styles are applied to the controls. For example, if you create styles within a ContentPage, those styles are only available on that one page and no others. If you create styles in the App.xaml file, those styles are applied to all pages within the application. You may even create styles in a Class Library that allows many different .NET MAUI applications to reuse those same styles.

Creating Style Elements

Style elements belong inside a <Resources> element. A <Resources> element can be within a single control, on a ContentPage, for the whole application, and within resource dictionary files. The <Style> starting tag must contain a TargetType property into which you specify the type of control to target, as shown below.

<Style TargetType="HorizontalStackLayout">
   // Add Setter elements here
</Style>

Within the Style element, place one or more <Setter> elements. Each Setter element sets a single property to a specific value for the TargetType specified. For example, on a HorizontalStackLayout, you can set the Spacing property using the following Setter element. Assign to the Property attribute the name of the property on the HorizontalStackLayout you wish to set. Set the Value property to the value you want for that property, as shown in the code snippet below.

<Style TargetType="HorizontalStackLayout">
   <Setter Property="Spacing" 
           Value="5" />
</Style>

If you place this style within a <ContentPage.Resources> element at the top of the ContentPage, all the Spacing properties on all HorizontalStackLayout controls are set to the value of five (5). If you place this style in the App.xaml file within the <Application.Resources> element, this style applies to all HorizontalStackLayout controls on all pages in the application.

Place Styles on the Content Page

Let's start out by removing all the Margin, Padding, and Spacing properties from the XAML and place those settings into Style elements. Open the MainPage.xaml file and just after the starting <ContentPage> element and before the first starting <Grid> tag, add a new element called <ContentPage.Resources>, as shown in Listing 1.

Listing 1: Add a set of Style elements to apply attributes to the same types of controls.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...> 

<ContentPage.Resources>
   <Style TargetType="Grid">
      <Setter Property="ColumnSpacing"
              Value="10" />
      <Setter Property="RowSpacing"
              Value="10" />
      <Setter Property="Margin"
              Value="10" />
      <Setter Property="Padding"
              Value="10" />
   </Style>
   <Style TargetType="Label">
      <Setter Property="VerticalOptions"
              Value="Center" />
   </Style>
   <Style TargetType="HorizontalStackLayout">
      <Setter Property="Spacing"
              Value="5" />
   </Style>
</ContentPage.Resources>

<Grid ...>

// REST OF THE XAML

Remove the ColumnSpacing, RowSpacing, Margin, and Padding from the <Grid> element on this page. Remove the Spacing="5" and Padding="10" from the <VerticalStackLayout> element on this page. Remove the Spacing="5" from all <HorizontalStackLayout> elements on this page.

Try It Out

Run the application and notice that the spacing is still the same on the grid and stack layout controls as it was before you removed all the attributes. Also notice that the labels are aligned to each entry control appropriately.

Place Styles at the Application Level

Instead of limiting the defined styles to affect only one page, let's move these styles out of the <ContentPage.Resources> section and move them to the application level so all pages will be styled the same. Open the MainPage.xaml file and cut all Style elements from the <ContentPage.Resources> element. Don't remove the <ContentPage.Resources> element from the page yet. Open the App.xaml file and paste the <Style> elements you copied after the </ResourceDictionary.MergedDictionaries> closing element and before the </ResourceDictionary> closing element.

Try It Out

Most often when you change <Style> elements in the App.xaml file, you must restart the application for the changes to take effect. Restart the application and all the spacing should still be applied on your main page.

What's a Resource Dictionary?

In the previous exercise, you placed styles within a <ResourceDictionary> element. A ResourceDictionary is a set of resources that can be used in a .NET MAUI application. You may store styles, colors, string and number constants, and data templates within a ResourceDictionary. ResourceDictionary elements can be defined in the App.xaml file as you just saw, or you can create a ResourceDictionary within a separate XAML file, which you'll do later in this article.

Closest Style Wins

Just like CSS in HTML, the closest style to the control it's targeting will take precedence. For example, in the App.xaml file, you defined all Label controls to set their VerticalOptions property to “Center”. However, open the MainPage.xaml file and add the Style within the <ContentPage.Resources> element from the next snippet. This Style element overrides the global style for all labels on this page because it's closer to those controls.

<Style TargetType="Label">
   <Setter Property="VerticalOptions"
           Value="End" />
</Style>

Try It Out

Run the application and notice that all labels on this page are now positioned toward the bottom of each entry control. After you have observed this change, remove the <ContentPage.Resources> element from the MainPage.xaml so it goes back to using the global styles.

Keyed Styles

All of the styles you created in the App.xaml file are called implicit styles because they implicitly affect all controls set with the TargetType property. Another kind of style is called a Keyed Style. A keyed style is where you set the x:Key="UniqueName" on the starting Style tag. Open the App.xaml file and change the <Style TargetType="Grid"> to add an x:Key attribute, as shown below.

<Style TargetType="Grid"
       x:Key="Grid.Page">

Once you add the x:Key attribute, this style no longer affects all Grid controls. Instead, you need to explicitly reference this key name to apply it to a Grid control.

Try It Out

To see that this style no longer affects a Grid, stop and restart the application to get the global styles to take effect. When the page is displayed, you should notice that the margins are missing on the Grid control.

Apply a Keyed Style

To apply the Grid.Page keyed style to a Grid control, add the Style property on the Grid you want to apply the style to and reference the name of the key by using the StaticResource markup extension as shown below.

<Grid RowDefinitions="Auto,Auto,Auto, ..."
      ColumnDefinitions="Auto,*"
      Style="{StaticResource Grid.Page}">

Within the double quotes for the Style property, you're using curly braces and a StaticResource markup extension. Think of a markup extension as a .NET class that performs some service for you. In this case, the StaticResource extension searches for a resource defined with the x:Key attribute equal to the value “Grid.Page”. When the StaticResource extension locates the resource, it applies any Setter values to the control it's attached to.

Try It Out

Restart the application and you should see that the spacing is once again being applied to the Grid control on your page.

Styling the Shell

To make the title bar area stand out from the rest of the page, add a style to change the background color and the title color. Open the App.xaml file and add the following keyed style where you placed the other styles in this file.

<Style TargetType="Element"
       x:Key="ShellStyle">
    <Setter Property="Shell.BackgroundColor"
            Value="Blue" />
    <Setter Property="Shell.TitleColor"
            Value="White" />
</Style>

Open the AppShell.xaml file and in the <Shell> starting tag add the following Style attribute to apply the keyed style you just created named ShellStyle.

Style="{StaticResource ShellStyle}"

Try It Out

Restart the application and you should see that the title bar area is now white text on a blue background.

Place Styles in a Resource Dictionary File

Within your Visual Studio project, drill down into the Resources\Styles folder and you should see two files; Colors.xaml and Styles.xaml. It's in these two files where the default look is contained for the standard .NET MAUI controls. The Colors.xaml file contains several color resources. The Styles.xaml files uses the colors from the Colors.xaml file to define the look of the .NET MAUI controls as well as setting various properties such as Padding, Heights, Widths, and Fonts of the controls.

Open the App.xaml file and within the <ResourceDictionary> element where you placed your styles, there's a <ResourceDictionary.MergedDictionaries> element in which you'll find two ResourceDictionary elements with the Source property set to the Colors.xaml and Styles.xaml files respectively. As a normal practice, you'll most likely place your styles after these two files because you'll want to override the default styles.

Previously, you added styles directly in the App.xaml file. You're now going to create your own Resource Dictionary file within the Resources\Styles folder and reference that file by adding your own ResourceDictionary in the <ResourceDictionary.MergedDictionaries> element. Right mouse-click on the Resources\Styles folder and select Add > New Item… > .NET MAUI > .NET MAUI ResourceDictionary (XAML) from the menu and set the name to AppStyles.xaml. Within the <ResourceDictionary> element in this new file, place the XAML shown in Listing 2.

Listing 2: Create a resource dictionary file into which you create your custom styles.

<!-- ****************** -->
<!-- ** My Keyed Styles -->
<!-- ****************** -->
<Style TargetType="Grid"
       x:Key="Grid.Page">
    <Setter Property="ColumnSpacing"
            Value="10" />
    <Setter Property="RowSpacing"
            Value="10" />
    <Setter Property="Margin"
            Value="10" />
    <Setter Property="Padding"
            Value="10" />
</Style>
<Style TargetType="Element"
       x:Key="ShellStyles">
   <Setter Property="Shell.BackgroundColor"
           Value="Blue" />
   <Setter Property="Shell.TitleColor"
           Value="White" />
</Style>

<!-- ************* -->
<!-- Global Styles -->
<!-- ************* -->
<Style TargetType="Label">
   <Setter Property="VerticalOptions"
           Value="Center" />
</Style>
<Style TargetType="HorizontalStackLayout">
   <Setter Property="Spacing"
           Value="5" />
</Style>

Open the App.xaml file and just after the ResourceDictionary element that has the Source property set to Resources/Styles/Styles.xaml, add a reference to your new AppStyles.xaml file, as shown in the following XAML.

<ResourceDictionary Source="Resources/Styles/AppStyles.xaml" />

Because you added all the styles from the App.xaml file into your own resource dictionary file, you may now remove all the styles from the App.xaml file.

Try It Out

Restart the application to ensure that your new AppStyles.xaml file is compiled and your page looks just like it did before.

Override a Style from the Styles.xaml File

To further illustrate that your AppStyles.xaml file overrides the default styles created by Microsoft in the Styles.xaml file, let's change the text and background colors of the Button control. Open the Resources\Styles\AppStyles.xaml file and add the following Style element under the Global Styles comment.

<Style TargetType="Button">
   <Setter Property="TextColor"
           Value="White" />
   <Setter Property="BackgroundColor"
           Value="Black" />
</Style>

Try It Out

Restart the application and you should see that the two buttons on your page have white text on a black background. Once you've verified that this works, remove the Button style you just added so it goes back to the default colors.

String Resources

In a ResourceDictionary, you can create other types of resources besides styles. For example, you have repeated the string value “Adventure Works” twice in your application: once in the MainPage.xaml and once in the AppShell.xaml file. Instead of hard coding this string value in multiple places, open the Resources\Styles\AppStyles.xaml file and before all other styles, add the following keyed resource.

<!-- *************** -->
<!-- Other Resources -->
<!-- *************** -->
<x:String x:Key="ApplicationTitle">
     Adventure Works
</x:String>

Now that you have a keyed string resource, reference it from wherever in the application it is needed. Open the AppShell.xaml file and modify the Title property to reference this keyed resource using the StaticResource markup extension as shown in the following code:

Title="{StaticResource ApplicationTitle}"

Open the MainPage.xaml file and modify the Title property to reference this keyed resource using the StaticResource markup extension as shown in the following code:

Title="{StaticResource ApplicationTitle}"

Try It Out

Run the application to ensure the title appears as you expect on the window shell and your page.

Numeric Resources

In the AppStyles.xaml file, there are four Value properties in the Grid.Page keyed style set to the number ten (10). Instead of repeating the number ten that many times, create a double resource set to the number ten, as shown in the following XAML:

<x:Double x:Key="DefaultSpacingForGrid">
    10
</x:Double>

Modify the Grid.Page keyed style to use this new double resource by using the StaticResource markup extension in the Value attribute on each Setter as shown in the following XAML:

<Style TargetType="Grid"
       x:Key="Grid.Page">
   <Setter Property="ColumnSpacing"
           Value="{StaticResource DefaultSpacingForGrid}" />
   <Setter Property="RowSpacing"
           Value="{StaticResource DefaultSpacingForGrid}" />
   <Setter Property="Margin"
           Value="{StaticResource DefaultSpacingForGrid}" />
   <Setter Property="Padding"
           Value="{StaticResource DefaultSpacingForGrid}" />
</Style>

Try It Out

Restart the application and you should see that the spacing on the Grid is the same as it was before.

Create Styles in a .NET MAUI Class Library

To create a set of styles that you can reuse across multiple .NET MAUI applications, place your styles into a .NET MAUI Class Library project. A class library project is a DLL (assembly) that can be referenced from any .NET MAUI application. Let's add one to the solution by right mouse-clicking on the solution and selecting Add > New Project… from the menu. Search for .NET MAUI Class Library and select that template.

Set the Project name field to Common.Library.MAUI and click the Next button. Choose the same .NET version you used to create your .NET MAUI application. Once the application is created, delete the Class1.cs file as this isn't needed in this assembly.

I like keeping the same folder structure for my .NET MAUI Class Library projects as for the main application, so right mouse-click on the new project and select Add > New Folder. Set the name of this new folder to Resources. Right mouse-click on the Resources folder and select Add > New Folder. Set the name of this new folder to Styles.

Right mouse-click on the Styles folder and select Add > New Item… from the menu. Click on the .NET MAUI tab and select the template .NET MAUI ResourceDictionary (XAML). Set the Name field to CommonStyles.xaml. After adding this new ResourceDictionary, add the XAML shown in Listing 3. This is the same XAML you have in the AdventureWorks.MAUI project, but you're going to delete that XAML in just a minute.

Listing 3: Move resources and styles into the common MAUI library resource dictionary.

<!-- *************** -->
<!-- Other Resources -->
<!-- *************** -->
<x:Double x:Key="DefaultSpacingForGrid">
  10
</x:Double>

<!-- ****************** -->
<!-- ** My Keyed Styles -->
<!-- ****************** -->
<Style TargetType="Grid"
       x:Key="Grid.Page">
    <Setter Property="ColumnSpacing"
            Value="{StaticResource DefaultSpacingForGrid}" />
    <Setter Property="RowSpacing"
            Value="{StaticResource DefaultSpacingForGrid}" />
    <Setter Property="Margin"
            Value="{StaticResource DefaultSpacingForGrid}" />
    <Setter Property="Padding"
             Value="{StaticResource DefaultSpacingForGrid}" />
</Style>
<Style TargetType="Element"
       x:Key="ShellStyles">
   <Setter Property="Shell.BackgroundColor"
           Value="Blue" />
   <Setter Property="Shell.TitleColor"
           Value="White" />
</Style>

<!-- ************* -->
<!-- Global Styles -->
<!-- ************* -->
<Style TargetType="Label">
   <Setter Property="VerticalOptions"
           Value="Center" />
</Style>
<Style TargetType="HorizontalStackLayout">
   <Setter Property="Spacing"
           Value="5" />
</Style>

Go back to the AdventureWorks.MAUI project and right mouse-click on the Dependencies folder. Choose Add Project Reference… from the menu and add a reference to the Common.Library.MAUI project you just created. Open the Resources\Styles\AppStyles.xaml file in the AdventureWorks.MAUI project and remove everything from this file except the <x:String x:Key=ApplicationTitle"> element.

Open the App.xaml file and add an XML namespace to reference the ResourceDictionary namespace you created in the Common.Library.MAUI project, as shown in the following code. Due to space limitations of this article, I had to break this across multiple lines. Please make sure when adding to your project that the following is all on one line.

xmlns:common="clr-namespace:Common.Library.MAUI.Resources.Styles;
    assembly=Common.Library.MAUI"

The CommonStyles.xaml file you created earlier has a partial class defined in the CommonStyles.xaml.cs file. This class, CommonStyles, is defined within the Common.Library.Resources.Styles namespace. The resources in the CommonStyles.xaml file are compiled into resources that you may now reference from the “common” XML namespace alias you added to App.xaml. Just before the <ResourceDictionary Source="Resources\Styles\AppStyles.xaml" /> element, add the following element.

<common:CommonStyles />

The styles from the CommonStyles.xaml file are now available for you to use throughout the AdventureWorks.MAUI application.

Try It Out

Run the application and you should see that the main page still looks the same.

Create Several Pages

So far, you've worked only with a single page, MainPage.xaml. In most applications, you're going to have many different pages. You're obviously going to need some way to navigate from one page to another. .NET MAUI has a nice navigation system built-in, but before you can learn about that, you need to create some pages, so let's do that now.

Create a User Detail Page

I prefer to put all my pages underneath a folder named Views. Right mouse-click on the project and add a new folder named Views. Right mouse-click on the Views folder and select Add > New Item > .NET MAUI > .NET MAUI ContentPage (XAML)... from the context-sensitive menu. Set the Name of the page to UserDetailView. Once the page has been added, change the Title attribute on the ContentPage to “User Information”. Delete the <VerticalStackLayout> element and all XAML within it on this new page. Open the MainPage.xaml file, cut the complete <Grid> element from this page and paste it into the ContentPage element on the UserDetailView.xaml file.

Update the Main Page

Open the MainPage.xaml and where the <Grid> control was that you cut out, add the <Label> element you see in the following XAML snippet. The MainPage is only going to be used to show the application title in this article series. Other uses for the main page might be as a dashboard with large buttons to navigate to the pages most commonly used, or as description of the application.

<Label Text="{StaticResource ApplicationTitle}"
       FontSize="Large"
       VerticalOptions="Center"
       HorizontalOptions="Center" />

Open the MainPage.xaml.cs file and cut the SaveButton_Clicked() event procedure and paste it into the UserDetailView.xaml.cs file.

Create Several More Pages

You're now going to create a few more content pages that you can navigate to in your application. On each page, you're going to be adding a Label control with text to identify each page. Right mouse-click on the Views folder and add a new .NET MAUI ContentPage (XAML) named LoginView. Change the Title attribute to “Login”. Replace the entire <VerticalStackLayout> element with the following XAML:

<Label Text="Login"
       FontSize="Large"
       VerticalOptions="Center"
       HorizontalOptions="Center" />

Right mouse-click on the Views folder and add a new .NET MAUI ContentPage (XAML) named ProductDetailView. Change the Title attribute to “Product Information”. Replace the entire <VerticalStackLayout> element with the following XAML:

<Label Text="Product Information"
       FontSize="Large"
       VerticalOptions="Center"
       HorizontalOptions="Center" />

Right mouse-click on the Views folder and add a new .NET MAUI ContentPage (XAML) named CustomerDetailView. Change the Title attribute to “Customer Information”. Replace the entire <VerticalStackLayout> element with the following XAML:

<Label Text="Customer Information"
       FontSize="Large"
       VerticalOptions="Center"
       HorizontalOptions="Center" />

Right mouse-click on the Views folder and add a new .NET MAUI ContentPage (XAML) named ColorListView. Change the Title attribute to “Color List”. Replace the entire <VerticalStackLayout> element with the following XAML:

<Label Text="Color List"
       FontSize="Large"
       VerticalOptions="Center"
       HorizontalOptions="Center" />

Right mouse-click on the Views folder and add a new .NET MAUI ContentPage (XAML) named PhoneTypesListView. Change the Title attribute to “Phone Types List”. Replace the entire <VerticalStackLayout> element with the following XAML.

<Label Text="Phone Types List"
       FontSize="Large"
       VerticalOptions="Center"
       HorizontalOptions="Center" />

Now that you have several pages created, it's time to learn to navigate among the different pages. The Shell control in .NET MAUI uses a URI-based navigation scheme to route between different pages in your application. There are a few different types of navigation schemes that you may employ in your application. You may use a FlyoutItem, which is a tab bar for your application and is accessible through an icon, or by swiping from the side of the screen. Another navigation scheme is a TabBar, which is a set of tabs displayed either at the top or bottom of the screen depending upon which OS your application is running.

Regardless of which navigation scheme you choose, both ShellContent and Tab objects are used. A ShellContent object references a single page in your application to which you want to navigate. A Tab object is a wrapper around one or more ShellContent objects. If you're running on Windows, a Tab object with more than one ShellContent object creates a drop-down set of tabs of the enclosed ShellContent objects. If you're running on a mobile device, the Tab object appears as bottom tabs with the ShellContent objects within that Tab shown as top tabs when the bottom tab is selected.

The ShellContent Object

The ShellContent object has many properties, but there are a few that you'll set most often. The Title property is used to set the word(s) you want to display on the tab. The Icon property is the icon to display along with the text. You also have the IsChecked, IsEnabled, and IsVisible properties to set the currently highlighted tab, whether or not the current tab is enabled, or whether or not it's visible, respectively. The Route property is an optional string to uniquely identify the tab/route. If you set the Route property, this property is used to navigate to that specific route. The ContentTemplate property is set to a reference of the actual page to display within the application shell. The syntax you use in the ContentTemplate property is a DataTemplate markup extension followed by the name of the page. For example, ContentTemplate="{DataTemplate views:UserDetailView}" navigates to the UserDetailView page you created.

Using TabBar Navigation

Let's create a TabBar navigation system for the pages you've added to your .NET MAUI application. Because you added all of the pages to the Views folder, they're located within the AdventureWorks.MAUI.Views namespace. Thus, if you want to use a page from the Views namespace in a ShellContent object, you need to add a reference to that namespace. Open the AppShell.xaml file and add a new XML namespace, as shown in the following code snippet. The entire code should be on a single line when you add it to the <Shell> element.

xmlns:views="clr-namespace: AdventureWorks.MAUI.Views"

The <Shell> element currently in the AppShell.xaml file is a single ShellContent object pointing to the MainPage. Delete that ShellContent object from within the <Shell> element and add the following XAML to display tabs to navigate to the home, the UserDetailView, the ProductDetailView, and the CustomerDetailView page:

<TabBar>
    <ShellContent Title="Home" 
        ContentTemplate="{DataTemplate local:MainPage}" />
    <ShellContent Title="Users"
        ContentTemplate="{DataTemplate views:UserDetailView}" />
    <ShellContent Title="Products"
        ContentTemplate="{DataTemplate views:ProductDetailView}" />
    <ShellContent Title="Customers" 
        ContentTemplate="{DataTemplate views:CustomerDetailView}" />
</TabBar>

Try It Out on Windows

Run the application on Windows to display the window shell with the tabs shown in Figure 1. Click on each of the tab items to navigate to the corresponding pages. Finally, click on the Home tab to navigate back to the Main page.

Figure 1: Running the application on Windows displays tab items at the top of the window.
Figure 1: Running the application on Windows displays tab items at the top of the window.

Try It Out on the Android Emulator

Change Visual Studio to run the application on the Android Emulator. The tabs should now be displayed at the bottom of the page, as shown in Figure 2. Click on each of the various tabs at the bottom of the page to navigate to the corresponding pages. Click on the Home menu to navigate back to the Main page.

Figure 2: ShellContent objects appear as a set of bottom tabs on an Android device.
Figure 2: ShellContent objects appear as a set of bottom tabs on an Android device.

Display Subordinate Tabs

You can create a tab that doesn't directly navigate to a page, but instead displays a set of tabs either in a drop-down on Windows, or as a set of top tabs on an Android device. Open the AppShell.xaml file and immediately after the Customers ShellContent object, add the following XAML:

<Tab Title="Maintenance">
    <ShellContent Title="Colors"
        ContentTemplate="{DataTemplate views:ColorListView}" />
    <ShellContent Title="Phone Types"
        ContentTemplate="{DataTemplate views:PhoneTypesListView}" />
</Tab>
<ShellContent Title="Login"
    ContentTemplate="{DataTemplate views:LoginView}" />

Try It Out on Windows

Restart the application on the Windows Machine, click on the down arrow next to the word Maintenance and you should see a drop-down list of tabs, as shown in Figure 3.

Figure 3: Display a set of drop-down tab items on Windows machines.
Figure 3: Display a set of drop-down tab items on Windows machines.

Try It Out on the Android Emulator

Stop the application and change Visual Studio to use the Android Emulator. Run the application and you should now see a More… tab displayed, as shown in Figure 4. When there are more than four tabs, this More… tab is displayed to allow you to get to the next set of tabs.

Figure 4: A More tab item is displayed when there are more tabs than fit within the displayable area on a mobile device.
Figure 4: A More tab item is displayed when there are more tabs than fit within the displayable area on a mobile device.

Click on the More… tab to see the Maintenance and Login tabs appear, as shown in Figure 5.

Figure 5: The next set of tabs are displayed after clicking on the More tab.
Figure 5: The next set of tabs are displayed after clicking on the More tab.

Click on Maintenance and you should see Colors and Phone Types menus appear at the top of the screen, as shown in Figure 6.

Figure 6: ShellContent objects wrapped within a Tab object are displayed on the top bar on mobile devices.
Figure 6: ShellContent objects wrapped within a Tab object are displayed on the top bar on mobile devices.

Programmatically Navigate from Page to Page

Right mouse-click on the Views folder and add a new content page named UserListView. Set the Title attribute to “User List”. Replace the <VerticalStackLayout> with the following XAML:

<VerticalStackLayout VerticalOptions="Center"
                     HorizontalOptions="Center"
                     Spacing="10">
    <Label Text="User List"
           FontSize="Header"
           HorizontalOptions="Center" />
    <Button Text="Navigate to Detail"
            Clicked="NavigateToDetail_Clicked" />
</VerticalStackLayout>

Create the Clicked event procedure by positioning your cursor anywhere within the double quotes of the Clicked attribute. Press the F12 key and the NavigateToDetail_Clicked() event procedure stub is created for you. Modify the procedure declaration to include the async keyword and add a single line of code within the procedure, as shown in the following code snippet:

private async void 
    NavigateToDetail_Clicked(object sender, EventArgs e) {
        await Shell.Current.GoToAsync(nameof(Views.UserDetailView));
}

In the above code, you access the application shell object through the Shell property of the ContentPage class to navigate to another page. Pass the name of the route to the GoToAsync() method and the Shell loads that page and displays it. Always use the C# nameof() function to resolve the route name, as it avoids any typos that could produce a runtime error. If you add the Route attribute to a Shell object as a string, you may pass this string to the GoToAsync() method as well.

Modify the Shell to call this new page instead of the user detail page you created earlier. Open the AppShell.xaml file and modify the <ShellContent> element that used to call the UserDetailView page and replace it with a call to the UserListView page, as shown in the following XAML:

<ShellContent Title="Users"
              ContentTemplate="{DataTemplate views:UserListView}" />

Register the Route

All of the ShellContent objects in the AppShell.xaml file create a global route table that .NET MAUI uses to navigate to each page. Because you removed the UserDetailView ShellContent object, it's now no longer a part of this global route table. If you tried to run the application right now and clicked on the Navigate to Detail button, you'd receive a runtime error that the route does not exist. Instead of using XAML to create the route table, you may add as many other routes as you need using C#. Open the AppShell.xaml.cs file and in the constructor register the route to the UserDetailView page using the following code:

public AppShell() {
    InitializeComponent();

    // Register routes
    Routing.RegisterRoute(
       nameof(Views.UserDetailView),
       typeof(Views.UserDetailView));
}

The above code adds the name of the UserDetailView page, and the C# type of UserDetailView to the global route table. Now the routing engine knows which page to display when you request the route name of UserDetailView.

Try It Out

Restart the application and click on the Users menu. Click on the Navigate to Detail button and you should see the User Information page appear.

Create Reusable UI

One of the basic tenets of programming is that you should only write code once, then reuse it as many times as you want. This applies when designing your UI as well. On the user detail view page, the first three rows would be nice to have on all of the pages, so each looks consistent. The first row is a short title to uniquely identify the page. The second row is a short description to describe what this page is used for. The third row is a line to separate the header information from the unique details on the page. Of course, the title and short description should be able to be modified from each page on which these two labels are placed.

To create a custom reusable view onto which you can place any controls and then have those controls appear on any other page, use a ContentView control. The ContentView control is a container onto which you place as many controls as you want. You then place the ContentView control onto the page(s) you wish to use that group of controls.

Create a Header View

Let's create a custom “header” view onto which you can place the two labels and the box view control you used on the user detail view page. Right mouse-click on the project and add a new folder named ViewsPartial. Right mouse-click on the ViewsPartial folder and select Add > New Item > .NET MAUI > .NET MAUI ContentView (XAML)... from the context-sensitive menu. Set the name of the ContentView to HeaderView. Add to the <ContentView> starting tag an x:Name attribute, as shown below, so you may reference the ContentView control named HeaderView by using the name “header”.

x:Name="header"

Replace the <VerticalStackLayout> element of this new file with the XAML shown in Listing 4.

Listing 4: Create a reusable header view that can be placed onto many pages.

<Grid BindingContext="{x:Reference header}"
      RowDefinitions="Auto,Auto,Auto">
   <Label Grid.Row="0"
          Text="{Binding Path=ViewTitle, FallbackValue='View Title'}"
          HorizontalOptions="Center"
          FontSize="Title" />
   <Label Grid.Row="1"
          Grid.ColumnSpan="2"
          Text="{Binding Path=ViewDescription, FallbackValue='View Description'}"
          HorizontalOptions="Center"
          FontSize="Body" />
   <BoxView Grid.Row="2"
            HeightRequest="1"
            Color="Black" />
 </Grid>

The Basics of Data Binding

There are a few pieces of this code that need a little explanation as you're now using data binding. Data binding is a powerful tool that helps you eliminate C# code to control how data is shared between different objects. In the code in Listing 4, two Label controls have been placed onto this ContentView control and their Text property has been set to a markup extension called Binding. This Binding class has a Path property to which you set the name of a property on your ContentView control. You're going to create two C# properties in the code-behind of this HeaderView control named ViewTitle and ViewDescription. There is also a FallbackValue property on the Binding class to which you specify a default value if the property being bound to is null or empty.

For the Binding class to know where the property is located, a BindingContext must be set either on the Label controls or on one its parent controls. If you look at the Grid control (which is the parent of the Label controls) you see that the BindingContext property is set on this control. This BindingContext property is set the markup extension x:Reference, which references the x:Name attribute that has been set on the ContentView control. By referencing the ContentView control using the name, it identifies the HeaderView class is the object that has the properties to reference.

Create Bindable Properties

You now need to create the ViewTitle and ViewDescription properties on the HeaderView class. You can't just create normal C# properties with simple getters and setters. A BindableProperty object is used to create the backing fields for both the properties. By using a BindableProperty object, the UI is automatically notified when a property value changes. Open the ViewsPartial\HeaderView.xaml.cs file and add two bindable properties just below the constructor, as shown in Listing 5.

Listing 5: Connect UI label text with data set into the bindable properties.

public string ViewTitle {
    get { 
        return (string)GetValue(ViewTitleProperty); 
    }
    set {
        SetValue(ViewTitleProperty, value); 
    }
}

public static readonly BindableProperty
    ViewTitleProperty = BindableProperty.Create("ViewTitle",
        typeof(string), typeof(HeaderView), string.Empty);

public string ViewDescription {
    get { 
        return (string)GetValue(ViewDescriptionProperty); 
    }
    set { 
        SetValue(ViewDescriptionProperty, value);
    }
}

public static readonly BindableProperty
    ViewDescriptionProperty = BindableProperty.Create(
        "ViewDescription",
        typeof(string), typeof(HeaderView), string.Empty);

Add Header to the User Detail View

Now that you have your reusable header view, it's time to add it to the different pages. Open the Views\UserDetailView.xaml file and add the following XML namespace within the <ContentPage>.

xmlns:partial="clr-namespace:AdventureWorks.MAUI.ViewsPartial"

Delete the first two Label controls and the BoxView control within the Grid and replace those controls with the following XAML. You're adding a reference to the HeaderView control located in the namespace aliased by partial. Because the ViewTitle and ViewDescription are public properties, you can set these properties on this control. Be sure that the quoted string for ViewDescription is only on a single line when you type this code into Visual Studio.

<partial:HeaderView Grid.Row="0"
                    Grid.ColumnSpan="2"
                    ViewTitle="User Information"
                    ViewDescription="Use this screen to 
                        modify user information." />

You deleted three rows from the Grid control on this page and only replaced one row, so remove two “Auto” values from the RowDefinitions property on the Grid. You now also need to renumber the remaining rows in the <Grid> by reducing each by two (2). Go ahead and renumber all the rows now.

Try It Out

Run the application and click on Users > Navigate to Detail to view this page with the new reusable header. Notice that the ViewTitle and ViewDescription properties are set to the values you used in the XAML you just typed in.

Add a Header to the Login View

Open the Views\LoginView.xaml file and add the following XML namespace as an attribute on the <ContentPage> element:

xmlns:partial="clr-namespace:AdventureWorks.MAUI.ViewsPartial"

Delete the <Label> element that is currently in this ContentPage and add the XAML shown in Listing 6 where the <Label> was. Notice that the ViewTitle and ViewDescription properties are set to different values than those you used on the user detail view page. Also notice the use of the IsPassword property on the second Entry control. Setting this property ensures that no one can see the password as you are typing into this control.

Listing 6: Use the header view partial page on all pages for a consistent look and feel.

<Grid RowDefinitions="Auto,Auto,Auto,Auto"
      ColumnDefinitions="Auto,*"
      Style="{StaticResource Grid.Page}">

   <partial:HeaderView Grid.Row="0"
                       Grid.ColumnSpan="2"
                       ViewTitle="Login"
                       ViewDescription="Use this screen to 
                          login to this application."/>

   <Label Grid.Row="1"
          Text="Login ID" />
   <Entry Grid.Row="1"
          Grid.Column="1"
          Text="" />
   <Label Grid.Row="2"
          Text="Password" />
   <Entry Grid.Row="2"
          Grid.Column="1"
          IsPassword="True" />
   <HorizontalStackLayout Grid.Row="3"
                          Grid.Column="1">
      <Button Text="Login" />
      <Button Text="Cancel" />
   </HorizontalStackLayout>
</Grid>

Try It Out

Run the application and click on the Login menu to view this page. You should see the reusable header above the other controls on this page.

Add a Header to the Product Detail View

Open the Views\ProductDetailView.xaml file and add the following XML namespace as an attribute on the <ContentPage> element.

xmlns:partial="clr-namespace:AdventureWorks.MAUI.ViewsPartial"

Delete the <Label> element that is currently in this ContentPage and add the XAML shown in Listing 7 where the <Label> was.

Listing 7 Create the product detail view with many label and entry controls.


<Grid RowDefinitions="Auto,Auto,Auto,Auto,
  Auto,Auto,Auto,Auto,Auto,Auto,Auto,
  Auto,Auto,Auto" 
      ColumnDefinitions="Auto,*"
      Style="{StaticResource Grid.Page}">

    <partial:HeaderView Grid.Row="0"
                        Grid.ColumnSpan="2"
                        ViewTitle="Product Information"
                        ViewDescription="Use this screen to 
                           modify product information."  />

    <Label Text="Product Name"
            Grid.Row="1" />
    <Entry Grid.Column="1"
            Grid.Row="1" />
    <Label Text="Product Number"
            Grid.Row="2" />
    <Entry Grid.Row="2"
            Grid.Column="1" />
    <Label Text="Color"
            Grid.Row="3" />
    <Entry Grid.Row="3"
            Grid.Column="1" />
    <Label Text="Cost"
            Grid.Row="4" />
    <Entry Grid.Row="4"
            Grid.Column="1" />
    <Label Text="Price"
            Grid.Row="5" />
    <Entry Grid.Row="5"
            Grid.Column="1" />
    <Label Text="Size"
            Grid.Row="6" />
    <Entry Grid.Row="6"
            Grid.Column="1" />
    <Label Text="Weight"
            Grid.Row="7" />
    <Entry Grid.Row="7"
            Grid.Column="1" />
    <Label Text="Category"
            Grid.Row="8" />
    <Entry Grid.Row="8"
            Grid.Column="1" />
    <Label Text="Model"
            Grid.Row="9" />
    <Entry Grid.Row="9"
            Grid.Column="1" />
    <Label Text="Selling Start Date"
            Grid.Row="10" />
    <Entry Grid.Row="10"
            Grid.Column="1" />
    <Label Text="Selling End Date"
            Grid.Row="11" />
    <Entry Grid.Row="11"
            Grid.Column="1" />
    <Label Text="Discontinued Date"
            Grid.Row="12" />
    <Entry Grid.Row="12"
            Grid.Column="1" />
    <HorizontalStackLayout Grid.Row="13"
                           Grid.Column="1">
       <Button Text="Save" />
       <Button Text="Cancel" />
    </HorizontalStackLayout>
</Grid>

Try It Out

Run the application and click on the Products menu to view this page with the reusable header and the various controls. Don't worry if some of the controls are cropped off at the bottom of the window. You'll fix this later.

Add a Border on All Pages

A Border control allows you to draw any size border around a control, or a set of controls. Use the StrokeThickness property to set the size in device-independent pixels. The default size is one pixel. The Stroke property sets the color for the border outline. The default color is light gray. Open the MainPage.xaml file and replace the <Label> element with the following XAML to draw a border around the label on the main page.

<Border StrokeThickness="2"
        HorizontalOptions="Center"
        VerticalOptions="Center"
        Padding="10">
   <Label Text="{StaticResource ApplicationTitle}"
          FontSize="Large" />
</Border>

Try It Out

Run the application and see the border around the application title on the main page.

Create a Keyed Border Style

I like to wrap a border around each of my pages to make it stand out a little from the edges of the window on which it's hosted. Wrap a Border control around all grids on each page by creating a keyed style to define the standard look for the border on each page. Open the Resources\Styles\CommonStyles.xaml file in the Common.Library.MAUI project and add the keyed style named Border.Page as shown in the XAML below:

<Style TargetType="Border"
       x:Key="Border.Page">
    <Setter Property="Stroke"
            Value="Gray" />
    <Setter Property="StrokeThickness"
            Value="1" />
    <Setter Property="Margin"
            Value="10" />
    <Setter Property="HorizontalOptions"
            Value="Fill" />
    <Setter Property="VerticalOptions"
            Value="Fill" />
</Style>

Let's now apply this keyed border style on a Border control that you wrap around each Grid control on each of your pages. Open the Views\LoginView.xaml file and wrap a <Border> element around the Grid control. Don't forget to add a closing </Border> tag just after the closing </Grid> tag. Add the Style attribute to the border and reference the Border.Page keyed style.

<Border Style="{StaticResource Border.Page}">
   <Grid ...>
       // REST OF THE XAML HERE
   </Grid>
</Border>

Open the Views\ProductDetail.xaml file and wrap a <Border> around the Grid control using the keyed style Border.Page.

<Border Style="{StaticResource Border.Page}">
   <Grid ...>
      // REST OF THE XAML HERE
   </Grid>
</Border>

Open the Views\UserDetailView.xaml file and wrap a <Border> around the Grid control using the keyed style Border.Page.

<Border Style="{StaticResource Border.Page}">
   <Grid ...>
      // REST OF THE XAML HERE
   </Grid>
</Border>

Try It Out

Run the application and click on each menu item of the changed files to view the border around each screen.

Add a ScrollView to all Pages

With the application running, click on the Products menu and reduce the main window to show how the controls on the bottom of the page are cropped. Because you never know what the screen size is going to be that the user is running on, it's a good idea to wrap your controls on a page within a scrollable area. This is accomplished by using a ScrollView control. Open the Views\ProductDetailView.xaml file and wrap a <ScrollView> element around the <Grid> element as shown in the following XAML:

<Border ...>
   <ScrollView>
      <Grid ...>
      </Grid>
   </ScrollView>
</Border>

Wrap the same <ScrollView> element around the <Grid> element shown in the previous XAML to both the Views\UserDetailView.xaml file and the Views\LoginView.xaml file.

Try It Out

Run the application, click on the Products menu, and reduce the main window size to show that it now displays a scroll bar. Click on the Users menu > Navigate to Detail button and see the scroll bar appear on the User Detail page.

Summary

In this article, you used styles to make all pages in your application look consistent. Styles may be placed at many different locations. Which location you choose determines how reusable those styles are. Several pages were created, and you learned how to use the built-in navigation to move from one page to another. Using a ContentView control when you want to create reusable UI for several (or all) pages in your application. Use data binding to change property values on the ContentView control.

Coming up in the next article, you'll start using the many different input controls and learn which ones should be used for specific input data types. In addition, you're going to learn much more about data binding. You'll see how data binding can be used to bind one control to another, and to bind properties in a class to controls on your forms.