Using TreeListView in VB.net

1.3k views Asked by At

I'm working on creating a simple example of using a TreeListView in VB.NET (or at least one I can follow), and I'm running into an issue. When I run the code below, everything initially works. I have a tree of pet owners with branches of pet names. But after I expand one of the nodes, and move my mouse, I get an error telling me I can't convert a string object to a petowner object (my object class). I understand what that means, but VS doesn't tell me where the error is, and I can't trap it in a try-catch either. I'm looking for some insights.

Also: can anyone tell me if my conversion from C# to VB is correct; specifically the lambda functions in place of the delegates in the ChildrenGetter and AspectGetter methods? I'm fairly certain that's where the error is.

Thanks in advance.

Public Class Form1

Public Sub New()

    ' This call is required by the designer.
    InitializeComponent()

    ' Add any initialization after the InitializeComponent() call.

    Dim PetOwners As New List(Of PetOwner)
    Dim PetOwner As PetOwner

    PetOwner = New PetOwner
    PetOwner.OwnerName = "Steve"
    PetOwner.PetNames.Add("Bob the Cat")
    PetOwner.PetNames.Add("Snoop the Dog")
    PetOwners.Add(PetOwner)

    PetOwner = New PetOwner
    PetOwner.OwnerName = "Ann"
    PetOwners.Add(PetOwner)

    PetOwner = New PetOwner
    PetOwner.OwnerName = "Joe"
    PetOwner.PetNames.Add("Shoeless")
    PetOwners.Add(PetOwner)

    Try
        tlvPetOwners.CanExpandGetter = Function(po As PetOwner) po.PetNames.Count > 0
        tlvPetOwners.ChildrenGetter = Function(po As Object)
                                          Dim RetVal As Object = Nothing
                                          Try
                                              If TypeOf po Is PetOwner Then
                                                  RetVal = CType(po, PetOwner).PetNames
                                              Else
                                                  RetVal = po
                                              End If
                                          Catch ex As Exception
                                              Debug.Print(ex.ToString)
                                          Finally

                                          End Try

                                          Return RetVal

                                      End Function


        Dim OwnerColumn As New OLVColumn()


        OwnerColumn.AspectGetter = Function(po As Object)
                                       Dim RetVal As Object = Nothing
                                       Try
                                           If TypeOf po Is PetOwner Then
                                               RetVal = CType(po, PetOwner).OwnerName
                                           Else
                                               RetVal = po
                                           End If
                                       Catch ex As Exception
                                           Debug.Print(ex.ToString)
                                       Finally

                                       End Try

                                       Return RetVal

                                   End Function
        tlvPetOwners.Columns.Add(OwnerColumn)

        tlvPetOwners.Roots = PetOwners
    Catch ex As Exception
        Debug.Print(ex.ToString)
    End Try
End Sub

End Class


Public Class PetOwner
    Public OwnerName As String
    Public  PetNames As New List(Of String)
End Class
2

There are 2 answers

0
Charles Dodson On

Okay, I figured it out, and learned a couple of things as well (yea me - lol). First off, I had come to the conclusion my problem existed from trying to use a list of strings as pet names, and had not created a list of custom objects called Pet with a property of Name to use as an AspectName for the TreeListView column like you would an ObjectListView. I didn't want to go through this much trouble to create a checklist of names in my actual project (this is just a test).

If you try something like this on an ObjectListView, you'll run into a problem with the AspectName method, and the same holds true for a TreeListView.

Private Sub MakeObjectList()

    Dim Pets As New List(Of String)
    Pets.Add("Smokey")
    Pets.Add("Rufus")
    Pets.Add("Petey")
    Pets.Add("Garfield")
    Pets.Add("Ren")
    Pets.Add("Stimpy")
    Pets.Add("Smokey")

    Dim PetCol As New OLVColumn()
    PetCol.Text = "Pets"
    'You would never be able to do this, because you'd have nothing to set it to, and without it your list would never appear.
    'PetCol.AspectGetter = "??????"

    'But using the AspectGetter, you can supply a Lambda, using the pet name itself as the aspect name.
    PetCol.AspectGetter = Function(p As String) p

    olvPets.Columns.Add(PetCol)

    olvPets.SetObjects(Pets)

End Sub

But, you can get around the problem by using a Lambda for the AspectGetter method, which essentially provides the pet name as the AspectName. Not useful in database applications, but useful if you're only trying to supply a checklist of names.

I then decided to use this approach for the TreeListView. I did have to create a second column to hold the pet name, and in doing so set the AspectGetter to this:

PetColumn.AspectGetter = Function(po As Object)
                                     Dim RetVal As String
                                     If TypeOf po Is String Then
                                         RetVal = CType(po, String)
                                     Else
                                         RetVal = String.Empty
                                     End If
                                     Return RetVal
                                 End Function

The trick is this line:

  RetVal = CType(po, String)

This allows you to define the string itself as the aspect name.

Here's my final code. It consists of both an ObjectListView (olvPets) and a TreeListView (tlvPetOwners) you'd have to place on a form. Anyway, I hope this helps someone else out.

Public Class Form1

Public Sub New()

    ' This call is required by the designer.
    InitializeComponent()

    ' Add any initialization after the InitializeComponent() call.

    MakeTreeView()
    MakeObjectList()

End Sub

Private Sub MakeTreeView()
    Dim PetOwners As New List(Of PetOwner)
    Dim PetOwner As PetOwner

    PetOwner = New PetOwner
    PetOwner.OwnerName = "Steve"
    PetOwner.PetNames.Add("Bob the Cat")
    PetOwner.PetNames.Add("Snoop the Dog")
    PetOwners.Add(PetOwner)

    PetOwner = New PetOwner
    PetOwner.OwnerName = "Ann"
    PetOwners.Add(PetOwner)

    PetOwner = New PetOwner
    PetOwner.OwnerName = "Joe"
    PetOwner.PetNames.Add("Shoeless")
    PetOwners.Add(PetOwner)

    Dim OwnerColumn As New OLVColumn()

    tlvPetOwners.Columns.Add(OwnerColumn)

    Dim PetColumn As New OLVColumn()
    tlvPetOwners.Columns.Add(PetColumn)

    Try
        tlvPetOwners.CanExpandGetter = Function(po As Object)
                                           Dim RetVal As Boolean = False
                                           If TypeOf po Is PetOwner Then
                                               If po.PetNames.Count > 0 Then
                                                   RetVal = True
                                               End If
                                           End If
                                           Return RetVal
                                       End Function

        tlvPetOwners.ChildrenGetter = Function(po As Object) CType(po, PetOwner).PetNames

        OwnerColumn.AspectGetter = Function(po As Object)
                                       Dim RetVal As String = String.Empty
                                       If TypeOf po Is PetOwner Then
                                           RetVal = CType(po, PetOwner).OwnerName
                                       Else
                                           RetVal = String.Empty
                                       End If
                                       Return RetVal
                                   End Function

        PetColumn.AspectGetter = Function(po As Object)
                                     Dim RetVal As String
                                     If TypeOf po Is String Then
                                         RetVal = CType(po, String)
                                     Else
                                         RetVal = String.Empty
                                     End If
                                     Return RetVal
                                 End Function

        tlvPetOwners.SetObjects(PetOwners)
        tlvPetOwners.ExpandAll()

    Catch ex As Exception
        Debug.Print(ex.ToString)
    End Try

End Sub

Private Sub MakeObjectList()

    Dim Pets As New List(Of String)
    Pets.Add("Smokey")
    Pets.Add("Rufus")
    Pets.Add("Petey")
    Pets.Add("Garfield")
    Pets.Add("Ren")
    Pets.Add("Stimpy")
    Pets.Add("Smokey")

    Dim PetCol As New OLVColumn()
    PetCol.Text = "Pets"
    'You would never be able to do this
    'PetCol.AspectGetter = "??????"

    PetCol.AspectGetter = Function(p As String) p

    olvPets.Columns.Add(PetCol)

    olvPets.SetObjects(Pets)

End Sub

End Class

Public Class PetOwner
Public OwnerName As String
Public PetNames As New List(Of String)
End Class
0
Captain Fantastic On

I've been using TreeListView for a while now and instead of your Lambda functions, I use AddressOf to point to a routine in the same space..

With tlv

  tlv.BeginUpdate()

  .CanExpandGetter = AddressOf b_tlvCanExpandGetter   ! ***** 
  .ChildrenGetter = AddressOf mcol_GetOccupants       ! *****
  .Roots = colRoot   ' Collection of objects to make the root nodes

  tlv.EndUpdate()

  Application.DoEvents()
  ... etc

The routines pointed to can then use any properties, functions etc to make the decision or return the list of values. Eg

Function mcol_GetOccupants(ByVal oTI As Object) As List(Of TreeItem)

  Dim ti As TreeItem = CType(oTI, TreeItem)
  Select Case ti.ObjectType

    Case BaseObject.BaseObjectType.eTypeA
      Return CType(oTI, TypeA).Occupants

    Case BaseObject.BaseObjectType.eTypeB,
          BaseObject.BaseObjectType.eTypeC

      Return CType(oTI, TypeBC).Occupants

    Case BaseObject.BaseObjectType.eTypeD
      Return CType(oTI, TypeD).Occupants

    Case Else
      Return New List(Of TreeItem)
  End Select

End Function

The other thing I would note is that even moving your mouse over the TreeListView can trigger a RePaint so putting breakpoints into the refresh code is fraught with danger. Instead I put lots of Console.WriteLine's to tell me what's going on. This occurs especially on a small screen where the IDE covers the TreeListView and once you Run again after your breakpoint stop the project has yet again to call repaint to draw it's screen that was covered - which jumps back into the IDE - which covers the project screen - and round the loop you go..... So, if you really have to debug the refresh code ensure the IDE and project screen do not overlap in any way.