リストの管理
アプリケーションのユーザーインターフェイスに、項目をリストしなければならないことはよくあります。例えば、すべての顧客をコンボボックスに表示して、その中から編集する顧客を選択する場合が考えられます。顧客の種類、状態、支払い予定のリストを表示することもあるでしょう。顧客が購入したすべての製品のリストをグリッドで表示する場合もあるかもしれません。
最初に、適度なサイズの項目リストを実装する方法について見ていきましょう。このリストでは、項目のいくつかのプロパティを表示し、場合によっては編集する必要があります。ここでは、顧客が購入した項目のリストをグリッドで表示することにします。
まずプロジェクトにPurchasedItemクラスを追加し、ItemName、Description、ItemIDなどの適当なプロパティを設定します。CustomerクラスのCreateメソッドに似たCreateメソッドを実装しますが、今回のメソッドでは、IDではなくDataRowを渡します(このクラスの実装については、ダウンロードサンプルを参照してください)。
プロジェクトにPurchasedItemCollectionクラスを追加し、このクラスをBindingList(Of T)から継承します。BindingList汎用コレクションは、System.ComponentModelにあることに注意してください。
Public Class PurchasedItemCollection Inherits BindingList(Of PurchasedItem)
class PurchasedItemCollection :
BindingList<PurchasedItem>
Factoryパターンに従い、コレクションエントリを作成するCreateメソッドを実装します。
Public Shared Function Create(ByVal CustomerID _ As Int32) As PurchasedItemCollection Dim oCollection As New PurchasedItemCollection Dim dt As DataTable ' Perform the appropriate retrieve dt = oCollection.Retrieve(CustomerID) ' For each row, create an object in the list For Each dr As DataRow In dt.Rows Dim oItem As PurchasedItem oItem = PurchasedItem.Create(dr) oCollection.Add(oItem) Next Return oCollection End Function
public static PurchasedItemCollection Create( Int32 CustomerID) { PurchasedItemCollection oCollection = new PurchasedItemCollection(); // Perform the appropriate retrieve DataTable dt = oCollection.Retrieve(CustomerID); // For each row, create an object in the list foreach (DataRow dr in dt.Rows) { PurchasedItem oItem = PurchasedItem.Create(dr); oCollection.Add(oItem); } return oCollection; }
CustomerクラスのRetrieveメソッドに似たRetrieveメソッドを追加します(このメソッドの実装については、ダウンロードサンプルを参照してください)。
両クラスの実装が完成したら、プロジェクトをビルドして、[Data Sources]ウィンドウから新しいクラスにアクセスできるようにします。次に、Customerクラスの場合と同じステップで、PurchasedItemCollectionクラスのデータソースを作成します。必ずDataGridViewを既定として設定し、PurchasedItemCollectionデータソースをフォームの下部にドラッグ&ドロップします。これにより、PurchasedItemクラスで定義した各プロパティを列として持つグリッドが表示されます。
フォームのLoadイベントの中で、PurchasedItemCollectionBindingSourceのデータソースを、PurchasedItemCollectionクラスのインスタンスに設定します。
Private Sub CustomerWin_Load(ByVal sender As _ Object, ByVal e As System.EventArgs) _ Handles Me.Load Dim CustomerInstance As Customer CustomerInstance = Customer.Create(1) CustomerBindingSource.DataSource = _ CustomerInstance Dim ItemCollection As PurchasedItemCollection ItemCollection = _ PurchasedItemCollection.Create(1) PurchasedItemCollectionBindingSource. _ DataSource = ItemCollection End Sub
private void CustomerWin_Load(object sender, EventArgs e) { Customer CustomerInstance = Customer.Create(1); customerBindingSource.DataSource = CustomerInstance; PurchasedItemCollection ItemCollection = PurchasedItemCollection.Create(1); purchasedItemCollectionBindingSource. DataSource = ItemCollection; }
BindingList(Of T)を継承するクラスへのオブジェクトバインディングは、項目のリストを表示および管理するのに非常に適しています。ただし、リスト内のすべての項目に対して、インスタンスが作成および設定されることに注意してください。この例のように、適度な数の項目であれば問題ありません。しかし、リスト内の項目数が数十万にも及ぶ場合は、適切に動作しない可能性があります。
例えば編集対象の顧客を選択するための顧客名リストのように、大量の読み取り専用のキー値が必要な場合には、値ごとにインスタンスを作成するのは非効率的に感じられます。この場合は、キー値のDataTableに直接バインドしたいと思うかもしれません。しかし、この場合でも、ちょっとしたテクニックによって、一種のオブジェクトバインディングを引き続き使用することができます。
そのためには、CustomerListクラスをプロジェクトに追加し、CustomerIDやFullNameなど、キー値の名前に一致するプロパティを用意します。また、キー値のDataTableを返すCustomerDataTableプロパティも用意します。最後に、Retrieveメソッドを実装して、DataTableを取得します。リスト3およびリスト4は、結果のコードを示しています。
Public Class CustomerList Public ReadOnly Property FullName() As String Get Return "" End Get End Property Public ReadOnly Property CustomerID() As Int32 Get Return 0 End Get End Property Public ReadOnly Property CustomerDataTable() As DataTable Get Return Retrieve() End Get End Property Private Function Retrieve() As DataTable Dim dt As New DataTable dt.Columns.Add("FullName") dt.Columns.Add("CustomerID") dt.Rows.Add("Einstein, Albert", 1) dt.Rows.Add("Curie, Marie", 2) dt.Rows.Add("Brahe, Tycho", 3) dt.Rows.Add("Faraday, Michael", 4) Return dt End Function End Class
class CustomerList { public string FullName { get { return "";} } public Int32 CustomerID { get { return 0;} } public DataTable CustomerDataTable { get { return Retrieve();} } private DataTable Retrieve() { DataTable dt = new DataTable(); dt.Columns.Add("FullName"); dt.Columns.Add("CustomerID"); dt.Rows.Add("Einstein, Albert", 1); dt.Rows.Add("Curie, Marie", 2); dt.Rows.Add("Brahe, Tycho", 3); dt.Rows.Add("Faraday, Michael", 4); return dt; } }
プロジェクトをビルドし、CustomerListクラスのデータソースを作成します。[Data Sources]ウィンドウ内で、FullNameプロパティをコンボボックスとして表示するように設定します。次に、FullNameプロパティをフォームにドラッグします。図2のように表示されます。

コンボボックスのスマートタグをクリックして、コンボボックスのプロパティを設定します。DisplayMemberプロパティをFullNameに、ValueMemberプロパティをCustomerIDに設定します。
最後に、ユーザーインターフェイスを修正して、コンボボックスに値を読み込むためのコードと、ユーザーがコンボボックスから顧客を選択したときに他のコントロールの内容を更新するためのコードを追加します(以前に作成したLoadイベントをコメントアウトするか、削除するのを忘れないようにしてください)。
Private Sub CustomerWin_Load(ByVal sender As _ Object, ByVal e As System.EventArgs) _ Handles Me.Load ' Bind the list Dim oList As New CustomerList CustomerListBindingSource.DataSource = _ oList.CustomerDataTable End Sub Private Sub _ FullNameComboBox_SelectionChangeCommitted( _ ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles FullNameComboBox.SelectionChangeCommitted Dim oCustomer As Customer oCustomer = Customer.Create( FullNameComboBox.SelectedValue) CustomerBindingSource.DataSource = oCustomer Dim oCollection As PurchasedItemCollection oCollection = PurchasedItemCollection.Create( _ FullNameComboBox.SelectedValue) PurchasedItemCollectionBindingSource. _ DataSource = oCollection End Sub
private void CustomerWin_Load(object sender, EventArgs e) { CustomerList oList = new CustomerList(); customerListBindingSource.DataSource = oList.CustomerDataTable; } private void fullNameComboBox_SelectionChangeCommitted( object sender, EventArgs e) { Int32 selectedID = Convert.ToInt32( fullNameComboBox.SelectedValue); Customer oCustomer = Customer.Create(selectedID); customerBindingSource.DataSource = oCustomer; PurchasedItemCollection oCollection = PurchasedItemCollection.Create(selectedID); purchasedItemCollectionBindingSource. DataSource = oCollection; }
これは基本的に見せかけのオブジェクトバインディングで、オブジェクトにバインドしていると思わせていますが、実際のバインド先はDataTableです。この方法は、クラスのインスタンスは不要だが、どのデータソースでもオブジェクトバインディングを使用するように統一したい場合に、非常に効果的な方法です(データソースの混在を気にしない場合は、ここに示す「見せかけの」オブジェクトバインディングではなく、データベースバインディングを使ってTableAdapterに直接バインドすることもできます)。
この例の場合、顧客が多数に及ぶ可能性があるため、DataTableへのバインドには意味があります。また、ワークフローの観点からは、ユーザーが一度に編集する顧客数はわずかであることが予想されます。従って、数十万もの個別のインスタンスを作成し、それぞれのインスタンスにバインドするのは無駄が多すぎます。
この「見せかけのオブジェクトバインディング」の手法は、顧客の種類、状態、支払い予定などのリストが必要なときにも使えます。このようなケースでは、リストは常に読み取り専用であり、インスタンスを本当に作成して管理する必要はありません。
例として、CustomerクラスにStateプロパティを追加してみましょう。プロジェクトをビルドすると、[Data Sources]ウィンドウにStateが追加されます。次に、本稿で作成したCustomerListクラスに似たStateListクラスをビルドします。このStateListクラスは、状態のリストを管理します。
このようなリストを使用する場合は、コンボボックスの3つのプロパティを操作する必要があります。このプロパティはすべて、コンボボックスのスマートタグからアクセスできます。
| プロパティ名 | 説明 |
DisplayMember | コンボボックスに表示する値のソースとなるプロパティ。StateListクラスのプロパティを指定する必要があります("StateName"など)。 |
ValueMember | コンボボックスの選択値を表すときに使用するプロパティ。StateListクラスのプロパティを指定する必要があります("StateID"など)。 |
SelectedValue | ユーザーの選択値の代入先となるバインディングソースおよびプロパティ。Customerクラスのプロパティ(ひいてはCustomerBindingListのプロパティ)を指定する必要があります("State"など)。 |
このように、すべての型コードに対してクラスとインスタンスを作成しなくても、完全な機能を備えたオブジェクトバンディングを実現できます。
ユーザーインターフェイスに関する注意
オブジェクトバインディングに関連して、ユーザーインターフェイスにはいくつか注意を要することがあります。例えば、ラジオボタンにバインドしたり、親プロパティを子データのグリッドに追加する簡単な方法はありません。最後に、この点に触れておきたいと思います。
ラジオボタンにバインドするには、「RadioButtonList」というようなコントロールを使用するのが最も簡単です。しかし、Windows Formsにはこのようなコントロールがありません。一連のラジオボタンにバインドするには、ラジオボタンごとに個々のビジネスオブジェクトプロパティを作成しなければなりません。
例えば、顧客が請求書を受け取る方法(電子メール、FAX、郵送のいずれか)を定義する一連のラジオボタンがあるものとします。これらにバインドするには、3つの個別のブール型のビジネスオブジェクトプロパティ(E-mail、FAX、Postal)を作成し、各プロパティを個々のラジオボタンにバインドする必要があります。各プロパティのプロパティプロシージャでは、このプロパティがtrueの場合は他のプロパティ値をfalseに設定するコードを追加します(この実装については、ダウンロードサンプルを参照してください)。
ユーザーインターフェイスに関する2番目の注意事項は、親情報を子データのグリッドに簡単に表示できないということです。例えば、すべての請求書をリストするユーザーインターフェイスを作成するときに、図2のCustomerフォームと同様のグリッドを配置する一方で、他の詳細情報はフォームにいっさい表示しないというデザインにしたとします。このグリッドをユーザーが実用的に使うためには、関連するCustomerインスタンスからCustomerの名前を表示する必要があります。
そのためには、PurchasedItemビジネスオブジェクトから親オブジェクトCustomerを参照して、Customerインスタンスを作成するのが一番分かりやすいでしょう。この参照は可能であり、[Data Sources]ウィンドウにもCustomerインスタンスが表示されていますが、グリッドからそのCustomerインスタンスにアクセスすることはできません。
これに対処するには、PurchasedItemビジネスオブジェクトにCustomerNameプロパティを追加するしかありません。このプロパティのプロパティプロシージャで、関連するCustomerの顧客名を取得します。具体的な例については、ダウンロードサンプルを参照してください。
オブジェクトバインディングは、ユーザーインターフェイス上のコントロールをビジネスオブジェクトプロパティに結び付ける、非常に論理的なメカニズムを提供します。ここで紹介したようなヒントとテクニックを実装することによって、オブジェクトバインディングを大いに活用できます。
オブジェクトバインディングは完全ではありませんが、この世に完全なものなどないのですから、どんどん使ってみることをお勧めします。
