Window Forms change notification

Karen Payne - Dec 24 '23 - - Dev Community

When a developer works with a DataGridView, ComboBox or ListBox in a Windows Forms project were the underlying data is expected to change the common method to provide notifications from data to a control is to use INotifyPropertyChanged which works perfectly but requires a lot of code to achieve.

Note
How does the average developer handle updating a DataGridView, ComboBox or ListBox? Many will have unbound controls and need to touch the control while this is bad practice it can work but tends to be awkward and in some cases will trigger control events that can cause unexpected issues which in short makes more work.

Lets look at not touch control, setup classes/models for change notification with bound controls. Not the reading type, check out the source code than come back and read the rest of this article

Example were each property of this person class requires additional code.

public class Person : INotifyPropertyChanged
{
    private string _firstName;
    private string _lastName;
    private DateOnly _birthDate;

    public Person()
    {

    }

    public Person(int id)
    {
        Id = id;
    }
    public int Id { get; set; }

    public string FirstName
    {
        get => _firstName;
        set
        {
            if (value == _firstName) return;
            _firstName = value;
            OnPropertyChanged();
        }
    }

    public string LastName
    {
        get => _lastName;
        set
        {
            if (value == _lastName) return;
            _lastName = value;
            OnPropertyChanged();
        }
    }

    public DateOnly BirthDate
    {
        get => _birthDate;
        set
        {
            if (value.Equals(_birthDate)) return;
            _birthDate = value;
            OnPropertyChanged();
        }
    }

    public override string ToString() => $"{FirstName} {LastName}";

    public event PropertyChangedEventHandler? PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
Enter fullscreen mode Exit fullscreen mode

Alternate

Using PropertyChanged.Fody NuGet package.

Sample person class as above with the same functionality using Fody.

[AddINotifyPropertyChangedInterface]
public class Person
{
    public Person() { }

    public Person(int id)
    {
        Id = id;
    }
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateOnly BirthDate { get; set; }
    public override string ToString() => $"{FirstName} {LastName}";

}
Enter fullscreen mode Exit fullscreen mode

Let's inspect the code generated by Fody.

Fody creation for Person model

How implement

  1. Add Fody package
  2. Add PropertyChanged.Fody package
  3. Add FodyWeavers.xml file at the root of the project.
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
    <PropertyChanged />
</Weavers>
Enter fullscreen mode Exit fullscreen mode
  1. Add [AddINotifyPropertyChangedInterface] attribute to the class for change notification.
[AddINotifyPropertyChangedInterface]
public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateOnly BirthDate { get; set; }
    public override string ToString() => $"{FirstName} {LastName}";
}
Enter fullscreen mode Exit fullscreen mode

Example project

One Windows Forms project is used to demonstrate using INotifyPropertyChanged and Fody change notification. To achieve this there are two Person models in separate folders aliased in the project file as shown below.

<ItemGroup>
   <Using Include="FodyWinFormsApp.ForFodyModels" Alias="M1" />
   <Using Include="FodyWinFormsApp.NotifyModels" Alias="M2" />
</ItemGroup>
Enter fullscreen mode Exit fullscreen mode

By using aliases both models can easily be referenced in the same form.

Each time the project runs, the list of people changes as data is generated with Bogus NuGet package.

For each dataset, a special BindingList (included in the project) is utilized as without it a conventual BindingList does not have sorting functionality. Each BindingList is associated with a BindingSource which has useful functionality not used other than to index into the BindingList to get at the current person in the DataGridView.

The two button click events update the current row in a DataGridView and also a ComboBox, note M1 and M2 which as mentioned above are aliases to each of the Person models residing in two separate folders. Click on one of the buttons and the current row is updated. Without change notification a developer tends to reload the controls which if reading this knows a better way now.

private void FodyChangeCurrentBButton_Click(object sender, EventArgs e)
{

    M1.Person person = FodyBindingList[FodyBindingSource.Position];
    M1.Person newPerson = BogusOperations.FodyPeople(1).FirstOrDefault()!;

    person.FirstName = newPerson.FirstName;
    person.LastName = newPerson.LastName;
    person.BirthDate = newPerson.BirthDate;

}

private void ConventionalChangeCurrentBButton_Click(object sender, EventArgs e)
{
    M2.Person person = ConventionalBindingList[ConventionalBindingSource.Position];
    M2.Person newPerson = BogusOperations.People(1).FirstOrDefault()!;

    person.FirstName = newPerson.FirstName;
    person.LastName = newPerson.LastName;
    person.BirthDate = newPerson.BirthDate;
}
Enter fullscreen mode Exit fullscreen mode

Source code

Clone the following GitHub repository written using Microsoft Visual Studio 2022 using NET8.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .