データグリッドビュー内のデータ検索

タグの編集
投稿者 vb初心者  (学生) 投稿日時 2017/4/9 03:21:13
ご質問失礼します。
vb初心者です。
今回初めてvbでのアプリケーション製作を行っているのですが、
戸籍データ登録システムを作成しています。
データを入力してそのデータをデータグリッドビュー内に収めた後に
検索機能を使って名前の検索を行えるようにしたいのですが、

http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=44999&forum=7

こちらのサイトを参考に作成しているのですが

    Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
        Dim hasData As Boolean = False
        Dim resultIndex As Int32 = 0
        For i As Integer = 0 To DataGridView1.RowCount - 1
            Dim inputString1 As String = DataGridView1.Rows(i).Cells("namae").Value 
            Dim strText As String = TextBox1.Text
            If strText = inputString1 Then
                hasData = True
                resultIndex = i
            End If
        Next
        If hasData = True Then
            DataGridView1.Rows(resultIndex).Selected = True
        Else
            MessageBox.Show("候補なし")
        End If
    End Sub
End Class

これでプログラムを実行してみると、検索を行ってくれない状態です。
例えばデータを5件ほど登録した際に、検索ボックスに何を入力しても
一番最後の行が選択される状態になっています。

引用させていただいたコードは特に大きな変更はしていないと思うのですが、
検索を行ってくれないということは、for文あたりのループが回っていないと
いうことでしょうか。

投稿者 魔界の仮面弁士  (社会人) 投稿日時 2017/4/10 12:26:14
> データを入力してそのデータをデータグリッドビュー内に収めた後に
> 検索機能を使って名前の検索を行えるようにしたいのですが、

DataGridView.DataSource にデータをバインドする手法でしょうか?
それとも、セル単位でデータを割り当てる手法でしょうか?

パフォーマンス上の理由により、通常は前者の利用をお奨めします。



> 例えばデータを5件ほど登録した際に、検索ボックスに何を入力しても
> 一番最後の行が選択される状態になっています。

DataGridView は、既定で MultiSelect = True になっています。
単一選択にしたいのであれば、MultiSelect = False にしておきましょう。
そうしないと、現在のカーソル行と検索結果の両方が選択状態になってしまいます。

MultiSelect = True のままで処理するのであれば、現状のコードの先頭に
DataGridView1.ClearSelection()
を追加しておいた方が良いかもしれません。



> 検索を行ってくれないということは、for文あたりのループが回っていないと
> いうことでしょうか。
ループ行の上で [F9] キーを押して、ブレークポイントを貼っておいてください。
そうすると、その行が実行される直前で一時停止されるようになります。

一時停止中に [F11]キーを押すと、一行ずつステップ実行されますので、
『ループ内を通過しているのか』とか、『その時、それぞれの変数の内容はどうなっているか』を
確認することができると思います。




以下蛇足:
> Dim resultIndex As Int32 = 0
> For i As Integer = 0 To DataGridView1.RowCount - 1
『Int32』表記と『Integer』表記が混在していますが、
どのような意図で使い分けているのでしょうか?


それと、「行番号」をあらわす変数の場合は、
i, j, k などの変数名のかわりに、
r, rowIndex, row などを使うと分かりやすくなります。
(列番号なら、c, col, column, colIndex など)

あるいは、列番号に x、行番号に y を使うケースもあります。


> Dim inputString1 As String = DataGridView1.Rows(i).Cells("namae").Value
パフォーマンス上の事情により、
『DataGridView1.Rows(i).Cells("namae").Value』ではなく
『DataGridView1("namae", i).Value』をお奨めします。
https://msdn.microsoft.com/ja-jp/library/ms171621%28vs.100%29.aspx

また、Value プロパティの戻り値は Object 型なので、いきなり String 型の変数に
代入するのではなく、両辺の型を一致させることが望ましいです。
「Option Strict On」の宣言をつけておくと、このような型の不一致を防ぐことができます。


> Dim strText As String = TextBox1.Text
TextBox の内容はループ中で変化するものではありませんので、
この部分はループの外に出してしまった方が良いでしょう。


> If hasData = True Then

値を比較した結果は Boolean になりますので、
If hasData Then
If hasData = True Then
If (hasData = True) = True Then
If ((hasData = True) = True) = True Then
はいずれも同じ結果となりますが、このケースでは
「If hasData Then」で十分かも知れません。


> ご質問失礼します。
『自分がする動作・行為』に対しては、敬語の「ご」「お」は普通付けません。

ただし謙譲語として、相手のために行う動作であれば、自分の動作であっても
「ご説明いたしますと…」のような言い方はアリですね。
投稿者 vb初心者  (学生) 投稿日時 2017/4/11 01:04:55
様々なご指摘ありがとうございます。
ループの中を一つずつ確認できたおかげで何が問題だったのか確認できました。
int32とintegerの使い分けは特に意味はありません。
参考にさせて頂いたサイトのプログラムを自身のプログラムに流用させる際に
修正し忘れいていただけです。
もう一つ分からず解決出来ないことがあり質問を書き忘れたのですが、
データグリッドビューにテキストボックスで入力した内容(技術不足の為、項目ごとにデータをセルにボタンクリックで送信しています)を反映させる際に、
        Me.DataGridView1.Rows.Add(TextBox1.Text, NumericUpDown1.value,TextBox2.Text)
のようなコードでグリッドビューに反映させているのですが、一部分の文字の色を変化させたい
場合は、どういった記述で変化させられるのでしょうか。
例えば、if文による判定文でAの場合は'X'(文字色は赤)、Bの場合は'Y'(文字色青)とし、
判定の結果Aだったので 
Dim s1 As String = 'X'と定義し、 s1 = Color.Redとしようとすると、
型'color'の値を'string'に変換できないと出ます。



投稿者 魔界の仮面弁士  (社会人) 投稿日時 2017/4/11 11:31:22
> 技術不足の為、項目ごとにデータをセルにボタンクリックで送信しています
参考にされたというページに、下記のコードが掲載されていましたが、これがデータバインドです。
DataGridView1 を直接操作するのではなく、DataTable を編集することでデータを操作できます。
Dim table As DataTable = New DataTable() 
table.Columns.Add("C1"GetType(String)) 
table.Columns.Add("C2"GetType(String)) 
table.Columns.Add("C3"GetType(String)) 

table.Rows.Add("aaa""あああ""123"
table.Rows.Add("bbb""いいい""456"
table.Rows.Add("ccc""ううう""789"
table.Rows.Add("ddd""えええ""012"
table.Rows.Add("eee""おおお""345"
table.Rows.Add("fff""かかか""678"

DataGridView1.DataSource = table

この方法の利点は、DataGridView の負荷が軽減されることや、
絞込み表示や並び替えが容易になる点です。
質問の本質からは外れますが、余力があれば併せて習得してみてください。


> 一部分の文字の色を変化させたい
> 場合は、どういった記述で変化させられるのでしょうか。

文字色の変更としては、難易度別に 3 パターンあります。

(1) 「セル単位で着色する」たとえば 2 行目の 0 列目を赤文字にする
(2) 「データ内容に応じて着色する」たとえば マイナスの数値の入っているセルを赤文字にする
(3) 「文字単位で着色する」たとえば "VB.NET" というデータの ".NET" 部分だけを赤文字にする


(1) のパターンの場合は、
DataGridView1(0, 2).Style.ForeColor = Color.Red
という感じです。比較的単純ですね。
必要に応じて、セル選択時の文字色や背景色も操作すると良いでしょう。
行単位や列単位での着色については後述。


(2) の場合は、こんな感じです。データ内容に応じて変化するので、セル値を手動編集した場合にも文字色を変化させられるのが利点です。
Private Sub DataGridView1_CellFormatting(sender As Object, e As DataGridViewCellFormattingEventArgs) Handles DataGridView1.CellFormatting
    Dim dec As Decimal
    If Decimal.TryParse(e.Value, dec) AndAlso dec < 0 Then
        e.CellStyle.ForeColor = Color.Red
    End If
End Sub



(3) の場合は、スタイル指定では適用できないので、CellPainting イベントを使って
自身で文字列をカスタム描画して対応することになります。
手間がかかる分、もっとも自由度が高いです。

[DataGridViewのセルを自分で描画する]
http://dobon.net/vb/dotnet/datagridview/ownerdrawcell.html

[文字を描く]
http://dobon.net/vb/dotnet/graphics/drawstring.html


なお、(1) や (2) では、セルスタイル(DataGridViewCellStyleクラス)という物を利用しています。
セルスタイルを指定する箇所は色々あり、それぞれ優先順位が異なります。



各スタイルは、フォント・文字色・背景色・書式・文字の折り返し・右寄せ指定等々の
指定がありますが、上図のように幾つかの階層が重ね合わさっています。各スタイルの指定は
上位の階層では未設定となっており、その場合は下位の階層のスタイルが適用されます。

イベント引数 DataGridViewCellFormattingEventArgs の CellStyle
 セル単位の最終的なスタイル指定。
 上記(2)のパターンのように、既定のスタイル設定で補えない場合に利用します。

DataGridView1.DefaultCellStyle
 すべてのセル(列ヘッダーや行ヘッダーも含む)を対象とした既定のスタイル。

DataGridView1.ColumnHeadersDefaultCellStyle
 列ヘッダー(TopLeftHeaderCell も含む)を対象とした既定のスタイル。
 ただし EnableHeadersVisualStyles = True の場合は無視されます。

DataGridView1.RowHeadersDefaultCellStyle
 行ヘッダー(TopLeftHeaderCell は含まない)を対象とした既定のスタイル。
 ただし EnableHeadersVisualStyles = True の場合は無視されます。

DataGridView1.RowsDefaultCellStyle
 ヘッダー部以外のすべてのセルを対象とした既定のスタイル。

DataGridView1.AlternatingRowsDefaultCellStyle
 奇数行のセル(ヘッダー部は含まない)を対象とした既定のスタイル。
 一行おきに背景色を変化させるようなケースで利用されます。

DataGridView1.Columns(r).DefaultCellStyle / .HasDefaultCellStyle / .InheritedStyle
 列単位(ヘッダー部は含まない)を対象とした既定のスタイル。
 DefaultCellStyle は 初期状態で Nothing ですが、DefaultCellStyle に
 アクセスした瞬間に新しいスタイルが割り当てられてしまうため、
 未設定かどうかを確認するためには、If ~.HasDefaultCellStyle Then で
 判断することになります。
 また、ReadOnly である InheritedStyle プロパティは、ここまでの階層の
 すべてのスタイルを重ね合わせた状態を返すようになっています。

DataGridView1.Rows(r).DefaultCellStyle / .HasDefaultCellStyle / .InheritedStyle
 行単位(ヘッダー部は含まない)を対象とした既定のスタイル。
 DefaultCellStyle / HasDefaultCellStyle / InheritedStyle の役割は
 上記の列単位スタイルの説明と同じです。

セル.Style / .HasStyle / .InheritedStyle
 セル単位での既定のスタイル指定。
 イベントで上書きされるケースを除けば、これが最終的に適用されるスタイルとなります。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2017/4/11 11:44:30
> (3) 「文字単位で着色する」

参考までに、「2 行目 1 列目」のセル内の文字列を、
文字ごとに別の色で描画するコードを書いてみました。

一文字ごとに、Red 成分 を+90、Green 成分を +25、Blue 成分を + 40 しています。


Private Sub DataGridView1_CellPainting(sender As Object, e As DataGridViewCellPaintingEventArgs) Handles DataGridView1.CellPainting
    '2 行目 1 列目 のみ、カスタム描画する 
    If e.RowIndex = 2 AndAlso e.ColumnIndex = 1 Then

        '背景は標準描画に任せる 
        e.PaintBackground(e.CellBounds, True)

        'セルに表示する文字列を取得 
        Dim sText As String = If(CStr(e.Value), "")

        '1 文字ごとの位置を切り出すための構造体配列 
        Dim cr(sText.Length - 1) As CharacterRange
        For i = 0 To sText.Length - 1
            cr(i) = New CharacterRange(i, 1)
        Next

        Using fmt As New StringFormat(StringFormat.GenericDefault)
            'セルスタイルの Alignment を、StringFormat の文字位置に変換 
            Dim align As DataGridViewContentAlignment = e.CellStyle.Alignment

            If (align And &HF) <> 0 Then
                fmt.LineAlignment = StringAlignment.Near
            ElseIf (align And &HF0) <> 0 Then
                fmt.LineAlignment = StringAlignment.Center
            ElseIf (align And &HF00) <> 0 Then
                fmt.LineAlignment = StringAlignment.Far
            End If
            If (align And &H111) <> 0 Then
                fmt.Alignment = StringAlignment.Near
            ElseIf (align And &H222) <> 0 Then
                fmt.Alignment = StringAlignment.Center
            ElseIf (align And &H444) <> 0 Then
                fmt.Alignment = StringAlignment.Far
            End If

            '折り返し調整など 
            fmt.FormatFlags = fmt.FormatFlags Or StringFormatFlags.NoClip
            If e.CellStyle.WrapMode = DataGridViewTriState.False Then
                fmt.FormatFlags = fmt.FormatFlags Or StringFormatFlags.NoWrap
            End If

            ''余白領域の調整 
            'Dim bounds As Rectangle = e.CellBounds 
            'bounds.X += e.CellStyle.Padding.Left 
            'bounds.Y += e.CellStyle.Padding.Top 
            'bounds.Width -= e.CellStyle.Padding.Horizontal 
            'bounds.Height -= e.CellStyle.Padding.Vertical 

            '各文字の位置を示す領域を構築 
            fmt.SetMeasurableCharacterRanges(cr)
            Dim regions() As Region = e.Graphics.MeasureCharacterRanges(sText, e.CellStyle.Font, bounds, fmt)

            '1文字ずつ色を変えて描画 
            Dim r, g, b As Integer
            For i = 0 To sText.Length - 1
                r = (r + 90) And &HFF
                g = (g + 25) And &HFF
                b = (b + 40) And &HFF
                Using sb As New SolidBrush(Color.FromArgb(r, g, b))
                    Dim rectF As RectangleF = regions(i).GetBounds(e.Graphics)
                    Dim rect As Rectangle = Rectangle.Truncate(rectF)

                    e.Graphics.DrawString(sText.Substring(i, 1), e.CellStyle.Font, sb, rect, fmt)
                End Using
            Next
        End Using

        'カスタム描画を行ったときは、Handled を True にして 
        '標準描画が行われることを抑制する 
        e.Handled = True

    End If
End Sub



長いコードになりましたが、これでも手抜き実装です。

行末の三点リーダー(ellipsis)などは省いているので、
長すぎる文字列の表示、特に Alignment が右寄せに
なっている場合の処理などは、もう少し見直しが必要です。