Visual Basic データベース講座 |
DataGridView, ComboBox等のデータの一覧表示について説明します。
概要 ・DataSourceプロパティを使用してデータを一覧表示できるのはDataGridView, ComboBox, ListBox, CheckedListBoxであり、方法はどれもほとんど同じ。 ・2列表示できるComboBoxの例 ・BindingContextを使うと連結されているデータソースを一元的に制御できる。 ・ビューを使うと連結されているデータの「表示」を制御できる。ビューを制御するにはDataViewやDataRowViewを使用する。 |
前回も紹介しましたが、取得したデータをDataGridViewに一覧形式で表示するにはDataGridViewのDataSourceプロパティにDataTableをセットすればよいのでした。これは下の例のようになります。
この例を実行するためにはフォームの先頭にImports System.Data.OleDbと記述する必要がある点を忘れないでください。
'▼データ取得 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だけでなくComboBoxやListBox, CheckedListBoxでも使用できます。
ComboBoxの例を見てみましょう。残念ながら表形式で表示できないComboBoxの性質上、上記の例のDataGridViewの部分をComboBoxに変えただけではだめで、次のように何行か書き換えることになります。
'▼データ取得 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には何も表示されなくなってしまうのです。DataGridViewとComboBoxになぜこのような違いがあるのかは不明ですが、考えてみるとDataGridViewの方がおかしい感じもします。この件は不問のまま話をすすめます。
ListBoxやCheckedListBoxの場合は、ComboBoxと全く同じようにしてデータ一覧を表示することができます。ただし、CheckedListBoxのDataSourceプロパティは公式にはサポートされていませんので重要なプログラムには使用すべきではないようです。
DataGridViewのときがちょっと手法が違うとはいえ全体的には統一感があ ります。ひとつ新しいことを覚えるとどんどん応用が広がっていくのは.NET Frameworkのいいところです。
今回はこの方法での一覧表示について掘り下げていきます。
ComboBoxとListBoxの場合は、1つの列だけが一覧形式で表示されます。複数列表示できるようにするにはカスタムコントロールを作成することになります。複数列表示は本題からはずれてしまうのですが興味 のある方が多いと思うのでこのページの下の方に掲載しておきます。
さて、テーブルの中から実際に表示する列を選択するには先程の例にもあったようにDisplayMemberプロパティを使用します。DisplayMemberに指定しなかった列は表示はされませんが、内部ではちゃんと保持されておりプログラムからアクセスすることができます。
現在選択されている行を取得するには次のようにします。この例ではlblNoteというLabelを配置して、ComboBoxの選択内容が変わるたび に選択されている動物の「説明」がLabelに表示されるようにしています。
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を配置してComboBox1のSelectedIndexChangedイベントを次の通りに書き換えてください。
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:画像があるととプログラムがしょぼくてもそれらしく見える。なお、この画像の場合はPictureBoxのSizeModeはZoomにしてある。
これで他にもいろいろな画像を登録するだけでも楽しいデータベースアプリケーションになりそうですね。
DataTableと各コントロールの結びつきは一元管理されており、特に工夫をしていない場合はレコードの移動は連動します。
たとえば、DataGridViewとComboBoxとListBoxをフォームに配置してこの3つに同じテーブルの内容を表示させてみます。先程の例と似ていますが説明の便宜のために少し変えてあります。
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を取得します。
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クラスの基底クラスです。どうしても気にいならければDirectCastやCTypeを使って明示的に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を加えるだけです。
Dim Table As
DataTable = DirectCast(DataGridView1.DataSource,
DataTable) Dim Bind As BindingManagerBase = Me.BindingContext(Table) Bind.Position += 1 |
■リスト7:次のレコードへ移動
Positionの値に実際にレコードが存在する範囲外の値をセットすると、セットしようとして値に一番近いレコードにジャンプします。エラーにはなりません。
たとえば、[T_動物マスタ]テーブルには100件のレコードがありますが、500件目に移動しようとすると100件目にジャンプします。
DataTableはデータそのものを表していますが、これとにたものにDataViewというクラスがあり、こちらはデータの「表示」を制御しています。さきほどDataRowとDataRowViewが登場しましたがその関係と同じです。
DataViewを使用するとデータの並び替え、フィルタリング、検索などを行うことができます。これらの機能はDataViewを使用しなくても実現は可能ですが、DataViewを使用した方が簡単に実現できます。
DataViewは自分でインスタンスを生成して使用することもできますが、DataTableにはじめから既定のDataViewが用意されておりDefaultViewプロパティでアクセスできます。
手始めにデータの並び替えを行ってみましょう。データを並び替えるにはDataViewのSortプロパティを使用します。次の例では名前順に動物を並べて表示します。
Dim
Table As DataTable =
DirectCast(DataGridView1.DataSource, DataTable) Dim View As DataView = Table.DefaultView View.Sort = "名前" |
■リスト8:名前による並び替え
この例を実行するには今までの例と同様に[T_動物マスタ]テーブルをあらかじめDataGridViewのDataSourceをセットしておく必要があります。
なお、DataGridViewにはヘッダー部をクリックすると自動的にレコードを並び替える機能があるので上記の例は実際にはほとんど出番はないでしょう。
今度はデータのフィルタリングを紹介します。次の例は名前が「サーバル」であるデータだけを表示します。
Dim Table
As DataTable =
DirectCast(DataGridView1.DataSource, DataTable) Dim View As DataView = Table.DefaultView View.RowFilter = "名前='サーバル'" |
■リスト9:フィルターの設定
データをフィルタリングするにはRowFilterプロパティを使用します。RowFilterプロパティに設定できる内容はSQLのWhere句とだいたい同じです。フィルタリングは表示を制御しているだけなので見えなくなったデータは消えたわけではありません。
次のようにしてフィルターを解除すると再びすべてのデータが表示されます。
Dim
Table As DataTable =
DirectCast(DataGridView1.DataSource, DataTable) Dim View As DataView = Table.DefaultView View.RowFilter = Nothing |
■リスト10:フィルターの解除
このフィルター機能は単一の値を表示させる以外にも使い道があります。「=」の他に「<」、「>」などが使用できるので数値データの場合は指定した範囲を表示させることができますし、文字データの場合には「LIKE」を使用してパターンマッチングを行うことができます。
パターンマッチングとはワイルドカードとような指定方法のことで、たとえば、「イ」からはじまる動物を抽出するにはRowFilterプロパティに「名前 LIKE 'イ%'」と指定します。「%」はLIKE演算子と一緒にもちいると「0文字以上の文字」という意味になります。
これを利用して検索用テキストボックスに値を入力するたびにリアルタイムに値を抽出する機能が簡単に作成できます。
検索用のテキストボックスとDataGridViewだけを配置したフォームで次のようにプログラムしてみてください。
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のDataView、DataRowViewもこういったビューの考え方に基づいているクラスです。
メモ - 禁則文字 この例ではテキストボックスに「'」(シングルクォーテーション)を入力するとエラーになります。不完全なフィルター式が生成されるからです。 |
ComboBoxに複数の列を表示できるようにするにはComboBoxを継承したカスタムコントロールを作成し、OnDrawItemメソッドを上書きします。勘所が分かっていれば大したことのないプログラムですが、経験のない方にはハードルが高いと思いますのでごく簡単な例を紹介しておきます。
■画像6
まず、クラスをひとつ追加して次のように記述してください。
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) |
■リスト12:2列表示できるコンボボックス
これでいったんビルドするとツールボックスに「ComboBoxEx」というコントロールが追加されて使用可能になります。ここから貼り付けて使用してもよいのですが、今回の例はプロパティウィンドウとの連携など関連のない部分を省いていることもありますので、すべてをプログラムから制御することにります。
フォーム側に次のように記述すれば完成です。フォームの先頭にImports System.Data.OleDbと記述することも忘れないでください。
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クラスの方は一度作成してしまえばいろいろなプロジェクトで使いまわすことができます。もっと機能を充実させてクラスライブリ化しておくと便利です。