datagridviewのセルのデータ書き換え

タグの編集
投稿者 伊右衛門  (社会人) 投稿日時 2020/9/5 22:17:17
初めまして、質問よろしくお願いします。
ざっくり?書くと、親フォームから子フォームを呼び出し、孫フォームで修正後、子フォームのデータを書き換えたいです。

form1からbuttonでform2をshowdialogにて呼び出します。
form2にはdatagridviewが作成してあり、csvファイルからデータを読み込んで表示しています。

・セルのダブルクリックイベントからform3にデータを渡して表示させます
   Private Sub DataGridView_CellDoubleClick(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles DataGridView1.CellDoubleClick
        '◇◇-- DataGridView1をダブルクリックすると呼び出されるイベント --◇◇
        '◇- ヘッダー以外をダブルクリックしているか判定 -◇
        If e.ColumnIndex >= 0 And e.RowIndex >= 0 Then
            '◇- 編集画面表示 -◇
            Dim Form_DAILY As New Form3
            '◇- ダブルクリックされたデータを「編集画面」に表示させる -◇
            Form_DAILY.TextBox1.Text = Trim(sender.Rows(e.RowIndex).Cells(0).Value)
            Form_DAILY.TextBox2.Text = Trim(sender.Rows(e.RowIndex).Cells(1).Value)
            Form_DAILY.ComboBox1.Text = Trim(sender.Rows(e.RowIndex).Cells(2).Value)
            Form_DAILY.ComboBox2.Text = Trim(sender.Rows(e.RowIndex).Cells(3).Value)
            Form_DAILY.TextBox5.Text = e.RowIndex
            Form_DAILY.Button2.Visible = False
            Form_DAILY.StartPosition = FormStartPosition.CenterScreen
            Form_DAILY.ShowDialog(Me)
            Form_DAILY.Dispose()
        End If
    End Sub

・form3で修正後、form2のdatagridviewを書き換えたいのですが、form2のrowcountもcolumncountも0になってしまい
「追加情報:インデックスが範囲を超えています。負でない値で…」のエラーが出てしまい書き換えることができません。
        Form2.DataGridView1.Rows(CInt(TextBox5.Text)).Cells(0).Value = TextBox1.Text
        Form2.DataGridView1.Rows(CInt(TextBox5.Text)).Cells(1).Value = TextBox2.Text
        Form2.DataGridView1.Rows(CInt(TextBox5.Text)).Cells(2).Value = ComboBox1.Text
        Form2.DataGridView1.Rows(CInt(TextBox5.Text)).Cells(3).Value = ComboBox2.Text
        Me.Close()

このようなやり方で書き換えることはできないのでしょうか?
よろしくお願いします。
投稿者 (削除されました)  () 投稿日時 2020/9/7 01:37:28
(削除されました)
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2020/9/7 09:20:20
> 親フォームから子フォームを呼び出し、孫フォームで修正後、子フォームのデータを書き換えたいです。
> form1からbuttonでform2をshowdialogにて呼び出します。
> form3で修正後、form2のdatagridviewを書き換えたいのですが、

えぇと。
Form1 が親で、Form2 が子で、Form3 が孫でしょうか。


前半部のコードでは、Form3 を Form_DAILY 変数を通じた
明示的なインスタンスを使用しているのに、
後半部のコードでは、Form2 という暗黙のフォームインスタンスを
使っているように見えました。
前者の書き方に統一した方が良いと思いますよ。


> Trim(sender.Rows(e.RowIndex).Cells(0).Value)
DataGridView1.Rows(rowIndex).Cells(colIndex).Value ではなく、
DataGridView1(colIndex, rowIndex).Value という構文もあります。


> '◇- ダブルクリックされたデータを「編集画面」に表示させる -◇
親画面が子画面のコントロールを直接操作したり、
子画面から親画面のコントロールを直接操作するのは、
画面間の依存性が強くなりすぎるため、あまり望ましくありません。


元のコードだと、Form2 の方では、
> Form_DAILY.TextBox1.Text = Trim(sender.Rows(e.RowIndex).Cells(0).Value)
のようにして、Form3 の TextBox1 を、Form2 側から直接操作していましたし、
また、Form3 の方でも、
> Form2.DataGridView1.Rows(CInt(TextBox5.Text)).Cells(0).Value = TextBox1.Text
のように書かれており、Form3 が Form2 の DataGridView1 にアクセスしていました。


自分ならこの部分、Form2 側はもっとシンプルに、下記のように書きます。

If e.ColumnIndex >= 0 And e.RowIndex >= 0 Then
    Dim dgv = DirectCast(sender, DataGridView)
    Using Form_DAILY As New Form3(dgv.Rows(e.RowIndex))  '行データをコンストラクタで渡す 
        Form_DAILY.ShowDialog(Me)
    End Using
End If



そして Form3 側は、たとえば下記のような実装にします。

Public Class Form3
    Private sourceRow As DataGridViewRow
    Public Sub New(row As DataGridViewRow)
        InitializeComponent()
        sourceRow = row
        TextBox1.Text = row.Cells(0).Value?.ToString()?.Trim()
        TextBox2.Text = row.Cells(1).Value?.ToString()?.Trim()
        'TextBox5.Text = CStr(row.Index)  
        StartPosition = FormStartPosition.CenterScreen
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        sourceRow.Cells(0).Value = TextBox1.Text
        sourceRow.Cells(1).Value = TextBox2.Text
        'Close()  
    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        Close()
    End Sub
End Class



この方法だと、Form2 も Form3 も「自分自身のコントロール」は操作していますが、
他のフォームのコントロールは操作していません。それでも、データを受け渡すことが出来ています。
これにより、フォーム間の依存性をある程度軽減できます。

たとえば将来的に、Form3 の TextBox2 が ComboBox3 に変わったとしても
修正するのは Form3 だけであり、Form2 のコードは変更せずに済みます。

なお、上記は VB2015 以降のバージョンを想定したコードになっています。VB2008~VB2013 以下の場合は、
「row.Cells(0).Value?.ToString()?.Trim()」で使われている Null 条件演算子を If 演算子に置き換えて
「If(row.Cells(0).Value, "").ToString().Trim()」のように書き換えてみてください。もしも
VB2005 まで遡る場合は、If 演算子を If ステートメントに置き換える必要があるでしょう。



さらに言えば、今回は元ソースに従って、列番号を使ったアクセスとしていますが、
要件次第では、データバインドの利用を検討してみることもおすすめしたいです。

DataGridView の DataSource プロパティを割り当てて管理している場合、
Form3 側でもデータバインドを使うことで、上記のようなコントロールへの転記
あるいは書き戻しのためのコードを書く手間を省くことができます。
(バインドする場合は、DataGridViewRow を渡すのではなく、
 DataRow または DataViewRow を渡す方が望ましいです)

また、これらのサンプルでは、ShowDialog の戻り値を利用していませんが、画面構成によっては
FontDialog や OpenFileDialog のように、呼び出し側で OK / Cancel 判定を付けるといった
実装もよく用いられます。(AcceptButton プロパティや CancelButton を併用すると良いでしょう)

そのようなダイアログを真似て実装する場合には、呼び出されるダイアログ側にイベント処理を
追加することもあります――FontDialog1_Apply イベント や OpenFileDialog1_FileOk イベントのように。

子画面での操作結果をイベントとして通知すると、子画面での編集結果を親で処理することができます。

たとえば「Form3 に OK / Canel / 適用 ボタン」を設けて、その OK や 適用 を押された時に、
Form2 の DataGridView へ書き戻すような処理があった場合でも、その書き戻し処理を
Form2 自身に設ける事ができるため、汎用性がさらに高まります。
投稿者 伊右衛門  (社会人) 投稿日時 2020/9/7 11:18:49
魔界の仮面弁士様

ご返信ありがとうございます。
Form_DAILYやForm2表記はコピーする箇所を誤って貼ってしまったため
見づらくなってしまいました。大変申し訳ないです。

今回の提示していただいたサンプルにて自分が思っていた動きを確認することができました。
他所でも同じような書き方をしてしまっている部分があったので
修正するとともに汎用性が上がるような書き方を勉強していきたいと思います。

ありがとうございました。