Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chriss Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Adding data from an array to a Datagrid 1

Status
Not open for further replies.

besto1a

Programmer
Aug 20, 2006
41
GB
Is it possible to diplay the data from a 2 dimentional array (type varient) to a data grid. if so How?

Thanks
 
Unfortunately, I don't think there's a way to directly bind a DataGrid to a multi-dimensional array. In VS2005, the DataGridView control's DataSource property only accepts objects that implement the following interfaces:
[ul]
[li]IList[/li]
[li]IListSource[/li]
[li]IBindingList[/li]
[li]IBindingListView[/li]
[/ul]

Array does implement IList, but the implementation seems to only be valid for one-dimensional arrays.

That said, here's a couple options that I came up with:

Replace the two-dimensional array with a one-dimensional array of objects. Assuming the columns in your array could represent properties of an object and each row in your array is a single object (similar to a record in a database), then you might want to consider creating a class that represents a single row in your array. That class will have a property for each column in your current array. Then, create a one-dimensional array of objects of your new class' type. When you pass this array to your DataGridView as the DataSource, .NET will create a column for each property of your object.

Wrap the two-dimensional array in an implementation of IList. If for some reason you cannot implement my first suggestion in your application, then you can create a class that implements IList and wraps your array. This class could take your array as a parameter in its constructor (Sub New) and implement the IList interface methods required by the DataGridView. For a read-only view of the data, you only need to implement the following methods/properties:
[ul]
[li]Count - return the # of elements in dimension 0[/li]
[li]GetEnumerator - returns the result of the array's GetEnumerator method[/li]
[li]IsFixedSize - returns True[/li]
[li]IsReadOnly - returns True[/li]
[li]Item - see my notes below[/li]
[/ul]

For all the other methods, you can throw an exception.

The [tt]Item[/tt] property is a bit of a special case. In order to get this to work properly, you'll still need to create a class that represents one row in your array. The [tt]Item get[/tt] will then create and return a new instance of this class for the specified row. The [tt]Item set[/tt] would accept an instance of your array row class and then update the specified row from that object (I don't think that [tt]set[/tt] is actually used, but the IList interface requires a declaration for it).

Wrap the two-dimensional array in a class that inherits from BindingList and bind to your DataGridView through a BindingSource. This option is more complicated than the previous one, but allows you to edit the underlying array through the DataGridView. The BindingList and BindingSource contain all the required code to enable editing through the grid. Instead of creating a wrapper class that implements [tt]IList[/tt], create a wrapper class that inherits from [tt]System.ComponentModel.BindingList[/tt] (note, this is a .NET 2.0 feature and uses Generics).

The implementation of your BindingList class is actually quite simple. Create a constructor that accepts your array as an argument. In the constructor, loop through dimension 0 of the array, create a new object based on each row (again, you'll need the array row class for this), and then call the Add method of your BindingList class, passing it the row object.

Instantiate your BindingList object in your code, passing it the array to be bound to the grid. Then, create a new instance of BingingSource, set its [tt]DataSource[/tt] property to your BindingList object, and finally set your DataGridView's [tt]DataSource[/tt] property to the BindingSource. For example:

Code:
        Dim bs as BindingSource = New BindingSource
        Dim bl as ArrayBL = New ArrayBL(aData)  ' the custom BindingList class
        bs.DataSource = bl
        dgvArray.DataSource = bs  ' bind the grid

Important Note: if you create your array row object by copying array cells to properties in the object, make sure you're actually creating references instead of copies! If your array contains objects as opposed to primitive data types, this should not be a problem. If your array contains primitive types (such as Integer or String), the easiest solution might be to pass a reference to the entire array into your row object along with the row index. Then in your row class, you can act directly on the array.

One thing I noticed is that the Data
GridView doesn't seem to be guaranteed to create its columns in the same order as the columns in the grid. I'm not sure exactly how it determines what order to create them (they're not alphabetical or in the same order they're defined in the source code). If that is an issue, you'll need to set the DataGridView's [tt]AutoGenerateColumns[/tt] property to false in your code and then manually add the columns in the order you want, binding each column to the appropriate propety of your array row class.

The main drawback to all of these methods is that you need to create a strongly-typed class to represent one row in your array. Every time you need to bind a two-dimensional array to a DataGridView, you'll need to create a new class specific to the columns of that array. It might be possible to create a generalized solution to this problem by writing a class that uses the Reflection API to dynamically create a property for each column in your array, but I haven't looked into that at all (yet).

I hope this helps. I tested the above using VS2005 and .NET 2.0, so if you're using .NET 1.1 the methods may be a bit different, but hopefully you can adapt them. If you would like to see the sample code I came up with to test these ideas, let me know.
 
If you could show your code that would be great

Thanks
 
Here you go.

I tweaked the implementation that I previously described to make it (a little) more generic. The main drawback to the code I described is the need to create a special row class for any array. Until I get a chance to play with the Reflection API a bit I won't be able to eliminate that, but I was able to add a factory class and a base class for array rows that combined allowed me to eliminate the need for a custom BindingList class. Previously, that class was tied to a specific array row class, so you really had to write two classes for each type of array you wanted to bind. With the code below, you can use the standard BindingList class and specify the type of row object the BindingList will contain. Essentially, you could say I've built a mini-framework.

Here's the code for the various classes that go into this framework:

Code:
' A factory base class for creating objects representing a row in a
' two-dimensional array.
Public Class ArrayRowFactory

    Private _rowType As Type

    Public Sub New(ByVal rowType As Type)
        If rowType.IsSubclassOf(GetType(GenericArrayRow)) Then
            _rowType = rowType
        Else
            Throw New ArgumentException
        End If
    End Sub

    Public Property RowType() As Type
        Get
            Return _rowType
        End Get
        Set(ByVal value As Type)
            If value.IsSubclassOf(GetType(GenericArrayRow)) Then
                _rowType = value
            Else
                Throw New ArgumentException
            End If
        End Set
    End Property

    ' factory method for creating array row objects
    Public Function CreateRowObject(ByVal arr As System.Array, ByVal row As Integer) As GenericArrayRow
        Dim args(1) As Object
        args(0) = arr
        args(1) = row
        ' use reflection to intantiate a row object of the correct type
        Return Activator.CreateInstance(_rowType, args)
    End Function

End Class

Code:
' Base class for array row classes.
Public Class GenericArrayRow

    Protected _arr As System.Array
    Protected _row As Integer

    Protected Sub New()

    End Sub

    Public Sub New(ByVal arr As System.Array, ByVal row As Integer)
        ' save a reference to the parent array of this row
        _arr = arr
        ' save the index value of this row in the parent array
        _row = row
    End Sub

    ' no methods declared; classes that implement this interface
    ' must create a property for each column in the array

End Class

Code:
' A representation of a single row of data within the 3 column,
' two-dimensional array being used as a data source
Public Class ArrayRow
    Inherits GenericArrayRow

#Region "--- Constructors ---"
    Protected Sub New()

    End Sub

    Public Sub New(ByVal arr As System.Array, ByVal row As Integer)
        MyBase.New(arr, row)
    End Sub
#End Region

    ' the following properties could be given more descriptive names to
    ' represent their actual contents, such as "FirstName," "LastName,"
    ' etc.
#Region "--- Column Properties ---"
    Public Property Col0() As String
        Get
            Return _arr(_row, 0)
        End Get
        Set(ByVal value As String)
            _arr(_row, 0) = value
        End Set
    End Property

    Public Property Col1() As String
        Get
            Return _arr(_row, 1)
        End Get
        Set(ByVal value As String)
            _arr(_row, 1) = value
        End Set
    End Property

    Public Property Col2() As String
        Get
            Return _arr(_row, 2)
        End Get
        Set(ByVal value As String)
            _arr(_row, 2) = value
        End Set
    End Property
#End Region

End Class

To test this, I created a very simple form, consisting of a DataGridView, a multi-line text box, and a button. When the form loads, it fills the array with some default data and then binds the array to the grid. Pressing the button causes the current contents of the array to dump to the text box, just to confirm that changes to the grid are actually updating the underlying array.

Here's the code for that form:

Code:
Imports System.Collections
Imports System.Collections.Generic
Imports System.ComponentModel

Public Class ArrayDGForm

    Dim aData As System.Array
    Dim bs As BindingSource
    Dim bl As New BindingList(Of ArrayRow)

    Private Sub ArrayDG_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        ' fill array with sample data
        aData = Array.CreateInstance(GetType(String), 5, 6)
        For i As Integer = 0 To 4
            For j As Integer = 0 To 5
                aData(i, j) = "(i=" & i & ", j=" & j & ")"
            Next
        Next

        ' bind array to grid
        bs = New BindingSource
        Dim rowFact As New ArrayRowFactory(GetType(ArrayRow))
        For i As Integer = aData.GetLowerBound(0) To aData.GetUpperBound(0)
            bl.Add(rowFact.CreateRowObject(aData, i))
        Next
        bs.DataSource = bl
        dgvArray.DataSource = bs

    End Sub

    Private Sub btnDump_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDump.Click

        TextBox1.Text = String.Empty
        For i As Integer = aData.GetLowerBound(0) To aData.GetUpperBound(0)
            For j As Integer = aData.GetLowerBound(1) To aData.GetUpperBound(1)
                TextBox1.Text = TextBox1.Text & aData(i, j) & Constants.vbTab
            Next
            TextBox1.Text = TextBox1.Text & Constants.vbCrLf
        Next

    End Sub
End Class
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top