listbar — u_pbt_listbar #
← Component reference · Guide contents
Side navigation bar: collapsible accordion sections holding entries with icons, an accent band on the current entry, and collapsing into an icon rail.
▶ See it live — Demo application, Listbar tile: the preview, the code behind it and this page, side by side.
At a glance #
| Userobject | u_pbt_listbar |
| Item classes | n_pbt_listbar_section (section) → n_pbt_listbar_item (entry) |
| Used for | Replacing a side menu improvised out of buttons with structured, themed, collapsible navigation |
| Opt-in options | ib_auto_width, ib_reorderable |
This is the only component in the library that publishes ib_auto_width: its natural width actually means something, since the collapsed rail is far narrower than the expanded bar. The common case is in fact already covered without enabling anything — ib_collapsed = true shrinks the bar down to the rail, and gives its width back when it expands.
Quick start #
// window open event
uo_nav.of_add_section(/*key*/ "nav", /*title*/ "Navigation")
uo_nav.of_add_item(/*keys*/ "nav/accueil", /*label*/ "Home", /*icon*/ "mono:img\home.svg")
uo_nav.of_add_item(/*keys*/ "nav/documents", /*label*/ "Documents", /*icon*/ "mono:img\doc.svg")
uo_nav.of_add_item(/*keys*/ "nav/recherche", /*label*/ "Search", /*icon*/ "mono:img\find.svg")
uo_nav.of_add_section(/*key*/ "config", /*title*/ "Settings")
uo_nav.of_add_item(/*keys*/ "config/preferences", /*label*/ "Preferences", /*icon*/ "mono:img\gear.svg")
// The selection is set on the ENTRY, through its full path
uo_nav.of_select_item("nav", "accueil")
// ue_selection_changed event of uo_nav : (string as_from_keys, string as_keys)
choose case as_keys
case "nav/accueil" ; of_ouvrir_accueil()
case "nav/documents" ; of_ouvrir_documents()
case "config/preferences"; of_ouvrir_preferences()
end choose
Two levels, a mandatory path #
An entry identifier is only unique within its section, so there is no shortcut straight to an entry. Every access goes through the section, which makes the code unambiguous — see Hierarchies.
// Component -> section -> entry -> property
uo_nav .of_section("nav") .of_item("docs") .is_text = "Documents"
The events also carry the full path, and the entry left with it: ue_selection_changed(as_from_keys, as_keys).
Properties #
| Property | Type | Default | Purpose |
|---|---|---|---|
ib_collapsed | boolean | false | true collapses the bar into an icon rail: the labels disappear, the icons stay clickable |
ib_auto_width | boolean | false | Opt-in: same for the width, including when expanded (the bar sizes itself to the longest label). Collapsing into the rail already shrinks by itself; ue_auto_width follows in both cases |
ib_reorderable | boolean | false | Opt-in: the user can drag an entry to another position. The move stays inside its section — an entry id is only unique there, so crossing would risk two identical keys (raises ue_item_reordered) |
ib_veto_selection | boolean | true | Ask before the selection moves (raises ue_selection_changing, which can refuse). On by default: scripting nothing always lets the move happen. Set it to false to drop the round trip to PowerBuilder (~35 ms) where it would show — keyboard navigation, a selection moved in a loop |
is_theme_style | string | fluent | Visual style of the component (THEME_STYLE_* constants) |
is_theme_mode | string | light | Light or dark variant (THEME_MODE_* constants) |
il_theme_accent | long | -1 | Accent color of this component (-1 = the theme accent) |
is_tooltip | string | "" | Simple tooltip shown when hovering the component |
is_super_tooltip_title | string | "" | Title of the rich tooltip (takes precedence over is_tooltip) |
is_super_tooltip_text | string | "" | Text of the rich tooltip (rich markup accepted) |
is_super_tooltip_image | string | "" | Image of the rich tooltip |
Properties of a section — n_pbt_listbar_section #
| Property | Type | Default | Purpose |
|---|---|---|---|
is_title | string | "" | Section title. Accepts rich text markup. An empty title shows no header row at all: the section becomes a plain invisible grouping |
ib_collapsed | boolean | false | Accordion: true collapses the entries of this section. The header stays visible and its chevron rotates |
Properties of an entry — n_pbt_listbar_item #
| Property | Type | Default | Purpose |
|---|---|---|---|
is_text | string | "" | Entry label, which can be changed on the fly without rebuilding the bar. Accepts rich text markup |
is_image | string | "" | Icon, changeable on the fly (accepted forms: file path, mono:, tint:, DLL resource) |
ib_enabled | boolean | true | false grays the entry out and blocks its click |
ib_visible | boolean | true | false hides the entry without removing it from the bar |
of_is_selected ( ) → boolean | — | — | Is this entry the selected one? Read only, and deliberately: the selection is one state of the whole bar, not a flag on each entry. To set it, use of_select on the component — which always raises the event |
is_tooltip | string | "" | Simple tooltip shown when hovering the item |
is_super_tooltip_title | string | "" | Title of the item rich tooltip (takes precedence over is_tooltip) |
is_super_tooltip_text | string | "" | Text of the item rich tooltip (rich markup accepted) |
is_super_tooltip_image | string | "" | Image of the item rich tooltip |
Methods #
On the component #
| Method | Purpose |
|---|---|
of_add_section (string as_key, string as_text) | Adds a section. Returns 0 (-5 on an invalid argument, -2 when the component is not created), like every structural gesture: the handle comes from of_section("nav") when you want to set a property |
of_insert_section (string as_key, string as_text, integer ai_index) | Adds a section at the rank you choose (first position = 1) rather than at the end. The index counts sections, not rows. A name already taken is left alone: two sections under one name would make every address of their entries ambiguous. Returns 0 once applied, -5 on an invalid argument (empty key, wrong address), -2 when the component is not created |
of_move_section (string as_key, integer ai_index) | Moves a section to rank ai_index, carrying its entries with it. Moving the header alone would drop its entries into whatever section then stands above them. Returns 0 once applied, -5 on an invalid argument (empty key, wrong address), -2 when the component is not created |
of_remove_section (string as_key) | Removes a section and everything it holds. Emptying it while keeping its entries would orphan them: they would carry a section id that names nothing. Returns 0 once applied, -5 on an invalid argument (empty key, wrong address), -2 when the component is not created |
of_section (string as_key) | Handle of an existing section (created on first access) |
of_add_item (string as_keys, string as_text, string as_image) | Adds an entry at its address, "nav/accueil": the section it lands in, then its own id. Returns 0 (-5 on an invalid argument, -2 when the component is not created) — -5 if the parent is not a section, rather than letting the entry vanish. An overload omits the icon |
of_item (string as_keys) | Handle of one entry, by its address (created on first access) |
of_insert_item (string as_keys, string as_text, integer ai_index) | Inserts an entry at position ai_index within its section. Returns 0 once applied, -5 on an invalid argument (empty key, wrong address), -2 when the component is not created |
of_insert_item (string as_keys, string as_text, string as_image, integer ai_index) | Same, with the entry’s icon: of_add_item takes one, so inserting must be able to take one too. Returns 0 once applied, -5 on an invalid argument (empty key, wrong address), -2 when the component is not created |
of_move_item (string as_keys, integer ai_index) | Moves an existing entry within its section, preserving its state. Returns 0 once applied, -5 on an invalid argument (empty key, wrong address), -2 when the component is not created |
of_remove_item (string as_keys) | Removes one entry, designated by its section / identifier pair. Returns 0 once applied, -5 on an invalid argument (empty key, wrong address), -2 when the component is not created |
of_clear ( ) | Empties the bar: every section and every entry. Returns 0 once applied, -2 when the component is not created |
of_select_item (string as_keys) | Selects an entry — strictly equivalent to the user clicking it: ue_selection_changing is asked first, then ue_selection_changed announces the move. Returns 0 once applied, -5 on an invalid argument (empty key, wrong address), -2 when the component is not created |
of_get_layout ( ) | Reads the current arrangement back as JSON: the sections in order, each with its entries in order and whether it is folded. Store it (file, database, registry) and hand it back with of_set_layout at the next start. The same pair carries the same names on every component that can be rearranged |
of_set_layout (string as_layout_json) | Restores an arrangement read with of_get_layout or received with ue_layout_changed. What the layout does not name keeps its place at the end: a layout saved yesterday must not make what was added since disappear. Applying it raises no event — you supplied it. Returns 0 once applied, -5 on an invalid argument (empty key, wrong address), -2 when the component is not created |
of_clear_selection ( ) | Leaves no entry selected. Announced like any other move. Returns 0 once applied, -2 when the component is not created |
of_selected_key ( ) | Address of the selected entry — "nav/docs", "" if there is none. It is exactly what ue_selection_changed hands you: a comparison is a comparison, not a reassembly. Always the current one: a click and of_select_item both come back through the event |
of_reset ( ) | Empties the bar, then brings the component back to its brand-new state. Returns 0 once applied, -2 when the component is not created |
of_set_redraw (boolean) | Groups a burst of changes into a single render. Returns 0 once applied, -2 when the component is not created |
of_save_as_png (string) · of_save_as_jpg (string) | Exports the rendering as an image. Returns 0 once the image is written, -4 when writing fails, -2 when the component is not created |
On a section — n_pbt_listbar_section #
| Method | Purpose |
|---|
Events #
| Event | Raised when |
|---|---|
ue_selection_changed (string as_from_keys, string as_keys) | The selection has moved — by a click or through of_select_item. Same arguments as ue_selection_changing: the question and its outcome read the same way, and the as_from_* pair designates the entry left (empty if there is none) |
ue_section_toggled (string as_key, boolean ab_collapsed) | The user collapses or expands a section from its header |
ue_item_reordered (string as_keys, integer ai_index) | The user finished dragging an entry. ai_index is its new rank inside its section, starting from 1, like of_move_item. Keep that order to give the user their bar back as they left it |
ue_layout_changed (string as_layout_json) | The arrangement changed — the user rearranged something, or your own code did. Carries the whole layout, not just what moved: persisting it is one assignment |
ue_selection_changing (string as_from_keys, string as_keys) → boolean | Cancelable, raised before the selection moves. Raised by default; ib_veto_selection = false removes it. Return false to keep the user where they are |
ue_auto_width (long al_width) | The component has recomputed its ideal width — requires ib_auto_width = true |
ue_ready ( ) | The component has finished loading; everything sent beforehand has been replayed |
ue_runtime_missing ( ) | The WebView2 runtime is missing: the component stays empty |
ue_bg_color (long al_color) | The component has computed its theme background color; the userobject has already adopted it (backcolor) |
Examples #
A complete side menu #
uo_nav.of_set_redraw(false)
uo_nav.of_add_section("dossiers", "Folders")
uo_nav.of_add_item("dossiers/recents", "Recent", "mono:img\clock.svg")
uo_nav.of_add_item("dossiers/clients", "Customers", "mono:img\user.svg")
uo_nav.of_add_item("dossiers/archives", "Archives", "mono:img\box.svg")
uo_nav.of_add_section("outils", "Tools")
uo_nav.of_add_item("outils/import", "Import", "mono:img\import.svg")
uo_nav.of_add_item("outils/export", "Export", "mono:img\export.svg")
uo_nav.of_set_redraw(true)
uo_nav.of_select_item("dossiers", "recents")
A collapsible rail that frees up space #
// Collapse into an icon rail : the bar shrinks by itself (and gets its width
// back when you expand it).
uo_nav.ib_collapsed = true
// ue_auto_width event of uo_nav : (long al_width)
// The bar has just adopted its ideal width : realign whatever sits to its right.
uo_contenu.x = uo_nav.x + al_width
uo_contenu.width = parent.width - uo_contenu.x
Accordion: collapsing a section #
// Collapse the archives section, which is rarely used
uo_nav.of_section("archives").ib_collapsed = true
// ue_section_toggled event of uo_nav : (string as_keys, boolean ab_collapsed)
of_enregistrer_preference("section_" + as_keys, String(ab_collapsed))
Responding to navigation #
// ue_selection_changed event of uo_nav : (string as_from_keys, string as_keys)
// The address carries both levels : two sections may both have a "liste" entry.
n_pbt_utils lnv_utils // autoinstantiate : nothing to create, nothing to destroy
string ls_ids[]
if lnv_utils.of_split_path(as_keys, ls_ids) < 2 then return
choose case ls_ids[1]
case "dossiers" ; of_ouvrir_dossier(ls_ids[2])
case "outils" ; of_lancer_outil(ls_ids[2])
end choose
Refusing a change of selection #
// The question is asked by DEFAULT: nothing to enable. This line does the
// opposite, dropping it where arbitration is useless and the cost would show.
uo_nav.ib_veto_selection = false
// ue_selection_changing event of uo_nav :
// (string as_from_keys, string as_keys)
// Returning FALSE keeps the user on the entry being left.
if of_saisie_en_cours(as_from_keys) then
MessageBox("Entry", "Finish the current record before navigating away.")
return false
end if
return true
Updating an entry on the fly #
n_pbt_listbar_item lnv_entree
// The full path is mandatory : component -> section -> entry
lnv_entree = uo_nav.of_section("dossiers").of_item("recents")
lnv_entree.is_text = "Recent (12)"
lnv_entree.is_image = "mono:img\clock_full.svg"
// Gray out or hide according to permissions, without rebuilding the bar
uo_nav.of_section("outils").of_item("import").ib_enabled = of_a_le_droit("import")
uo_nav.of_section("outils").of_item("export").ib_visible = gb_mode_expert
Selection driven from code #
// Move the selection elsewhere : the accent band follows
uo_nav.of_select_item("dossiers", "clients")
// Or clear it completely
uo_nav.of_clear_selection()
Rebuilding the bar #
// of_clear empties sections and entries ; of_reset also restores the component defaults
uo_nav.of_clear()
of_construire_menu_selon_profil()
Best practices #
- An entry is added by its address:
of_add_item("nav/accueil", ...). The section handle is now only for setting a property, andof_section("nav")hands it over on first access. - Use
mono:for icons: they recolor with the theme, light and dark alike, and stay legible once the bar is collapsed into a rail. - An empty section title works as a discreet grouping: the entries are spaced apart without any header row appearing.
ib_collapsedshrinks the userobject down to the rail, but it does not move your other controls: handleue_auto_widthto take up the freed space, otherwise it just stays empty.- In rail mode only the icons remain: set a tooltip on every entry so the labels stay available.
- The question is asked by default: a
ue_selection_changingleft empty always allows the move, you have nothing to do. Switch it off withib_veto_selection = falsewhere the click is repeated — keyboard navigation, a selection driven in a loop — since every question costs a round trip to PowerBuilder. - Prefer
ib_enabled = falseoverib_visible = falsewhen the entry will become available again: the menu does not change shape under the user's eyes.
Inherited from the common base #
These members exist on every visual component — they are not specific to this one. They are detailed once, in the transverse chapters; this table only says where to read them.
| Members | Role | Detailed in |
|---|---|---|
of_count · of_keys_at · of_has | Walk what the component holds | 3.2 Items |
of_reset | Put the component back to zero | 3.6 Resetting a component: of_reset() |
of_register_shortcut · of_clear_shortcuts | The component's keyboard chords | 3.5 Keyboard shortcuts |
of_is_created · of_is_ready · of_get_last_error | Whether it was born, whether it is ready, what failed | 3.7 Diagnostics |
of_save_as_png · of_save_as_jpg | Export the rendering as an image | 3.8 Exporting the rendering as an image |
of_set_redraw | Group changes into a single repaint | 3.10 Best practices |
of_preload_icons | Icons shown with no delay | Instant display: of_icon |
of_set_translation | Translate one of the component's labels | 5.2 Adapting a label: of_set_translation |
of_focus_webview | Give the component the focus | 6.4 Keyboard and focus |
of_print · of_print_to_pdf | Print, or write a PDF | 6.9 Printing |
of_set_property · of_get_property · of_component_name | Driving a property by its name | 3.1 The property engine |
Two helpers are not inherited: of_icon and of_escape_markup live on n_pbt_utils. Declare one — n_pbt_utils lnv_utils, nothing to create — and call them on it.