Collections
Control catalog · Collection contract · Binding coverage
Examples use the shared fragment context.
C++ examples also need xui\collections.hpp and xui\data_grid.hpp.
Class definitions belong at file scope.
C# fragments assume using System; and using Xui;.
Rust fragments use use xui::*; inside the shared example function.
That function returns std::result::Result<(), Box<dyn std::error::Error>>.
Rust callbacks use weak handles to avoid ownership cycles.
Choose a collection
Virtualization limits visible content work. It does not make a mutable source safe or provide automatic storage paging. Sources must preserve stable identity and remain immutable. Identity lookup must not enumerate all rows.
FileList
Use FileList for immutable FileItem records with stable IDs.
The synchronous convenience setters suit small sources.
FileList has no markup constructor. This component accepts the configured control through an Element parameter.
namespace ControlExamples;
component WorkspaceFiles {
param global::Xui.Element Files;
view {
VStack() {
Content(Files);
}
}
}
C# setup:
var files = window.FileList("Workspace files").SetItems([
new(1, "Notes.txt", @"C:\Data\Notes.txt"),
new(2, "Archive", @"C:\Data\Archive", Directory: true)
]);
files.Event += e => {
if (e.Kind == EventKind.Selection)
Console.WriteLine("File selection changed");
};
var component = new ControlExamples.WorkspaceFiles(window, files);
The bindings expose selection events, but not the C++ FileList activation callback or empty-text setter.
var files = window.FileList("Workspace files").SetItems([
new(1, "Notes.txt", @"C:\Data\Notes.txt"),
new(2, "Archive", @"C:\Data\Archive", Directory: true)
]);
files.Event += e => {
if (e.Kind == EventKind.Selection)
Console.WriteLine("File selection changed");
};
root.Add(files, 1);
The binding exposes selection events, but not the C++ activation callback or set_empty_text.
let files = window.file_list("Workspace files")?;
files.set_items(&[
FileItem { id: 1, name: "Notes.txt", path: r"C:\Data\Notes.txt", directory: false },
FileItem { id: 2, name: "Archive", path: r"C:\Data\Archive", directory: true },
])?;
files.on_event(|event| {
if event.kind == 5 {
println!("File selection changed");
}
Ok(())
})?;
root.add(&files, 1.)?;
Ok(())
Event kind 5 is selection. The binding does not expose the C++ activation callback or empty-text setter.
auto files = std::make_shared<xui::FileList>(L"Workspace files");
files->set_items(std::make_shared<const std::vector<xui::FileItem>>(
std::vector<xui::FileItem>{
{1, L"Notes.txt", L"C:\\Data\\Notes.txt", false},
{2, L"Archive", L"C:\\Data\\Archive", true}
}));
files->set_empty_text(L"No matching files", L"Try another filter.");
auto selected = std::make_shared<xui::Label>(L"No file opened");
files->on_activate([selected](const xui::FileItem& item, xui::FileActivation) {
selected->set_text(item.path);
});
root->add(files, 1);
root->add(selected);
select uses the visible row index.
focused_id and model selection remain separate.
on_selection_change and on_view_change observe the updated model.
Activation does not open a path unless the application implements that action.
For expensive enumeration or filtering, use Window::create_view_task.
The complete worker example shows loader, delivery, and cancellation.
Retain the task handle while it is necessary.
Honor its cancellation check during source work.
A null source result means cancellation, not an empty successful list.
set_thumbnails(true) requests visible previews through the shared image workers.
on_thumbnail_error reports failed visible requests.
Shell icons and vector fallbacks remain available.
The image contract defines limits and lifetime.
The style target is file_list.
Row, text, selection, icon, and scrollbar parts do not create per-row controls.
Styles do not recolor decoded thumbnails or Shell icon pixels.
ItemsView
Use ItemsView for generic immutable sources.
By default, a mouse click selects a row. A double-click or Enter activates it.
C# SetSingleClickActivation(true) enables menu-style activation on an unmodified primary press.
C++ and Rust expose set_single_click_activation(true).
The C ABI exposes the Boolean feature property XUI_F_SINGLE_CLICK_ACTIVATION (54).
Keyboard and programmatic selection remain separate from activation. Modified clicks retain their selection gestures.
This example source generates rows without a retained array of row strings.
Source callbacks belong in C#, not markup.
This complete component accepts an application-supplied ImmutableSource source from the same window.
namespace ControlExamples;
component GeneratedItems {
view {
VStack() {
ItemsView("Results", ref: Items);
}
}
}
C# setup, with ImmutableSource source as an additional function parameter:
var component = new ControlExamples.GeneratedItems(window);
component.Items.SetSource(source);
The C# tab supplies a complete source declaration.
This whole declaration belongs at file scope.
public sealed class ExampleItems : IReadOnlyImmutableSource
{
public ulong Count => 1000;
public ItemKey Key(ulong index) => new(index + 1, 1);
public ulong? Find(ItemKey key) =>
key.Version == 1 && key.Id > 0 && key.Id <= Count ? key.Id - 1 : null;
public ItemContent Item(ulong index, ulong column = 0) =>
new($"Item {index + 1}", "Example row");
}
This source declaration can live inside the example function.
struct ExampleItems;
impl ReadOnlyImmutableSource for ExampleItems {
fn count(&self) -> u64 { 1000 }
fn key(&self, index: u64) -> xui::Result<ItemKey> {
Ok(ItemKey { id: index + 1, version: 1 })
}
fn find(&self, key: ItemKey) -> xui::Result<Option<u64>> {
Ok(if key.version == 1 && key.id > 0 && key.id <= self.count() {
Some(key.id - 1)
} else { None })
}
fn item(&self, index: u64, _column: u64) -> xui::Result<ItemContent> {
Ok(ItemContent {
primary: format!("Item {}", index + 1), secondary: "Example row".into(),
enabled: true, progress: None, checked: None,
})
}
}
let source = window.immutable_source(ExampleItems)?;
let items = window.items_view("Results")?;
items.set_source(&source)?;
root.add(&items, 1.)?;
Ok(())
class ExampleItems final : public xui::ItemsSource {
public:
std::size_t size() const override { return 1000; }
xui::ItemKey key(std::size_t index) const override {
return {index + 1, 1};
}
std::optional<std::size_t> find(xui::ItemKey value) const override {
if (value.version != 1 || value.id == 0 || value.id > size())
return {};
return static_cast<std::size_t>(value.id - 1);
}
xui::ItemContent item(std::size_t index) const override {
return {L"Item " + std::to_wstring(index + 1), L"Example row"};
}
};
The next fragment requires the file-scope ExampleItems definition:
The setup accepts an additional ImmutableSource source parameter.
namespace ControlExamples;
component ResultTiles {
view {
VStack() {
ItemsView("Results", ref: Items);
Text("No item activated", ref: Status);
}
}
}
C# setup:
var component = new ControlExamples.ResultTiles(window);
component.Items.SetSource(source).SetPresentation(ItemsPresentation.Tiles).ItemSize(180, 56);
component.Items.Event += e => {
if (e.Kind == EventKind.Click) component.Status.Text = $"Activated {e.Value}";
};
There is no binding setter for the C++ select-all scope.
The source declaration is in the preceding example.
using var source = window.ImmutableSource(new ExampleItems());
var items = window.ItemsView("Results").SetSource(source)
.SetPresentation(ItemsPresentation.Tiles).ItemSize(180, 56);
var status = window.Label("No item activated");
items.Event += e => {
if (e.Kind == EventKind.Click) status.Text = $"Activated {e.Value}";
};
root.Add(items, 1).Add(status);
The control retains the attached source after the local lease is disposed. There is no binding setter for the C++ select-all scope.
This fragment accepts an additional source: &ImmutableSource parameter.
let items = window.items_view("Results")?;
items.set_source(source)?;
items.set_presentation(ItemsPresentation::Tiles)?;
items.item_size(180., 56.)?;
let status = window.label("No item activated")?;
let weak_status = status.downgrade();
items.on_event(move |event| {
if event.kind == 1 && let Some(status) = weak_status.upgrade() {
status.set_text(&format!("Activated {}", event.value))
.inspect_err(|error| eprintln!("Item activation: {error}"))?;
}
Ok(())
})?;
root.add(&items, 1.)?;
root.add(&status, 0.)?;
Ok(())
There is no binding setter for the C++ select-all scope.
auto items = std::make_shared<xui::ItemsView>(L"Results");
items->set_items(std::make_shared<const ExampleItems>());
items->set_presentation(xui::ItemsPresentation::tiles);
items->set_item_size({180, 56});
items->set_select_all_scope(xui::SelectAllScope::filtered);
auto status = std::make_shared<xui::Label>(L"No item activated");
items->on_activate([status](xui::ItemKey key) {
status->set_text(L"Activated " + std::to_wstring(key.id));
});
root->add(items, 1);
root->add(status);
list, tiles, and grouped share the same selection model.
Groups come from ItemsSource::groups and use ordered, nonoverlapping ranges.
Group IDs must not collide with item IDs.
on_action handles the separate inline action.
on_selection reports membership changes.
CollectionSelection separates membership, focus, and anchor.
Select-all uses a compact term instead of one entry per selected row.
Full-source selection needs an explicit full index and stable identity namespace.
The selection contract defines the term and UIA enumeration limits.
The style target is items_view.
Rows, tiles, group headers, inline actions, and progress remain virtual parts.
Tile width supports base/local values only.
Root row metrics remain uniform.
Per-item colors do not imply a per-item style object.
TreeView
Use TreeView for cached roots and lazily supplied children.
TreeSource::roots returns an immutable ItemsSource.
has_children must use cached, nonblocking data.
This fragment requires std::shared_ptr<const xui::TreeSource> tree_source from the application:
TreeView has no markup constructor.
The setup accepts an additional ImmutableSource source parameter for cached roots.
namespace ControlExamples;
component FolderHierarchy {
param global::Xui.Element Tree;
view {
VStack() {
Content(Tree);
}
}
}
C# setup:
var tree = window.TreeView("Folder hierarchy").SetSource(source);
tree.Event += e => {
if (e.Kind == EventKind.Click) Console.WriteLine($"Node {e.Value}");
};
var component = new ControlExamples.FolderHierarchy(window, tree);
This example supplies cached roots only. Expandable nodes also require an OnRequest handler.
This fragment accepts an additional ImmutableSource source parameter for cached roots.
var tree = window.TreeView("Folder hierarchy").SetSource(source);
var status = window.Label("No node activated");
tree.Event += e => {
if (e.Kind == EventKind.Click) status.Text = $"Node {e.Value}";
};
root.Add(tree, 1).Add(status);
The source reports cached child availability through HasChildren.
Expandable nodes require OnRequest and owner-specific TreeRequest.Complete.
This fragment accepts an additional source: &ImmutableSource parameter for cached roots.
let tree = window.tree_view("Folder hierarchy")?;
tree.set_source(source)?;
let status = window.label("No node activated")?;
let weak_status = status.downgrade();
tree.on_event(move |event| {
if event.kind == 1 && let Some(status) = weak_status.upgrade() {
status.set_text(&format!("Node {}", event.value))
.inspect_err(|error| eprintln!("Tree activation: {error}"))?;
}
Ok(())
})?;
root.add(&tree, 1.)?;
root.add(&status, 0.)?;
Ok(())
This example has no lazy provider. on_request uses the same subscription slot as on_event, so a later registration replaces it.
auto tree = std::make_shared<xui::TreeView>(L"Folder hierarchy");
tree->set_tree(tree_source);
auto status = std::make_shared<xui::Label>(L"No node activated");
tree->on_activate([status](xui::ItemKey key) {
status->set_text(L"Node " + std::to_wstring(key.id));
});
root->add(tree, 1);
root->add(status);
For lazy children:
- Register
on_requestbefore users expand a branch. - Start application-owned work with the supplied
TreeRequest. - Honor
request.cancellationduring that work. - Deliver
complete(request, children, error)on the UI thread. - Use a weak tree reference in completion callbacks.
The provider recipe shows the callback shape.
complete returns false for obsolete, canceled, duplicate, or foreign requests.
Collapse retains cached children and hidden selection.
Right retries a failed branch.
Source callbacks must not perform filesystem or network I/O.
The style target is tree_view.
Disclosure, indentation, pending text, and error text extend the virtual collection parts.
Loading and error styles do not change request lifetime.
UIA resolves stable nodes, not a retained native peer per node.
DataGrid
Use DataGrid for virtual tabular data.
Use layout Grid for retained form cells.
This file-scope source supplies one logical column:
Source callbacks belong in C#. This component accepts the source through C# setup, not a markup source declaration.
namespace ControlExamples;
component RecordSourceGrid {
view {
VStack() {
DataGrid("Records", ref: Records, columns: [new("Name", 240)]);
}
}
}
C# setup, with an additional ImmutableSource source parameter:
var component = new ControlExamples.RecordSourceGrid(window);
component.Records.SetSource(source);
This whole declaration belongs at file scope.
public sealed class ExampleGrid : IReadOnlyImmutableSource
{
public ulong Count => 1000;
public ItemKey Key(ulong index) => new(index + 1, 1);
public ulong? Find(ItemKey key) =>
key.Version == 1 && key.Id > 0 && key.Id <= Count ? key.Id - 1 : null;
public ItemContent Item(ulong index, ulong column = 0) => new($"Record {index + 1}");
}
struct ExampleGrid;
impl ReadOnlyImmutableSource for ExampleGrid {
fn count(&self) -> u64 { 1000 }
fn key(&self, index: u64) -> xui::Result<ItemKey> {
Ok(ItemKey { id: index + 1, version: 1 })
}
fn find(&self, key: ItemKey) -> xui::Result<Option<u64>> {
Ok(if key.version == 1 && key.id > 0 && key.id <= self.count() {
Some(key.id - 1)
} else { None })
}
fn item(&self, index: u64, _column: u64) -> xui::Result<ItemContent> {
Ok(ItemContent {
primary: format!("Record {}", index + 1), secondary: String::new(),
enabled: true, progress: None, checked: None,
})
}
}
let source = window.immutable_source(ExampleGrid)?;
let grid = window.data_grid("Records")?;
grid.set_columns(&[GridColumn {
name: "Name".into(), width: 240., numeric: false, filterable: false, checkable: false,
}])?;
grid.set_source(&source)?;
root.add(&grid, 1.)?;
Ok(())
class ExampleGrid final : public xui::GridSource {
public:
std::size_t size() const override { return 1000; }
xui::RowKey key(std::size_t index) const override {
return {index + 1, 1};
}
std::optional<std::size_t> find(xui::RowKey value) const override {
if (value.version != 1 || value.id == 0 || value.id > size())
return {};
return static_cast<std::size_t>(value.id - 1);
}
std::wstring text(std::size_t row, std::size_t) const override {
return L"Record " + std::to_wstring(row + 1);
}
};
The next fragment requires the file-scope ExampleGrid definition:
The setup accepts an additional ImmutableSource source parameter.
namespace ControlExamples;
component SelectedRecords {
view {
VStack() {
DataGrid("Records", ref: Records, columns: [new("Name", 240)]);
}
}
}
C# setup:
var component = new ControlExamples.SelectedRecords(window);
component.Records.SetSource(source).Select(new(1, 1));
The source declaration is in the preceding example.
using var source = window.ImmutableSource(new ExampleGrid());
var grid = window.DataGrid("Records").SetColumns([new("Name", 240)])
.SetSource(source).Select(new(1, 1));
root.Add(grid, 1);
This fragment accepts an additional source: &ImmutableSource parameter.
let grid = window.data_grid("Records")?;
grid.set_columns(&[GridColumn {
name: "Name".into(), width: 240., numeric: false, filterable: false, checkable: false,
}])?;
grid.set_source(source)?;
grid.select(ItemKey { id: 1, version: 1 })?;
root.add(&grid, 1.)?;
Ok(())
auto grid = std::make_shared<xui::DataGrid>(L"Records");
grid->set_columns({{L"Name", 240}});
grid->set_source(std::make_shared<const ExampleGrid>());
grid->select({1, 1});
root->add(grid, 1);
The application supplies actual sorted and filtered snapshots.
on_sort requests sorting. A sort indicator alone does not reorder the source.
on_filter supplies a cancelable GridFilterRequest.
complete_filter accepts current results on the UI thread.
Source-column identities and display ordinals are distinct.
GridSource::text, sort callbacks, and filters use source identities.
Width and reorder operations use display ordinals.
set_source preserves column order and widths.
set_columns resets them.
The grid contract defines every mapping.
Selection shares CollectionSelection.
UIA Grid, Table, selection, and scroll operations resolve stable keys.
Headers support keyboard sort, resize, and reorder without pointer-only actions.
Cancellation does not commit a pending column move.
The style target is data_grid.
Root row and header metrics remain uniform.
Cell and header text parts own fonts.
Styles do not replace sources, sort records, or allocate a visual tree per cell.
HistoryChart
Use HistoryChart for a bounded recent metric.
It retains 60 samples.
HistoryChart has no markup constructor. The binding supports numeric samples, but not the C++ scale, gap, or history-read APIs.
namespace ControlExamples;
component CpuHistory {
param global::Xui.Element History;
view {
VStack() {
Content(History);
}
}
}
C# setup:
var history = window.HistoryChart("CPU usage percent");
history.Append(25);
history.Append(40);
var component = new ControlExamples.CpuHistory(window, history);
var history = window.HistoryChart("CPU usage percent");
history.Append(25);
history.Append(40);
root.Add(history);
The binding supports numeric samples, but not the C++ scale, gap, or history-read APIs. The missing sample is omitted, not replaced with zero.
let history = window.history_chart("CPU usage percent")?;
history.append(25.)?;
history.append(40.)?;
root.add(&history, 0.)?;
Ok(())
The binding supports numeric samples, but not the C++ scale, gap, or history-read APIs. The missing sample is omitted, not replaced with zero.
auto history = std::make_shared<xui::HistoryChart>(L"CPU usage percent");
history->set_scale(100);
history->append(25);
history->append(std::nullopt);
history->append(40);
root->add(history);
std::nullopt records a gap, not an invented zero.
at(index) reads retained history.
Window::create_sample_task can deliver immutable measurements without a UI sampling loop.
The application contract defines worker and payload lifetime.
The accessible name must identify the metric and units.
The chart exposes read-only metric text, not an editable range.
The style target is history_chart.
Title, caption, grid lines, and plot line styles do not rewrite sample values.
MillerColumns
Use MillerColumns for adjacent sibling lists along a hierarchy path.
The application supplies immutable snapshots and loads the next column after selection.
These examples reuse the file-scope ExampleItems definition from
There is no MillerColumns(...) markup constructor.
Create the control in C# and pass it through Content.
namespace ControlExamples;
component HierarchyColumns {
param global::Xui.Element Columns;
view {
VStack() {
Content(Columns, flex: 1);
}
}
}
C# setup:
using var source = window.ImmutableSource(new ExampleItems());
var columns = window.MillerColumns("Hierarchy").SetColumnWidth(240);
columns.SetColumns([new("Root", source)]);
columns.SelectionChanged += item =>
Console.WriteLine($"Selected column {item.Column}, item {item.Key.Id}");
_ = new ControlExamples.HierarchyColumns(window, columns);
The component attaches its own Stack root.
using var source = window.ImmutableSource(new ExampleItems());
var columns = window.MillerColumns("Hierarchy").SetColumnWidth(240);
columns.SetColumns([new("Root", source)]);
columns.SelectionChanged += item =>
Console.WriteLine($"Selected column {item.Column}, item {item.Key.Id}");
root.Add(columns, 1);
ItemActivated reports explicit activation separately from selection.
SetColumns silently replaces the path.
The typed wrapper currently supplies construction but not column population or typed item events.
This fragment mounts an empty control, not the populated C# or C++ example.
The raw xui-sys crate exposes the xui_miller_* ABI for applications that implement their own unsafe adapter.
let columns = window.miller_columns("Hierarchy")?;
root.add(&columns, 1.)?;
Ok(())
Also include xui\miller_columns.hpp.
auto columns = std::make_shared<xui::MillerColumns>(L"Hierarchy");
columns->set_column_width(240);
columns->set_columns({{L"Root", std::make_shared<ExampleItems>(), {}}});
auto status = std::make_shared<xui::Label>(L"No selection");
columns->on_selection([status](std::size_t column, xui::ItemKey key) {
status->set_text(L"Selected column " + std::to_wstring(column) +
L", item " + std::to_wstring(key.id));
});
root->add(columns, 1);
root->add(status);
Each MillerColumnList belongs to its MillerColumns owner and has no public constructor.
C++ column_list(index) and C# Column(index) return retained lists, not independently mountable controls.
Set their sources through the owner, not through a borrowed list.
The composite root has no style schema. Retained lists use the ItemsView schema.
The control retains at most 32 columns. Each column has independent vertical scrolling and single selection. Column width accepts 120 to 2,000 DIPs. The application must reject obsolete query results before it replaces descendants. The Miller column contract describes focus, scrolling, activation, and cancellation.
Language notes
C# and Rust use ImmutableSource for bounded source callbacks.
Find or find must provide identity lookup without enumeration.
Callbacks must stay nonblocking and must not mutate their source window.
Tree completion tokens belong to one control.
C# request objects require disposal. Rust requests cancel on drop.
The binding contract defines the exact limits and error behavior.