Posts Tagged ‘mvvm’

Convert sample Maui app to MVVM

May 1st, 2023

Create base from Maui template

Open Visual studio, create new project from “.NET MAUI App”. Name it “MauiApp1”.

Set target to “Windows Machine” and press play just to make sure all dots are connected.

The start and result are on Github:LosManos/MauiApp1_MVVM.

If you prefer video instead of text James Montemagno has a similar Youtube video.

Plans

Investigate the code.
In MainPage.xaml there is a tight coupling from the xaml file to the code behind through

1
Clicked="OnCounterClicked"
. That method has code like
1
CounterBtn.Text = $"Clicked {count} time";
which means it is tightly coupled with the GUI.

When running the code, the counter and button text, will be updated by the code above.

Let’s get rid of the tight coupling.

Note: I am not satisfied how the click command will be wired up in the final solution. Please comment and help me make it better.

View model

MVVM works by GUI logic writing to an object/data structure (the view model) and the GUI graphic reading from it. And vice verse. It can be compared to a data contract with talking over the wire

This means we can test the logic without having a GUI and the GUI won’t have to rely on complicated GUI logic.

Create a class “MainPageViewModel.cs”, in a new folder “ViewModel”. We want to replace the counter.

To make it easier to react on the changes of the values we will use CommunityToolkit. To make it work the MVVM class must be public and partial. The field must be a field (not a property, and it must be with a leading lower case. Add a counter and a buttonText. Add a method to update the counter and replace the button text.

The reason for the above is that CommunityToolkit creates a Property for every field with ObservableProperty. You can find the rendered code in MauiApp1/Dependencies/.net7.0-xxxx/Analyzers.
I have some reason for the view model to be public but won’t delve into it here.

Install Nuget package CommunityToolkit.MAUI and CommunityToolkit.Mvvm.

(Sorry for the &#91; and &lt;, but I don’t want to dig through WordPress. Just replace with [ and < respectively.)

Class “MainPageViewModel.cs” will look like this:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace MauiApp1.ViewModel;

public partial class MainPageViewModel : ObservableObject
{
    public MainPageViewModel()
    {
        ButtonText = "Click Me";
    }

    &#91;ObservableProperty]
    public int myCount;

    &#91;ObservableProperty]
    public string buttonText;

    &#91;RelayCommand]
    public void IncreaseCount()
    {
        MyCount += 1;

        if (MyCount == 1)
            ButtonText = $"Clicked {MyCount} time";
        else
            ButtonText = $"Clicked {MyCount} times";
    }
}

Pressing play should work now as we only have added a class. Not changed or connected anything.

Connect

As per the readme you received when installing CommunityToolkit we have to manipulate the startup. Make it use the CommunityToolkit. Open MauiProgram.cs and add


1
    .UseMauiCommunityToolkit()

Then we need to wire up the dependency injection. Alas add


1
2
    builder.Services.AddSingleton&lt;ViewModel.MainPageViewModel&gt;();
    builder.Services.AddSingleton&lt;MainPage&gt;();

You will now have something like


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using CommunityToolkit.Maui;
using Microsoft.Extensions.Logging;

namespace MauiApp1;

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

        builder.Services.AddSingleton&lt;ViewModel.MainPageViewModel&gt;();
        builder.Services.AddSingleton&lt;MainPage&gt;();

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

        return builder.Build();
    }
}

In MainPage.xaml.cs we get rid of the OnCounterClicked method and the counter. We also need to inject the view model to the main page MainPage.xaml.cs. All that is left is:


1
2
3
4
5
6
7
8
9
10
namespace MauiApp1;

public partial class MainPage : ContentPage
{
    public MainPage(ViewModel.MainPageViewModel vm)
    {
        InitializeComponent();
        BindingContext = vm;
    }
}

Do the xaml dance

As we have removed the event handler we also need to remove it from the xaml. Find the button and get rid of “Clicked=”OnCounterClicked””. We also don’t need the name as the code does not reference the button any more. The result looks like


1
2
3
4
5
&lt;Button
  Text="Click me"
  SemanticProperties.Hint="Counts the number of times you click"
  HorizontalOptions="Center"
/&gt;

If you press play now the program will start but nothing will happen if you press play.

Add dynamic button text and the the cryptic command binding to the button


1
2
3
4
5
6
&lt;Button
  Text="{Binding ButtonText}"
  SemanticProperties.Hint="Counts the number of times you click"
  Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:MainPageViewModel}}, Path=IncreaseCountCommand}"
  HorizontalOptions="Center"
/&gt;

The above will not work unless we tell the xaml where to find the command. So update the ContentPage element


1
2
3
4
5
6
7
&lt;ContentPage
  xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  xmlns:viewmodel="clr-namespace:MauiApp1.ViewModel"
  x:Class="MauiApp1.MainPage"  
  x:DataType="viewmodel:MainPageViewModel"
&gt;

Press play.

Every time you press the button the text button will change.