Friday, August 9, 2024

FODY

The Problem

I spent some time looking at ways to reduce all the repetitive property declarations required for MVVM. So have a lot of other people, most of whom are far smarter than me.

The Community Toolkit looked very promising.


But even though it claims to work in VB and .Net Framework it does not. No errors, no warnings, no binding errors, modifying properties in controls does not update the bound properties. So that's a shame.

The good news

One of my colleagues recommend FODY - a GitHub project that claims to work for VB in .Net Framework. OK - I'm game. I put together the most trivial project I could think of. We have our ViewModels in our code-behind, we use VB, and we're still on .Net Framework. Yes, we're practically Neanderthal.


FODY is much bigger than simply providing MVVM functionality. It's a framework for all sorts of things. The specific package I need is called PropertyChanged.Fody. Let's get started then.

In Visual Studio 2022 create a WPF, VB, .NetFramework project called FODY


In the menu select Tools -> NuGet Package Manager -> Manage NuGet Packages for Solution...

Click the Browse tab and search for FODY


Install Fody and PropertyChanged.Fody into your solution. Your packages.config will look something like this.

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Fody" version="6.8.1" targetFramework="net472" developmentDependency="true" />
  <package id="PropertyChanged.Fody" version="4.1.0" targetFramework="net472" />
</packages>


Now we will write some standard XAML with a text box and a text block. Whatever you type in the text box will appear in the text block thanks to some MVVM bindings. Fody does not require any changes to your XAML.

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:FODY"
        mc:Ignorable="d"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="FODY Test" Height="450" Width="800">
    <StackPanel Orientation="Vertical" HorizontalAlignment="Left">
        <TextBox Text="{Binding theText, UpdateSourceTrigger=PropertyChanged}" Width="100"/>
        <TextBlock Text="{Binding theText}"/>
    </StackPanel>
</Window>

Without clever add-ins the view model would look something like this. Which requires a lot of typing.

Imports System.ComponentModel
Imports System.Runtime.CompilerServices

Class MainWindow
    Implements INotifyPropertyChanged

    Private _theText As String = ""
    Public Property theText As String
        Get
            Return _theText
        End Get
        Set(value As String)
            SetProperty(_theText, value)
        End Set
    End Property

    Public Event PropertyChanged As System.ComponentModel.PropertyChangedEventHandler Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

    Public Function SetProperty(Of T)(ByRef storage As T, value As T, <System.Runtime.CompilerServices.CallerMemberName> Optional PropertyName As String = Nothing) As Boolean
        If Object.Equals(storage, value) Then Return False
        storage = value
        NotifyPropertyChanged(PropertyName)
        Return True
    End Function

    Public Sub NotifyPropertyChanged(<System.Runtime.CompilerServices.CallerMemberName> Optional PropertyName As String = Nothing)
        RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs(PropertyName))
    End Sub
End Class

The end result predictably looks like this

But with Fody we can replace these 30 lines of code with something far more succinct.

Imports PropertyChanged
<AddINotifyPropertyChangedInterface>
Class MainWindow
    Public Property theText As String = ""
End Class

Which gives us exactly the same result.

So what's the bad news?

A common technique for investigating binding issues is to put break points on property getters and setters to make sure they are being called when expected. Setter not called when the user modifies a control - check UpdateSourceTrigger. Getter not called after the setter is called - control is not bound to the property. When we don't have explicit setters and getters, we can't use these techniques.

So maybe all that repetitive code has value after all.