DataGridViewでの選択したセルの合計値を計算する方法

タグの編集
投稿者 くらす  (社会人) 投稿日時 2011/3/1 20:09:00
いつも参考にさせて頂き、お世話になっております。

標題の件、DataGridViewにおいてチェックボックス列を用意し
チェックが入ってる行の値の合計値を取得したいのですが
色々と試したのですが上手くいきません。

サンプルプログラムを解説付きで頂けたら助かります。

宜しくお願いします。

下記、イメージになります。
選択    値
□レ    A
□      B
□レ    C
□レ    D
 
合計値=A+C+D    


下記は私が書いたプログラムの最終形です。
右往左往しているので纏まっていませんが…。


    Private Sub DataGridView1_CellValueChanged(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles DataGridView1.CellValueChanged
        Dim CurrentRow As DataGridViewRow = DataGridView1.Rows(e.RowIndex)
        Dim Total() As Integer

        For i As Integer = 0 To 1
            If CurrentRow.Cells(0).Value = True Then
                Total = DataGridView1.CurrentRow.Cells(5).Value
            End If
        Next
        For i As Integer = 1 To Me.DataGridView1.RowCount - 1
            If CurrentRow.Cells(0).Value = True Then
                Total = Total(i) + DataGridView1.CurrentRow.Cells(5).Value
            End If
        Next
        Label1.Text = Total
    End Sub


VB2005を使用しています。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2011/3/1 22:33:50
こんな感じでしょうか。初期状態は
 ☑     100
 ☐    2000
 ☑   30000
 ☑  400000
にしているので、合計「430,100」と表示されるかと思います。

Public Class Form1
    'DataTable の値を合計する場合は、Compute メソッドを使うと便利です。 
    Private Sub UpdateSummaryLabel()
        Dim o As Object = ds.Tables("T").Compute("SUM(値)""選択=TRUE")
        If IsDBNull(o) Then
            label1.Text = "選択されていません。"
        Else
            label1.Text = CDec(o).ToString("#,0")   '小数部も必要なら適宜修正 
        End If
    End Sub

    '行が削除されたら、計算処理を実行する。 
    Private Sub dgv_RowsRemoved(ByVal sender As Object, _
      ByVal e As DataGridViewRowsRemovedEventArgs) Handles dgv.RowsRemoved
        UpdateSummaryLabel()    '合計額をラベルに表示する処理 
    End Sub

    'セルの値が変更されたら、計算処理を実行する。 
    Private Sub dgv_CellValueChanged(ByVal sender As Object, _
      ByVal e As DataGridViewCellEventArgs) Handles dgv.CellValueChanged
        bs.EndEdit()            'DataGridView → DataSet への反映作業を確定させる。 
        UpdateSummaryLabel()    '合計額をラベルに表示する処理 
    End Sub

    'CheckBox が変更された場合、その行は即座に確定状態にする。 
    Private Sub dgv_CurrentCellDirtyStateChanged(ByVal sender As Object, _
      ByVal e As EventArgs) Handles dgv.CurrentCellDirtyStateChanged
        If dgv.CurrentCellAddress.X = 0 Then    'CheckBox 列か否か 
            If dgv.IsCurrentCellDirty Then      '編集作業中か否か 
                dgv.CommitEdit(DataGridViewDataErrorContexts.Commit)
            End If
        End If
    End Sub

    '数値項目に文字列が入力された場合への対処 
    Private Sub dgv_DataError(ByVal sender As Object, _
      ByVal e As DataGridViewDataErrorEventArgs) Handles dgv.DataError
        MessageBox.Show(e.Exception.Message, "入力エラー", _
            MessageBoxButtons.OK, MessageBoxIcon.Information)
        e.ThrowException = False
    End Sub


    '画面構築。通常はデザイナで設定しておけばOK。 
    Private Sub Form1_Load(ByVal sender As ObjectByVal e As EventArgs) Handles Me.Load
        If components Is Nothing Then
            components = New System.ComponentModel.Container()
        End If
        bs = New BindingSource(Me.components)

        dgv = New DataGridView()
        dgv.Dock = DockStyle.Fill
        dgv.EditMode = DataGridViewEditMode.EditOnEnter
        dgv.AutoGenerateColumns = True
        Controls.Add(dgv)

        label1 = New Label()
        label1.Dock = DockStyle.Top
        label1.Text = "0"
        label1.TextAlign = ContentAlignment.BottomRight
        label1.Font = New Font(Me.Font.FontFamily, 18)
        Controls.Add(label1)

        ds = New DataSet()
        Dim tbl As DataTable = ds.Tables.Add("T")
        tbl.Columns.Add("選択"GetType(Boolean))
        tbl.Columns.Add("値"GetType(Decimal))

        tbl.Rows.Add(True, 100)
        tbl.Rows.Add(False, 2000)
        tbl.Rows.Add(True, 30000)
        tbl.Rows.Add(True, 400000)
        tbl.AcceptChanges()

        bs.DataSource = ds
        bs.DataMember = "T"
        dgv.DataSource = bs
        dgv.Columns(1).DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
        'dgv.Columns(1).DefaultCellStyle.Format = "#,0" 

        UpdateSummaryLabel()
    End Sub
    Private WithEvents label1 As Label
    Private WithEvents dgv As DataGridView
    Private WithEvents bs As BindingSource
    Private WithEvents ds As DataSet
End Class
投稿者 くらす  (社会人) 投稿日時 2011/3/2 12:35:48
> 魔界の仮面弁士 さま

ご連絡が遅くなり申し訳ございません。

まさしくこの動きが実現させたかったことです。
本当にありがとうございます。

さらにエラー制御等まで考慮していただき
勉強になります。

データテーブルという言葉は耳にしたことがあったのですが
調べてみるとデータベース、データグリッドビューとのやり取りもあり
勉強しないといけないなと思いました。

そこら辺がよく分かっていないので私が作成しているプログラムでは
すでにデータテーブルの宣言をしていたので
魔界の仮面弁士 さまのプログラムと組み合わせるのに
試行錯誤しましたがうまくいきませんでした。

下記に抜粋いたしますのでご確認お願いいたします。


Public Class Form
    Public dt As New DataTable
 
    Private Sub Form_Load(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles MyBase.Load
        Dim Chk1 As New DataGridViewCheckBoxColumn
        Dim Txt6 As New DataGridViewTextBoxColumn
       
        dt.Columns.Add("選択")
        dt.Columns.Add("金額")
        
        Chk1.Name = "選択"
        Txt6.Name = "金額"
        
        DGV.Columns.Add(Chk1)
        DGV.Columns.Add(Txt6)
        
        DGV.Columns("選択").DataPropertyName = "選択"
        DGV.Columns("金額").DataPropertyName = "金額"
        
        DGV.Columns(0).ReadOnly = False '選択 
        DGV.Columns(6).ReadOnly = True '金額 
        
        DGV.Columns(6).DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight

        DGV.Columns(6).DefaultCellStyle.Format = "#,0"
        
        DGV.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells
        DGV.AllowUserToAddRows = False
        DGV.AllowUserToDeleteRows = False

        DGV.DataSource = dt


        DGV.AutoGenerateColumns = False
        'Oracleに接続してDBアクセスしています。 
    '~略~ 
                
                Dim i As String = rdr1.Item("AMOUNT")
               
                dt.Rows.Add(False, i ) '選択列にはデフォルトでFalseを入れる。 

        '~略~ 

          UpdateSummaryLabel()

    End Sub
                 
    Private Sub dgv_CellValueChanged(ByVal sender As Object, _
      ByVal e As DataGridViewCellEventArgs) Handles DGV.CellValueChanged
        bs.EndEdit()             
        UpdateSummaryLabel()     
    End Sub

    Private Sub dgv_CurrentCellDirtyStateChanged(ByVal sender As Object, _
      ByVal e As EventArgs) Handles DGV.CurrentCellDirtyStateChanged
        If DGV.CurrentCellAddress.X = 0 Then    
            If DGV.IsCurrentCellDirty Then       
                DGV.CommitEdit(DataGridViewDataErrorContexts.Commit)
            End If
        End If
    End Sub

    Private Sub dgv_DataError(ByVal sender As Object, _
      ByVal e As DataGridViewDataErrorEventArgs) Handles DGV.DataError
        MessageBox.Show(e.Exception.Message, "入力エラー", _
            MessageBoxButtons.OK, MessageBoxIcon.Information)
        e.ThrowException = False
    End Sub

    Private Sub UpdateSummaryLabel()
        Dim SumObject As Object = dt.Compute("Sum(金額)""選択 = True")
        If IsDBNull(SumObject) Then
            Label.Text = "選択されていません。"
        Else
            Label.Text = CDec(SumObject).ToString("#,0")
        End If

    End Sub
End Class


投稿者 魔界の仮面弁士  (社会人) 投稿日時 2011/3/2 13:19:53
> DGV.DataSource = dt
直接割り当てるのではなく、先の私のコードにある
     bs.DataSource = ds
     bs.DataMember = "T"
     dgv.DataSource = bs
のように、BindingSource コンポーネントを経由させてください。


もしも DataTable を直接割り当てたいのであれば、
先の私のコードを以下のように変更する必要があります。

《Sub Form1_Load》
'--> 
'bs.DataSource = ds 
'bs.DataMember = "T" 
'dgv.DataSource = bs 
'-- 
dgv.DataSource = tbl
'<-- 


《Sub dgv_CellValueChanged》
'--> 
'bs.EndEdit()            'DataGridView → DataSet への反映作業を確定させる。 
'--- 
Dim rowView As DataRowView = TryCast(dgv.Rows(e.RowIndex).DataBoundItem, DataRowView)
If rowView IsNot Nothing Then
    rowView.EndEdit()    'DataGridView → DataSet への反映作業を確定させる。 
End If
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2011/3/2 16:40:56
> DGV.Columns("選択").DataPropertyName = "選択"
> DGV.Columns("金額").DataPropertyName = "金額"
選択 / 金額列のデータ型は何ですか?
たとえば金額列が String 型の場合、DataTable に対して
  .Compute("SUM(金額)", "選択=TRUE")
という問い合わせはできません。金額列が数値項目では無いためです。
    
> Dim i As String = rdr1.Item("AMOUNT")
> dt.Rows.Add(False, i ) '選択列にはデフォルトでFalseを入れる。 
この False, i は、上記の 選択列と金額列なのでしょうか?

> 試行錯誤しましたがうまくいきませんでした。
「うまくいかない」とは、具体的にはどのような結果になってしまっているのでしょうか?

エラーが出るなら発生場所とエラー内容を示してください。
期待動作しないなら、何を期待していて実際にはどうなってしまうのかを。
投稿者 くらす  (社会人) 投稿日時 2011/3/2 17:41:15
>魔界の仮面弁士さま

返信をいただきありがとうございます。

ご教授いただいたにも関わらずうまくいかず困っていたところ、
更なるご教授ありがとうございます。

教えていただいたDataTable を直接割り当てる方法を試しています。


> DGV.Columns("選択").DataPropertyName = "選択"
> DGV.Columns("金額").DataPropertyName = "金額"
 
こちらはDecimalに変更しました。
 Dim i As Decimal = rdr1.Item("AMOUNT")

> dt.Rows.Add(False, i ) '選択列にはデフォルトでFalseを入れる。
Falseは選択列、iは金額列になります。

この実行結果でUpdateSummaryLabel()が動くと
SumObject = dt.Compute("SUM(金額)", "選択=TRUE")
の部分に
「DataExceptionはハンドルされませんでした。
 集約関数Sum()および型:Stringの使用が無効です。」
とエラーが出てしまいます。

下記にプログラムを載せますのでご確認お願いいたします。
投稿者 くらす  (社会人) 投稿日時 2011/3/2 17:48:54
Public Class Form
    Public dt As New DataTable
 
    Private Sub Form_Load(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles MyBase.Load
        Dim Chk1 As New DataGridViewCheckBoxColumn
        Dim Txt6 As New DataGridViewTextBoxColumn
       
        dt.Columns.Add("選択")
        dt.Columns.Add("金額")
        
        Chk1.Name = "選択"
        Txt6.Name = "金額"
        
        DGV.Columns.Add(Chk1)
        DGV.Columns.Add(Txt6)
        
        DGV.Columns("選択").DataPropertyName = "選択"
        DGV.Columns("金額").DataPropertyName = "金額"
        
        DGV.Columns(0).ReadOnly = False '選択  
        DGV.Columns(6).ReadOnly = True '金額  
        
        DGV.Columns(6).DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight

        DGV.Columns(6).DefaultCellStyle.Format = "#,0"
        
        DGV.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells
        DGV.AllowUserToAddRows = False
        DGV.AllowUserToDeleteRows = False

        DGV.DataSource = dt


        DGV.AutoGenerateColumns = False
        'Oracleに接続してDBアクセスしています。  
    '~略~  
                
                Dim i As Decimal = rdr1.Item("AMOUNT")
               
                dt.Rows.Add(False, i )  

        '~略~  

          UpdateSummaryLabel()

    End Sub
                 
    Private Sub dgv_CellValueChanged(ByVal sender As ObjectByVal e As DataGridViewCellEventArgs) Handles DGV.CellValueChanged
        Dim rowView As DataRowView = TryCast(Dg_ConversionSuppliers.Rows(e.RowIndex).DataBoundItem, DataRowView)
        If rowView IsNot Nothing Then
            rowView.EndEdit()     
        End If

        UpdateSummaryLabel()     
    End Sub

    Private Sub dgv_CurrentCellDirtyStateChanged(ByVal sender As Object, _
      ByVal e As EventArgs) Handles DGV.CurrentCellDirtyStateChanged
        If DGV.CurrentCellAddress.X = 0 Then    
            If DGV.IsCurrentCellDirty Then       
                DGV.CommitEdit(DataGridViewDataErrorContexts.Commit)
            End If
        End If
    End Sub

    Private Sub dgv_DataError(ByVal sender As Object, _
      ByVal e As DataGridViewDataErrorEventArgs) Handles DGV.DataError
        MessageBox.Show(e.Exception.Message, "入力エラー", _
            MessageBoxButtons.OK, MessageBoxIcon.Information)
        e.ThrowException = False
    End Sub

    Private Sub UpdateSummaryLabel()
    Dim SumObject As New Object        
    SumObject = dt.Compute("Sum(金額)""選択 = True")
        If IsDBNull(SumObject) Then
            Label.Text = "選択されていません。"
        Else
            Label.Text = CDec(SumObject).ToString("#,0")
        End If

    End Sub
End Class


投稿者 魔界の仮面弁士  (社会人) 投稿日時 2011/3/2 18:10:13
> DGV.Columns("選択").DataPropertyName = "選択"
> DGV.Columns("金額").DataPropertyName = "金額"
> こちらはDecimalに変更しました。
> Dim i As Decimal = rdr1.Item("AMOUNT")
違います。「AMOUT」の型ではなく、DataTable の「金額」列の型です。

> dt.Columns.Add("選択")
> dt.Columns.Add("金額")
これらの記述法は、String 型の列が作成される事になります。
先の私のコードと見比べてみてください。


金額列が文字列であるがゆえに、
> dt.Compute("Sum(金額)", "選択 = True")
の処理が
> 集約関数Sum()および型:Stringの使用が無効です。
の例外を発生させることになるわけです。


> Dim SumObject As New Object        
> SumObject = dt.Compute("Sum(金額)", "選択 = True")
ここでの New は無意味です。As Object にしましょう。
投稿者 くらす  (社会人) 投稿日時 2011/3/2 18:57:37
>魔界の仮面弁士さま

ご教授ありがとうございます。

ご指摘いただいた通り、DataTable の列の型を
dt.Columns.Add("選択", GetType(Boolean))
dt.Columns.Add("金額", GetType(Decimal))
とすることにより解決いたしました。

最初から最後まで本当にありがとうございました。
型については勉強させていただきます。