Desktop List Box
From Xojo Documentation
- 1 Events
- 2 Properties
- 3 Methods
- 4 Usage
- 4.1 ListBox Example Projects
- 4.2 Adding Rows
- 4.3 Row Selection
- 4.4 Row Height
- 4.5 Drag and Drop
- 4.6 Hierarchical Lists (Tree View)
- 4.7 Making a Source List
- 5 See Also
List Box controls display a scrolling list of values in one or more columns. The user can use the mouse or the arrow keys to choose an item. List Box controls can contain one or more columns of data, can be hierarchical, and can allow single row selection or multiple row selection. You can change the number of columns of any List Box by setting the ColumnCount property in the Inspector.
Below are some of the more common events, properties and methods. Refer to ListBox in the Language Reference for the complete list.
CellAction, CellBackgroundPaint, CellClick, CellGotFocus, CellKeyDown, CellLostFocus, CellTextChange, CellTextPaint
- These events are called for cell-specific changes.
- The change event is called when the selected row of the List Box changes, either by the user clicking on a new row or by code changing the row.
- Called when a folder in a hierarchical List Box is expanded or collapsed.
- Use this event to implement custom sorting for a column. Normally a column is sorted alphabetically, which results in incorrect ordering for columns containing only numbers. Use this event to sort the numbers properly.
- Called when rows of the List Box are dragged.
- Allows you to override the sorting of a column in the List Box.
- Called when the user has clicked on a column, also allowing you to prevent the sorting.
- Used to hide the scrollbars until they are needed.
- Specifies the opacity of the border.
- Used to specify cell alignments for specific cells (AlignDefault, AlignLeft, AlignCenter, AlignRight, AlignDecimal). Use CellAlignmentOffset (or ColumnAlignmentOffset) to control alignment for decimal values.
CellBorderBottom, CellBorderLeft, CellBorderRight, CellBorderTop
- Controls the borders that are displayed in individual cells (BorderDefault, BorderNone, BorderThinDotted, BorderThinSolid, BorderThickSolid, BorderDoubleThinSolid).
- Used to check or uncheck a checkbox that is in a cell.
CellTag, ColumnTag, RowTag
- Used to get or set the tag value (a Variant) for a specific cell, column or row.
- Specifies the type of a cell or column (TypeDefault, TypeNormal, TypeCheckBox, TypeEditable).
- Provides access to the ListColumn class to alter column-specific properties such as widths and resizability.
- Used to specify cell alignments for a column (AlignDefault, AlignLeft, AlignCenter, AlignRight, AlignDecimal). Use ColumnAlignmentOffset (or ColumnAlignmentOffset) to control alignment for decimal values.
- The number of columns.
- Controls the sort direction for a column (SortDescending, SortNone, SortAscending).
- A text string that specifies the widths to use for all the columns. You can specify widths as fixed pixel sizes, percentages or even calculations.
- Indicates that columns can be resized by the user by dragging the separator in the column header. The List Box must have a header in order for the columns to be resizable.
- The default row height for all the rows. All rows always have the same height.
- Enables row dragging, dropping and reordering.
- For hierarchical List Boxes, indicates if the specified folder row is currently expanded.
- Specifies the type of grid lines to use (BorderDefault, BorderNone, BorderThinDotted, BorderThinSolid, BorderThickSolid, BorderDoubleThinSolid).
- Indicates that the ListBox should have a heading. A heading is required in order to sort and resize columns.
- When HasHeading is True, contains an array of the column heading names.
- A hierarchical List Box has rows that can be expanded to show children (for rows added using AddFolder).
- A list of the initial values displayed in the List Box. Separate columns using tab characters. When HasHeading is True, the first row is the name of the headings.
- The last row added to the List Box by AddRow, AddFolder, InsertRow or InsertFolder.
- The number of rows in the List Box (1-based).
- The currently selected row in the List Box (0-based). Returns -1 when no row is selected.
- When True, a row remains selected even when the user clicks on an empty area of the List Box.
- Controls the vertical and horizontal scroll positions.
- If multiple selections are allowed, SelCount returns the number of selected rows. Selected is used to check if a row is selected.
- Specifies whether to allow single or multiple row selection (SelectionSingle, SelectionMultiple).
- Specifies the current sort column, but does not sort the data (call the Sort method).
- The text of the first column of the currently selected row.
- Indicates if the focus ring is drawn around the List Box.
- For hierarchical List Boxes, adds a folder (a row that may have children) to the end or inserts a folder at the specified position.
- Adds a row to the end or inserts a row at the specified position.
- Gets or sets the value of a specific cell (row, column).
CellBold, CellItalic, CellUnderline
- Used to set bold, italic and underline font settings for a specific cell.
- Specifies the value that appears as a tooltip when the mouse hovers over the cell.
- Returns either the column or row under the coordinates of the mouse cursor.
- Removes all rows from the List Box.
- Activates in-line editing for the specified cell.
- Used to allow or prevent the user from sorting a column by clicking on its header.
- Causes the specified header to be pressed.
- Allows you to specify a picture that appears on the far left of the List Box for each row. You can have a different picture for each row.
- A tag value for a row.
- Sorts the columns of the List Box, using the values specified in SortedColumn and ColumnSortDirection properties.
ListBox Example Projects
The List Box is one of the most powerful desktop controls. It can be made to display data in lots of different ways, from simple scrolling lists to more complicated layouts with custom graphics.
There are many ListBox example projects included in the Xojo Examples:
If your List Box has fixed data you can specify it using the InitialValue property in the Inspector. The values are tab-delimeted, with each value after a tab being a new column.
More commonly, rows are added to List Boxes dynamically. You can add rows using the AddRow method, which adds the row to the end. This code in the Open event handler adds rows from an array to the List Box:
For i As Integer = 0 To names.UBound
You can supply multiple columns as separate values in the AddRow method so you can easily populate an entire row of data at once. This code retrieves data from a SQLite database table and adds it to a List Box:
// Get all rows from the Team table.
Dim sql As String = "SELECT * FROM Team"
Dim data As RecordSet
data = App.DB.SQLSelect(sql) // App.DB is connected to a SQLite database
If App.DB.Error Then
MsgBox("DB Error: " + App.DB.ErrorMessage)
// Loop through each row, one-by-one, and add it to the ListBox called DataList.
If data <> Nil Then
While Not data.EOF
DataList.AddRow(data.Field("ID").StringValue, data.Field("Name").StringValue, _
// Add the Primary Key to the RowTag so it can be used later to
// edit or delete the row.
DataList.RowTag(DataList.LastIndex) = data.Field("ID").IntegerValue
Note the use of the DeleteAllRows method to clear the List Box before rows are added to it. LastIndex is the row index of the last row that was added. The InsertRow method can be used to add rows at a specific location in the List Box.
Use the RemoveRow method to removes rows from the List Box.
You can set the List Box so that single or multiple rows can be selected. The default is single-row selection. Use the SelectionType property to change how the rows are selected, the constant ListBox.SelectionSingle sets single selection and ListBox.SelectionMultiple sets multiple selection.
When single selection is enabled the Change event is called when the user clicks on rows. You can also use the ListIndex property to get the currently selected row number. A common technique is to put code in the Change event to check if a row was selected and then do something basedon the selected row. For example this code in the Change event displays what is in the first cell of the selected row:
Dim firstCell As String = Me.Cell(Me.ListIndex, 0)
MsgBox("You clicked: " + firstCell)
You can also set the ListIndex to a row number to select the row in your code:
To deselect all rows, set ListIndex to -1:
When multiple select is enabled, the user can use a modifier key (Command on macOS, Control on Windows/Linux) to select multiple rows. The Change event is still called as rows are selected, but the ListIndex property only tells you the last row that was selected. The SelCount property tells you how many rows are selected, but to determine which rows are selected you have to loop through the entire List Box and check if the row is selected by using the Selected method.
This code creates an array containing the row numbers of the selected rows:
For row As Integer = 0 To ListBox1.ListCount - 1 // Rows are zero-based
If ListBox1.Selected(row) Then
selectedRows.Append(row) // This row is selected
You can also use the Selected method to select rows in your code. This code selects odd-numbered rows:
For i As Integer = 0 To ListBox1.ListCount - 1
If i Mod 2 = 0 Then
ListBox1.Selected(i) = True
Rows in the List Box all have the same height. You can change the height of the rows by using the DefaultRowHeight to set a height in points. Use the value "-1" to have the height be determined by the font size.You can display multiple rows in a ListBox cell by setting a DefaultRowHeight large enough for the multiple rows and then drawing the text for the cell yourself using the CellTextPaint event.
Here is a brief example. Start with two array properties on a Window:
Add a ListBox and set its DefaultRowHeight to 48.
The Open event of the Window populates the properties and adds blank rows to the ListBox:
TeamCities = Array("Boston", "New York", "Baltimore", "Toronto", "Tampa Bay")
For i As Integer = 0 To TeamNames.Ubound
In the CellTextPaint event you then draw the contains of the arrays like this:
g.TextSize = 14
g.DrawString(TeamNames(row), 0, 20) // line 1
g.DrawString(TeamCities(row), 0, 40) // line 2
The result is a ListBox that displays the team name on the first line of the row and the team name on the second line.
Drag and Drop
For both single and multicolumn List Boxes, you can allow the user to drag rows to rearrange them. When you drag, the target for the drop is indicated by a solid line between rows. To enable this, turn ON the EnableDragReorder property for the List Box. When you drag a row to a new position, the DragReorderRows event is called.
Accept a Drop from Another Source
A List Box can accept items being dropped on it. Suppose you want to allow users to drop files from the Finder/Explorer onto a List Box. To do this you'll want to first create a File Type Set for "special/any". Then in the Open event of the List Box you can tell it to accept drops of any type of file:
Now add the DropObject event to the List Box. In this event you can add a row for the dropped file:
Me.RowTag(Me.LastIndex) = obj.FolderItem
This also saves a reference to the FolderItem to the file in the RowTag. This allows you to use it later. For example, you could try to run or display the file when the user double-clicks it. To do that, add the DoubleClick event to the List Box with this code:
Dim file As FolderItem = Me.RowTag(Me.ListIndex)
If file <> Nil And file.Exists Then
Drag and Drop Between List Boxes
You may have a need to allow people to drag a row from one List Box to another List Box. In order to enable the dragging of List Box rows like this you will need to turn ON the EnableDrag property.
You can now use the DragRow event to create a DragItem. To try this out, add two List Boxes to a Window. On List Box make sure the EnableDrag property is ON in the Inspector. Also make sure the SelectionType property is set to Single. Add the DragRow event to ListBox1 with this code:
The above code allows you to drag the row from the first List Box. Now you need to add the code to the second List Box so it can accept a dropped row. In the Open event for ListBox2, add this code to accept the drop:
Add the DropObject event to ListBox2 with this code to add the dropped text as a row:
Go back to ListBox1 and add some initial rows so you have something to drag. Now run the project and try dragging rows from the first List Box to the second List Box.
Drag and Drop Multiple Rows Between ListBoxes
You can also drag multiple rows from a List Box to drag onto another List Box. This process is similar to what you did above except that you will add multiple items to the DragItem, one for each selected row.Start with the project you made for Drag and Drop Between List Boxes above. In the Inspector for ListBox1, change the SelectionType property to Multiple.
Also in ListBox1 you'll want to change the code in the DragRow event to find all the selected rows. This is the code:
If Me.Selected(i) Then
Drag.AddItem(0, 0, 20, 4)
Drag.Text = Me.List(i) // get text
Return True // allow the drag
Note the use of the DragItem.AddItem method to add multiple items to the DragItem.
The last step is to update the DropObject code on ListBox2 so that it loops through all the drag items and add them as rows. Here is the code:
You can now run the project and select multiple rows to drag to the second List Box.
More About Drag and Drop
To learn more about drag and drop, visit the UserGuide:Desktop Drag and Drop topic.
These example projects use ListBox Drag & Drop:
Hierarchical Lists (Tree View)
A hierachical List Box is one where there are rows that are parents (or folders) that can be expanded to display children rows beneath them. Windows uses plus and minus signs to indicate a parent row that has children; macOS and Linux use disclosure triangles. Turn the Hierarchical property to ON in the Inspector to enable hierarchical List Boxes. Sometimes this type of list is called a tree view or tree view.
With a Hierarchical List Box, you use the AddFolder method to add parent rows to the List Box. When the user clicks to open the parent then the ExpandRow event is called. In this event you you can add the rows to be displayed as children.
You can use AddFolder with a List Box that has its Hierarchical property OFF, but then the parent rows do not show an indicator that the user can click on to expand the children. You would have to provide another way to expand the rows that manually calls the Expanded method. You'll also need to manually remove rows when the CollapseRow event is called.As an example, you will create a hierarchical List Box that displays the divisions for Major League baseball. When you expand a division you'll see the teams in the division displayed as children.
To start, add a ListBox to a Window and turn its Hierarchical property ON. In the Open event of ListBox1, use this code to add the divisions:
If you run the project now you'll see these division displayed with the indicator to their left. You can click the indicator now but nothing appears below it. Quit the app and go back to ListBox1 and add the ExpandRow event with this code to display the appropriate teams depending on which division was expanded:
Select Case value
Case "AL East"
Case "AL Central"
Case "AL West"
Run the project again. When you click to expand a division you'll see the appropriate teams.
When you collapse a parent, the children rows are automatically removed. It is important to understand that the List Box does not persist your children rows when the parent is collapsed, so you will always need to add them back in the ExpandRow event.
Creating a File Browser
The file system is a hierarchical list of files so it works well with a hierarchical List Box. Here's how you can create a List Box that displays the file system starting at the root level and lets you expand folders to see the files they contain.
Add a List Box to a Window and sets its ColumnCount to 3 and its ColumnWidths to "60%,30%,10%".
In the Open event of the List Box populate it with the list of all the connected drives:
Dim file As FolderItem = Volume(i)
Me.RowTag(Me.LastIndex) = file
Now add the ExpandRow event. In this event you will use the FolderItem that you saved in the RowTag to get the files in the folder and then add them appropriately.
For i As Integer = 1 To parent.Count
Dim file As FolderItem = parent.Item(i)
If file.Visible Then
If file.Directory Then
// Display the filename, modification date and its size
Me.AddRow(file.Name, file.ModificationDate.SQLDate, Str(file.Length))
Me.RowTag(Me.LastIndex) = file
Run the project and navigate through your file system as if you were using Finder or Explorer.
You can add a small enhancement to allow you to double-click on a folder to expand it (or collapse it) or to attempt to display/launch the file. Add the DoubleClick event to ListBox1 with this code:
If file.Directory Then
// Toggle the expansion of the folder
Me.Expanded(Me.ListIndex) = Not Me.Expanded(Me.ListIndex)
// Attempt to launch the file which will either display it in a default app if one is available
// or start it is if it is an app
Hierarchical Example Projects
These example projects use a hierarchical List Box:
Making a Source List
As an example for how powerful a List Box is, in this section you will create a List Box subclass to emulate a Source List control. A Source List control is like the sidebar you see in Mail apps, File Browsers, iTunes, and even the Navigator in Xojo. Here's an example of the Sidebar Source List in macOS Mail.
To emulate this control you'll be adding two project items. The first is a class that is used to create items to add to the Source List. The second item is the List Box subclass.
To start, add a new class to your desktop project and name it SourceListItem. Looking at the image above you can see that a Source List has three common types of items in it: sections (where is says "Mailboxes" and "Smart Mailboxes"), expandable items ("Inbox"), and the items themselves ("AOL", "Outlook"). So this class will be used to create each of these items so that they can be added to the Source List control itself.
Add an enumeration to the SourceListItem class and name it ItemTypes. It should have these elements: Section, Parent, Item.
Now add two properties (with Scope = Public), one to contain the name of the item and another to track its type:
To make it a little easier to create an item, add a Constructor that takes these two values as parameters so they can be assigned. This is the Constructor declaration:
This is its code:
ItemType = type
Now it is time to create the Source List control itself. Add another class to the project and in the Inspector change its Name to SourceList and its Super to "ListBox".
Add the Open event so that you can do some initialization for the control. In this case you'll want to sets its Hierarchical property to True and the row height to 22:
Notice the last line says "RaiseEvent Open". When you implement an event on a control subclass, the event is no longer available for its own subclasses. In this case it means that were you to drag SourceList onto a Window it would not have an Open event. But you can easily add the Open event back. On the SourceList class, go to the Insert menu and select "Event Definition". Name this Event Definition "Open". Now when you add SourceList to a Window it will have an Open event that will be called when you put code in it. You'll use this technique again later to add your own custom events to the SourceList control. The "RaiseEvent" command is optional. You could also call the Open event by just typing "Open".
Moving along, add a public method to SourceList and call it AddItem. It takes one parameter, which is the item to add. Here is the declaration:
This method adds a row for the type of item and saves the item in the CellTag for the row's first column so that the information can be used by other ListBox events. Here is the code for AddItem:
CellTag(LastIndex, 0) = item
It is now time to implement the events to handle the drawing of the Source List. Add the CellTextPaint event. This event will get the SourceListItem that is saved in the CellTag and use it to determine how to draw it. To make a Section stand out you will draw it in bold and in all caps. A Parent or Item will be drawn normally so you don't have to handle it separately. Here is the code:
Dim item As SourceListItem = CellTag(row, 0)
Const kMargin = 4
Select Case item.ItemType
g.ForeColor = &c7F7F7F00 // dark gray
g.Bold = True
g.DrawString(Uppercase(item.ItemName), 0, g.Height - kMargin)
The "Return True" tells the ListBox that you handled the text drawing.
Before getting too far along, it's time to test this out. Drag the SourceList control from the Navigator onto the layout for Window1. This adds it to the window as SourceList1. Now add the Open even to SourceList1 with this code to add some intial rows to simulate a Mail app:
item = New SourceListItem("Mailboxes", SourceListItem.ItemTypes.Section)
item = New SourceListItem("Inbox", SourceListItem.ItemTypes.Parent)
item = New SourceListItem("Local", SourceListItem.ItemTypes.Section)
item = New SourceListItem("Trash", SourceListItem.ItemTypes.Item)
Run the project and you should see something like this (macOS/Windows):
One thing to note about the Section type is that it should not be selectable. But you can definitely click on "MAILBOXES" or "LOCAL" right now. Quit the project and go back to the SourceList class so you can fix this behavior. On the SourceList class (the one in the Navigator, not the one on the Window) add the CellClick event. In this event you want to check if the user clicked on a Section and if they did, do not allow the click to work. Here is the code:
Dim item As SourceListItem = CellTag(row, 0)
If item.ItemType = SourceListItem.ItemTypes.Section Then
Return True // prevent selection/click
The code gets the SourceListItem that is saved in the CellTag and checks it type. If it is a Section then it returns True to prevent the click. Note the code in the "Else" section, though. This code is actually calling a custom event definition. What you can do is add your own event to the SourceList control that is called when the user selects a row in the SourceList and it can even pass in which row was selected and the SourceListItem itself. This event can be used instead of the standard Change event to make the control a bit easier to use. You'll need to add the ItemSelected event, of course. So add an Event Definition with this declaration:
To use this event, go back to Window1 and click on SourceList1. Open the Add Event Handler window and you'll now see "ItemSelected" as one of the events. Choose it and add this code to it:
Now run the project again. Verify that you Section clicking is disabled. Now try clicking on "Inbox" or "Trash". You should see the message box. If you try to expand "Inbox" you should notice that it doesn't actually do anything. That is the final piece to add to the SourceList. Quit the app and go back to the SourceList control (again, the one in the Navigator).
Add the ExpandRow event. This event is called when you try to expand a hierachical row, such as one that has the type of "Parent". The code here will just get the SourceListItem from the CellTag and then pass it off to another custom event you will also create. Here is the ExpandRow event code:
Add the ItemExpanded event definition with this declaration:
You will use this ItemExpanded event to add rows to display when the item is expanded. In this case you'll want to add "Gmail" and "iCloud" then "Inbox" is expanded. To do this, go back to Window1 and select SourceList1. Open the Add Event Handler window and choose your new ItemExpanded event. Add this code to it:
Dim newItem As SourceListItem
newItem = New SourceListItem("Gmail", SourceListItem.ItemTypes.Item)
newItem = New SourceListItem("iCloud", SourceListItem.ItemTypes.Item)
The code checks the item to see if "Inbox" was the one that was expanded and if it was, it creates new SourceListItems (of type Item) and adds them to the SourceList.
Run the project and expand Inbox. You should see both "Gmail" and "iCloud":
You now have the start of a SourceList control that you can reuse in your projects.