At a glance, that looks about right... I'm not sure about using an array of structures though... I'm not sure if databinding can modify values without get/set statements.
This is the kind of stuff that makes binding to an ArrayList more trouble than it is worth (in my opinion). It is pretty easy to create your own strongly-typed collection for a cusotm object and well worth the effort.
I'm going to post an example that I wrote for some Visual RPG guys to get a handle around custom collections and relations. This example is going to build three objects (Customer, Order, and Item) and a strongly typed collection for each. The Customer object will contain an Order collection object and the Order object will contain an Item collection object. This allows the user to create instances of the Item object, add them to the Item collection on a Order object and then add the Order to the Order collection on the Customer object. You can then set the DataSource of a DataGrid to the Customer object and voila! The grid shows the customers and their properties and allows you to drill down into the orders and items.
You can create a new class file in a VB project and paste this code over the code page. You also probably want to use Find-Replace to remove all occurances of "MultidimensionalCollectionExample.", which was the original assembly name of this project:
'Declare namespaces, or named container, to seperate the 'Objects from their respective Collections Namespace ObjectClasses
'Create a class for each table of related data. 'This example will use Customers, which contain 'Orders, which contain Items. #Region " Customer Object " 'Use regions to organize code
'Define a CustomerObject to hold the data about each 'customer as we read it from the database Public Class CustomerObject
'Use private variables declared at the class level 'to store the data for the public properties we wish 'to expose. Initialize these variables by assigning 'values to them at creation. While not strictly 'necessary as used in this context, if we later wish 'to fully support DataBinding these vars are ready. Private myCustomerID As Integer = -1 Private myName As String = "" Private myPhone As Integer = -1 'Notice that this variable is a class, not just a 'structure, so we have to create an instance of it instead 'of just assigning it a value. Private myOrders As New CollectionClasses.OrderCollection
'Data validation can be written right into the properties 'themselves. To take advantage of this, define public 'events that your class can raise to notify the calling 'code that something has happend or is wrong. This example 'is going to require a seven position phone number and 'raise the following event if the user enters any other 'number of characters. Notice the event follows the 'standard event design with a generic sender object an 'and a single event arguments object that inherits 'from Sytem.EventArgs... more on this later Public Event PhoneError(ByVal sender As Object, ByVal e As MultidimensionalCollectionExample.ObjectClasses.CustomerObject.PhoneErrorEventArgs)
'It's necessary to include a Public Sub New() in order 'for a class to be instantiated (create an "Instanced 'Class"). Since we've given our internal variables 'values at creation, we don't need to actually do 'anything in the routine. Public Sub New()
End Sub
'Providing multiple Sub New() methods allows the 'object to be declared in different ways. Each Sub New() 'method requires a unique set of arguments. Sub New() is 'the only class element that doesn't require the Overloads 'keyword. Public Sub New(ByVal CustomerID As Integer, ByVal Name As String, ByVal Phone As Integer) myCustomerID = CustomerID myName = Name myPhone = Phone myOrders = New CollectionClasses.OrderCollection End Sub
'Define a property for each field in your DataSource. 'Each property requires a Get and Set statement that 'determine the actions taken when a calling code routine 'reads or writes to the property value. The exceptions 'are properties declared ReadOnly or WriteOnly; they 'only use the Get or Set statement as appropriate. Public Property CustomerID() As Integer Get 'This Get statement simply returns the 'private CustomerID variable Return myCustomerID End Get 'The Set statement always contains an argument of '"Value" which must be the same data type as the 'property. Value will contain the data set by 'the calling class. Set(ByVal Value As Integer) 'Set the private CustomerID to Value myCustomerID = Value End Set End Property
Public Property Name() As String Get Return myName End Get Set(ByVal Value As String) myName = Value End Set End Property
'This property shows a simple example of providing 'data validation within a Set statement. This can 'be an effecient way to validate in that multiple forms 'can change a phone number and the validation code 'doesn't need to be written on each form. Public Property Phone() As Integer Get Return myPhone End Get Set(ByVal Value As Integer) 'Rather than blindly set the private Phone 'variable to the value of Value, perform 'any desired data validation first.
'This code converts the absoloute value of 'the Integer to a string and checks it's length If System.Math.Abs(Value).ToString.Length = 7 Then 'If the length is 7 then set the variable myPhone = Value Else 'If it's not 7 then raise the PhoneError event 'When raising the event, create a new instance 'of the custom EventArgs object and provide 'any necessary information about what is wrong RaiseEvent PhoneError(Me, New MultidimensionalCollectionExample.ObjectClasses.CustomerObject.PhoneErrorEventArgs(Value, "Number of digits does not equal seven")) End If End Set End Property
'When collections are exposed as properties they are 'often declared ReadOnly so that the entire collection 'cannot be replaced. Items can still be fully 'manipulated (add, modify, remove) within the collection. 'ReadOnly properties do not have a Set statement. Public ReadOnly Property Orders() As CollectionClasses.OrderCollection Get Return myOrders End Get End Property
'The value of a property can be calculated on the fly. 'These properties have no need of a private variable. 'They also must be declared ReadOnly as it would not 'be logical to set a dynamically built property. Public ReadOnly Property PurchaseTotal() As Double 'This property will add the total of each order total 'in the customer object's Order collection. Get 'Declare a variable to hold the return value Dim pt As Double = 0 'Get an enumerator for the Order collection Dim enm As IEnumerator = myOrders.GetEnumerator 'Move through each order object in the collection While enm.MoveNext 'Cast the current object to an Order object 'and add it's OrderTotal property value to 'the return value pt += CType(enm.Current, MultidimensionalCollectionExample.ObjectClasses.OrderObject).OrderTotal End While 'Return the calculated value Return pt End Get End Property
'It is also possible to expose an underlying property 'value within a class. This property exposes the Count 'property of the Order collection. This is an easy way 'to get extra infomation into your datagrid. Exposed 'properties should be declared in the same manner as 'the exposing class. Public ReadOnly Property PurchaseCount() As Integer Get Return myOrders.Count End Get End Property
#Region " Custom EventArgs "
'Within the Order class define any needed EventArg classes Public Class PhoneErrorEventArgs 'Inherit from the base EventArgs class Inherits System.EventArgs
'Provide properties for each data value that needs 'to be passed when raising the event Private myAttemptedNumber As Integer Private myProblem As String
Public Sub New(ByVal AttemptedNumber As Integer, ByVal Problem As String) myAttemptedNumber = AttemptedNumber myProblem = Problem End Sub
Public ReadOnly Property AttemptedNumber() As Integer Get Return myAttemptedNumber End Get End Property
Public ReadOnly Property Problem() As String Get Return myProblem End Get End Property
End Class
#End Region
End Class
#End Region
#Region " Order Object "
Public Class OrderObject
Private myOrderID As Integer = -1 Private myItems As CollectionClasses.ItemCollection
Public Sub New(ByVal OrderID As Integer) myOrderID = OrderID myItems = New CollectionClasses.ItemCollection End Sub
Public Property OrderID() As Integer Get Return myOrderID End Get Set(ByVal Value As Integer) myOrderID = Value End Set End Property
Public ReadOnly Property Items() As CollectionClasses.ItemCollection Get Return myItems End Get End Property
Public ReadOnly Property OrderTotal() As Double Get Dim FoundTotal As Double = 0 Dim enm As IEnumerator = myItems.GetEnumerator While enm.MoveNext FoundTotal += CType(enm.Current, ObjectClasses.ItemObject).Price End While Return FoundTotal End Get End Property
End Class
#End Region
#Region " Item Object "
Public Class ItemObject
Private myItemID As Integer = -1 Private myName As String = "" Private myPrice As Double = 0
Public Sub New(ByVal ItemID As Integer, ByVal Name As String, ByVal Price As Double) myItemID = ItemID myName = Name myPrice = Price End Sub
Public Property ItemID() As Integer Get Return myItemID End Get Set(ByVal Value As Integer) myItemID = Value End Set End Property
Public Property Name() As String Get Return myName End Get Set(ByVal Value As String) myName = Value End Set End Property
Public Property Price() As Double Get Return myPrice End Get Set(ByVal Value As Double) myPrice = Value End Set End Property
End Class
#End Region
End Namespace
Namespace CollectionClasses
#Region " Item Collection "
'Create a Collection for each Object Public Class ItemCollection 'Inherit from CollectionBase and expose the minimum 'properties and methods necessary for complex databinding. 'Every strongly typed collection should contain each of 'the following members adjusted only for the data type 'of the object being stored in the collection. Inherits System.Collections.CollectionBase
Public Sub New() 'The object MyBase refers to the object this class 'inherits from. Call the CollectionBase.New() method 'in this class's New() method. MyBase.New() End Sub
Default Public Overloads Property Item(ByVal index As Integer) As ObjectClasses.ItemObject Get Return MyBase.List.Item(index) End Get Set(ByVal Value As ObjectClasses.ItemObject) MyBase.List.Item(index) = Value End Set End Property
'Multiple overloads of the Item property is not required. 'Providing an integer index to the List.Item() property 'is all that Complex Databinding requires. This 'overload allows the calling class to access Item() by 'the Name() property of the ItemObject. Default Public Overloads Property Item(ByVal Name As String) As ObjectClasses.ItemObject Get Dim enm As IEnumerator = MyBase.List.GetEnumerator While enm.MoveNext If CType(enm.Current, MultidimensionalCollectionExample.ObjectClasses.ItemObject).Name = Name Then Return CType(enm.Current, MultidimensionalCollectionExample.ObjectClasses.ItemObject) End If End While Return Nothing End Get Set(ByVal Value As ObjectClasses.ItemObject) For Each io As MultidimensionalCollectionExample.ObjectClasses.ItemObject In MyBase.List If io.Name = Name Then io = Value Exit For End If Next End Set End Property
Public Function Add(ByVal value As ObjectClasses.ItemObject) As Integer Return MyBase.List.Add(value) End Function
Public Sub AddRange(ByVal value As ItemCollection) MyBase.InnerList.AddRange(value) End Sub
Public Function Contains(ByVal value As ObjectClasses.ItemObject) As Boolean Return MyBase.List.Contains(value) End Function
Public Function IndexOf(ByVal value As ObjectClasses.ItemObject) As Integer Return MyBase.List.IndexOf(value) End Function
Public Sub Insert(ByVal index As Integer, ByVal value As ObjectClasses.ItemObject) MyBase.List.Insert(index, value) End Sub
Public Sub Remove(ByVal value As ObjectClasses.ItemObject) MyBase.List.Remove(value) End Sub End Class
#End Region
#Region " Order Collection "
Public Class OrderCollection Inherits System.Collections.CollectionBase
Public Sub New() MyBase.New() End Sub
Default Public Property Item(ByVal index As Integer) As ObjectClasses.OrderObject Get Return MyBase.List.Item(index) End Get Set(ByVal Value As ObjectClasses.OrderObject) MyBase.List.Item(index) = Value End Set End Property
Public Function Add(ByVal value As ObjectClasses.OrderObject) As Integer Return MyBase.List.Add(value) End Function
Public Sub AddRange(ByVal value As ItemCollection) MyBase.InnerList.AddRange(value) End Sub
Public Function Contains(ByVal value As ObjectClasses.OrderObject) As Boolean Return MyBase.List.Contains(value) End Function
Public Function IndexOf(ByVal value As ObjectClasses.OrderObject) As Integer Return MyBase.List.IndexOf(value) End Function
Public Sub Insert(ByVal index As Integer, ByVal value As ObjectClasses.OrderObject) MyBase.List.Insert(index, value) End Sub
Public Sub Remove(ByVal value As ObjectClasses.OrderObject) MyBase.List.Remove(value) End Sub End Class
#End Region
#Region " Customer Collection "
Public Class CustomerCollection Inherits System.Collections.CollectionBase
'A class that inherits from another class can provide 'it's own properties and methods in addition to 'overloading or overriding base ones. Private myName As String = "New Collection" Private myDate As Date = Now
Public Sub New() MyBase.New() End Sub
Public Sub New(ByVal Name As String) myName = Name End Sub
Public Sub New(ByVal [Date] As Date) myDate = [Date] End Sub
Public Sub New(ByVal Name As String, ByVal [Date] As Date) myName = Name myDate = [Date] End Sub
Default Public Overloads Property Item(ByVal index As Integer) As ObjectClasses.CustomerObject Get Return MyBase.List.Item(index) End Get Set(ByVal Value As ObjectClasses.CustomerObject) MyBase.List.Item(index) = Value End Set End Property
Default Public Overloads Property Item(ByVal Name As String) As ObjectClasses.CustomerObject Get Dim enm As IEnumerator = MyBase.List.GetEnumerator While enm.MoveNext If CType(enm.Current, MultidimensionalCollectionExample.ObjectClasses.CustomerObject).Name = Name Then Return CType(enm.Current, MultidimensionalCollectionExample.ObjectClasses.CustomerObject) End If End While Return Nothing End Get Set(ByVal Value As MultidimensionalCollectionExample.ObjectClasses.CustomerObject) For Each co As ObjectClasses.CustomerObject In MyBase.List If co.Name = Name Then co = Value Exit For Next End Set End Property
Public Function Add(ByVal value As ObjectClasses.CustomerObject) As Integer Return MyBase.List.Add(value) End Function
Public Sub AddRange(ByVal value As ItemCollection) MyBase.InnerList.AddRange(value) End Sub
Public Function Contains(ByVal value As ObjectClasses.CustomerObject) As Boolean Return MyBase.List.Contains(value) End Function
Public Function IndexOf(ByVal value As ObjectClasses.CustomerObject) As Integer Return MyBase.List.IndexOf(value) End Function
Public Sub Insert(ByVal index As Integer, ByVal value As ObjectClasses.CustomerObject) MyBase.List.Insert(index, value) End Sub
Public Sub Remove(ByVal value As ObjectClasses.CustomerObject) MyBase.List.Remove(value) End Sub
Public Property CollectionName() As String Get Return myName End Get Set(ByVal Value As String) myName = Value End Set End Property
Public Property CollectionDate() As String Get Return myDate End Get Set(ByVal Value As String) myDate = Value End Set End Property
End Class
#End Region
End Namespace
Rember that this is a basic intro to custom collections. As noted in the code comments, you can do more work yet to further exploit databinding.
If you look at the Order Collection class you get a basic template for a strongly-typed collection. You can take that class and just replace instances of OrderObject with whatever object type you wish to make a collection for.
Sorry this is so much to look at... hope it helps though! |