All posts tagged MVVM

Create an MVVM-based MAUI Application

Categories: .Net, C#, MAUI, MVVM, WPF, XAML
Comments: No

What is MAUI?

Microsoft released MAUI only a few weeks ago and it serves as the successor to UWP and Xamarin in the platform-agnostic space. Consider it Microsoft’s competitor with the Javascript-based UI frameworks we have all become familiar with over the last 10 years. You can read more about MAUI here:

https://docs.microsoft.com/en-us/dotnet/maui/what-is-maui

Paths to building the MAUI front-end

Microsoft offers 2 different paths to building your MAUI front-end: XAML or Blazor. In this article we will be looking at the default XAML application and we will modify it to utilize MVVM like we have been doing for years with other XAML implementation. The end result will offer better abstraction and simpler UI implementation. If you don’t know what MVVM is, read about it here:

https://docs.microsoft.com/en-us/xamarin/xamarin-forms/enterprise-application-patterns/mvvm

Let’s get to it…

Prerequisites

  • Visual Studio 17.3+ (2022+)

Step 1 – Create a new MAUI project

  1. In Visual Studio 2022 for to File -> New -> Project.
  2. Search for .NET MAUI App, select, and click Next.
  3. Make the Project Name MauiMvvmDemo, choose your repository location, check Place solution and project in the same directory, and click Next.
  4. For Framework choose .NET 6.0 (Long-term support) and click Create.

Step 2 – Install Libraries

  1. Open the Nuget Browser, by navigating to Tools -> NuGet Package Manager -> Manage Nuget Packages for Solution…

    Step 2 - Open Nuger Browser

  2. Select Browse and search for Xcalibur.Extensions.MVVM.V2.
  3. Select your project in the right pane (MauiMvvmDemo) and Choose Install. Accept the licensing prompts (if they appear).

    Step 2 - Install Library

  4. Close the Nuget Solution tab.

Step 3 – Create the ViewModel

  1. Create a new class in the project root and call it MainPageViewModel.cs.
  2. Paste in the following code:
using System.Windows.Input;
using Xcalibur.Extensions.MVVM.V2.Models;

namespace MauiMvvmDemo
{
    /// <summary>
    /// View Model - Main
    /// </summary>
    /// <seealso cref="ModelBase" />
    internal class MainViewModel : ModelBase
    {
        #region Members

        private int _count;
        private string _buttonText;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the button text.
        /// </summary>
        /// <value>
        /// The button text.
        /// </value>
        public string ButtonText
        {
            get => _buttonText;
            set => NotifyOfChange(value, ref _buttonText);
        }

        /// <summary>
        /// Gets or sets the button command.
        /// </summary>
        /// <value>
        /// The button command.
        /// </value>
        public ICommand ButtonCommand => new Command(UpdateCount);

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="MainViewModel"/> class.
        /// </summary>
        public MainViewModel()
        {
            ButtonText = "Click me!";
        }

        #endregion

        #region Methods

        /// <summary>
        /// Updates the count command.
        /// </summary>
        /// <returns></returns>
        private void UpdateCount()
        {
            _count++;

            // Update text
            var suffix = _count == 1 ? "time" : "times";
            var text = $"Clicked this {_count} {suffix}!";
            ButtonText = text;

            // Announce to screen reader
            SemanticScreenReader.Announce(text);
        }

        #endregion
    }
}

Deep Dive – What are the key pieces of this ViewModel?

Since we are working with a ViewModel, it is important to notice we are only working with properties and methods rather than control-based events.

ModelBase

https://xcalibursystems.com/wp-content/Documentation/Xcalibur.Extensions.MVVM/html/bb64d759-d714-fe71-b1c3-7ec574cdd8c2.htm

This class does a lot of the INPC work for us and makes implementation a lot simpler.

Here is the key:

/// <summary>
/// Gets or sets the button text.
/// </summary>
/// <value>
/// The button text.
/// </value>
public string ButtonText
{
	get => _buttonText;
	set => NotifyOfChange(value, ref _buttonText);
}

All of the INPC goodness can be simplified to a property using NotifyOfChange with a backing field.
https://xcalibursystems.com/creating-a-viewmodel-base-part-i-cleaning-up-your-custom-objects/

ICommand

Back in the WPF days we used to have to create a separate class to inherit from ICommand in order to make clean commands for WPF to trigger. Since those days this has been dramatically simplified as such:

/// <summary>
/// Gets or sets the button command.
/// </summary>
/// <value>
/// The button command.
/// </value>
public ICommand ButtonCommand => new Command(UpdateCount);

All you need to do is invoke a new Command and call your method, which can be made private.

UpdateCount

Lastly, we have our UpdateCount method which handles updating the button text via a bound property rather than affecting the control directly.

/// <summary>
/// Updates the count command.
/// </summary>
/// <returns></returns>
private void UpdateCount()
{
	_count++;

	// Update text
	var suffix = _count == 1 ? "time" : "times";
	var text = $"Clicked this {_count} {suffix}!";
	ButtonText = text;

	// Announce to screen reader
	SemanticScreenReader.Announce(text);
}

Anyhow, let’s continue…

Step 4 – Update MainPage.xaml

Let’s update the XAML file with the following code to use our ViewModel:

<?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"
             xmlns:vm="clr-namespace:MauiMvvmDemo"
             x:Class="MauiMvvmDemo.MainPage">

    <ContentPage.BindingContext>
        <vm:MainViewModel />
    </ContentPage.BindingContext>

    <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
                Text="{Binding Path=ButtonText}"
                SemanticProperties.Hint="Counts the number of times you click"
                Command="{Binding Path=ButtonCommand}"
                HorizontalOptions="Center" />

        </VerticalStackLayout>
    </ScrollView>

</ContentPage>

Deep Dive – What are the key changes?

Now that we have a ViewModel instead of direct calls in the codebehind, we will need to make a few key changes.

The Binding Context

In WPF we typically did this from the codebehind. To simplify that code, we add this to the XAML with the following block:

<ContentPage.BindingContext>
	<vm:MainViewModel />
</ContentPage.BindingContext>

Now our XAML’s binding context is our MainViewModel as desired.

The Button

For this example, the heart of the binding centers on the Button:

<Button
	Text="{Binding Path=ButtonText}"
	SemanticProperties.Hint="Counts the number of times you click"
	Command="{Binding Path=ButtonCommand}"
	HorizontalOptions="Center" />
  • ButtonText gives us the current button text.
  • ButtonCommand maps to our button click action.

Step 5 – Cleanup Mainpage.xaml.cs

Let’s update our codebehind page to be simply:

namespace MauiMvvmDemo;

public partial class MainPage : ContentPage
{
	public MainPage()
	{
		InitializeComponent();
	}
}

Remember, the work done originally is now abstracted to the ViewModel. So, this codebehind doesn’t need to do anything but Initialize the UI.

Step 6 – Build

Note, this might take a little time the first time through.

Step 6 – Output Window

Step 7 – Run

Step 8 - Testing
Step 8 – Testing

Keep clicking the button to see the label update.

Step 8 – All Done!

That’s all there is to it. Happy coding….

In this post I am going to introduce the idea of subscribing to property change events in our Model Base.

Note: Please read Part I before continuing here.
Note: Please read Part II before continuing here.

The purpose of this is to cover a potentially annoying situation:

Scenario: Let’s say you have a ViewModel, and inside that you have another ViewModel acting as a property like this:


    private MyCustomObject _customObjectInstance;

    public MyCustomObject CustomObjectInstance
    {
        get
        {
            return _customObjectInstance;
        }

        set
        {
            SetValue("CustomObjectInstance", ref _customObjectInstance, ref value);
        }
    }

Okay, now you want your UI bound to the current ViewModel to change every time CustomObjectInstance.MeterReading changes.

Q: (Panic moment) So, how do I do that without breaking my wonderful abstraction?
A: Implementing an ability to Subscribe to a nested property. All it takes is a little reflection and patience.

The idea is that we tell that property to fire a specific Action whenever it is changed in our ViewModelBase.

The first thing we will need to add is a private list of subscriptions:


/// <summary>
/// Subscription list.
/// </summary>
private readonly List<Tuple<string, Action<object>>> _subscriptions;

Instead of creating yet another custom object, I decided to use a Tuple because they are convenient.

  • The string value will serve as the name of the property you want to subscribe to.
  • The Action will serve as the Action you wish to call when the property is changed. The object parameter allows you to return an object if needed.

Next, we make sure to create a new instance of _subscriptions in the Constructor:


/// <summary>
/// Default constructor.
/// </summary>
protected ViewModelBase()
{
    _subscriptions = new List<Tuple<string, Action<object>>>();
}

Now we will expose a Subscribe method.

        /// <summary>
        /// Subscribes an action to a specific property that will be called
        /// during that property's OnPropertyChanged event.
        /// </summary>
        /// <param name="propertyName"></param>
        /// <param name="onChange"></param>
        public void Subscribe(string propertyName, Action<object> onChange)
        {
            // Verify property
            var propInfo = this.GetType().GetProperty(propertyName);

            // If valid, add to subscription pool.
            if (propInfo != null)
            {
                _subscriptions.Add(
                    new Tuple<string, Action<object>>(propertyName, onChange));
            }
            else
            {
                // Invalid property name provided.
                throw new Exception(
                    "Property "" + propertyName + "" could not be " +
                    "found for type "" + this.GetType().ToString() + ""!");
            }
        }

This idea here is fairly simple:

  • We pass our property name and intended Action that will fire OnPropertyChanged.
  • If the property name is not valid, we will throw an exception to ensure we didn’t pass invalid information into our Subscribe method.

Q: Alright, now we have a nice Tuple-list full of property names and Actions. Now what?
A: Glad you asked. Here comes the hard part:


        /// <summary>
        /// Processes existing subscriptions matching the provided property name.
        /// </summary>
        /// <param name="propertyName"></param>
        private void ProcessSubscriptions(string propertyName)
        {
            // Get matching subscriptions
            var subList =
                (from p in _subscriptions
                 where p.Item1 == propertyName
                 select p).ToList();

            // Check if any matches were found.
            if (subList.Any())
            {
                // Process actions
                foreach (var sub in subList)
                {
                    // Evaluate action
                    var onChange = sub.Item2;
                    if (onChange != null)
                    {
                        // Get property value by name
                        var propInfo = this.GetType().GetProperty(propertyName);
                        var propValue = propInfo.GetValue(this, null);

                        // Invoke action
                        onChange(propValue);
                    }
                }
            }
        }

ProcessSubscriptions does the following:

  • Looks up a specific property by name in _subscriptions and gets a list of all entries that are registered for that property.
  • Loop: If a specific entry has a valid Action assigned to it, it will use reflection to get that property value and pass it to the action (as out object parameter mentioned earlier).

So, the last piece is making sure ProcessSubscriptions is fired when the property has been changed. And that is as easy as augmenting our trusted OnPropertyChanged method:


        /// <summary>
        /// Calls the PropertyChanged event
        /// </summary>
        /// <param name="propertyName"></param>
        /// <param name="onChanged"></param>
        protected void OnPropertyChanged(string propertyName, Action onChanged = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                // Call handler
                handler(this, new PropertyChangedEventArgs(propertyName));

                // Subscriptions
                ProcessSubscriptions(propertyName);

                // On changed
                if (onChanged != null)
                {
                    onChanged();
                }
            }
        }

No worries if your specific property being changed is without entries. ProcessSubscriptions only acts on what is present in _subscriptions, so no entries means it just moves on.

Here is how you would use it in your parent ViewModel:


CustomObjectInstance.Subscribe("MeterReading", obj => MyActionThatDoesStuff());

Now, every time CustomObjectInstance.MeterReading is updated, the MyActionThatDoesStuff Action will be called allowing you to always have the latest values from your nested properties.

Here is our new ViewModelBase in it’s entirety:


    /// <summary>
    /// Extends the INotifyPropertyChanged interface to the class properties.
    /// </summary>
    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        #region Members

        /// <summary>
        /// Subscription list.
        /// </summary>
        private readonly List<Tuple<string, Action<object>>> _subscriptions;

        #endregion

        #region Constructors

        /// <summary>
        /// Default constructor.
        /// </summary>
        protected ViewModelBase()
        {
            _subscriptions = new List<Tuple<string, Action<object>>>();
        }

        #endregion

        #region Methods

        /// <summary>
        /// To be used within the "set" accessor in each property.
        /// This invokes the OnPropertyChanged method.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <param name="newValue"></param>
        /// <param name="onChanged"></param>
        protected void SetValue<T>(string name, ref T value, ref  T newValue,
            Action onChanged = null)
        {
            if (newValue != null)
            {
                if (!newValue.Equals(value))
                {
                    value = newValue;
                    OnPropertyChanged(name, onChanged);
                }
            }
            else
            {
                value = default(T);
            }
        }

        #endregion

        #region INotifyPropertyChanged

        /// <summary>
        /// The PropertyChanged event handler.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Calls the PropertyChanged event
        /// </summary>
        /// <param name="propertyName"></param>
        /// <param name="onChanged"></param>
        protected void OnPropertyChanged(string propertyName, Action onChanged = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                // Call handler
                handler(this, new PropertyChangedEventArgs(propertyName));

                // Subscriptions
                ProcessSubscriptions(propertyName);

                // On changed
                if (onChanged != null)
                {
                    onChanged();
                }
            }
        }

        /// <summary>
        /// Subscribes an action to a specific property that will be called
        /// during that property's OnPropertyChanged event.
        /// </summary>
        /// <param name="propertyName"></param>
        /// <param name="onChange"></param>
        public void Subscribe(string propertyName, Action<object> onChange)
        {
            // Verify property
            var propInfo = this.GetType().GetProperty(propertyName);

            // If valid, add to subscription pool.
            if (propInfo != null)
            {
                _subscriptions.Add(
                    new Tuple<string, Action<object>>(propertyName, onChange));
            }
            else
            {
                // Invalid property name provided.
                throw new Exception(
                    "Property "" + propertyName + "" could not be " +
                    "found for type "" + this.GetType().ToString() + ""!");
            }
        }
        
        /// <summary>
        /// Clears the subscriptions.
        /// </summary>
        public void ClearSubscriptions()
        {
            _subscriptions.Clear();
        }

        /// <summary>
        /// Processes existing subscriptions matching the provided property name.
        /// </summary>
        /// <param name="propertyName"></param>
        private void ProcessSubscriptions(string propertyName)
        {
            // Get matching subscriptions
            var subList =
                (from p in _subscriptions
                 where p.Item1 == propertyName
                 select p).ToList();

            // Check if any matches were found.
            if (subList.Any())
            {
                // Process actions
                foreach (var sub in subList)
                {
                    // Evaluate action
                    var onChange = sub.Item2;
                    if (onChange != null)
                    {
                        // Get property value by name
                        var propInfo = this.GetType().GetProperty(propertyName);
                        var propValue = propInfo.GetValue(this, null);

                        // Invoke action
                        onChange(propValue);
                    }
                }
            }
        }

        #endregion
    }

I hope this has been helpful for you.

In this article, we are going to look at calling an Action when our property changes.

Note: Please read Part I before continuing here.

Alright, so let’s look at our ViewModelBase from Part I:


    /// <summary>
    /// Extends the INotifyPropertyChanged interface to the class properties.
    /// </summary>
    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        #region Methods

        /// <summary>
        /// To be used within the "set" accessor in each property.
        /// This invokes the OnPropertyChanged method.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <param name="newValue"></param>
        protected void SetValue<T>(string name, ref T value, ref  T newValue)
        {
            if (newValue != null)
            {
                if (!newValue.Equals(value))
                {
                    value = newValue;
                    OnPropertyChanged(name);
                }
            }
            else
            {
                value = default(T);
            }
        }

        #endregion

        #region INotifyPropertyChanged

        /// <summary>
        /// The PropertyChanged event handler.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
        
        /// <summary>
        /// Calls the PropertyChanged event
        /// </summary>
        /// <param name="propertyName"></param>
        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }

Q: Okay, so what now?
A: Glad you asked. Let’s augment the above class:


    /// <summary>
    /// Extends the INotifyPropertyChanged interface to the class properties.
    /// </summary>
    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        #region Methods

        /// <summary>
        /// To be used within the "set" accessor in each property.
        /// This invokes the OnPropertyChanged method.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <param name="newValue"></param>
        protected void SetValue<T>(string name, ref T value, ref  T newValue, Action onChanged = null)
        {
            if (newValue != null)
            {
                if (!newValue.Equals(value))
                {
                    value = newValue;
                    OnPropertyChanged(name, onChanged);
                }
            }
            else
            {
                value = default(T);
            }
        }

        #endregion

        #region INotifyPropertyChanged

        /// <summary>
        /// The PropertyChanged event handler.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
        
        /// <summary>
        /// Calls the PropertyChanged event
        /// </summary>
        /// <param name="propertyName"></param>
        protected void OnPropertyChanged(string propertyName, Action onChanged = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));

                // On changed
                if (onChanged != null)
                {
                    onChanged();
                }
            }
        }

        #endregion
    }

Essentially, we added an Action as a parameter to the SetValue method. This is then passed whenever the OnPropertyChanged method is called and executed after the event fires (if it is not null).

Using it is as easy as this:


    private string phoneNumberValue = String.Empty;

    public string PhoneNumber
    {
        get
        {
            return this.phoneNumberValue;
        }

        set
        {
            SetValue("PhoneNumber", ref this.phoneNumberValue, ref value, () => { UpdateSomeOtherUISection(); });
        }
    }

It’s not a common need, but sometimes something like this can really get you out of a bind when you need to update other areas of your application when this property changes.

Try it sometime.

Having used properties in an ObservableCollection, you are probably familiar with implementing INotifyPropertyChanged in your custom objects.

A property utilizing this implementation would look like this:

private string phoneNumberValue = String.Empty;

public string PhoneNumber
{
	get
	{
		return this.phoneNumberValue;
	}

	set
	{
		if (value != this.phoneNumberValue)
		{
			this.phoneNumberValue = value;
			NotifyPropertyChanged("PhoneNumber");
		}
	}
}

To remedy this repetitive implementation, I created this:

/// <summary>
/// Extends the INotifyPropertyChanged interface to the class properties.
/// </summary>
public abstract class ViewModelBase : INotifyPropertyChanged
{
	#region Methods

	/// <summary>
	/// To be used within the "set" accessor in each property.
	/// This invokes the OnPropertyChanged method.
	/// </summary>
	/// <typeparam name="T"></param>
	/// <param name="name"></param>
	/// <param name="value"></param>
	/// <param name="newValue"></param>
	protected void SetValue&amp;amp;amp;amp;lt;T&amp;amp;amp;amp;gt;(string name, ref T value, ref  T newValue)
	{
		if (newValue != null)
		{
			if (!newValue.Equals(value))
			{
				value = newValue;
				OnPropertyChanged(name);
			}
		}
		else
		{
			value = default(T);
		}
	}

	#endregion

	#region INotifyPropertyChanged

	/// <summary>
	/// The PropertyChanged event handler.
	/// </summary>
	public event PropertyChangedEventHandler PropertyChanged;

	/// <summary>
	/// Calls the PropertyChanged event
	/// </summary>
	/// <param name="propertyName"></param>
	protected void OnPropertyChanged(string propertyName)
	{
		PropertyChangedEventHandler handler = PropertyChanged;
		if (handler != null)
		{
			handler(this, new PropertyChangedEventArgs(propertyName));
		}
	}

	#endregion
}

Then, all we need to do is inherit ViewModelBase in our custom object.

Now, our property would look something like this:

private string phoneNumberValue = String.Empty;

public string PhoneNumber
{
	get
	{
		return this.phoneNumberValue;
	}

	set
	{
		SetValue("PhoneNumber", ref this.phoneNumberValue, ref value);
	}
}

So, what was the reason for doing this?

  1. It avoids directly implementing INotifyPropertyChanged. This allows the ability to start up objects with this implementation much faster.
  2. Less coding and potential messes involving the set operator.

Update as of August 2022

Good news is since all the C# goodness over the last 10 years, I have greatly improved this implementation. It can be used like this:

private string _phoneNumberValue = String.Empty;

public string PhoneNumber
{
	get => _phoneNumberValue;
	set => NotifyOfChange(value, ref _phoneNumberValue);
}

Simple, right?