Language

変更通知を使用して優れたデータ ソースを設計する

Windows フォーム データ バインディングの最も重要な概念の 1 つは、変更通知です。 データ ソースとバインドされたコントロールに常に最新のデータが含まれるようにするには、データ バインディングの変更通知を追加する必要があります。 具体的には、バインドされたコントロールに、データ ソースに加えられた変更が通知されるようにする必要があります。 データ ソースには、コントロールのバインドされたプロパティに加えられた変更が通知されます。

データ バインディングの種類に応じて、さまざまな種類の変更通知があります。

  • 単一のコントロール プロパティがオブジェクトの 1 つのインスタンスにバインドされる単純なバインド。

  • リスト ベースのバインド。リスト内の項目のプロパティにバインドされた単一のコントロール プロパティ、またはオブジェクトのリストにバインドされたコントロール プロパティを含めることができます。

さらに、データ バインディングに使用する Windows フォーム コントロールを作成する場合は、PropertyName変更されたパターンをコントロールに適用する必要があります。 コントロールにパターンを適用すると、コントロールのバインドされたプロパティがデータ ソースに反映されます。

シンプルバインディングの変更通知

単純なバインドの場合、ビジネス オブジェクトは、バインドされたプロパティの値が変更されたときに変更通知を提供する必要があります。 ビジネス オブジェクトの各プロパティに対して PropertyNameChanged イベントを公開することで、変更通知を提供できます。 また、ビジネス オブジェクトを、BindingSource またはビジネス オブジェクトが INotifyPropertyChanged インターフェイスを実装し、プロパティの値が変更されたときに PropertyChanged イベントを発生させる優先メソッドを使用してコントロールにバインドする必要もあります。 INotifyPropertyChanged インターフェイスを実装するオブジェクトを使用する場合、BindingSource を使用してオブジェクトをコントロールにバインドする必要はありません。 ただし、BindingSource を使用することをお勧めします。

リスト ベースのバインドの変更通知

Windows フォームは、バインドされたリストに依存して、プロパティの変更 と リストの変更 情報をバインドされたコントロールに提供します。 プロパティの変更 はリスト アイテムのプロパティ値の変更であり、リスト 変更 は、リストに削除または追加されたアイテムです。 そのため、データ バインディングに使用されるリストは、両方の種類の変更通知を提供する IBindingListを実装する必要があります。 BindingList<T> は、IBindingList の一般的な実装であり、Windows フォームのデータ バインディングで使用するように設計されています。 BindingList を実装するビジネス オブジェクトの種類を含む INotifyPropertyChanged を作成すると、PropertyChanged イベントが ListChanged イベントに自動的に変換されます。 バインドされたリストが IBindingListでない場合は、BindingSource コンポーネントを使用して、オブジェクトのリストを Windows フォーム コントロールにバインドする必要があります。 BindingSource コンポーネントは、BindingListと同様のプロパティからリストへの変換を提供します。 詳細については、「方法: BindingSource と INotifyPropertyChanged インターフェイスを使用して変更通知を生成する」を参照してください。

カスタム コントロールの変更通知

最後に、コントロール側から、データにバインドするように設計された各プロパティの PropertyNameChanged イベントを公開する必要があります。 コントロール プロパティへの変更は、バインドされたデータ ソースに反映されます。 詳細については、「PropertyNameChanged パターンを適用する」を参照してください。

PropertyNameChanged パターンを適用する

次のコード例では、PropertyNameChanged パターンをカスタム コントロールに適用する方法を示します。 Windows フォーム データ バインディング エンジンで使用されるカスタム コントロールを実装するときにパターンを適用します。

// 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);
    }
}
' This class implements a simple user control 
' that demonstrates how to apply the propertyNameChanged pattern.
<ComplexBindingProperties("DataSource", "DataMember")>
Public Class CustomerControl
    Inherits UserControl
    Private dataGridView1 As DataGridView
    Private label1 As Label
    Private lastUpdate As DateTime = DateTime.Now

    Public DataSourceChanged As EventHandler

    <DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)>
    Public Property DataSource() As Object
        Get
            Return Me.dataGridView1.DataSource
        End Get
        Set
            If DataSource IsNot Value Then
                Me.dataGridView1.DataSource = Value
                OnDataSourceChanged()
            End If
        End Set
    End Property

    <DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)>
    Public Property DataMember() As String
        Get
            Return Me.dataGridView1.DataMember
        End Get
        Set
            Me.dataGridView1.DataMember = Value
        End Set
    End Property

    Private Sub OnDataSourceChanged()
        If (DataSourceChanged IsNot Nothing) Then
            DataSourceChanged(Me, New EventArgs())
        End If

    End Sub

    Public Sub New()
        Me.dataGridView1 = New System.Windows.Forms.DataGridView()
        Me.label1 = New System.Windows.Forms.Label()
        Me.dataGridView1.ColumnHeadersHeightSizeMode =
           System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize
        Me.dataGridView1.ImeMode = System.Windows.Forms.ImeMode.Disable
        Me.dataGridView1.Location = New System.Drawing.Point(19, 55)
        Me.dataGridView1.Size = New System.Drawing.Size(550, 150)
        Me.dataGridView1.TabIndex = 1
        Me.label1.AutoSize = True
        Me.label1.Location = New System.Drawing.Point(19, 23)
        Me.label1.Name = "label1"
        Me.label1.Size = New System.Drawing.Size(76, 13)
        Me.label1.TabIndex = 2
        Me.label1.Text = "Customer List:"
        Me.Controls.Add(Me.label1)
        Me.Controls.Add(Me.dataGridView1)
        Me.Size = New System.Drawing.Size(650, 300)
    End Sub
End Class

INotifyPropertyChanged インターフェイスを実装する

次のコード例は、INotifyPropertyChanged インターフェイスを実装する方法を示しています。 Windows フォームのデータ バインディングで使用されるビジネス オブジェクトにインターフェイスを実装します。 実装されると、インターフェイスは、ビジネス オブジェクトのプロパティの変更をバインドされたコントロールと通信します。

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();
                }
            }
        }
    }
}
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Drawing
Imports System.Runtime.CompilerServices
Imports System.Windows.Forms

' 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 Class Form3
    Inherits System.Windows.Forms.Form
    ' This button causes the value of a list element to be changed.
    Private changeItemBtn As New Button()

    ' This DataGridView control displays the contents of the list.
    Private customersDataGridView As New DataGridView()

    ' This BindingSource binds the list to the DataGridView control.
    Private customersBindingSource As New BindingSource()

    Public Sub New()
        InitializeComponent()
        ' Set up the "Change Item" button.
        Me.changeItemBtn.Text = "Change Item"
        Me.changeItemBtn.Dock = DockStyle.Bottom
        Me.changeItemBtn.Size = New System.Drawing.Size(100, 100)
        AddHandler Me.changeItemBtn.Click, AddressOf changeItemBtn_Click
        Me.Controls.Add(Me.changeItemBtn)

        ' Set up the DataGridView.
        customersDataGridView.Dock = DockStyle.Top
        Me.Controls.Add(customersDataGridView)
        Me.Size = New Size(400, 200)
    End Sub

    Private Sub Form3_Load(ByVal sender As System.Object,
        ByVal e As System.EventArgs) Handles Me.Load
        Me.Top = 100
        Me.Left = 100
        Me.Height = 600
        Me.Width = 1000
        ' Create and populate the list of DemoCustomer1 objects
        ' which will supply data to the DataGridView.
        Dim customerList As New BindingList(Of DemoCustomer1)
        customerList.Add(DemoCustomer1.CreateNewCustomer())
        customerList.Add(DemoCustomer1.CreateNewCustomer())
        customerList.Add(DemoCustomer1.CreateNewCustomer())

        ' Bind the list to the BindingSource.
        Me.customersBindingSource.DataSource = customerList

        ' Attach the BindingSource to the DataGridView.
        Me.customersDataGridView.DataSource = Me.customersBindingSource
    End Sub

    ' This event handler changes the value of the CompanyName
    ' property for the first item in the list.
    Private Sub changeItemBtn_Click(ByVal sender As Object, ByVal e As EventArgs)
        ' Get a reference to the list from the BindingSource.
        Dim customerList As BindingList(Of DemoCustomer1) =
            CType(customersBindingSource.DataSource, BindingList(Of 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"
    End Sub
End Class

' This class implements a simple customer type 
' that implements the IPropertyChange interface.
Public Class DemoCustomer1
    Implements INotifyPropertyChanged

    ' These fields hold the values for the public properties.
    Private idValue As Guid = Guid.NewGuid()
    Private customerNameValue As String = String.Empty
    Private phoneNumberValue As String = String.Empty

    Public Event PropertyChanged As PropertyChangedEventHandler _
        Implements INotifyPropertyChanged.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 Sub NotifyPropertyChanged(<CallerMemberName()> Optional ByVal propertyName As String = Nothing)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub

    ' The constructor is private to enforce the factory pattern.
    Private Sub New()
        customerNameValue = "Customer"
        phoneNumberValue = "(312)555-0100"
    End Sub

    ' This is the public factory method.
    Public Shared Function CreateNewCustomer() As DemoCustomer1
        Return New DemoCustomer1()
    End Function

    ' This property represents an ID, suitable
    ' for use as a primary key in a database.
    Public ReadOnly Property ID() As Guid
        Get
            Return Me.idValue
        End Get
    End Property

    Public Property CustomerName() As String
        Get
            Return Me.customerNameValue
        End Get

        Set(ByVal value As String)
            If Not (value = customerNameValue) Then
                Me.customerNameValue = value
                NotifyPropertyChanged()
            End If
        End Set
    End Property

    Public Property PhoneNumber() As String
        Get
            Return Me.phoneNumberValue
        End Get

        Set(ByVal value As String)
            If Not (value = phoneNumberValue) Then
                Me.phoneNumberValue = value
                NotifyPropertyChanged()
            End If
        End Set
    End Property
End Class

バインドを同期する

Windows フォームでのデータ バインディングの実装時に、複数のコントロールが同じデータ ソースにバインドされます。 場合によっては、コントロールのバインドされたプロパティが互いおよびデータ ソースと確実に同期されるように、追加の手順を実行する必要がある場合があります。 これらの手順は、次の 2 つの状況で必要です。

前者の場合は、BindingSource を使用してデータ ソースをコントロールにバインドできます。 後者の場合は、BindingSource を使用して BindingComplete イベントを処理し、関連付けられている EndCurrentEditに対して BindingManagerBase を呼び出します。

この概念の実装の詳細については、BindingComplete API リファレンス ページを参照してください。

こちらも参照ください