DataGridViewでのソート処理について

タグの編集
投稿者 ヒロ  (学生) 投稿日時 2024/1/29 16:04:34
DataGridViewのソート処理が正常に行えない問題で困っています。
String型の列では問題ないのですが、列のデータ型が Long型 や Date型 になると、セルに値を入力したあと、その列のヘッダをクリックしてソートすると、データ型の不一致でエラーになってしまいます。
CellEndEdit イベントで、編集したセルの内容やデータ型を取得してみましたが、列のデータ型と一緒しており、エラーの内容と異なり、問題箇所が特定できません。
また、金額列と日付列にはそれぞれ書式 ”0,#” と "yyyy/MM/dd" を設定しています。

これだけの情報では何とも言えないかと思います。
必要な情報があれば、わかる範囲で回答させていただきますので、解決方法を探る糸口をおしえていただけないでしょうか?
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2024/1/29 18:19:47
> Long型 や Date型 に
ということは、Visual Basic での開発ですね。

DataGridView の DataSource を設定して使っていますか?(データバインド)
それとも、セル一つ一つに直接値をセットしていますか?(非バインド)

> セルに値を入力したあと
DataSet などをバインドしている場合は、DataTable の列で型を明示できます。
(個人的にはデータバインドで利用することをお奨めします)

そして画面から値を入力した場合は、基本的に『文字列』としての入力になりますので、
非バインドの場合は入力値をそのまま利用するのではなく、TryParse メソッドを併用するなどして、
入力された値を、「文字列型の 12345」を「整数型の 12345」に変更するとか
「文字列型の 2024/01/29」を「日付型の 2024/01/29」に変更するなどといった、
データ型の検査と変換処置が必要になります。

もちろん、ユーザーが編集した値だけでなく、プログラムでセットした値についても同じことが言えます。
新規フォームに、空の DataGridView を貼って、そのまま実行してみましょう。

'非バインドの場合 
Public Class Form1
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Me.DataGridView1.ColumnCount = 1
        Me.DataGridView1.RowCount = 4

        Me.DataGridView1(0, 0).Value = 12L  'これらは Long 型(System.Int64 構造体) 
        Me.DataGridView1(0, 1).Value = 12345L
        Me.DataGridView1(0, 2).Value = "12345"  'ここだけ、文字列型(System.String クラス)が混入している 
        Me.DataGridView1(0, 3).Value = 123L

        ' 見た目では区別がつきませんが、 
        ' 実際のデータ型では Long / String の差異があることがわかります。 
        ' MsgBox(TypeName(Me.DataGridView1(0, 1).Value)) 
        ' MsgBox(TypeName(Me.DataGridView1(0, 2).Value)) 

    End Sub
End Class


'DataTableをバインドした場合 
Public Class Form1
    Private ds As New DataSet()
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim tbl = ds.Tables.Add("tbl")
        tbl.Columns.Add("Num"GetType(Long))

        tbl.Rows.Add(12L)
        tbl.Rows.Add(12345L)
        tbl.Rows.Add("12345")  '本来は Long 型を渡すべきだが、あえて文字列側をセット 
        tbl.Rows.Add(123L)

        ds.AcceptChanges()

        Me.DataGridView1.DataSource = ds
        Me.DataGridView1.DataMember = tbl.TableName


        'この DataTable の Num 列は、予め Long 型である事を明示しているため、 
        '文字列が渡されても、自動的に Long 型に変換されて登録されています。 
        ' MsgBox(TypeName(tbl.Rows(2)(0))) 
        ' MsgBox(TypeName(Me.DataGridView1(0, 2).Value)) 
    End Sub
End Class
投稿者 ヒロ  (学生) 投稿日時 2024/1/30 10:22:54
回答ありがとうございます。
サンプルプログラムで問題点が理解できました。
DataGridView には非バインドでデータをセルにセットしています。
TypeNameを使って確認したところ、Long型の列のセルにはプログラムでセットした値はすべてString型になっていることがわかりました。
逆に直接入力した値はInt64型(恐らくLong型のこと?)になっていました。
プログラムでセルに値をセットする処理でLong型に変換することで、ソート時のエラーを回避することができました。
本当にありがとうございます。
投稿者 ヒロ  (社会人) 投稿日時 2024/1/30 10:25:48
問題が解決しましたので、解決チェックを入れておきます。
ありがとうございます。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2024/1/30 12:42:41
> そして画面から値を入力した場合は、基本的に『文字列』としての入力になりますので、
> 非バインドの場合は入力値をそのまま利用するのではなく、TryParse メソッドを併用するなどして、
> 入力された値を、「文字列型の 12345」を「整数型の 12345」に変更するとか
> 「文字列型の 2024/01/29」を「日付型の 2024/01/29」に変更するなどといった、
> データ型の検査と変換処置が必要になります。

下記は非バインド状態の時に、0 列目に Long 値の入力を強制させる例です。
入力値は文字列型で入ってくるので、数値に変換なら Long.TryParse メソッドで変換します。
日付型の列ならば、Date.TryParseExact を使うなどしてみてください。

Private Sub DataGridView1_CellValidating(sender As Object, e As DataGridViewCellValidatingEventArgs) Handles DataGridView1.CellValidating
    ''0列目は Long 値を必須とする。Long 値にできない文字列が入力されたら拒絶。 
    'If e.ColumnIndex = 0 AndAlso e.RowIndex >= 0 Then 
    '    Dim n As Long = 0 
    '    If Not Long.TryParse(e.FormattedValue, n) Then 
    '        e.Cancel = Me.DataGridView1.NewRowIndex <> e.RowIndex 
    '    End If 
    'End If 
End Sub

Private Sub DataGridView1_CellValidated(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellValidated
    '0列目の値が Long 型でなかった場合は、Long 値に変換する。 
    If e.ColumnIndex = 0 AndAlso e.RowIndex >= 0 Then
        Dim v = Me.DataGridView1(0, e.RowIndex).Value
        If TypeOf v IsNot Long Then
            Dim n As Long = 0
            If Long.TryParse(v?.ToString(), n) Then
                Me.DataGridView1(0, e.RowIndex).Value = n
            Else
                'Me.DataGridView1(0, e.RowIndex).Value = DBNull.Value 
            End If
        End If
    End If
End Sub


ただし、CellValidating/CellValidated イベントが効くのは、ユーザー操作に対してです。
プログラムから直接、
 Me.DataGridView1.BeginEdit(True)
 Me.DataGridView1(0, 0).Value = "1234"  'Long 値では無いがセットできてしまう
 Me.DataGridView1.EndEdit()
などと値をセットした場合には、これらのイベントが呼び出されません。

プログラムからの直接入力の際には、値をセットする段階で検査し、
必要に応じて「正しいデータ型」の値に変換してから代入するようにします。