DOBON.NET DOBON.NETプログラミング掲示板過去ログ

BindingListの要素内の値が変更されたらイベントを発生させたい

環境/言語:[Windows7 Pro SP1 x64, VB.NET(2012 Express)]
分類:[.NET]

お世話になります。
表題の件、
BindingListをDataGeidViewのDataSourceに指定し、一覧を表示させています。
データ検索時にBindingListの要素(listItem.deposit または listItem.commission)
の値が変更されたらイベントを発生させ、listItem.differenceの値を計算して設定
させたいのです。
listItem.commission をDataGridView上で入力させた際にも同様な処理を行いたい場合、
DataGridView_CellValueChanged で処理できますが、その場合だとsetData()時に処理ができません。
どちらにも対応させる方法はないでしょうか。
お知恵をお貸しください。
宜しくお願いします。

--以下ソース--
Public class frm
  Private dataList As BindingList(Of listItem)

  Public sub frm_Load(sender as Object, e as EventArgs)
    dataList = New BindingList(Of listItem)
    dgvList.AutoGenerateColumns = False
    dgvList.DataSource = dataList
    setColumns()
    
    setData()
  End Sub
  
  Private Sub setColumns()
    setTextColumn("code", "コード")
    setTextColumn("name", "名前")
    setTextColumn("bill", "請求額")
    setTextColumn("deposit", "入金額")
    setTextColumn("commission", "手数料")
    setTextColumn("difference", "回収残")
  End Sub
  
  Private Sub setTextColumn(name As String, head as String)
    Dim col As New DataGridViewTextBoxColumn
    col.Name = name
    col.DataPropertyName = name
    col.HeaderText = head
    dgvList.Columns.Add(col)
  End Sub
  
  Private Sub setData()
    Dim common As New clsCommon
    Dim sql As String = "データ検索用SQL"
    Dim db As New clsDB
    'SQLを実行し、結果をDataTableに格納
    Dim dt As DataTable = db.getData(sql)
    For Each rec As DataRow In dt.Rows
      Dim item As New listItem
      With item
        .code = common.getString(rec("code"))
        .name = common.getString(rec("name"))
        .bill = common.getInt(rec("bill"))
        .deposit = common.getInt(rec("deposit"))
        .commission = common.getInt(rec("commission"))
        
        'difference = bill - (deposit + commission)
        'この処理をdeposit もしくは commission に値が入ったことでイベントを発生させ計算を行いたい
      End With
      dataList.Add(item)
    Next
  End Sub
End Class

Public Class listItem
  'コード
  Public Property code As String = ""
  '名称
  Public Property name As String = ""
  '請求額
  Public Property bill As Integer = 0
  '入金額
  Public Property deposit As Integer = 0
  '手数料
  Public Property commission As Integer = 0
  '回収残
  Public Property difference As Integer = 0
End Class
■No31589に返信(やむさんの記事)
> listItem.differenceの値を計算して設定させたいのです。

であれば、
  '回収残
  Public Property difference As Integer = 0
とするのではなく
  '回収残
  Public ReadOnly Property difference As Integer
    Get
      Return bill - (deposit + commission)
    End Get
  End Property
としておいた方が手っ取り早いかと。


> どちらにも対応させる方法はないでしょうか。

そもそも、DataTable/DataSet のまま管理しては駄目なのでしょうか?

DataTable そのものを DataGridView にバインドしていれば、
元の DataTable を編集することで、DataGridView の表示は変わりますし、
difference 列の件についても、「式列」を用意するだけで済むはずです。

Public Class Form1

    Private WithEvents DataGridView1 As DataGridView
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        DataGridView1 = New DataGridView()
        DataGridView1.Dock = DockStyle.Fill
        Me.Controls.Add(DataGridView1)

        DataGridView1.DataSource = CreateSampleTable()
    End Sub

    Private Function CreateSampleTable() As DataTable
        Dim tbl As New DataTable()
        '実際はデータベースから取得
        tbl.Columns.Add("code")
        tbl.Columns.Add("name")
        tbl.Columns.Add("bill", GetType(Decimal))
        tbl.Columns.Add("deposit", GetType(Decimal))
        tbl.Columns.Add("commission", GetType(Decimal))
        tbl.PrimaryKey = {tbl.Columns("code")}


        'サンプルデータ
        Dim r As New Random()
        For n As Integer = 10 To 50
            tbl.Rows.Add(CStr(n * 10 + 1), StrDup(n \ 2, "*"), r.Next(6, 10), r.Next(3, 5), r.Next(0, 3))
        Next

        '本題の「difference」列を作成
        tbl.Columns.Add("difference", GetType(Decimal), "bill - (deposit + commission)")

        Return tbl
    End Function
End Class


もしも BindingList で管理した方が都合が良いのあれば、
「変更通知」の機能を実装する必要があります。
http://msdn.microsoft.com/ja-jp/library/xz45s2bh.aspx

具体的には、listItem クラスに INotifyPropertyChanged を
Implements した上で、それぞれのプロパティを自動実装プロパティから
カスタム実装にして、値が変更されたときに INotifyPropertyChanged の
PropertyChanged イベントを発行するようにします。

'請求額
Private _bill As Integer = 0
Public Property bill() As Integer
  Get
    Return _bill
  End Get
  Set(ByVal value As Integer)
    _bill = value
    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("bill"))
  End Set
End Property
魔界の仮面弁士様
ありがとうございます。

> そもそも、DataTable/DataSet のまま管理しては駄目なのでしょうか?

  ダメというわけではないのですが、個人的な好みの問題で
  わざわざDataSourceに指定するクラスを作成して管理しています。

> もしも BindingList で管理した方が都合が良いのあれば、
> 「変更通知」の機能を実装する必要があります。
> http://msdn.microsoft.com/ja-jp/library/xz45s2bh.aspx

  教えていただいた方法で、listItemクラスを変更し、frm側でイベントを拾う
  方法を試してみました。
  ところが、最初のsetData()時には問題なくイベントを拾ってくれるのですが、
  DataGridViewに直接入力した場合にはイベントを拾いません。
  何がおかしいのか、アドバイス頂けると幸いです。
  宜しくお願いします。

-- 以下ソース 変更したlistItemクラスと、frmクラスの追加部分のみ抜出
Public Class frm
    Private WithEvents item As listItem  '追加

    '追加  DataGridViewに直接値を入力した際にここを通らない
    Private Sub item_PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Handles item.PropertyChanged
        Dim line As listItem = DirectCast(sender, listItem)
        line.difference = line.bill - (line.deposit + line.commission)
    End Sub
End Class

Public Class listItem
    Implements INotifyPropertyChanged

    Dim _bill As Integer = 0
    Dim _deposit As Integer = 0
    Dim _commission As Integer = 0

    Public Property code As String = ""
    Public Property name As String = ""
    Public Property bill As Integer
        Get
            Return _bill
        End Get
        Set(value As Integer)
            If Not value = _bill Then
                _bill = value
                RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("bill"))
            End If
        End Set
    End Property
    Public Property deposit As Integer
        Get
            Return _deposit
        End Get
        Set(value As Integer)
            If Not value = _deposit Then
                _deposit = value
                RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("deposit"))
            End If
        End Set
    End Property
    Public Property commission As Integer
        Get
            Return _commission
        End Get
        Set(value As Integer)
            If Not value = _commission Then
                _commission = value
                RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("commission"))
            End If
        End Set
    End Property
    Public Property difference As Integer = 0
    Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
End Class
自己解決です。
setData()内でitem を new した際、イベントの紐づけを行うのが抜けていました。
大変お騒がせしました。
アドバイス頂いた魔界の仮面弁士様、ありがとうございました 。
これにて解決としたいと思います。

    Private Sub setData()
        'データ取得処理

        For Each rec As DataRow In dt.Rows
            '↓この部分を変更
            item = New listItem
            AddHandler item.PropertyChanged, AddressOf item_PropertyChanged
            '↑この部分を変更
            ’以降データ格納処理
        Next
    End Sub
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板