locked
Custom Add method for a generic collection. RRS feed

  • Question

  • The key here is that I need the generic collection to bind to a datagridview - and yet implement my own Add method. Here's how I used to do it (this is a collection of databases - DBs)


    private class objDb
       public  pathToDB as string
    end Class

    Dim DBs as new List of (objDB)
    Dim DB as New objDB
    DBs.Add(DB)
    dgv.DataSource = DBs

    How do I do pretty much the same thing while customizing the Add method?
    Wednesday, April 8, 2009 8:21 AM

Answers

  • You're going to have to create a new class which implements the iList(Of T) interface, I think.
    I've looked at inheriting the List(Of T) class, but Add is not an Overridable method.

    Just type:

    Public Class myCollectionName(Of T)
        Implements IList(Of T)

    After hitting Enter, you'll get the framework methods for implementing iList(Of T).

    I recommend simply having your new class contain a List object that you pass messages to for the most part, i.e.:
    Public Class thing(Of T)
        Implements IList(Of T)
        Private _myList As List(Of T)
        Public Function Contains(ByVal item As T) As Boolean Implements System.Collections.Generic.ICollection(Of T).Contains
            Return Me._myList.Contains(item)
        End Function
    And just do what you need to do in your implementation of the Add method.
    • Marked as answer by jal2 Thursday, April 9, 2009 5:56 AM
    Wednesday, April 8, 2009 11:27 AM
  • You can create a class that Inherits List(of T). Then Shadow the Add function of the List(of ). I don't use databinding very often but I think in order for the binding to work you have to have a Property not a Field. Try this it seems to work notice the property declaration in your objDB

    Public Class Form1
        Private MyList As New CustomList
    
        Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
            Dim newGrid As New DataGridView
            
            MyList.Add("\\SomePath\")
            MyList.Add("\\SomeOtherPath\")
    
            With newGrid
                .DataSource = MyList
                .Dock = DockStyle.Fill
            End With
    
            Me.Controls.Add(newGrid)
        End Sub
    End Class
    
    Public Class CustomList
        Inherits List(Of objDb)
    
        Public Shadows Function Add(ByVal dBPath As String) As objDb
            Dim newDB As New objDb
    
            newDB.pathToDB = dBPath
    
            MyBase.Add(newDB)
    
            Return newDB
        End Function
    End Class
    
    Public Class objDb
        Private mpathToDB As String
        Public Property pathToDB() As String
            Get
                Return mpathToDB
            End Get
            Set(ByVal value As String)
                mpathToDB = value
            End Set
        End Property
    End Class
    • Marked as answer by jal2 Thursday, April 9, 2009 5:56 AM
    Wednesday, April 8, 2009 9:30 PM
  • Just to clarify the comment on readonly...  That was a really a mistake on my part.  Like I said, I normally find binding more trouble than it's worth :)  Though, I do know you can't support sorting in your grid with a List(Of T).  That's why I implemented my own SortableBindingList(Of T) for this sort of thing.

    Anyway, now I see more what you are trying to accomplish - I misunderstood that you were trying to create a custom collection :)  Glad you got your answer there...   Though, I do have a couple of thoughts on this that may enhance the answers already given.  One, you might consider implementing IBindingList rather then IList (IBindingList inherits from IList) as this interface will give you the ability to support custom sorting, etc in your grid as well as change notification.  I would look at the documentation for BindingList(Of T) for ideas on the interfaces you may want to support for full binding support.

    Another suggestion related to creating custom collections, rather then inherit from List(Of T), you might consider inherting from System.Collections.ObjectModel.Collection(Of T).  This gives you an immediately usable strongly typed collection, that you can then customize the behavior of the various add/remove/insert/etc methods by overriding virtual protected members.  So, a simple example in your case would be:

    Option Explicit On
    Option Strict On
    
    Imports System
    Imports System.Collections.ObjectModel
    
    Module Module1
    
        Sub Main()
            Dim dbs As New Databases()
    
            dbs.Add(New Database("Mine"))
    
        End Sub
    
        Class Database
            Private _name As String
    
            Public Sub New()
                MyBase.New()
            End Sub
    
            Public Sub New(ByVal name As String)
                _name = name
            End Sub
    
            Public Property Name() As String
                Get
                    Return _name
                End Get
                Set(ByVal value As String)
                    _name = value
                End Set
            End Property
        End Class
    
        Class Databases
            Inherits Collection(Of Database)
    
            Protected Overrides Sub InsertItem(ByVal index As Integer, ByVal item As Database)
                Console.WriteLine("Inserting {0} at {1}", item.Name, index)
                MyBase.InsertItem(index, item)
            End Sub
        End Class
    End Module
    

    As for the nameing convention - .NET has pretty much dropped the whole use of prefix's (Hungarian notation).  There are naming guidlines on the msdn website (see here for example).  Anyway, I would just look at some of the code around, and I think you'll start to get the general picture.

    Tom Shelton
    • Marked as answer by jal2 Thursday, April 9, 2009 4:42 PM
    Thursday, April 9, 2009 4:11 PM

All replies

  • You're going to have to create a new class which implements the iList(Of T) interface, I think.
    I've looked at inheriting the List(Of T) class, but Add is not an Overridable method.

    Just type:

    Public Class myCollectionName(Of T)
        Implements IList(Of T)

    After hitting Enter, you'll get the framework methods for implementing iList(Of T).

    I recommend simply having your new class contain a List object that you pass messages to for the most part, i.e.:
    Public Class thing(Of T)
        Implements IList(Of T)
        Private _myList As List(Of T)
        Public Function Contains(ByVal item As T) As Boolean Implements System.Collections.Generic.ICollection(Of T).Contains
            Return Me._myList.Contains(item)
        End Function
    And just do what you need to do in your implementation of the Add method.
    • Marked as answer by jal2 Thursday, April 9, 2009 5:56 AM
    Wednesday, April 8, 2009 11:27 AM
  • Thanks for responding. Actually I already tried that before starting this thread. I was able to add items to the collection but the datagridview remained empty onscreen. That's why I wrote above "The key here is that I need my collection to be bindable to a dgv."   Did you actually try this and get it working? I'd like to see some working code please.

    The only difference between my code and yours is that i wrote:

    Public
     Class
     thing
    Implements IList(Of T)
    Private _myList As List(Of T)


    'whereas you wrote:

    Public Class thing (of T) '<------- the difference
    Implements IList(Of T)
    Private _myList As List(Of T)


    I implemented all the methods but didn't  "use" them all. I only populated the Add method, Count, Remove, GetEnumerator, and a  few others. (I can't remember them all but I populated most of them).

    Wednesday, April 8, 2009 4:49 PM
  • You can create a class that Inherits List(of T). Then Shadow the Add function of the List(of ). I don't use databinding very often but I think in order for the binding to work you have to have a Property not a Field. Try this it seems to work notice the property declaration in your objDB

    Public Class Form1
        Private MyList As New CustomList
    
        Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
            Dim newGrid As New DataGridView
            
            MyList.Add("\\SomePath\")
            MyList.Add("\\SomeOtherPath\")
    
            With newGrid
                .DataSource = MyList
                .Dock = DockStyle.Fill
            End With
    
            Me.Controls.Add(newGrid)
        End Sub
    End Class
    
    Public Class CustomList
        Inherits List(Of objDb)
    
        Public Shadows Function Add(ByVal dBPath As String) As objDb
            Dim newDB As New objDb
    
            newDB.pathToDB = dBPath
    
            MyBase.Add(newDB)
    
            Return newDB
        End Function
    End Class
    
    Public Class objDb
        Private mpathToDB As String
        Public Property pathToDB() As String
            Get
                Return mpathToDB
            End Get
            Set(ByVal value As String)
                mpathToDB = value
            End Set
        End Property
    End Class
    • Marked as answer by jal2 Thursday, April 9, 2009 5:56 AM
    Wednesday, April 8, 2009 9:30 PM
  • It is possible to bind to a DataGridView using a regular List(Of T) - though, I believe it will be readonly.  If you aren't seeing anything appear in your grid, then the problem is most likely your objDB class (ewww! sorry, that naming convention has long been discouraged).    Databinding will only work with properties, not methods or fields.

    I believe that if you want to actually update then you need to use BindingList(Of T) or inherit from it....

    Anyway, I'm far from a binding expert, because in general I find it more problematic then it's worth :)

    HTH
    Tom Shelton
    Wednesday, April 8, 2009 9:55 PM
  • Thanks Tom for responding. My initial post provided an abbreviated example of objDB - too abbreviated it seems, so I apologize. Yes, in the actual code I was using real Get-Set properties, not fields. So that's not the root of the problem here.

    And yes, I had considered shadowing but the compiler doesn't seem to encourage it so I gather that this is not the ideal way to go. In fact it makes me a bit nervous because it may forfeit some critical functionality in the base Add method.

    I'm not sure why you suggest that binding a regular List(of T) results in a ReadOnly grid. I have bound such lists on several occasions, and doing so saves a lot of code because the collection becomes user-editable (via the grid) rather than read-only.

    For lack of a better solution I am considering this (notice that I neither use Inheritance nor interfaces). Also I added a method called 'Bind to Grid'

      <Serializable()> _
        Private Class collectionOfDBs
            Private DBs As New List(Of objDB)
            Public Sub Add(ByVal newDB As objDB)
                Dim numsUsed As New Dictionary(Of Integer, Integer)
                For Each D As objDB In DBs
                    numsUsed.Add(D.IdNo, D.IdNo)
                Next
                Dim i As Integer = 1
                Do While numsUsed.ContainsKey(i)
                    i = i + 1
                Loop
                newDB.IdNo = i
                DBs.Add(newDB)
            End Sub
            Public Sub Remove(ByVal DB As objDB)
                DBs.Remove(DB)
            End Sub
            'Need an indexer. 
            Default Public Property Item(ByVal Index As Integer) As objDB
                Get
                    Return DBs(Index)
                End Get
                Set(ByVal value As objDB)
                    DBs(Index) = value
                End Set
            End Property
    
            Public Function Count() As Integer
                Return DBs.Count
            End Function
            Public Function GetEnumerator()
                Return DBs.GetEnumerator
            End Function
    
            Public Sub bindToGrid(ByVal dg As DataGridView)
                dg.DataSource = DBs
            End Sub
        End Class




    The downside of the above is that the consumer of this class might forget to call the special "bindToGrid" method - the consumer might accidentially try:

    Dim DBs as New collectionOfDBs

    dg.DataSource = DBs

    which doesn't work. That's why I started this thread - I was hoping to find a way to bind the collection using standard binding  code. I've heard rumors it can be done (perhaps by impementing IList) but I still don't know how to do it.

    BTW, per your suggestion, how can I improve my naming convention?

    Thursday, April 9, 2009 4:48 AM
  • Maybe I was too hesitant about shadowing. I suppose I could call, at the end of my custom code:

    myBase.Add(DB)

    as to recover all the functionality of the base class.

    Thursday, April 9, 2009 4:52 AM
  • Woops, I suppose that should be

    Me.Add(Db)

    I supose I should clarify what my custom code does.  I am serializing this collection. Had I saved the collection to a database, I would have the advantage of AutoIncrement (a unique ID#). I want to be sure that each item in the collection has a unique ID# when serialized. The custom code uses a dictionary to detect any  ID#s already used and thus create a new one for each item added.  At times the program will ask the user to select  an item by ID# (for reasons I can't get into here).
    Thursday, April 9, 2009 4:56 AM
  • I've never done shadowing. I'm confused. Maybe I was right the first time: myBase.Add(DB)
    Thursday, April 9, 2009 5:04 AM
  • Ok both methods are  now working. With shadowing I used myBase.Add.

    And I went back and tried the IList again just to see if I could do it.  Turns out the reason IList didn't work for me originally - I just found out -  was that this doesn't work.

    Dim DBs as New collectionOfDBs 'IList
    dg.Datasource = DBs

    But this works fine:

    Dim bs as New BindingSource
    bs.DataSource = DBs
    dg.DataSource = bs

    Don't know why this should be necessary.  Thanks guys.

    Thursday, April 9, 2009 5:55 AM
  • Just to clarify the comment on readonly...  That was a really a mistake on my part.  Like I said, I normally find binding more trouble than it's worth :)  Though, I do know you can't support sorting in your grid with a List(Of T).  That's why I implemented my own SortableBindingList(Of T) for this sort of thing.

    Anyway, now I see more what you are trying to accomplish - I misunderstood that you were trying to create a custom collection :)  Glad you got your answer there...   Though, I do have a couple of thoughts on this that may enhance the answers already given.  One, you might consider implementing IBindingList rather then IList (IBindingList inherits from IList) as this interface will give you the ability to support custom sorting, etc in your grid as well as change notification.  I would look at the documentation for BindingList(Of T) for ideas on the interfaces you may want to support for full binding support.

    Another suggestion related to creating custom collections, rather then inherit from List(Of T), you might consider inherting from System.Collections.ObjectModel.Collection(Of T).  This gives you an immediately usable strongly typed collection, that you can then customize the behavior of the various add/remove/insert/etc methods by overriding virtual protected members.  So, a simple example in your case would be:

    Option Explicit On
    Option Strict On
    
    Imports System
    Imports System.Collections.ObjectModel
    
    Module Module1
    
        Sub Main()
            Dim dbs As New Databases()
    
            dbs.Add(New Database("Mine"))
    
        End Sub
    
        Class Database
            Private _name As String
    
            Public Sub New()
                MyBase.New()
            End Sub
    
            Public Sub New(ByVal name As String)
                _name = name
            End Sub
    
            Public Property Name() As String
                Get
                    Return _name
                End Get
                Set(ByVal value As String)
                    _name = value
                End Set
            End Property
        End Class
    
        Class Databases
            Inherits Collection(Of Database)
    
            Protected Overrides Sub InsertItem(ByVal index As Integer, ByVal item As Database)
                Console.WriteLine("Inserting {0} at {1}", item.Name, index)
                MyBase.InsertItem(index, item)
            End Sub
        End Class
    End Module
    

    As for the nameing convention - .NET has pretty much dropped the whole use of prefix's (Hungarian notation).  There are naming guidlines on the msdn website (see here for example).  Anyway, I would just look at some of the code around, and I think you'll start to get the general picture.

    Tom Shelton
    • Marked as answer by jal2 Thursday, April 9, 2009 4:42 PM
    Thursday, April 9, 2009 4:11 PM
  • Wow. That's a lot of excellent information for one post, Tom.  You just saved me a lot of research. Thanks!
    Thursday, April 9, 2009 4:42 PM