top of page

FileBrowser Control for Xojo

16 hours ago
10 min read

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).



Sample Project that lets you play around with some of the properties.
Sample Project that lets you play around with some of the properties.
The Details View
The Details View
Large Icons View / MultiSelect
Large Icons View / MultiSelect











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.

Dragging the FileBrowser into your Xojo project.

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 | Size

SortFields 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 // 1

Events

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 Function

Programmatic 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 first

Selection

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:

  1. It isn't hidden, or ShowHiddenFiles is True.

  2. It isn't a folder, or ShowFolders is True.

  3. For files only: FileMask is empty, or the file's extension is in the mask.

  4. 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 Sub

The 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 Sub

For 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


Commenting on this post isn't available anymore. Contact the site owner for more info.

Copyright © Christian Wheel. All Rights Reserved.

bottom of page