WinUI 3.0 and C# Examples

Taming Your Code with MVVM

Have you ever tried to find your favorite pair of socks in a messy room? It's quite a challenge, isn't it? Well, coding without using a proper design pattern is a lot like that.

In the grand universe of programming, we use design patterns like MVVM to keep our coding socks organized, clean, and well, easier to find! Just like you wouldn't mix your freshly baked cookies with yesterday's sushi in your lunchbox, you don't want your data handling mixed up with your UI updates. That's where MVVM (Model-View-ViewModel) comes into play.

What is MVVM?

MVVM stands for Model-View-ViewModel. This design pattern separates our code into three distinct parts, like a perfect Neapolitan ice cream.

View (user interface)
(commands and updates)
ViewModel
(process data)
Model (business logic)

The Model is like the chocolate layer, where your data lives. This could be your application's data objects or services interacting with a database.

The View, the strawberry layer, is what the user sees and interacts with. This includes buttons, menus, dialog boxes - all the shiny things!

Lastly, the ViewModel, the vanilla layer, acts as a liaison between the Model and the View. It's a bit like an interpreter in a negotiation. The View says "I want data," the ViewModel fetches the data from the Model and delivers it back to the View. It's all very civil and organized.

Before MVVM: The Spaghetti Incident

Before the adoption of design patterns like MVVM, our code was like a bowl of spaghetti. Everything was mixed together, and finding a specific function was like trying to find that elusive last meatball. Debugging was a nightmare. The UI code was entangled with business logic and data access code, creating a tangled mess. Every time we needed to update or refactor, we risked breaking something else. Just like when you pull on one noodle, you could end up dragging half the plate with it. Not fun!

Also, compared to something like MVC (Model–view–controller), MVVM offers better separation of concerns and improved testability. With MVVM, the view model acts as a mediator between the view and the model, allowing for easier unit testing of the business logic. It also promotes a more modular and maintainable codebase by decoupling the UI from the underlying data and behavior.

Using MVVM: The Magic Wand

Let's walk through a simple example of implementing MVVM.

Imagine you are building a book store app. You have a list of books (Model) that you want to display (View) to your users. Also, you want to implement a feature that allows users to filter books by genre.

1. Model:

To retrieve a list of books from a database, you can use a data access layer or an ORM (Object-Relational Mapping) framework to handle the communication with the database. Here's a quick example of how the model could be implemented to fetch books from a database:

C#
using System.Data.SQLite;

public class Book
{
	public string Title { get; set; }
	public string Genre { get; set; }
	// Other properties like Author, ISBN etc.
}

public class BookModel
{
	private readonly SQLiteConnection _sQLiteConnection;

	public BookModel(string databasePath)
	{
		_sQLiteConnection = new SQLiteConnection($"Data Source={databasePath}");
		_sQLiteConnection.Open();
	}

	public List<Book> GetBooks()
	{
		// Assuming a SQLite connection is established
		string query = "SELECT Title, Genre FROM books";

		using (var command = new SQLiteCommand(query, _dbConnection))
		{
			using (var reader = command.ExecuteReader())
			{
				while (reader.Read())
				{
					string title = reader.GetString(0);
					string genre = reader.GetString(1);

					Book book = new Book
					{
						Title = title,
						Genre = genre
					};

					books.Add(book);
				}
			}
		}

		return books;
	}
}

In this example, the BookModel class takes an SQLiteConnection object as a dependency in its constructor. This allows the model to be easily testable and decoupled from the specific database implementation. The GetBooks method executes a SQL query to fetch the books from the database and constructs a list of Book objects.

2. ViewModel:

The BookViewModel class is responsible for managing the collection of books and handling the logic related to genre selection. It implements the INotifyPropertyChanged interface to support data binding and property change notifications.

The class has the following properties:

  • Books: An ObservableCollection of Book objects. This collection holds the books to be displayed in the UI. When the collection is modified, the PropertyChanged event is raised for the Books property.
  • SelectedGenre: A string representing the currently selected genre. When the value of this property changes, the PropertyChanged event is raised for the SelectedGenre property, and the LoadBooks method is called to update the book list accordingly.

The class also includes a LoadBooks method that retrieves the books based on the selected genre and updates the Books collection. If no genre is selected, all books are loaded.

C#
public class BookViewModel : INotifyPropertyChanged
{
	// Make sure to have one for your other properties, like Genre
	private ObservableCollection<Book> _books;
	public ObservableCollection<Book> Books
	{
		get { return _books; }
		set
		{
			_books = value;
			OnPropertyChanged(nameof(Books));
		}
	}

	private string _selectedGenre;
	public string SelectedGenre
	{
		get { return _selectedGenre; }
		set
		{
			_selectedGenre = value;
			OnPropertyChanged(nameof(SelectedGenre));
			// Update the book list when the genre changes
			LoadBooks(); 
		}
	}

	private readonly BookModel _bookModel;
	public BookViewModel()
	{
		// Create an instance of the BookModel
		string path = "path/to/your/database.db";
		_bookModel = new BookModel(path);
		_books = new ObservableCollection();
		// Load the books
		LoadBooks();
	}

	// Code for the PropertyChanged event and the OnPropertyChanged method
	public event PropertyChangedEventHandler PropertyChanged;

	protected virtual void OnPropertyChanged(string propertyName)
	{
		PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
	}

	public void LoadBooks()
	{
		// Code to fetch books from a database or an API goes here
		// Apply the genre filter if it is selected
		if (!string.IsNullOrEmpty(SelectedGenre))
		{
			// Filter the books based on the selected genre
			var filteredBooks = GetBooks().Where(book => book.Genre == SelectedGenre).ToList();
			Books = new ObservableCollection<Book>(filteredBooks);
		}
		else
		{
			// No genre selected, load all books
			Books = new ObservableCollection<Book>(GetBooks());
		}
	}

	private List<Book> GetBooks()
	{
		// Call the GetBooks method of the BookModel
		List<Book> books = _bookModel.GetBooks(); 
		return books;
	}
}

The above code demonstrates the structure and functionality of the BookViewModel class.

3. View:

Let's take a look at creating a ComboBox and ListView in XAML:

XAML
<StackPanel>
	<ComboBox ItemsSource="{x:Bind _viewModel.AvailableGenres, Mode=OneWay}"
			  SelectedItem="{x:Bind _viewModel.SelectedGenre, Mode=TwoWay}"
			  DisplayMemberPath="GenreName"/>

	<ListView ItemsSource="{x:Bind _viewModel.Books, Mode=OneWay}">
		<ListView.ItemTemplate>
			<DataTemplate x:DataType="local:Book">
				<TextBlock Text="{x:Bind Title}"/>
			</DataTemplate>
		</ListView.ItemTemplate>
	</ListView>
</StackPanel>

In this example, we have a StackPanel that contains a ComboBox and a ListView. The ComboBox is bound to a collection of available genres and has its selected item bound to a property called SelectedGenre. The ListView is bound to a collection of books and the DataTemplate makes sure the Title of the book is visible in the ListView (assuming that Book class is using the same namespace). One thing to be aware of: {x:Bind} defaults to OneTime binding, so always set the Mode to OneWay or TwoWay for the dynamic properties.

Then, in the C# code, you set the BookViewModel instance so that the Controls can get it when required.

C#
public sealed partial class MainWindow : Window
{
	public BookViewModel _viewModel;
	public MainWindow()
	{
		this.InitializeComponent();
		_viewModel = new BookViewModel();
	}
}

This way, you've separated your concerns. Your data (Model), the way your data is represented and manipulated (ViewModel), and how your data is displayed (View) are all nicely isolated.

Common Missteps on the MVVM Path

Now, as powerful as MVVM is, it's also easy to trip over when not used carefully. So, here are some common 'bananas on the pavement' to avoid:

  • God-like ViewModels: Just like that friend who volunteers for everything and ends up overwhelmed, a ViewModel that takes on too much responsibility can lead to bloated, hard-to-maintain code. Remember, the ViewModel should primarily act as an intermediary, not a do-everything hero. Delegate tasks appropriately.
  • Bypassing the ViewModel: Sometimes, programmers might be tempted to let the View interact directly with the Model. It's like letting your kids have a free pass to the cookie jar - sure it's easier, but you're going to end up with a mess. Always use the ViewModel as the bridge between the View and the Model.
  • Ignoring INotifyPropertyChanged: This is the secret sauce that lets your View automatically update when your data changes. Not using this is like ignoring the 'wet paint' sign and sitting on a freshly painted bench. You don't want to manually update your view every time your data changes, trust me.

So there you have it! Like that drawer organizer you never thought you needed, but once you have it, you wonder how you lived without it. MVVM keeps your code clean, manageable, and debuggable. And remember, a happy codebase makes a happy coder!