BindingNavigatorコントロールの拡張
ナビゲーションツールバーの問題を解決して機能を拡張する方法の1つは、BindingNavigatorコントロールをベースとしたユーザーコントロールを作成し、必要なプロパティとメソッドを追加することです。
- Windowsプロジェクトを右クリックし、[Add]-[New Item]をクリックします 項目のリストから[User Control]を選び、「exBindingNavigator.vb」と名前を付けます(この拡張を他のアプリケーションでも使いたい場合は、ユーザーコントロールとカスタムコントロールを独立したプロジェクトに配置する必要があります。今回のサンプルでは、Windowsプロジェクトの中にそのまま残します)。
- 空のコンテナを持った空白のコントロールが作成されます。右下の角を、BindingNavigatorツールバーと同じ大きさになるまでドラッグします。
- [Customer]フォーム(あるいはデータソースウィザードがナビゲーションコントロールの作成に利用した任意のフォーム)に戻り、ナビゲーションツールバーをコピーします。
- ユーザーコントロールに戻り、ナビゲーションバーをコンテナに貼り付けます。
- プロパティボックスで、Navigatorの名前を「CustomerBindingNavigator」から「GenericBindingNavigator」などの汎用的な名前に変更します。
- 保存ボタン(フロッピーディスクのアイコン)をクリックし、プロパティパネルで名前をより汎用的な「BindingNavigatorSaveItem」に変更します。
- フォーム上で右クリックし、[View Code]をクリックしてコードページを開きます。
Sub Newコンストラクタを追加し、コンストラクタ内でDockプロパティをフォームの上端にセットします。 - まず必要なのは、ナビゲーションバーのバインディングソースの参照を追跡するプロパティです。バインディングソースを定義するときに必要な作業の1つは、基となるテーブルへの参照を定義することです。ここで、そのテーブルへの参照を取得するコードを含めることもできます。ただし場合により、バインディングソースが定義されないうちはデータリソースも定義されないか、あるいはデータソースが変更される可能性があります。そのため、
DataSourceChangedイベントを処理するイベントハンドラを追加して、テーブル参照をセットするメソッドを呼び出します。テーブルに自動的にデータを読み込むために、格納元のフォームへの参照を取得し、Form Loadイベントをサブスクライブします。 - 上の
DataSourceChangedイベントを処理するメソッドを追加します。 - 次に、
GetTableFromBindingSourceというメソッドを追加します。このメソッドは、BindingSourceをパラメータとして受け取り、データテーブルへの参照を戻します。 - 以下のメソッドを
exBindingNavigatorクラスに追加して、保存ボタンのクリックイベントを処理します(保存ボタンをダブルクリックして、コードスタブを生成することもできます)。 Form Loadイベントを処理するメソッドをもう1つ追加すると、フォームが開いたときにテーブルに自動的に値を読み込むことができます。テーブルは大抵の場合、必要な場合にロジックを使って値を読み込むことが多いので、この処理は必須というわけではありません。そのため、この機能を有効にするかどうかを判別するプロパティを追加して、有効/無効を開発者が選択できるようにします。- 最後に、親フォームの
Loadイベントをサブスクライブすることによって、これらすべてが実行されるようにします。ユーザーコントロールのLoadイベントが起こると、親フォームへの参照が取得できるようになります。
Public Class exBindingNavigator Public Sub New() ' This call is required by the Windows Form Designer. InitializeComponent() ' Add any initialization after the InitializeComponent() call. Me.Dock = DockStyle.Top End Sub End Class
Private WithEvents _BindingSource As BindingSource Public Property BindingSource() As BindingSource Get Return _BindingSource End Get Set(ByVal value As BindingSource) GenericBindingNavigator.BindingSource = value _BindingSource = value If Not _BindingSource Is Nothing Then 'subscribe to the events in case not yet set AddHandler _BindingSource.DataSourceChanged, _ AddressOf bs_DataSourceChanged 'get a reference to the table now bs_DataSourceChanged(New Object, New EventArgs) End If End Set End Property
Private Sub bs_DataSourceChanged(ByVal sender As Object, _ ByVal e As EventArgs) If Not _BindingSource Is Nothing Then _DataTable = GetTableFromBindingSource( _ GenericBindingNavigator.BindingSource) If Not _DataTable Is Nothing Then 'if child BS, get reference to parent BS Dim testBS As BindingSource = _ TryCast(GenericBindingNavigator.BindingSource.DataSource, _ BindingSource) If Not testBS Is Nothing Then 'call the getter to capture event ParentBindingSource = testBS End If End If End If End Sub
DataSourceとDataMenberという2つのプロパティがあり、それによってどのテーブルにバインドされるかが決まります。大抵の場合、DataSourceにはデータセットのインスタンスを指定し、DataMemberにはデータセット内のテーブルの名前を指定します。しかし、親子関係が確立されている状況では、DataSourceにもう1つのBindingSourceを指定し、DataMemberにリレーションの名前を指定することができます。この場合、テーブルを導き出すには、少し計算が必要です。Public Function GetTableFromBindingSource(ByVal bs As BindingSource) 'get a reference to the dataset Dim ds As DataSet, dt As DataTable 'try to cast the data source as a binding source Dim bsTest As BindingSource = bs Do While Not TryCast(bsTest.DataSource, BindingSource) Is Nothing 'if cast was successful, walk up the chain until dataset is reached bsTest = CType(bsTest.DataSource, BindingSource) Loop 'since it is no longer a binding source, it must be a dataset If TryCast(bsTest.DataSource, DataSet) Is Nothing Then 'Cast as dataset did not work Throw New ApplicationException("Invalid Binding Source ") End If ds = CType(bsTest.DataSource, DataSet) 'check to see if the Data Member is the name of a table in the dataset If ds.Tables(bs.DataMember) Is Nothing Then 'it must be a relationship instead of a table Dim rel As System.Data.DataRelation = ds.Relations(bs.DataMember) If Not rel Is Nothing Then dt = rel.ChildTable Else Throw New ApplicationException("Invalid Data Member") End If Else dt = ds.Tables(bs.DataMember) End If If TryCast(dt, ITableUpdate) Is Nothing Then Throw New ApplicationException("Table " & dt.TableName & _ " does not implement ITableUpdate interface") End If Return dt End Function
DataSourceがBindingSourceである場合は、基になるデータセットを取得できる時点まで道筋をさかのぼっていく必要があります。ひとたびデータセットを取得すれば、DataMemberがデータセット内のテーブルを指しているかどうかを確認できます。テーブルでない場合は、DataMemberはリレーションを指しています。データセット内の一連のリレーションのなかから該当するリレーションを探し、そのリレーションのChildTableプロパティからテーブル参照を取得することができます。Private Sub SaveItem_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Me.Validate() _BindingSource.EndEdit() 'cast table as ITableUpdate to get the Update method CType(_DataTable, _Interface.ITableUpdate).Update() IsDataDirty = False End Sub
Private _AutoFillFlag As Boolean = True Public Property AutoFillFlag() As Boolean Get Return _AutoFillFlag End Get Set(ByVal value As Boolean) _AutoFillFlag = value End Set End Property Private Sub Form_Load(ByVal sender As Object, ByVal e As EventArgs) If _AutoFillFlag Then 'cast table as ITableUpdate to get the Fill method CType(_DataTable, _Interface.ITableUpdate).Fill() End If End Sub
Private Sub exBindingNavigator_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load 'get the reference to the hosting form Dim frm As Object = CType(Me, ContainerControl).ParentForm While TryCast(frm, System.Windows.Forms.Form) Is Nothing 'if not a form, walk up chain If Not TryCast(frm, System.ComponentModel.Container) Is Nothing Then frm = CType(frm, ContainerControl).Parent Else frm = CType(frm, Control).Parent End If End While _Form = CType(frm, System.Windows.Forms.Form) 'add the handler for the Form Load to fill the table AddHandler _Form.Load, AddressOf Form_Load End Sub
このユーザーコントロールをフォームに追加し、BindingSourceプロパティをセットすると、保存ボタンはプログラムで使用するあらゆるデータセットとプロパティセットを扱えるようになり、テーブルに自動的に値が読み込まれるようになります(インターフェイスを実装している限り)。これで、1つ目の問題は解決です。
