Visual Basic データベース講座
VB2005 対応

 

Visual Basic 中学校 > データベース講座 >

第5回 データの一覧表示

2006.12.23

DataGridView, ComboBox等のデータの一覧表示について説明します。

概要

・DataSourceプロパティを使用してデータを一覧表示できるのはDataGridView, ComboBox, ListBox, CheckedListBoxであり、方法はどれもほとんど同じ。

・2列表示できるComboBoxの例

・BindingContextを使うと連結されているデータソースを一元的に制御できる。

・ビューを使うと連結されているデータの「表示」を制御できる。ビューを制御するにはDataViewやDataRowViewを使用する。

 

1.DataSourceプロパティ

 

前回も紹介しましたが、取得したデータをDataGridViewに一覧形式で表示するにはDataGridViewDataSourceプロパティにDataTableをセットすればよいのでした。これは下の例のようになります。

この例を実行するためにはフォームの先頭にImports System.Data.OleDbと記述する必要がある点を忘れないでください。

VB2005 対応

'▼データ取得
Dim Cn As New OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Database\Animals.mdb")
Dim SQLCm As OleDbCommand = Cn.CreateCommand
Dim Adapter As New OleDbDataAdapter(SQLCm)
Dim Table As New DataTable

SQLCm.CommandText =
"SELECT * FROM T_動物マスタ"
Adapter.Fill(Table)

'▼値の表示
DataGridView1.DataSource = Table

'▼後処理
Table.Dispose()
Adapter.Dispose()
SQLCm.Dispose()
Cn.Dispose()

■リスト1:DataGridViewへの連結。

この例では表示するデータをDataGridViewに渡す手段としてDataSourceプロパティを使用しています。この ようにDataSourceプロパティにデータをセットするという手法はDataGridViewだけでなくComboBoxListBox, CheckedListBoxでも使用できます。

ComboBoxの例を見てみましょう。残念ながら表形式で表示できないComboBoxの性質上、上記の例のDataGridViewの部分をComboBoxに変えただけではだめで、次のように何行か書き換えることになります。

VB.NET2002対応 VB.NET2003対応 VB2005対応

'▼データ取得
Dim Cn As New OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Database\Animals.mdb")
Dim SQLCm As OleDbCommand = Cn.CreateCommand
Dim Adapter As New OleDbDataAdapter(SQLCm)
Dim Table As New DataTable

SQLCm.CommandText =
"SELECT * FROM T_動物マスタ"
Adapter.Fill(Table)

'▼値の表示
ComboBox1.DataSource = Table
ComboBox1.DisplayMember =
"名前"

'▼後処理
Adapter.Dispose()
SQLCm.Dispose()
Cn.Dispose()

■リスト2:ComboBoxへの連結。

DataGridViewの場合とどこが違うか見てみると、まずDisplayMemberというプロパティを設定している点が目立ちます。これは何を表示するかを制御ができるプロパティです。DataGridViewは本当に表形式なので何も考えなくでもデータがそのまま一覧表示されるのですが、ComboBoxは値を1つしか表示できないので、どの値を表示するか指定する必要があるのです。

今回は「名前」列の値を一覧表示するように指定しています。これがDisplayMemberプロパティの役割です。

それから、目立たない違いですがComboBox版の方は後処理のところでTable.Disposeを呼び出していません。Table.Disposeを呼び出すとComboBoxには何も表示されなくなってしまうのです。DataGridViewComboBoxになぜこのような違いがあるのかは不明ですが、考えてみるとDataGridViewの方がおかしい感じもします。この件は不問のまま話をすすめます。

ListBoxCheckedListBoxの場合は、ComboBoxと全く同じようにしてデータ一覧を表示することができます。ただし、CheckedListBoxDataSourceプロパティは公式にはサポートされていませんので重要なプログラムには使用すべきではないようです。

DataGridViewのときがちょっと手法が違うとはいえ全体的には統一感があ ります。ひとつ新しいことを覚えるとどんどん応用が広がっていくのは.NET Frameworkのいいところです。

今回はこの方法での一覧表示について掘り下げていきます。

 

2.ComboBoxとListBox

 

ComboBoxListBoxの場合は、1つの列だけが一覧形式で表示されます。複数列表示できるようにするにはカスタムコントロールを作成することになります。複数列表示は本題からはずれてしまうのですが興味 のある方が多いと思うのでこのページの下の方に掲載しておきます。

さて、テーブルの中から実際に表示する列を選択するには先程の例にもあったようにDisplayMemberプロパティを使用します。DisplayMemberに指定しなかった列は表示はされませんが、内部ではちゃんと保持されておりプログラムからアクセスすることができます。

現在選択されている行を取得するには次のようにします。この例ではlblNoteというLabelを配置して、ComboBoxの選択内容が変わるたび に選択されている動物の「説明」がLabelに表示されるようにしています。

VB.NET2002対応 VB.NET2003対応 VB2005対応

Private Sub ComboBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged

    Dim
Row As DataRowView

    Row = ComboBox1.SelectedItem
    lblNote.Text = Row("説明")

End
Sub

■リスト3:説明を表示する

ここでは行をDataRowクラスではなく、DataRowViewというクラスとして取得している点に注意してください。DataRowクラスはデータベース側の視点で見たレコードで、DataRowViewクラスは表示する側の視点から見た「行」です。この2つのクラスはなかなか似ています。

データそのものを扱っているときはDataRowクラス、表示を扱っているときはDataRowViewクラスと覚えておいてください。必要ならばDataRowViewクラスのRowプロパティを使ってDataRowを取得することもできます。

 

この講座で使用しているAnimals.mdbの[T_動物マスタ]テーブルには「画像」という列もあり、画像ファイルの場所を文字列で登録できるようになっています。上記の例と同じ手段で画像を取得して表示するというプログラムも 書けるわけで構想が膨らみます。

画像表示の例も少し試してみましょう。Animals.mdbのT_動物マスタには動物ID=51にイエネコが登録されています。このレコードの「画像」列の値にネコの画像を登録しましょう。ネコの画像が用意できない場合は下の画像をダウンロードしてください。

ペトロニウス

■画像1:イエネコ。近所に猫のたまり場があり、夜な夜な多数の猫が出没する。

ここではC:\Database\Images\にネコの画像であるdb5_Cat.jpgが存在するという前提で話を進めます。この場合は「画像」列には「C:\Database\Images\db5_Cat.jpg」と登録します。

データを登録・変更するにはデータベースエクスプローラまたはサーバーエクスプローラを使うと簡単ですが、自分でプログラムして変更しても構いません。データベースエクスプローラとサーバーエクスプローラの使用方法については第2回 データベースを見る、いじるを参照してください。

準備ができたらフォームにPictureBoxを配置してComboBox1SelectedIndexChangedイベントを次の通りに書き換えてください。

VB.NET2002対応 VB.NET2003対応 VB2005対応

Private Sub ComboBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged

    '▼現在の行を取得

   
Dim Row As DataRowView
    Row = ComboBox1.SelectedItem

   
'▼画像に何も登録されていない場合

   
If IsDBNull(Row("画像")) Then
       
'画像をクリアして終了
       
PictureBox1.Image = Nothing
        Return
   
End If

   
'▼画像の表示

   
Dim FileName As String
   
FileName = Row("画像")

   
'ファイルが存在する場合
   
If IO.File.Exists(FileName) Then
       
Try
           
PictureBox1.Image = Image.FromFile(FileName)
        Catch ex As Exception
           
'エラーが発生した場合は画像をクリア
           
PictureBox1.Image = Nothing
       
End Try
   
End If

End Sub

■リスト4:画像が登録されている場合、その画像を表示する。

コントロールの配置やプロパティをちょっと整えて実行すると次の例のようになります。

■画像2:画像があるととプログラムがしょぼくてもそれらしく見える。なお、この画像の場合はPictureBoxSizeModeZoomにしてある。

これで他にもいろいろな画像を登録するだけでも楽しいデータベースアプリケーションになりそうですね。

 

3.データソースの一元管理

 

DataTableと各コントロールの結びつきは一元管理されており、特に工夫をしていない場合はレコードの移動は連動します。

たとえば、DataGridViewComboBoxListBoxをフォームに配置してこの3つに同じテーブルの内容を表示させてみます。先程の例と似ていますが説明の便宜のために少し変えてあります。

VB2005対応

Dim MainTable As New DataTable

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

   
'▼データ取得
   
Dim Cn As New OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Database\Animals.mdb")
    Dim SQLCm As OleDbCommand = Cn.CreateCommand
    Dim Adapter As New OleDbDataAdapter(SQLCm)

    SQLCm.CommandText =
"SELECT * FROM T_動物マスタ"
   
Adapter.Fill(MainTable)

   
'▼値の表示
   
ComboBox1.DataSource = MainTable
    ComboBox1.DisplayMember =
"名前"
   
ListBox1.DataSource = MainTable
    ListBox1.DisplayMember =
"名前"
   
DataGridView1.DataSource = MainTable

   
'▼後処理
   
Adapter.Dispose()
    SQLCm.Dispose()
    Cn.Dispose()

End
Sub

■リスト5

この状態で、どれか1つのコントロールでレコードを選択するとほかのコントロールで選択されているレコードもそれに連動して変わります。

これはデータと表示の結びつきは個々のコントロールが管理しているのではなく、全体が一元管理されているために発生する現象です。この一元管理をしているのはCurrencyManagerというクラスです。

今表示されているのが何番目のレコードであるとか、次のレコードを表示したいだとかいうことはすべてこのCurrencyManagerを通じて行います。

連動して変わるなんて不便なように思えるかもしれませんが、実際のアプリケーション構築ではそれぞれのコントロールを個別に制御するという前提よりも、自動的に連動するという前提の基づいた方がなにかと便利です。移動だけではなくレコードの削除や更新も連動していますが、これは同じDataTableを見ていることから容易に想像がつきます。

CurrencyManagerを自分で制御するにはちょっと手順が必要です。まず次の プログラムのようにしてCurrencyManagerを取得します。

VB.NET2002対応 VB.NET2003対応 VB2005対応

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click

    Dim
Bind As BindingManagerBase = Me.BindingContext(MainTable)

End
Sub

■リスト6:CurrencyManagerの取得

この例でBindというのがCurrencyManagerクラスです。宣言上はBindingManagerBaseとなっていますがこれはCurrencyManagerクラスの基底クラスです。どうしても気にいならければDirectCastCTypeを使って明示的にCurrencyManagerに変換してもよいのですが、このままでも別に困らないのでシンプルな例として紹介しました。

また、Bindを取得するときにMe.BindingContextプロパティを使用している点に注目してください。実はこの部分はMeではなくてもいいのです。Meの部分をComboBox1にしても同じことができます。一元管理されているからどのコントロールからアクセスしても同じCurrencyManagerが取得できるというわけです。ただし、引数を見ればわかるようにデータソース(この場合はDataTable)によってはCurrenyManagerは区別されます。 さきほどから紹介している例はすべてDataTableを1つしか使っていないので引数で区別する必要はありませんが、プログラムが複雑になると複数のDataTableを使用することもあり得ます。この場合当り前の話ですが、異なるDataTableは連動したくてもできないし、無理に連動されてもかなり使いにくくなってしまう でしょう。ですから、BindingContextプロパティを利用するときには「どのデータソースの」という指定をする必要があるのです。

このことを逆手に取ると、連動されたくない場合はテーブルを分け るという手段がありえます。けれども連動の制御で困ることはまずありませんからあまり深く考えなくてもよいでしょう。

さて、CurrencuManagerが取得できればあとはBindに対して好きなように命令することができます。 とは言え、BindingManagerBase(CurrencyManager)クラスはデータを管理しているわけでも表示を制御しているわけでもなく 、ただその結びつきを管理しているだけなのでできることはそれほど多くありません。

ボタンを押すと次のレコードを、次のレコードと自動的に移動していくような仕組みはこのBingindManagerBaseを利用して実現できます。

データを取得して表示する手順がどのようなものであったか思い出してみるとすぐにわかると思いますが、はじめにすべての対象データを取得していて、その一部が画面に表示されているだけなのです。次のレコードまたその次のレコードと移動するためには何番目のレコードを表示するかという設定をするだけです。

現在のレコード番号を取得するにはPositionプロパティを使用します。またPositionプロパティに値をセットするとその番号のレコードにジャンプします。レコード番号とは単にレコードを配列と考えたときに0からはじまる番号を指しているに過ぎません。

ですから、次のレコードに移動するにはPositionに1を加えるだけです。

VB2005対応

Dim Table As DataTable = DirectCast(DataGridView1.DataSource, DataTable)
Dim Bind As BindingManagerBase = Me.BindingContext(Table)

Bind.Position += 1

■リスト7:次のレコードへ移動

Positionの値に実際にレコードが存在する範囲外の値をセットすると、セットしようとして値に一番近いレコードにジャンプします。エラーにはなりません。

たとえば、[T_動物マスタ]テーブルには100件のレコードがありますが、500件目に移動しようとすると100件目にジャンプします。

 

4.ビュー

 

DataTableはデータそのものを表していますが、これとにたものにDataViewというクラスがあり、こちらはデータの「表示」を制御しています。さきほどDataRowDataRowViewが登場しましたがその関係と同じです。

DataViewを使用するとデータの並び替え、フィルタリング、検索などを行うことができます。これらの機能はDataViewを使用しなくても実現は可能ですが、DataViewを使用した方が簡単に実現できます。

DataViewは自分でインスタンスを生成して使用することもできますが、DataTableにはじめから既定のDataViewが用意されておりDefaultViewプロパティでアクセスできます。

手始めにデータの並び替えを行ってみましょう。データを並び替えるにはDataViewSortプロパティを使用します。次の例では名前順に動物を並べて表示します。

VB2005対応

Dim Table As DataTable = DirectCast(DataGridView1.DataSource, DataTable)
Dim View As DataView = Table.DefaultView

View.Sort =
"名前"

■リスト8:名前による並び替え

この例を実行するには今までの例と同様に[T_動物マスタ]テーブルをあらかじめDataGridViewDataSourceをセットしておく必要があります。

なお、DataGridViewにはヘッダー部をクリックすると自動的にレコードを並び替える機能があるので上記の例は実際にはほとんど出番はないでしょう。

今度はデータのフィルタリングを紹介します。次の例は名前が「サーバル」であるデータだけを表示します。

VB2005対応

Dim Table As DataTable = DirectCast(DataGridView1.DataSource, DataTable)
Dim View As DataView = Table.DefaultView

View.RowFilter =
"名前='サーバル'"

■リスト9:フィルターの設定

データをフィルタリングするにはRowFilterプロパティを使用します。RowFilterプロパティに設定できる内容はSQLのWhere句とだいたい同じです。フィルタリングは表示を制御しているだけなので見えなくなったデータは消えたわけではありません。

次のようにしてフィルターを解除すると再びすべてのデータが表示されます。

VB2005対応

Dim Table As DataTable = DirectCast(DataGridView1.DataSource, DataTable)
Dim View As DataView = Table.DefaultView

View.RowFilter =
Nothing

■リスト10:フィルターの解除

このフィルター機能は単一の値を表示させる以外にも使い道があります。「=」の他に「<」、「>」などが使用できるので数値データの場合は指定した範囲を表示させることができますし、文字データの場合には「LIKE」を使用してパターンマッチングを行うことができます。

パターンマッチングとはワイルドカードとような指定方法のことで、たとえば、「イ」からはじまる動物を抽出するにはRowFilterプロパティに「名前 LIKE 'イ%'」と指定します。「%」はLIKE演算子と一緒にもちいると「0文字以上の文字」という意味になります。

これを利用して検索用テキストボックスに値を入力するたびにリアルタイムに値を抽出する機能が簡単に作成できます。

検索用のテキストボックスとDataGridViewだけを配置したフォームで次のようにプログラムしてみてください。

VB2005対応

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

    '▼データ取得
   
Dim Cn As New OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Database\Animals.mdb")
    Dim SQLCm As OleDbCommand = Cn.CreateCommand
    Dim Adapter As New OleDbDataAdapter(SQLCm)
    Dim Table As New DataTable

    SQLCm.CommandText =
"SELECT * FROM T_動物マスタ"
   
Adapter.Fill(Table)

   
'▼値の表示
   
DataGridView1.DataSource = Table

   
'▼後処理
   
Table.Dispose()
    Adapter.Dispose()
    SQLCm.Dispose()
    Cn.Dispose()

End
Sub
Private Sub TextBox1_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged

    Dim
Table As DataTable = DirectCast(DataGridView1.DataSource, DataTable)
    Dim View As DataView = Table.DefaultView

    View.RowFilter = "名前 LIKE '" & TextBox1.Text &
"%'"

End Sub

■リスト11:テキストボックスの入力内容に応じたフィルタリング

しかし考えてみたら日本語入力を確定させるまではイベントも発生しないので本当にリアルタイムというわけにはいかないですね。半角英数の列に対してはもっと快適に動作することでしょう。なお、この手法はデータ件数が少ない場合には快適に動作してもデータ件数が多いと処理に時間がかかってかえって不便になることもあります。

■画像3 ■画像4 ■画像5

この検索機能やさきほどの画像表示機能などを組み合わせればだんだんとデータベースアプリケーションっぽいものが完成していきますね。

DataViewにはこの他にも表示に関するさまざまな制御が提供されていますので、どのような機能があるかMSDNライブラリでメンバの一覧などに一通り目を通しておくとよいでしょう。

一般的にデータの表示、または表に関する制御のことを「ビュー」と呼び、「ビュー」はデータの本体ではないという暗黙の了解があります。VBのDataViewDataRowViewもこういったビューの考え方に基づいているクラスです。

メモ  -  禁則文字

この例ではテキストボックスに「'」(シングルクォーテーション)を入力するとエラーになります。不完全なフィルター式が生成されるからです。

 

参考:複数列のComboBox

ComboBoxに複数の列を表示できるようにするにはComboBoxを継承したカスタムコントロールを作成し、OnDrawItemメソッドを上書きします。勘所が分かっていれば大したことのないプログラムですが、経験のない方にはハードルが高いと思いますのでごく簡単な例を紹介しておきます。

■画像6

まず、クラスをひとつ追加して次のように記述してください。

VB.NET2002対応 VB.NET2003対応 VB2005対応

Public Class ComboBoxEx

    Inherits ComboBox

    Public ListMember1 As
String
   
Public ListMember2 As String

   
Private Const ListWidth1 As Integer = 30
    Private Const ListWidth2 As Integer = 100

    Public
Sub New()

        Me
.DrawMode = Windows.Forms.DrawMode.OwnerDrawFixed
        Me.DropDownWidth = ListWidth1 + ListWidth2 + 20

    End
Sub

    Protected Overrides Sub OnDrawItem(ByVal e As System.Windows.Forms.DrawItemEventArgs)

       
'▼データの内容を取得

        Dim
Row As DataRowView
        Dim ItemString1 As
String
       
Dim ItemString2 As String

       
Row = DirectCast(Me.Items(e.Index), DataRowView)
        ItemString1 = Row(ListMember1)
        ItemString2 = Row(ListMember2)

       
'▼項目と線を描画

        Dim
Rect As RectangleF
        Dim myBrush As Brush
        Dim LineLeft As Integer = e.Bounds.X + ListWidth1

        If
e.State And DrawItemState.Selected
Then
           
myBrush = New SolidBrush(Color.White)
       
Else
           
myBrush = New SolidBrush(Me.ForeColor)
        End
If

       
'背景
       
e.DrawBackground()

       
'コード
       
Rect = New RectangleF(e.Bounds.X, e.Bounds.Y, LineLeft, e.Bounds.Height)
        e.Graphics.DrawString(ItemString1, e.Font, myBrush, Rect)

       
'区切り線
       
e.Graphics.DrawLine(Pens.Black, LineLeft, e.Bounds.Y, LineLeft, e.Bounds.Y + e.Bounds.Height)

       
'名前
       
Rect = New RectangleF(LineLeft + 1, e.Bounds.Y, e.Bounds.Width - LineLeft - 1, e.Bounds.Height)
        e.Graphics.DrawString(ItemString2, e.Font, myBrush, Rect)
        e.DrawFocusRectangle()
        e.Graphics.Flush()

    End
Sub

End
Class

■リスト12:2列表示できるコンボボックス

これでいったんビルドするとツールボックスに「ComboBoxEx」というコントロールが追加されて使用可能になります。ここから貼り付けて使用してもよいのですが、今回の例はプロパティウィンドウとの連携など関連のない部分を省いていることもありますので、すべてをプログラムから制御することにります。

フォーム側に次のように記述すれば完成です。フォームの先頭にImports System.Data.OleDbと記述することも忘れないでください。

VB.NET2002対応 VB.NET2003対応 VB2005対応

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    '▼データ取得
   
Dim Cn As New OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Database\Animals.mdb")
    Dim SQLCm As OleDbCommand = Cn.CreateCommand
    Dim Adapter As New OleDbDataAdapter(SQLCm)
    Dim Table As New DataTable

    SQLCm.CommandText =
"SELECT * FROM T_動物マスタ"
   
Adapter.Fill(Table)

   
'▼値の表示
   
Dim MyCombo As New ComboBoxEx

    MyCombo.DataSource = Table
    MyCombo.DisplayMember =
"名前"
   
MyCombo.ListMember1 = "動物ID" '一覧の列目の値
   
MyCombo.ListMember2 = "名前" '一覧の列目の値
   
MyCombo.Location = New Point(20, 20)
    MyCombo.DropDownHeight = 140
    Me.Controls.Add(MyCombo)

   
'▼後処理
   
Adapter.Dispose()
    SQLCm.Dispose()
    Cn.Dispose()

End
Sub

■リスト13:ComboBoxExクラスの利用。すべてをプログラムから制御する例。

ComboBoxExクラスの方は一度作成してしまえばいろいろなプロジェクトで使いまわすことができます。もっと機能を充実させてクラスライブリ化しておくと便利です。