Hierarchical Custom Listbox

Happy New Year everyone.

I created a hierarchical Custom Listbox with Sections. Sections shouldn’t be select- and clickable (Keydown-Event). It works fine, if the structure is

  • Section
  • Item (a) // next Selection is b
  • Section
  • Item (b) // next Selection is a

But if i’ll have 2 or more closed Sections, it doesn’t work.

  • Section (1)
  • Section (2)
  • Item (a) // when use Keydown, Section 4 will be selected and not Item b (that’s what i want)
  • Section (3)
  • Section (4)
  • Section (5)
  • Item (b)

I know, it will be maybe a mathematical Thing?! Any Ideas? Below my Source :wink:

Thank you all!

[h]Source[/h]
Event Definitions

[code]Sub ExpandRow(row as Integer)
If Me.RowIsFolder(row) Then

Select Case Me.Cell(row, 0)
Case "Parents"
  Me.AddItem Array("Dad", "Max Mustermann")
  Me.AddItem Array("Mom", "Maria Mustermann")
Case "Children"
  Me.AddItem Array("I.", "Clara Mustermann")
Case "Grand-Parents"
  Me.AddItem Array("Greg Mustermann")
  Me.AddItem Array("Leila Mustermann")
End Select

End If
End Sub

Sub Open
Me.AddSection(“Parents”)
Me.AddSection(“Children”)
Me.AddSection(“Grand-Parents”)
End Sub

Event CellClick(row As Integer, column As Integer, x As Integer, y As Integer) As Boolean
Sub ()

Event KeyDown(Key As String) As Boolean
Sub ()[/code]
Events

[code]Function CellBackgroundPaint(g As Graphics, row As Integer, column As Integer) As Boolean
If row < Me.ListCount Then
If Me.RowIsFolder(row) Then
// Background
g.ForeColor = &cF8F8F800 g.FillRect 0, 0, g.Width, g.Height // Border
g.ForeColor = &cCCCCCC00
If row <> 0 Then
If Not Me.RowIsFolder(row-1) Then
g.DrawLine 0, 0, g.Width, 0 End If
End If
g.DrawLine 0, g.Height-1, g.Width, g.Height-1
Return True
End If
End If
End Function

Function CellClick(row as Integer, column as Integer, x as Integer, y as Integer) As Boolean
Call CellClick(row, column, x, y)
If RowTag(row) = RowType.Section And x > 14 Then Return True // 14 Pixels for the Triangular
End Function

Function CellTextPaint(g As Graphics, row As Integer, column As Integer, x as Integer, y as Integer) As Boolean
Dim Type As RowType = RowTag(row)

If Type = RowType.Section Then
g.Bold = True
g.ForeColor = &c23232300
g.DrawString(Cell(row, column), 0, g.Height - (g.TextHeight/2)) Return True
End If
End Function

Function KeyDown(Key As String) As Boolean
Call KeyDown(Key)

Dim idx As Integer = Me.ListIndex
Dim max As Integer = Me.ListCount

Select Case Asc(Key)
Case 30 // up

Select Case idx - 1
Case 0
  If Me.RowIsFolder(0) Then
    ListIndex = idx + 1
  Else
    ListIndex = idx - 1
  End If
Case Is > 0
  If Me.RowIsFolder(idx - 1) Then
    ListIndex = idx - 1
  Else
    ListIndex = idx
  End If
End Select

Case 31 // down
If idx <> Max Then
If idx + 1 < max Then
If Me.RowIsFolder(idx + 1) Then
ListIndex = idx + 1
Else
ListIndex = idx
End If
End If
Else
ListIndex = idx
End If
End Select
End Function[/code]
Methods

[code]Sub AddItem(itemName() As String)
AddRow(itemName)
RowTag(LastIndex) = RowType.Item
End Sub

Sub AddSection(sectionName As String)
AddFolder(sectionName)
RowTag(LastIndex) = RowType.Section
End Sub[/code]
Enumerations

Enum RowType Section Item End Enum

When pressing the down key, you’re only increasing the list index to n+1. You’ll need to search downward to find the next expanded row and then select the next item from there.

Thanks Greg, i know, but i need some input please. There are many ??? in my head :wink:

Well, in your code, you’re only checking to see if the next row is a folder and then selecting the very next item. The next row is Section 3, so it selects Section 4.

Instead, you’ll need to use a for-next loop to check consecutive rows until you either find an expanded row or you reach the end of the list. If you find an expanded row, you need to check and see if it has any contents. If it does, that’s the row you want selected. Otherwise, keep looping.

OK, with Gregs Help i got it working jumping from one to another Row with more then one Folder between the Rows. Please look, but it’s buggy, try it please. Don’t know why. If all Folders are closed, Listindex should be -1!
[h]ListBox KeyDown-Event[/h]

[code]Dim idx As Integer = Me.ListIndex
Dim Max As Integer = Me.ListCount
Dim Current As Integer

Select Case Asc(Key)
Case 30 // up

Select Case idx - 1
Case 0
  If Me.RowIsFolder(0) Then
    ListIndex = idx + 1
  Else
    ListIndex = idx - 1
  End If
Case Is > 0
  If Me.RowIsFolder(idx - 1) Then
    // ListIndex = idx - 1
    For Current = idx - 1 DownTo 0
      If Not Me.RowIsFolder(Current) Then
        Me.ListIndex = Current + 1
        Exit
      End If
    Next 
  Else
    If Me.RowIsFolder(idx) And Me.ListIndex = Max Then ListIndex = -1
    // ListIndex = idx
  End If
End Select

Case 31 // down

If idx <> Max Then
  If idx + 1 < max Then
    If Me.RowIsFolder(idx + 1) Then
      // ListIndex = idx + 1
      For Current = idx + 1 To Me.ListCount
        If Not Me.RowIsFolder(Current) Then
          Me.ListIndex = Current - 1
          Exit
        End If
      Next 
    Else
      ListIndex = idx
    End If
  End If
Else
  If Me.RowIsFolder(idx) And Me.ListIndex = Max Then ListIndex = -1
  // ListIndex = idx
End If

End Select[/code]

This is how I’d write it. Whether you’re going up or down the logic of finding a row is the same. So the key is turned into a direction. The direction is used to find the row to begin searching at. Then it just loops in that direction until a match is found or it reaches the end. The ListIndex isn’t reapplied until what it’s going to be is figured out. This lets the selection go in and out, I mean if there’s no selection one will come in and if you key in a direction where no rows are available then the selection is removed. Can easily be handled the other way.

[code]Function KeyDown(Key As String) As Boolean

dim searchDir As integer //init direction to search

Select Case Asc(Key)
Case 30 //up
searchDir = -1
Case 31 //down
searchDir = 1
Else
return false //or true to prevent beeps
End Select

dim searchIdx As integer //init index to start searching at

if me.ListIndex >= 0 then
searchIdx = me.ListIndex + searchDir
else //no selection, start at top or bottom
searchIdx = if (searchDir=1, 0, me.ListCount-1)
end

do //look for available row
if searchIdx < 0 or searchIdx >= me.ListCount then
me.ListIndex = -1 //handle reached end of search
exit
end
if not me.RowIsFolder(searchIdx) then
me.ListIndex = searchIdx //found new row
exit
end
searchIdx = searchIdx + searchDir //step
loop

//handled
return true

End Function[/code]

Actually, probably what I’d do is wrap the part after the Select Case into a utility method so the logic of finding a new, valid rows can easily be used in other places, like MouseDown.

@Will Shank : Perfect Will, ist works. Thank you so much :wink:

Hello everyone,

this works fine for me, but I ask myself, how to get everytime a selected row? What do I mean? If the Last Row is selected and the user pushes keydown (31) then the ListIndex becomes -1.

[quote=239341:@Will Shank]if searchIdx < 0 or searchIdx >= me.ListCount then me.ListIndex = -1 //handle reached end of search exit end[/quote]

Would be fine to find the first selectable row from the beginning of the Listbox which is not a FolderRow. How to realize that?

[h]Samples[/h]

[code]Row 1

  • Folder (Row 2)
    Row 3
    Row 4
    Row 5[/code]
    If Row 5 is selected, it should automatically jump to Row 1 and upside down, if Row 1 is selected and the Key is 30, Row 5 should be selected.

+ Folder (Row 1) Row 2 Row 3 Row 4
If Row 4 is selected, it should automatically jump to Row 2 and upside down, if Row 2 is selected and the Key is 30, Row 4 should be selected.

+ Folder (Row 1) Row 2 Row 3
If Row 3 is selected, it should automatically jump to Row 2 and upside down, if Row 2 is selected and the Key is 30, Row 3 should be selected.

Hope you can follow me and we’ll find a way to modify @Will Shank 's KeyDown Method to get it work.

or you can try this…http://www.rdS.com/treeview/

Thanks David, but I think should be possible with Xojo own Listbox Control :wink:

just providing an alternative… and FWIW… Treeview IS based on Xojo’s own listbox

Hi everyone,

until now the snippet of @Will Shank worked fine. I have 2 new scenarios. What if…

  1. row = first selectable row from top (does not mean it will be the fist physical row (ListIndex = 0), because this could also be a RowAsFolder Item) - if user press UP key, the row should remain selected
  2. analog to above: row = first selectable row from bottom (does not mean it will be the last physical row (ListIndex = ListCount), because this could also be a RowAsFolder Item) - if user press DOWN key, the row should remain selected

How can we modify this algorithm?