Skip to content
Home » Basics of .NET MAUI – Part 2 – A New Project

Basics of .NET MAUI – Part 2 – A New Project

Spread the love

In part 1 of this series I introduced the project we’re going to work on. Now it’s time to create the project in Visual Studio. So, without further ado, let’s jump right in.

Open Visual Studio and click on Create a new project.

Then find MAUI in the dropdown list (A), select the .NET MAUI App project template (B) and hit the Next button (C).

Set the project name to Slugrace (A), select a location for your project (B) and hit Next (C).

We’re going to target .NET 7.0, so make sure it’s selected in the dropdown list (A). Then hit Create (B).

This will create the project for you. Now let’s have a look at what has been created for us.

Anatomy of a .NET MAUI Project 

If you look at the Solution Explorer, you’ll see there is just one project in the solution. We don’t have separate projects for the particular platforms, the whole code sits in just one project.

There are three files with a .xaml extension. These files contain XAML code. Hit the little triangles to the left of their names to expand and you will see the three C# files that accompany them.

These are the code-behind files that take care of the logic of their corresponding XAML files. Whenever we create a XAML file later in the series, it will always be accompanied by a corresponding code-behind file.

Anyway, what are these three XAML files and their corresponding code-behind files for? Let’s have a look at them one by one.

The App.xaml and App.xaml.cs Files 

Let’s start with the App.xaml file. If you open the file in the editor, you will see the following code:

<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:Slugrace"
             x:Class="Slugrace.App">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Resources/Styles/Colors.xaml" />
                <ResourceDictionary Source="Resources/Styles/Styles.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

We’re going to talk about XAML syntax in detail later in the series, but if you know XML, you will notice lots of similarities here. So, here we have the root element, Application, and in it is nested the Application.Resources element. The nesting is clearly visible due to indentation. Inside the Application.Resources element you can see the ResourceDictionary element, the latter nested in the former. If we move deeper and deeper in the hierarchy, we have the ResourceDictionary.MergedDictionaries element and in it two ResourceDictionary elements. The App.xaml file defines the application resources that are available in the entire application, not just in a single page, not in a couple selected pages, but everywhere. We’ll be talking about ResourceDictionary elements and their scope in the application in due time.

Now, if you look at the innermost ResourceDictionary elements, you will see they have their Source attributes set to the Colors.xaml and Styles.xaml files contained in the Styles folder inside the Resources folder. Check it out. Expand the Resources folder in the Solution Explorer and you will see the two files there.

We’ll be talking about the Resources folder in a minute, but as you probably suspect, this is the place where you can put style files, among other things. The Colors.xaml and Styles.xaml files define the styles used in the app. In the App.xaml file the two resource dictionaries are merged together in a ResourceDictionary.MergedDictionaries element.

Fine, and now let’s open the code-behind file:

namespace Slugrace;

public partial class App : Application
{
	public App()
	{
		InitializeComponent();

		MainPage = new AppShell();
	}
}

Here we have the App class that inherits from Application. This class represents the application at runtime. As you can see, the class is defined as partial, which means the code you see in the file is not the full code the class consists of. The rest of the code is somewhere else, and to be precise, it’s in the XAML file. So, the code in the App.xaml file is combined with the code in the code-behind file to produce the complete App class.

In the constructor you can see the InitializeComponent method. This method is called right to create the class instance by merging with the code in the XAML file. You will see this method in every constructor in the code-behind files that have an accompanying XAML file. On the other hand, you won’t see it if you write all your code in C#, without the XAML file, which is also possible (we’ll see how to do it soon), although not recommended.

Finally, the code in the constructor creates the initial app window and assigns it to the MainPage property. This way the application knows what to display when you run the app. As you can see, the initial window is created by instantiating the AppShell class. Speaking of which…

The AppShell.xaml and AppShell.xaml.cs Files 

Let’s have a look at the AppShell.xaml file first:

<?xml version="1.0" encoding="UTF-8" ?>
<Shell
    x:Class="Slugrace.AppShell"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:local="clr-namespace:Slugrace"
    Shell.FlyoutBehavior="Disabled">

    <ShellContent
        Title="Home"
        ContentTemplate="{DataTemplate local:MainPage}"
        Route="MainPage" />

</Shell>

Here we have Shell as the root element. The purpose of this element is to give your app some structure. Your app may be structured in a couple ways. There may be a flyout, there may be tabs or just a single page, which is the case here. The single page that we get out of the box is represented by the ShellContent element. The attributes that are set on this element include the title of the page, the template of the page and the URI-based route to MainPage, which is to be displayed when the app starts running.

The AppShell.xaml.cs file is very simple:

namespace Slugrace;

public partial class AppShell : Shell
{
	public AppShell()
	{
		InitializeComponent();
	}
}

We just have the InitializeComponent method in the constructor that instantiates the AppShell class. So, as you can see in the XAML file above, the first page that is going to be displayed is MainPage. Let’s have a look at it next.

The MainPage.xaml and MainPage.xaml.cs Files 

In the XAML file we have some sample code:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Slugrace.MainPage">

    <ScrollView>
        <VerticalStackLayout
            Spacing="25"
            Padding="30,0"
            VerticalOptions="Center">

            <Image
                Source="dotnet_bot.png"
                SemanticProperties.Description="Cute dot net bot waving hi to you!"
                HeightRequest="200"
                HorizontalOptions="Center" />

            <Label
                Text="Hello, World!"
                SemanticProperties.HeadingLevel="Level1"
                FontSize="32"
                HorizontalOptions="Center" />

            <Label
                Text="Welcome to .NET Multi-platform App UI"
                SemanticProperties.HeadingLevel="Level2"
                SemanticProperties.Description="Welcome to dot net Multi platform App U I"
                FontSize="18"
                HorizontalOptions="Center" />

            <Button
                x:Name="CounterBtn"
                Text="Click me"
                SemanticProperties.Hint="Counts the number of times you click"
                Clicked="OnCounterClicked"
                HorizontalOptions="Center" />

        </VerticalStackLayout>
    </ScrollView>

</ContentPage>

This is the page that will be displayed when you run the app. The root element is ContentPage and inside it is a ScrollView element. This element contain a VerticalStackLayout, which in turn contains an image, two labels and a button. These elements are added here so that you can see something on your screen when you run the app, but eventually we will cut out all the code between the opening and closing tags of the ContentPage and replace it with our own code. But let’s leave it for now. What can you expect to see? Well, you’ll see an image, two labels and a button arranged vertically (this is what the VerticalStackLayout is for). Besides, if the view is too large to fit in your screen, a scrollbar will be provided (this is what the ScrollView is for).

Before we run the app and see what it all looks like, let’s have a look at the code-behind file. If you open the MainPage.xaml.cs file, you’ll see this:

namespace Slugrace;

public partial class MainPage : ContentPage
{
	int count = 0;

	public MainPage()
	{
		InitializeComponent();
	}

	private void OnCounterClicked(object sender, EventArgs e)
	{
		count++;

		if (count == 1)
			CounterBtn.Text = $"Clicked {count} time";
		else
			CounterBtn.Text = $"Clicked {count} times";

		SemanticScreenReader.Announce(CounterBtn.Text);
	}
}

Here we have more code than in the other two code-behind files. In the constructor we have the InitializeComponent method again, but then we have another method, OnCounterClicked. This method takes care of the button click event and just displays the number of your clicks on the button. This method is needed for the button to work.

So, let’s now run the app and see what the MainPage looks like. Let’s run it twice, actually, first in Windows, then in Android. Under the top menu you can see the Play button with a filled green triangle on it. It’s used to run the app with debugging. Make sure Windows Machine is selected in the dropdown next to it.

Now press the Play button. Visual Studio may prompt you to enable Developer Mode if you haven’t done it before. Just click the settings for developers link and switch on Developer Mode. Close the Enable Developer Mode for Windows dialog and now you can proceed. The app builds and runs. You can now see the image, the two labels and the button stacked vertically.

You can also see some other elements, like the blue bar near the top with the page title. We didn’t have them in the MainPage file, but they are defined somewhere else, in the layout, which we’re going to see later on.

Anyway, let’s try the app out. Click the button several times and watch the text on it. Here’s what I could see after clicking the button 8 times.

Let’s close the app. You can close the app window directly (A) or hit the Stop button in Visual Studio (B).

And now let’s run the app in Android. First of all, we have to select the device that we created in the previous part of the series. Here’s mine.

Now we can hit the Play button. It can take some time to start the first time, but the next time it’ll be much faster. Anyway, here’s what we can see after clicking the button several times:

Fine, stop the app now and let’s continue with our overview of the project. The next file I’d like to draw your attention to is the MauiProgram.cs file.

The MauiProgram.cs File

In the MauiProgram.cs file you will find the definition of the static MauiProgram class:

using Microsoft.Extensions.Logging;

namespace Slugrace;

public static class MauiProgram
{
	public static MauiApp CreateMauiApp()
	{
		var builder = MauiApp.CreateBuilder();
		builder
			.UseMauiApp<App>()
			.ConfigureFonts(fonts =>
			{
				fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
				fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
			});

#if DEBUG
		builder.Logging.AddDebug();
#endif

		return builder.Build();
	}
}

This class is the common entry point for your app. As you will see in a minute when we talk about the Platforms folder, each native platform defines its own entry point. However, the entry point code of each platform calls the CreateMauiApp method that is defined right here, in the MauiProgram class. So, when each platform creates and initializes the app, it calls the method in this common class.

But what does the CreateMauiApp method do? Well, it creates an app builder object which you can use to configure your app. Here you can see it register some fonts, but we also use it to add services for dependency injection (we’ll be talking about it later in the series) and more. But there is one thing the builder object always takes care of. It associates the application with the application class. To do that, it calls the generic UseMauiApp method and passes the application class as the type parameter, App. From now on the application is identified with the App class that we discussed before.

Now, as I mentioned, each platform has its own entry point. To see how it works, we must move on to the Platforms folder.

The Platforms Folder

Expand the Platforms folder to view its hierarchy.

Each platform has its own set of files and each platform contains a file with app initialization. In case of Windows it’s App.xaml.cs (A), in case of Android it’s MainApplication.cs (B), and there are naturally initialization files in the other platforms as well. If you open the initialization file of any platform you will see that after the initialization is done, the MauiProgram.CreateMauiApp method is called, which I mentioned before. Let’s have a look at the App.xaml.cs file in the Windows folder first:

using Microsoft.UI.Xaml;

namespace Slugrace.WinUI;

public partial class App : MauiWinUIApplication
{	
	public App()
	{
		this.InitializeComponent();
	}

	protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}

For clarity, I removed the comments. The method is called in the last line of the code.

And now let’s have a look at the MainApplication.cs file in the Android folder:

using Android.App;
using Android.Runtime;

namespace Slugrace;

[Application]
public class MainApplication : MauiApplication
{
	public MainApplication(IntPtr handle, JniHandleOwnership ownership)
		: base(handle, ownership)
	{
	}

	protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}

Again, the MauiProgram.CreateMauiApp method is called in the last line of the code.

App Startup Flow of Control

So, let’s recap. What is going on when the app starts? Here are the steps:

1. Platform-specific initialization code is executed and calls the CreateMauiApp method in the MauiProgram class.

protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();

2. The CreateMauiApp method creates the app builder object.

var builder = MauiApp.CreateBuilder();

3. The builder object associates the application with the App class.

builder.UseMauiApp<App>()

4. In the App object constructor the main app window is instantiated.

MainPage = new AppShell();

And only now the MainPage is displayed to you.  Fine, and now let’s have a look at the remaining folders in the project hierarchy.

The Project File

The project file, with the extension .csproj, contains some important information about your app. To open it, double-click on the name of the Project in the Solution Explorer, which in our case is Slugrace.

<Project Sdk="Microsoft.NET.Sdk">

	<PropertyGroup>
		<TargetFrameworks>net7.0-android;net7.0-ios;net7.0-maccatalyst</TargetFrameworks>
		<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net7.0-windows10.0.19041.0</TargetFrameworks>
		<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
		<!-- <TargetFrameworks>$(TargetFrameworks);net7.0-tizen</TargetFrameworks> -->
		<OutputType>Exe</OutputType>
		<RootNamespace>Slugrace</RootNamespace>
		<UseMaui>true</UseMaui>
		<SingleProject>true</SingleProject>
		<ImplicitUsings>enable</ImplicitUsings>

		<!-- Display name -->
		<ApplicationTitle>Slugrace</ApplicationTitle>

		<!-- App Identifier -->
		<ApplicationId>com.companyname.slugrace</ApplicationId>
		<ApplicationIdGuid>f87faa9d-1ae1-4bb3-879c-e99b35133d83</ApplicationIdGuid>

		<!-- Versions -->
		<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
		<ApplicationVersion>1</ApplicationVersion>

		<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">11.0</SupportedOSPlatformVersion>
		<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">13.1</SupportedOSPlatformVersion>
		<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
		<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
		<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
		<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
	</PropertyGroup>

	<ItemGroup>
		<!-- App Icon -->
		<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />

		<!-- Splash Screen -->
		<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />

		<!-- Images -->
		<MauiImage Include="Resources\Images\*" />
		<MauiImage Update="Resources\Images\dotnet_bot.svg" BaseSize="168,208" />

		<!-- Custom Fonts -->
		<MauiFont Include="Resources\Fonts\*" />

		<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
		<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
	</ItemGroup>

	<ItemGroup>
		<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
	</ItemGroup>

</Project>

Here you can see a PropertyGroup section and two ItemGroup sections. The PropertyGroup contains information about the frameworks your app targets, app title, app version, and so on. You can modify this code if you need to.

In the ItemGroup below you can see information about resources.  The section specifies an app icon, the splash screen and the default locations for images, fonts and other assets used by the app.

So, the project file contains information about the resources used in the app. But where actually are the resources?

The Resources Folder

The answer is here, in the Resources folder. Expand it and you will see all the resources described in the project file:

This is where we’ll be adding all the graphical resources that our app needs to make use of.

The Other Folders

The two remaining folders in our project hierarchy are Dependencies and Properties. The former contains .NET dependencies for the specific platforms. The latter contains a json file with launch settings.

So, that would be it. This is all the code that you get out of the box when you create a new .NET MAUI project. Now it’s time to tweak the code and start work on our Slugrace project. In the next part of the series we’ll have a look at the syntax of the XAML language.


Spread the love

Leave a Reply