FileBrowser Control for Xojo

With the release of Xojo 2026r3, we get the new DesktopTable control. I love this control. It's like a ListBox but fancier. And now that we can combine cells, we can do a lot of cool things with it. During the 2026r3 beta, I began building a few projects implementing DesktopTable, and one of the things I've always wanted in Xojo was a File Browser. It is sorely needed; even as far back as Visual Basic 1.0 for DOS, we had a FileListBox. Delphi has the TFileListBox, QT has QFileSystemModel, Python has wx.FileCtrl, but nothing for Xojo. That changes now! Using DesktopTable I have created a FileBrowser as a ContainerControl that you can just drop into your Window, set a few properties and go. It works on Windows, MacOS and Linux (tested on Ubuntu on X11, it should work in Wayland but I haven't personally tested it).



License: BSD 3-Clause License.
Please credit "Christian Wheel - https://ChristianWheel.com"
To use the control, open the sample project, copy the FileBrowser folder, and paste it into your project. Then, from inside the folder, drag the FileBrowser container into your own Window.

Properties
All properties can be set in code at any time. The ones marked Inspector also appear in the Inspector (Behavior group) and can be set at design time.
Property | Type | Description |
Folder | FolderItem | The folder shown. Assigning queues a load, then raises FolderChanged once it has been read. Nil, a file, or a missing folder is ignored. While a new folder is loading, reading Folder returns the new folder. |
ViewMode | FileBrowser.ViewModes | LargeIcons, SmallIcons, List or Details. |
AllowMultipleSelections | Boolean | Allows Ctrl/Cmd-click toggling, Shift ranges, Ctrl/Cmd+A, and makes FolderItemSelected(f) = True add to the selection. Turning it off keeps only the primary item selected. |
FileMask | String | Restricts the files shown to some extensions. Accepts "mp3;wav", "*.mp3; .wav", ".mp3,.wav", "mp3 wav", in any case. "", "" or "*.*" means all files. Folders are never hidden by the mask. |
FilterString | String | Shows only items whose name contains every word (space-separated, any order, case-insensitive). Applies to files and folders. Safe to set on every keystroke, because updates are coalesced. |
ShowFolders | Boolean | False lists files only. Users then can't open subfolders from the list. |
ShowHiddenFiles | Boolean | Shows items the OS marks hidden, names starting with ., $ or ~$, and desktop.ini, Thumbs.db and System Volume Information. |
SortField | FileBrowser.SortFields | The field items are sorted by, in every view: Name, DateModified, Type or Size. |
SortDirection | FileBrowser.SortDirections | Ascending or Descending. |
OpenFoldersOnDoubleClick | Boolean | When True, double-clicking a folder (or pressing Return on it) opens it after raising FolderDoubleClicked. When False, only the event is raised. |
ViewModes enumeration
FileBrowser.ViewModes.LargeIcons // 0 - 64-point icons in a grid, names below
FileBrowser.ViewModes.SmallIcons // 1 - 32-point icons in a grid
FileBrowser.ViewModes.List // 2 - one row per item: 16-point icon + name
FileBrowser.ViewModes.Details // 3 - Name | Date Modified | Type | SizeSortFields and SortDirections enumerations
FileBrowser.SortFields.Name // 0 (default)
FileBrowser.SortFields.DateModified // 1
FileBrowser.SortFields.Type // 2
FileBrowser.SortFields.Size // 3
FileBrowser.SortDirections.Ascending // 0 (default)
FileBrowser.SortDirections.Descending // 1Events
Event | Raised when |
FileSelected (f As FolderItem) | A file becomes the primary selected item, through a click, keys, type-ahead, SelectFile, or FolderItemSelected(f) = True. |
FolderSelected (f As FolderItem) | The same, for a folder. |
FileDoubleClicked (f As FolderItem) | A file is double-clicked, or Return/Enter (Cmd+Down on macOS) is pressed on it. FileBrowser does nothing else; opening, playing or importing the file is up to you. |
FolderDoubleClicked (f As FolderItem) | The same, for a folder. Afterwards the folder opens if OpenFoldersOnDoubleClick is True. |
NavigationRequested (f As FolderItem) As Boolean | The browser is about to show a different folder. Return True to block the move (the current folder stays and nothing is loaded); return False, or don't implement the event, to allow it. |
SortChanged() | The user clicked a Details column heading, which changed SortField and/or SortDirection. It isn't raised when your code sets those properties. |
FolderChanged (f As FolderItem) | A different folder has been listed, by any route. It is raised before the selection events of any queued SelectFile. |
SelectionChanged() | The selection changed (count, members or primary item). Use SelectedFolderItems / SelectedCount to read it. |
FilesDropped (files() As FolderItem, targetFolder As FolderItem) | Files or folders were dropped on the browser. targetFolder is the folder under the mouse, or otherwise the folder shown. FileBrowser does not copy or move anything itself. |
Methods
Method | Description |
Refresh (immediately As Boolean = False) | Re-reads the current folder from disk and keeps the selection, primary item and scroll position. If the folder no longer exists, its nearest existing parent is shown (with FolderChanged). It overrides the standard control Refresh, so your code calls it as usual. Repeated calls are merged into one re-read. |
SelectFile (f As FolderItem) | Loads f's parent folder if needed, scrolls so f is visible (centred when it was off-screen), and makes f the only selected item. |
ScrollToFile (f As FolderItem) | Like SelectFile but without selecting. Loading a different parent folder still clears the selection. |
FolderItemSelected (f As FolderItem) As Boolean | True when f is shown and selected. |
FolderItemSelected (f As FolderItem) = value | Selects or deselects f. With AllowMultipleSelections it adds f to the selection; otherwise f replaces the selection. If f is in another folder, that folder is loaded first. |
SelectedFolderItems() As FolderItem() | Every selected item, in on-screen order (this follows a Details sort). |
SelectedFolderItem() As FolderItem | The primary (most recently clicked or keyed-to) selected item, or Nil. |
SelectedCount() As Integer | How many items are selected. |
ItemCount() As Integer | How many items are shown after filtering (0 while loading). |
SelectAll() | Selects everything shown. Requires AllowMultipleSelections. |
DeselectAll() | Clears the selection. |
GoUp() | Shows the parent folder with the folder you came from selected. |
CopyIntoFolder (files() As FolderItem, dest As FolderItem) As Integer | Copies files and folders (recursively) into dest, then refreshes. It never overwrites or moves: name clashes become Name 2.ext, Name 3.ext, and so on. It skips items already in dest and folders copied into themselves. Returns how many were copied. Runs on the main thread. |
Approving navigation
NavigationRequested(f) is raised before every change to a different folder, whatever caused it:
a double-click, Return/Enter or Cmd+Down on a folder;
GoUp, Backspace or Cmd+Up;
assigning Folder;
SelectFile, ScrollToFile or FolderItemSelected(f) = True for an item in another folder.
It is not raised by Refresh, which re-reads the same folder, or by the very first default folder (your home folder, if you never set Folder).
If you return True, nothing happens: no loading, no FolderChanged, and any queued selection is dropped. FolderDoubleClicked is still raised before NavigationRequested on a double-click, because the double-click itself did happen.
Function NavigationRequested(f As FolderItem) As Boolean
// keep the user inside the project folder
Return Not f.NativePath.BeginsWith(ProjectRoot.NativePath)
End FunctionProgrammatic changes raise events too. Xojo's own controls do the same: their SelectionChanged fires for changes made in code. A Refresh or filter change that only restores the existing selection raises SelectionChanged only if the selection actually shrank.
Selecting items that are filtered out
SelectFile, ScrollToFile and FolderItemSelected only work on items that are shown. If FileMask, FilterString, ShowFolders or ShowHiddenFiles hides the item, its folder is still loaded but nothing is selected.
Views
View | Layout |
Large Icons | A grid of 124-point cells: a 64-point icon with the name wrapped onto up to two lines below it. |
Small Icons | The same with 92-point cells and 32-point icons. |
List | One row per item: a 16-point icon and the name, shortened with "…" if needed. |
Details | Name / Date Modified / Type / Size. The sorted column's heading shows ▲ (ascending) or ▼ (descending); by default Name ▲. Click a heading to sort by it; click it again to reverse. The Type column uses Windows' own type names ("MP3 Format Sound"); other platforms show "MP3 File". |
How the views are built:
Icon views: each line of icons is two table rows, an icon row and a name row. The grid re-flows when the control is resized, after a 60 ms pause so dragging a window edge isn't slow.
Folders with over 300 items: switching view first shows "Arranging files...".
Across view changes: the selection is kept, and the primary item stays in view.
Sorting
SortField and SortDirection apply to all four views. The Large/Small Icons grid and the List follow the same order as Details.
Folders always come first, in both directions. The sort field orders the folders among themselves and the files among themselves. Folders have no size and all have the type "Folder", so with Size or Type they fall back to sorting by name.
Ties are broken by name, so the order is stable.
Dates and sizes compare by value (9 KB before 10 KB), not as text.
Changing the sort re-sorts the items already read (no disk access) and keeps the selection.
Clicking a Details heading sorts by that column; clicking the same heading reverses it. This updates SortField/SortDirection and raises SortChanged.
New folders are listed in the current sort order.
FileBrowser1.SortField = FileBrowser.SortFields.DateModified
FileBrowser1.SortDirection = FileBrowser.SortDirections.Descending // newest firstSelection
FileBrowser keeps its own selection in every view rather than using the table's native row selection. That keeps the behaviour identical in the grid and list views, and lets a drag carry the whole selection.
Action | Single selection (default) | AllowMultipleSelections = True |
Click | select that item | select only that item |
Ctrl-click (Win/Linux), Cmd-click (macOS) | as click | toggle the item |
Shift-click | as click | select the range from the anchor, in on-screen order |
Press on an item of a multi-selection | — | keeps the group (so it can be dragged); releasing without dragging selects just that item |
Click empty space | clear | clear |
Double-click (450 ms, 6 pt) | open (see events) | open |
The primary item is the one clicked or keyed to last. It is the item reported by FileSelected/FolderSelected and SelectedFolderItem, and it gets an outline in the icon views.
The selection colour stays the same whether or not the browser has focus, so the selection stays visible while the user works elsewhere.
Keyboard
The browser takes focus when clicked.
Keys | Action |
Arrow keys | Move. In the icon views all four arrows work; in List and Details, Up and Down. |
Home / End | First / last item |
Page Up / Page Down | One screenful |
Shift + the above | Extend the selection (multiple selection) |
Ctrl+A / Cmd+A | Select all (multiple selection) |
Return / Enter | Open the primary item (same as a double-click) |
Cmd+Down (macOS) | Open |
Backspace (Win/Linux), Cmd+Up (macOS) | Up one folder |
Letters, digits, punctuation | Type-ahead (below) |
Type-ahead works like File Explorer and Finder:
One character: selects the next item after the current one, wrapping round, whose name starts with it.
The same character again: cycles to the next such item, so m, m, m steps through every name starting with M.
Several different characters typed within a second of each other: build a prefix, so mo jumps to the first name starting with "mo".
After a one-second pause: typing starts a new search.
Type-ahead ignores case, follows on-screen order, and only counts a space inside a prefix.
Filtering: FileMask, FilterString, ShowFolders, ShowHiddenFiles
The folder is read once, hidden items included, and the four settings only decide what is shown. So changing them never re-reads the disk. An item is shown when all of these hold:
It isn't hidden, or ShowHiddenFiles is True.
It isn't a folder, or ShowFolders is True.
For files only: FileMask is empty, or the file's extension is in the mask.
Its name contains every word of FilterString.
Selected items that are still shown stay selected after a filter change. Items that disappear leave the selection, and SelectionChanged is raised.
Extensions: FileBrowserIcons.ExtensionOf(name) is the text after the last dot, lowercased. Names that only start with a dot (.bashrc) have no extension.
Drag and drop
Out of the browser
Starting a drag: press on a selected item and move 5 points. The whole selection (files and folders) is dragged, with the primary item's icon as the drag image.
Where it can go: onto your window or controls, other applications, the desktop, or a folder inside the browser itself.
Receiving the items: a drag carries several items. Read them like this, which works for drags from FileBrowser or from the OS:
Sub DropObject(obj As DragItem, action As DragItem.Types)
Var files() As FolderItem
Var more As Boolean = True
While more
If obj.FolderItemAvailable Then
If obj.FolderItem <> Nil Then files.Add(obj.FolderItem)
End If
more = obj.NextItem
Wend
// use files()
End SubThe target control must accept files, e.g. in its Opening event: Me.AcceptFileDrop("special/any") and Me.AcceptFileDrop("special/folder").
Into the browser
FileBrowser accepts files and folders dropped from anywhere, including from itself:
While dragging: a folder under the mouse is highlighted, meaning the drop goes into it. Anywhere else, the drop goes into the folder shown.
Ignored drops: nothing happens for items already in the target folder, or a folder dropped onto itself. If nothing is left, no event is raised.
Otherwise: FilesDropped(files, targetFolder) is raised. The control never changes the disk by itself. Decide in the event; typically ask first, then call CopyIntoFolder:
Sub FilesDropped(files() As FolderItem, targetFolder As FolderItem)
Var n As Integer = files.Count
Var d As New MessageDialog
d.Message = "Copy " + n.ToString + " item(s) into " + targetFolder.Name + "?"
d.ActionButton.Caption = "Copy"
d.CancelButton.Visible = True
If d.ShowModal(Self) = d.ActionButton Then
Call Me.CopyIntoFolder(files, targetFolder)
End If
End SubFor moves, or for very large copies (CopyIntoFolder blocks the UI while it runs), do your own work in a Thread from this event and call Refresh when done.
System icons
FileBrowserIcons asks the operating system for the icon its own file manager shows. Only system libraries that are always present are used.
Platform | API | Notes |
Windows 64-bit | SHCreateItemFromParsingName and IShellItemImageFactory::GetImage (shell32), at 128 px, icon only (SIIGBF_ICONONLY) | Real alpha, any size. The COM method is called through its vtable with Xojo delegates (ComGetImageProc, ComReleaseProc). The 32-bit premultiplied HBITMAP is converted with GetDIBits. |
Windows (fallback, and 32-bit builds) | SHGetFileInfoW(SHGFI_ICON) + GetIconInfo | 32 px, so it looks soft in Large Icons. |
macOS | NSWorkspace iconForFile: → CGImageForProposedRect → NSBitmapImageRep → PNG → Picture.FromData | AppKit/Foundation declares with Selector. Packages (.app …) show their own icons. |
Linux | GIO g_file_query_info("standard::icon") + GTK 3 gtk_icon_theme_lookup_by_gicon → gdk_pixbuf_save_to_bufferv("png") | Soft Declare, so a system without these libraries falls back to the drawn icons. save_to_bufferv is used because it takes no C varargs. |
Anything that fails | GenericIcon | A drawn folder, or a page showing the extension. |
Caching:
Folders: all ordinary folders share one icon.
Files: one icon per extension.
Icons that live in the file itself: these are fetched per file. That covers .exe, .lnk, .ico, .url, .desktop, .AppImage, .icns and macOS packages.
Cache levels: each icon is fetched once at 128 px, then stored per draw size as a 1x + 2x Picture, so painting never scales.
When icons are fetched: only when a row or cell is first painted, so huge folders open fast.
Clearing: FileBrowserIcons.ClearCache empties both levels.
Update History
Version 1.0.1 | October 21, 2026 | Disabled Row Animations for faster drawing. Thanks to Martin T for the suggestion! |
Version 1.0.0 | October 21, 2026 | Initial Release |




Comments