Edit

Share via


Design great data sources with change notification

One of the most important concepts of Windows Forms data binding is the change notification. To ensure that your data source and bound controls always have the most recent data, you must add change notification for data binding. Specifically, you want to ensure that bound controls are notified of changes that were made to their data source. The data source is notified of changes that were made to the bound properties of a control.

There are different kinds of change notifications, depending on the kind of data binding:

  • Simple binding, in which a single control property is bound to a single instance of an object.

  • List-based binding, which can include a single control property bound to the property of an item in a list or a control property bound to a list of objects.

Additionally, if you're creating Windows Forms controls that you want to use for data binding, you must apply the PropertyNameChanged pattern to the controls. Applying pattern to the controls results in changes to the bound property of a control are propagated to the data source.

Change notification for simple binding

For simple binding, business objects must provide change notification when the value of a bound property changes. You can provide change notification by exposing a PropertyNameChanged event for each property of your business object. It also requires binding the business object to controls with the BindingSource or the preferred method in which your business object implements the INotifyPropertyChanged interface and raises a PropertyChanged event when the value of a property changes. When you use objects that implement the INotifyPropertyChanged interface, you don't have to use the BindingSource to bind the object to a control. But using the BindingSource is recommended.

Change notification for list-based binding

Windows Forms depends on a bound list to provide property change and list change information to bound controls. The property change is a list item property value change and list change is an item deleted or added to the list. Therefore, lists used for data binding must implement the IBindingList, which provides both types of change notification. The BindingList<T> is a generic implementation of IBindingList and is designed for use with Windows Forms data binding. You can create a BindingList that contains a business object type that implements INotifyPropertyChanged and the list will automatically convert the PropertyChanged events to ListChanged events. If the bound list isn't an IBindingList, you must bind the list of objects to Windows Forms controls by using the BindingSource component. The BindingSource component will provide property-to-list conversion similar to that of the BindingList. For more information, see How to: Raise Change Notifications Using a BindingSource and the INotifyPropertyChanged Interface

Change notification for custom controls

Finally, from the control side you must expose a PropertyNameChanged event for each property designed to be bound to data. The changes to the control property are then propagated to the bound data source. For more information, see Apply the PropertyNameChanged pattern.

Apply the PropertyNameChanged pattern

The following code example demonstrates how to apply the PropertyNameChanged pattern to a custom control. Apply the pattern when you implement custom controls that are used with the Windows Forms data binding engine.

// This class implements a simple user control
// that demonstrates how to apply the propertyNameChanged pattern.
[ComplexBindingProperties("DataSource", "DataMember")]
public class CustomerControl : UserControl
{
    private DataGridView dataGridView1;
    private Label label1;
    private DateTime lastUpdate = DateTime.Now;

    public EventHandler DataSourceChanged;

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public object DataSource
    {
        get
        {
            return this.dataGridView1.DataSource;
        }
        set
        {
            if (DataSource != value)
            {
                this.dataGridView1.DataSource = value;
                OnDataSourceChanged();
            }
        }
    }

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public string DataMember
    {
        get { return this.dataGridView1.DataMember; }

        set { this.dataGridView1.DataMember = value; }
    }

    private void OnDataSourceChanged()
    {
        if (DataSourceChanged != null)
        {
            DataSourceChanged(this, new EventArgs());
        }
    }

    public CustomerControl()
    {
        this.dataGridView1 = new System.Windows.Forms.DataGridView();
        this.label1 = new System.Windows.Forms.Label();
        this.dataGridView1.ColumnHeadersHeightSizeMode =
           System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
        this.dataGridView1.ImeMode = System.Windows.Forms.ImeMode.Disable;
        this.dataGridView1.Location = new System.Drawing.Point(100, 100);
        this.dataGridView1.Size = new System.Drawing.Size(500,500);
                    
        this.dataGridView1.TabIndex = 1;
        this.label1.AutoSize = true;
        this.label1.Location = new System.Drawing.Point(50, 50);
        this.label1.Name = "label1";
        this.label1.Size = new System.Drawing.Size(76, 13);
        this.label1.TabIndex = 2;
        this.label1.Text = "Customer List:";
        this.Controls.Add(this.label1);
        this.Controls.Add(this.dataGridView1);
        this.Size = new System.Drawing.Size(450, 250);
    }
}

Implement the INotifyPropertyChanged interface

The following code example demonstrates how to implement the INotifyPropertyChanged interface. Implement the interface on business objects that are used in Windows Forms data binding. When implemented, the interface communicates to a bound control the property changes on a business object.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.CompilerServices;
using System.Windows.Forms;

// Change the namespace to the project name.
namespace binding_control_example
{
    // This form demonstrates using a BindingSource to bind
    // a list to a DataGridView control. The list does not
    // raise change notifications. However the DemoCustomer1 type
    // in the list does.
    public partial class Form3 : Form
    {
        // This button causes the value of a list element to be changed.
        private Button changeItemBtn = new Button();

        // This DataGridView control displays the contents of the list.
        private DataGridView customersDataGridView = new DataGridView();

        // This BindingSource binds the list to the DataGridView control.
        private BindingSource customersBindingSource = new BindingSource();

        public Form3()
        {
            InitializeComponent();

            // Set up the "Change Item" button.
            this.changeItemBtn.Text = "Change Item";
            this.changeItemBtn.Dock = DockStyle.Bottom;
            this.changeItemBtn.Height = 100;
            //this.changeItemBtn.Click +=
              //  new EventHandler(changeItemBtn_Click);
            this.Controls.Add(this.changeItemBtn);

            // Set up the DataGridView.
            customersDataGridView.Dock = DockStyle.Top;
            this.Controls.Add(customersDataGridView);

            this.Size = new Size(400, 200);
        }

        private void Form3_Load(object sender, EventArgs e)
        {
            this.Top = 100;
            this.Left = 100;
            this.Height = 600;
            this.Width = 1000;

            // Create and populate the list of DemoCustomer objects
            // which will supply data to the DataGridView.
            BindingList<DemoCustomer1> customerList = new ();
            customerList.Add(DemoCustomer1.CreateNewCustomer());
            customerList.Add(DemoCustomer1.CreateNewCustomer());
            customerList.Add(DemoCustomer1.CreateNewCustomer());

            // Bind the list to the BindingSource.
            this.customersBindingSource.DataSource = customerList;

            // Attach the BindingSource to the DataGridView.
            this.customersDataGridView.DataSource =
                this.customersBindingSource;
        }

        // Change the value of the CompanyName property for the first
        // item in the list when the "Change Item" button is clicked.
        void changeItemBtn_Click(object sender, EventArgs e)
        {
            // Get a reference to the list from the BindingSource.
            BindingList<DemoCustomer1>? customerList =
                this.customersBindingSource.DataSource as BindingList<DemoCustomer1>;

            // Change the value of the CompanyName property for the
            // first item in the list.
            customerList[0].CustomerName = "Tailspin Toys";
            customerList[0].PhoneNumber = "(708)555-0150";
        }
                
    }

    // This is a simple customer class that
    // implements the IPropertyChange interface.
    public class DemoCustomer1 : INotifyPropertyChanged
    {
        // These fields hold the values for the public properties.
        private Guid idValue = Guid.NewGuid();
        private string customerNameValue = String.Empty;
        private string phoneNumberValue = String.Empty;

        public event PropertyChangedEventHandler PropertyChanged;

        // This method is called by the Set accessor of each property.
        // The CallerMemberName attribute that is applied to the optional propertyName
        // parameter causes the property name of the caller to be substituted as an argument.
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        // The constructor is private to enforce the factory pattern.
        private DemoCustomer1()
        {
            customerNameValue = "Customer";
            phoneNumberValue = "(312)555-0100";
        }

        // This is the public factory method.
        public static DemoCustomer1 CreateNewCustomer()
        {
            return new DemoCustomer1();
        }

        // This property represents an ID, suitable
        // for use as a primary key in a database.
        public Guid ID
        {
            get
            {
                return this.idValue;
            }
        }

        public string CustomerName
        {
            get
            {
                return this.customerNameValue;
            }

            set
            {
                if (value != this.customerNameValue)
                {
                    this.customerNameValue = value;
                    NotifyPropertyChanged();
                }
            }
        }

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

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

Synchronize bindings

During implementation of data binding in Windows Forms, multiple controls are bound to the same data source. In some cases, it may be necessary to take extra steps to ensure that the bound properties of the controls remain synchronized with each other and the data source. These steps are necessary in two situations:

In the former case, you can use a BindingSource to bind the data source to the controls. In the latter case, you use a BindingSource and handle the BindingComplete event and call EndCurrentEdit on the associated BindingManagerBase.

For more information on implementing this concept, see the BindingComplete API reference page.

See also