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));
}
}
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}";
}
Let's inspect the code generated by Fody.
How implement
- Add Fody package
- Add PropertyChanged.Fody package
- 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>
- 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}";
}
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>
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;
}
Source code
Clone the following GitHub repository written using Microsoft Visual Studio 2022 using NET8.