Visual Basic データベース講座 |
TextBox, Label等をDataTableと連動させる方法を説明します。
概要 ・コントロールのDataBindings.Addを使用するとプロパティとデータソースを連動させることができる。この手法を「データ連結」と呼ぶ。 ・個々のプロパティとデータソースの連結はBindingクラスにより管理されている。 ・データ連結は表示の制御だけではなく、値の更新もできる。ただし、更新した値をデータベースに反映させるためには別途コードを記述する必要がある。 ・レコードを削除するにはDataRow.Deleteを使用する。 ・レコードを新規追加するにはDataRow.AddNewを使用する。 ・BindingクラスのFormatイベントを使用すると表示される値の書式を設定することができる。 ・BindingクラスのParseイベントを使用すると入力された値の書式を解釈することができる。 |
TextBoxやLabelなど標準で用意されているほとんどのコントロールはDataTableなどと連動して自動的にデータを表示・処理する機能を持っています。このような連動機能のことをデータ連結と呼びます。またデータ連結を行うときに元となるDataTableなどの情報源のことを「データソース」と呼びます。
この講座では当初よりデータソースとしてDataTableのみを扱っています。今回もDataTableを基に説明を進めますが、DataTable以外のデータソースでも基本的な使用方法は同じなのでここでの説明は全般的に有益なものになります。
さて、データ連結はコントロール自体が持っているプロパティを使用して簡単に行うことができます。実際のところプログラマに必要なのはデータソースを準備する作業だけで、この作業も前回までとまったく同じ手法で行うことができます。
さっそく簡単な例を試してみましょう。フォームにLabelとTextBoxを1つずつ配置してください。名前は以下の表のとおりにします。
名前 | 種類 |
lblName | Label |
txtNote | TextBox |
■表1
プロパティの設定は特に必要ありませんが、txtNoteのMultiLineプロパティをTrueにして大きめに配置すると少し見栄えが良くなるかもしれません。
プログラムは次の通りです。
Imports System.Data.OleDb Public Class Form1 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) '▼データ連結 lblName.DataBindings.Add("Text", Table, "名前") txtNote.DataBindings.Add("Text", Table, "説明") '▼後処理 Table.Dispose() Adapter.Dispose() SQLCm.Dispose() Cn.Dispose() End Sub End Class |
■リスト1
このコードを実行するとlblNameとtxtNoteに[T_動物マスタ]から取得した動物の[名前]と[説明]が表示されます。
■画像1
DataGridViewを使用して一覧表示する例とほとんど変わらないプログラムでしょう。ただ、LabelもTextBoxもDataGridViewのようにデータを一覧で表示することはできず、一度に表示できる値を1つだけです。ですから、DataSourceプロパティにDataTableをセットすればすべてが完了というわけにはいきません。
この違いが上記のプログラムでは「データ連結」とコメントしてあるブロックに表れています。
一般にコントロールのデータ連結では「どのプロパティ」を「どのデータソース」の「どの項目」に結びつけるのかを指定する必要があります。lblNameの場合で言うと「Textプロパティ」に「Table」の「[名前]列」を結びつけるということになります。「どの項目」という部分の項目のことを「データメンバー」と呼ぶ場合もあり、この用法ではlblNameのTextプロパティのデータメンバーは[名前]と表現されます。
プログラム上でこういったことを指定している部分を抜き出してみます。
lblName.DataBindings.Add("Text", Table, "名前")
具体的にどう指定しているかは一目瞭然ですね。構造的にもう少しこの一行を考えてみるとデータ連結はDataBindingsプロパティによって管理されているとうことがわかります。DataBindingsプロパティはコレクションであり、複数のデータ連結を管理することができます。上記の例ではデータ連結は1つだけです。データ連結自体はBindingクラスによって表現されます。
ですからこの1行は次のように書き換えることもできます。
Dim Bind As
Binding Bind = New Binding("Text", Table, "名前") lblName.DataBindings.Add(Bind) |
■リスト2
ところで、このプログラムはテーブルの最初のレコードの値しか表示できません。次のレコードに移動したり、前のレコードに移動したりするにはどうしたらよいでしょうか?前回の説明した手法を使用すれば比較的簡単にこの機能を付けることができるのでやる気にあふれている方は挑戦してみてください。
■画像2
実装例は次の通りです。フォームには次のレコードに進むためのbtnNextと前に戻るためのbtnPreviousという2つのButtonを追加します。そしてこの2つのButtonに次の通りにプログラムすれば完了です。
Private Sub
btnNext_Click(ByVal sender
As System.Object,
ByVal e As System.EventArgs)
Handles btnNext.Click Dim Table As DataTable = DirectCast(lblName.DataBindings("Text").DataSource, DataTable) Dim Bind As BindingManagerBase = Me.BindingContext(Table) Bind.Position += 1 End Sub |
Private Sub
btnPrevious_Click(ByVal sender
As System.Object,
ByVal e As System.EventArgs)
Handles btnPrevious.Click |
■リスト3
この例ではlblNameのDataBindingsからデータソースを取得していますが、FormのLoadイベントで取得したDataTableを変数にとっておけばいちいちこのような手間をかける必要はなくなります。私は適用範囲の広い変数を増やしたくないので そのつどDataBindingsから取得する方が好きなのですが具体的な事例に応じて構造は変わります。
このプログラムでは前回紹介したようにBindingContextオブジェクトによるデータソースの一元管理機能を使用しています。プログラム中に登場する変数Bindもコード上はBindingManagerBase型ですが、実際にはCurrencyManager型です。この点も前回と同様です。
メモ リスト2では変数BindはBinding型であり、リスト3ではBindingManagerBase型です。変数の名前を同じにしていますが混同しないように注意してください。このあと登場する例でもBindは場合に応じてこらのどちらかを表す変数の名前になっています。 |
さて、このプログラムではデータを書き換えることも可能です。ためしに説明を表示しているtxtNoteの値を適当な値に書き換えてから次のレコードへ移動し、再び前のレコードに戻るとちゃんと値が書き変わっているのが確認できます。
しかし、この変更はあくまでもプログラム上で生成しているDataTableへの変更であってデータベース本体への変更ではない点に注意してください。ですから、いくら値を書き換えてもプログラムをいったん終了して、再度起動すると変更点はすべて失われて最初の状態に戻っています。データベース本体を変更するには変更されたDataTableの値をデータベースに反映させる処理を別途記述する必要があるのです。
この手法はデータベース講座第4回 DataTableの利用の中で「5.変更点のデータベースへの反映」の部分に詳しく紹介しています。
同じ手法ではありますが、今回のサンプルに適合する形で書き換えたものをここでも念のために掲載しておきます。
Dim Cn As
New OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data
Source=C:\Database\Animals.mdb") Dim SQLCm As OleDbCommand = Cn.CreateCommand Dim Table As DataTable = DirectCast(lblName.DataBindings(0).DataSource, DataTable) '●現在編集中の項目を確定させる Dim Bind As BindingManagerBase = Me.BindingContext(Table) Bind.EndCurrentEdit() '●DataTableの変更内容をデータベースに反映する。 For Each Row As DataRow In Table.Rows Dim SQL As String = "" '▼SQL文の生成 Select Case Row.RowState Case DataRowState.Modified '▼修正されたレコードの場合 SQL = "UPDATE T_動物マスタ SET " SQL &= " 動物ID = " & Row("動物ID") & ", " SQL &= " 名前= '" & Row("名前") & "', " SQL &= " 目ID = " & Row("目ID") & ", " SQL &= " 説明= '" & Row("説明") & "' " SQL &= " WHERE " SQL &= " 動物ID = " & Row("動物ID", DataRowVersion.Original) Case Else Continue For End Select '▼更新実行 SQLCm.CommandText = SQL Cn.Open() SQLCm.ExecuteNonQuery() Cn.Close() Next Table.Dispose() SQLCm.Dispose() Cn.Dispose() |
■リスト4
今回の例ではこの処理をFormのFormClosingイベントに記述しておけば、プログラム終了時点で変更点が自動的にデータベースに書き込まれるので楽です。別途更新用のButtonを用意してクリック時にのみ変更点が反映されるようにしてもよいでしょう。
このコードの中で先頭に方にあるBind.EndCurrentEditはコメントにもあるとおり現在編集中の内容を確定させます。これはどういうことかというと、何もしない場合レコードの値が書き変わるのはレコードを移動するタイミングなのです。つまり、いくらtxtNoteに値を書き込んでもレコードを移動しない限りDataTableにすら変更点は反映されていない状態なのです。これが「現在編集中」という状態です。この状態で値をDataTableに反映させるためにEndBurrentEditメソッドを使用しています。もちろんこのメソッドの代わりにPositionを変更させてレコードを移動させる処理を記述しても構いません。なお、明示的にこれとは逆の操作、つまり編集中の内容を破棄するにはCancelCurrentEditメソッドを使用します。
データの削除も以前にデータベース講座第4回で紹介したのと同じ手法、つまりDataRow.Deleteメソッドで可能です。このようにVBのデータベースアプリケーションでは一つのことができるようになるといろいろなシーンで応用できるようになります。
ここではレコードを削除するためにbtnDeleteというButtonを1つ追加して次のように記述します。
Private Sub
btnDelete_Click(ByVal sender
As System.Object,
ByVal e As System.EventArgs)
Handles btnDelete.Click '▼削除の確認 Dim Answer As MsgBoxResult Answer = MsgBox("このレコードを削除しますか?", MsgBoxStyle.Question Or MsgBoxStyle.YesNo Or MsgBoxStyle.DefaultButton2) If Answer = MsgBoxResult.No Then 'Noが選択された場合は何もしないで抜ける Return End If '▼削除対象レコードの取得 'データソースであるDataTableの取得 Dim Table As DataTable = DirectCast(lblName.DataBindings(0).DataSource, DataTable) 'DataTableとコントロールの連結を制御しているCurrencyManagerの取得 Dim Bind As BindingManagerBase = Me.BindingContext(Table) 'CurrencyManagerが現在対象としているDataRowViewの取得 Dim RowView As DataRowView = DirectCast(Bind.Current, DataRowView) 'DataRowViewからDataRowを取得 Dim Row As DataRow Row = RowView.Row '削除実行 Row.Delete() End Sub |
■リスト5
削除のような重要な操作はユーザーに確認することが多いのでユーザーに確認するコードも含めておきました。
実際の削除はDataRow.Deleteメソッドで可能ですが、対象のDataRowを取得するためにちょっとばかり回りくどい手順が必要で、その手順には逐一コメントをつけておきました。
この部分はわかりやすいように複数行に分けましたが、VBの遅延バインディング機能を利用して次のように書くことも可能です。
Me.BindingContext(lblName.DataBindings(0).DataSource).Current.Row.Delete() |
■リスト6
DataTableをはじめの時点で変数に保存しておく構成にすればさらに短く書けますね。
なお、削除の場合も修正の場合と同様でDataTableのレコードを削除しているに過ぎません。ここで削除したレコードをデータベース上からも削除するにはやはりDataTableの変更をデータベースに反映させる処理を別に書く必要があります。その処理はレコードの新規追加を説明した後でまとめて紹介します。
レコードの新規追加も新規レコードの入力を開始するためのButtonを用意しておくことにしましょう。このButtonはbtnAddNewという名前にします。
レコードの新規追加自体はCurrencyManagerクラスのAddNewメソッドを使用するだけなのですが、いろいろと配慮しなければならないことがあります。今回は[説明]しか入力することができないのですが、レコードを新規追加するためには[動物ID]、[名前]、[目ID]をセットする必要があるのです。[画像]は省略しても構いません。
[T_動物マスタ]のレイアウト(構造)を確認してみましょう。
T_動物マスタの列名 |
動物ID |
名前 |
画像 |
目ID |
説明 |
■表2
[動物ID]はもともと意味のない番号であり、各レコードを識別するためだけにあります。他のレコードと重複しない番号であれば何でもよいのでデータベースに登録するときに現在 登録されているの[動物ID]の中で最大のものに1を加えた値をセットすることにします。
[名前]はInputBoxを使ってユーザーに入力させることにします。
[目ID]は妙な話ですが必ず「1」を登録することにします。[T_目マスタ]を見ると、[目ID]が1なのは「霊長目」となっていますのでこの仕様はおかしいのですし、[目ID]までユーザーに指定して登録させることももちろんできるのですが今回のテーマから考えるとプログラムが複雑になってしまうので割愛します。ちゃんとした登録プログラムはデータベース講座のもっとあとの方で紹介する予定ですのでしばしお待ちください。
さて、以上の方針でいった場合のbtnAddNewのプログラム例は次のようになります。
Private Sub
btnAddNew_Click(ByVal sender
As System.Object,
ByVal e As System.EventArgs)
Handles btnAddNew.Click Dim Table As DataTable = DirectCast(lblName.DataBindings(0).DataSource, DataTable) Dim Bind As BindingManagerBase = Me.BindingContext(Table) '▼名前の取得 Dim AnimalName As String AnimalName = InputBox("動物の名前を入力してください。") If Len(Trim(AnimalName)) = 0 Then '名前を入力しなかった場合は新規追加をキャンセルしたものとみなす。 Return End If '▼レコードの新規追加 Bind.AddNew() '▼新規レコードの初期値 Dim Row As DataRow Row = DirectCast(Bind.Current, DataRowView).Row Row("名前") = AnimalName Row("目ID") = 1 Bind.EndCurrentEdit() End Sub |
■リスト7
[名前]と[目ID]の値は初期値としてここで設定してしまいます。設定した値がすぐに反映されるようにEndCurrentEditメソッドも呼び出します。[動物ID]はデータベースを閉じるときにセットすることにします。ここでセットしてもいいのですが、セットするためには再度データベースにアクセスして現在登録されている最大の[動物ID]を取得してこなければいけません。それはちょっと面倒です。
データベースに登録するときにどうせデータベースにアクセスするのですからその時についでに[動物ID]を持ってきた方が楽です。それに今はまだ説明していないことなのですが、将来排他制御を考慮したときにIDの採番は1つのトランザクション内で行った方が簡単で済みます。
「排他制御」や「トランザクション」と言ったキーワードは複数の人が同時に同じデータベースを更新しようとしているときに問題になる事柄です。今は無視しておいてください。
他には特に難しい点はないでしょう。AddNewメソッドを実行した時点で新しく追加されたレコードがCurrentになるので、新規レコードに対して処理を行いたい場合はAddNewの直後にCurrentプロパティでレコードを取得します。
InputBoxはあまり使ったことがない方も多いかもしれませんが、値を1つだけ入力させるときに大変便利なメソッドです。
これで、DataTableに対してレコードの削除と新規追加ができるようになりました。最後にDataTableに対する変更をデータベースに反映させるコードも紹介しておきます。ここでは新規追加の際に[動物ID]の最大値+1を取得するという動作のほかは新しい事柄は何もありません。
Dim Cn As
New OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data
Source=C:\Database\Animals.mdb") Dim SQLCm As OleDbCommand = Cn.CreateCommand Dim Table As DataTable = DirectCast(lblName.DataBindings(0).DataSource, DataTable) Dim Bind As BindingManagerBase = Me.BindingContext(Table) Bind.EndCurrentEdit() For Each Row As DataRow In Table.Rows Dim SQL As String = "" '●SQL文の生成 Select Case Row.RowState Case DataRowState.Added '▼新規追加されたレコードの場合 '動物IDを採番 Dim AnimalID As String Dim IDCommand As OleDbCommand = Cn.CreateCommand IDCommand.CommandText = "SELECT MAX(動物ID) + 1 FROM T_動物マスタ" Cn.Open() AnimalID = IDCommand.ExecuteScalar Cn.Close() IDCommand.Dispose() 'SQLを作成 SQL = "INSERT INTO T_動物マスタ VALUES (" SQL &= AnimalID & ", " '動物ID SQL &= "'" & Row("名前") & "', " '名前 SQL &= "'" & Row("画像") & "', " '画像 SQL &= Row("目ID") & ", " '目ID SQL &= "'" & Row("説明") & "' " '説明 SQL &= ")" Case DataRowState.Deleted '▼削除されたレコードの場合 SQL = "DELETE FROM T_動物マスタ WHERE " SQL &= " 動物ID = " & Row("動物ID", DataRowVersion.Original) Case DataRowState.Modified '▼修正されたレコードの場合 SQL = "UPDATE T_動物マスタ SET " SQL &= " 動物ID = " & Row("動物ID") & ", " SQL &= " 名前= '" & Row("名前") & "', " SQL &= " 目ID = " & Row("目ID") & ", " SQL &= " 説明= '" & Row("説明") & "' " SQL &= " WHERE " SQL &= " 動物ID = " & Row("動物ID", DataRowVersion.Original) Case Else Continue For End Select '●更新実行 SQLCm.CommandText = SQL Cn.Open() SQLCm.ExecuteNonQuery() Cn.Close() Next Table.Dispose() SQLCm.Dispose() Cn.Dispose() |
■リスト8
[動物ID]を取得するためのSQL文とそのSQL文を実行するExecuteScalarメソッドについては次回SQL文について詳しく取り上げるときにも説明する予定ですが、それでは気味が悪いという方もいらっしゃると思いますのでここでも簡単に書いておきます。
ExecuteScalarメソッドはデータベース講座第3回 データの読み書きですでに説明しています。このメソッドはSQLのSELECT文を実行して結果から値を1つだけとってきてくれる便利なメソッドです。ExecuteScalarメソッドがなかったら我々はデータベースから値を1つだけ取り出したい場合でもいちいちDataTableを生成してそこから値をとりださなければなりません。
それで肝心のSQL文の方ですが次のようにしました。
SELECT MAX(動物ID) + 1 FROM T_動物マスタ |
MAXというのはSQLの関数であり、最大値を取得します。つまりMAX(動物ID)とは[動物ID]の最大値という意味であり、「+ 1」とありますから、式全体としては[動物ID]の最大値に1を加えるという操作を表しています。
ですからExecuteScalarメソッドを使用してこのSQL文を実行すると[動物ID]の最大値に1を加えた値を取得することができるというわけです。
念のために書いておきますが、このSQL文はSELECT文ですのでデータ自体の更新は一切行いません。「+ 1」というのも「1を足したものを『取得する』」だけで、何かのデータに1を足すということではありません。
次回はSQL文をテーマに説明しますので、詳しいことは次回の説明を読んでから考えてみてください。
さて、今回コントロールとデータソースを結びつける基本的な機能を提供しているBindingクラスについてもう少し踏み込んで考えてみます。
BindingクラスはTextプロパティ以外にもたいていのプロパティとデータ連結を行うことができます。実際にはTextプロパティと連結するケースがほとんどでしょうが、必要であればあらゆるプロパティを対象とできるのです。もちろん型が一致していないなどの通常の問題は無視するわけにはいきませんが。
ちょっとした例としてCheckBoxのCheckedプロパティとのデータ連結例を紹介しましょう。残念ながらAnimals.mdbにはCheckedプロパティとして使用できるデータは格納されていないのDataTable自体を作成するところから始めます。
DataTableはデータベースから読み込んできた値を格納できるだけではなく、自分でゼロから構築することも可能なのです。DataTableを構築するコードは次のようになります。
Dim Table
As New
DataTable Table.Columns.Add("項目", GetType(String)) Table.Columns.Add("状態", GetType(Boolean)) |
■リスト9
これだけで、文字列型の[項目]列とブール型の[状態]列を持つDataTableが作成できます。
さらにサンプルとしてこのDataTableにデータを書き込むには次のようにします。
Dim
Row As DataRow Row = Table.NewRow Row("項目") = "東洋大学" Row("状態") = True Table.Rows.Add(Row) Row = Table.NewRow Row("項目") = "駒沢大学" Row("状態") = False Table.Rows.Add(Row) Row = Table.NewRow Row("項目") = "専修大学" Row("状態") = True Table.Rows.Add(Row) |
■リスト10
これでこのDataTableの内容は次のようになります。
項目 | 状態 |
東洋大学 | True |
駒沢大学 | False |
専修大学 | True |
■表3
最後に[項目]列をTextプロパティに連結し、[状態]列をCheckedプロパティに連結するようにBindingを調整します。
CheckBox1.DataBindings.Add("Text",
Table, "項目") CheckBox1.DataBindings.Add("Checked", Table, "状態") |
■リスト11
これだけだとテストにならないので2つのButton(btnNextとbtnPrevious)も追加して完成版の全コードは次のようになります。
Private Sub Form1_Load(ByVal
sender As System.Object,
ByVal e As
System.EventArgs) Handles
MyBase.Load Dim Table As New DataTable Table.Columns.Add("項目", GetType(String)) Table.Columns.Add("状態", GetType(Boolean)) Dim Row As DataRow Row = Table.NewRow Row("項目") = "東洋大学" Row("状態") = True Table.Rows.Add(Row) Row = Table.NewRow Row("項目") = "駒沢大学" Row("状態") = False Table.Rows.Add(Row) Row = Table.NewRow Row("項目") = "専修大学" Row("状態") = True Table.Rows.Add(Row) CheckBox1.DataBindings.Add("Text", Table, "項目") CheckBox1.DataBindings.Add("Checked", Table, "状態") End Sub |
Private Sub btnNext_Click(ByVal
sender As System.Object,
ByVal e As
System.EventArgs) Handles btnNext.Click Dim Table As DataTable = DirectCast(CheckBox1.DataBindings(0).DataSource, DataTable) Dim Bind As BindingManagerBase = Me.BindingContext(Table) Bind.Position += 1 End Sub |
Private
Sub btnPrevious_Click(ByVal
sender As System.Object,
ByVal e As
System.EventArgs) Handles btnPrevious.Click |
■リスト12
これでプログラムを実行して、レコードを切り換えるとCheckBoxの文字だけではなくチェック状態もちゃんと切り替わるのが確認できます。
次にBindingクラスのイベントを紹介しましょう。Formatイベントを使うと表示する文字を自由に設定することができます。また、Paraseイベントを使うと入力された文字を自由に解釈してデータソースに登録することができます。
プログラム内で生成したクラスのイベントをトラップするにはWithEventsかAddHandlerを使用します。今回はWithEventsを使用する方法で説明を進めます。
次のプログラムではTextBoxに[金額]を表示するときに3桁区切りのカンマを付加します。つまりデータ上は「1234」という金額でも、表示するときは「1,234」のように表示されます。逆にユーザーがTextBoxに「1,234」と入力した場合はカンマがを取り除いて「1234」と解釈して登録します。
LabelとTextBoxをフォームに配置して以下のプログラムを試してみてください。
Dim
WithEvents MoneyBinder
As Binding Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Dim Table As New DataTable Table.Columns.Add("商品名", GetType(String)) Table.Columns.Add("金額", GetType(Integer)) Dim Row As DataRow Row = Table.NewRow Row("商品名") = "ビグザム" Row("金額") = 8000 Table.Rows.Add(Row) Row = Table.NewRow Row("商品名") = "ギャン" Row("金額") = 5200 Table.Rows.Add(Row) Row = Table.NewRow Row("商品名") = "サザビー" Row("金額") = 12500 Table.Rows.Add(Row) Label1.DataBindings.Add("Text", Table, "商品名") MoneyBinder = New Binding("Text", Table, "金額") TextBox1.DataBindings.Add(MoneyBinder) End Sub |
Private Sub MoneyBinder_Format(ByVal
sender As Object,
ByVal e As
System.Windows.Forms.ConvertEventArgs) Handles
MoneyBinder.Format '3桁区切りのカンマを付ける e.Value = Format(e.Value, "#,0") End Sub |
Private Sub MoneyBinder_Parse(ByVal
sender As Object,
ByVal e As
System.Windows.Forms.ConvertEventArgs) Handles
MoneyBinder.Parse 'カンマを取り除く e.Value = Replace(e.Value, ",", "") End Sub |
Private Sub btnNext_Click(ByVal
sender As System.Object,
ByVal e As
System.EventArgs) Handles btnNext.Click Dim Table As DataTable = DirectCast(Label1.DataBindings(0).DataSource, DataTable) Dim Bind As BindingManagerBase = Me.BindingContext(Table) Bind.Position += 1 End Sub |
Private
Sub btnPrevious_Click(ByVal
sender As System.Object,
ByVal e As
System.EventArgs) Handles btnPrevious.Click Dim Table As DataTable = DirectCast(Label1.DataBindings(0).DataSource, DataTable) Dim Bind As BindingManagerBase = Me.BindingContext(Table) Bind.Position -= 1 End Sub |
■リスト13
FormatイベントもParseイベントも最終的にe.Valueに設定した値が使用される値となります。Formatイベントの場合は使用される値とは表示される値のことです。ParseイベントではDataTableに登録される値のことです。
ですから、たとえばFormatイベントで単にe.Value = "Hello"と記述すると、データがどのような値であれ常に「Hello」と表示されることになります。
データベース上で定義されている型とプロパティの型に互換性がない場合もFormatイベントとParseイベントで型変換を記述することでデータ連結が可能になります。どのような型に変換すべきかはプログラマにはわかっているはずですが、e.DesiredTypeプロパティを使って調べることもできます。
この仕組みを利用してPictureBoxに画像を表示させる例を紹介します。Animals.mdbの[T_動物マスタ]の[画像]列には画像のファイル名をセットするようになっています。ファイル名は文字列です。ところがPictureBoxのImageプロパティにはファイル名ではなくImage型である画像そのものを指定する必要があるので単純にImageプロパティに[画像]列を連結しただけではエラーになってしまいます。
ここでFormatイベントを使用すると画像のファイル名から画像を取得することができますのでPictureBoxには常にファイル名ではなく画像を渡すことができるようになります。
以下の例では画像が指定されていない場合に備えて空の画像(EmptyImage)も用意してあります。
前回[T_動物マスタ]のイエネコに画像を設定した方はそのままの状態で実行すると、ちゃんとネコの画像が表示されます。そうでない方も適当な動物に画像を設定して試してみてください。
プログラムを実行するには動物の名前を表示するためのLabelと、画像を表示するためのPictureBoxが必要です。レコードを移動するための2つのButtonも必要です。
Dim
WithEvents ImageBinder
As Binding Dim EmptyImage As New Bitmap(1, 1) '空の画像 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) '▼データ連結 Label1.DataBindings.Add("Text", Table, "名前") ImageBinder = New Binding("Image", Table, "画像") PictureBox1.DataBindings.Add(ImageBinder) '▼後処理 Table.Dispose() Adapter.Dispose() SQLCm.Dispose() Cn.Dispose() End Sub |
Private Sub ImageBinder_Format(ByVal
sender As Object,
ByVal e As
System.Windows.Forms.ConvertEventArgs) Handles
ImageBinder.Format If IsDBNull(e.Value) = False AndAlso IO.File.Exists(e.Value) Then '値に設定されているファイルが存在する場合 '元の値(=ファイル名)をTagプロパティにセットしてとっておく。 DirectCast(sender, Binding).Control.Tag = e.Value '画像を値としてセットする e.Value = Image.FromFile(e.Value) Else '値がNullかファイルが存在しない場合は空の画像を指定 e.Value = EmptyImage End If End Sub |
Private Sub ImageBinder_Parse(ByVal
sender As Object,
ByVal e As
System.Windows.Forms.ConvertEventArgs) Handles
ImageBinder.Parse If e.Value Is EmptyImage Then '空の画像が設定されている場合、値をNullにする。 e.Value = DBNull.Value Else '画像が表示されている場合、画像のファイル名をセットする。 e.Value = DirectCast(sender, Binding).Control.Tag End If End Sub |
Private Sub btnNext_Click(ByVal
sender As System.Object,
ByVal e As
System.EventArgs) Handles btnNext.Click Dim Table As DataTable = DirectCast(Label1.DataBindings(0).DataSource, DataTable) Dim Bind As BindingManagerBase = Me.BindingContext(Table) Bind.Position += 1 End Sub |
Private
Sub btnPrevious_Click(ByVal
sender As System.Object,
ByVal e As
System.EventArgs) Handles btnPrevious.Click Dim Table As DataTable = DirectCast(Label1.DataBindings(0).DataSource, DataTable) Dim Bind As BindingManagerBase = Me.BindingContext(Table) Bind.Position -= 1 End Sub |
■リスト14
このプログラムにはファイル名を画像に変換するためのFormatイベントのほかにParseイベントも存在します。実はParseイベントがない場合は、はじめの1回だけはうまくいくのですが、レコードを移動させてまた戻ってくると今度は画像が表示されなくなります。
これはレコード移動のタイミングでDataTableの値が更新されているためです。DataTable上は[画像]列はあくまでも文字列型ですので、FormatイベントでImage型に変換してもレコード移動のタイミングでまた文字列型に戻されてしまうのです。この動作はParseイベントが存在しない場合は自動的に行われます。Image型を自動的に文字列に変換すると元のファイル名に変換されるのではなく、単なる型の名前、つまり「System.Drawing.Bitmap」という文字列に変換されてしまい、元のファイル名は失われてしまいます。
そのためParseイベントを使って今度はImage型から元のファイル名に変換できるようにプログラムしているのです。
しかし、この変換は元のファイル名を覚えておかないとできないことですので、この例ではFormatイベントで画像を表示するときにコントロールのTagプロパティに元の画像のファイル名を記録しておくようにして、ParseイベントではそのTagプロパティの値を復元しているだけです。
メモ -
でも、データ連結を使わない方が楽です。 これらの例はあくまでデータ連結を使用した場合の例であって、FormatイベントやParseイベントが必要なケースでは、レコード移動のタイミングで独自のコードを仕掛けた方が楽だし応用が利きます。
■リスト15 |