PBToolboxAI v1 ← Site

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 #

Userobjectu_pbt_listbar
Item classesn_pbt_listbar_section (section) → n_pbt_listbar_item (entry)
Used forReplacing a side menu improvised out of buttons with structured, themed, collapsible navigation
Opt-in optionsib_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
n_pbt_listbar_section lnv_section

lnv_section = uo_nav.of_add_header(/*id*/ "nav", /*title*/ "Navigation")
lnv_section.of_add_item(/*id*/ "accueil",   /*label*/ "Home",   /*icon*/ "mono:img\home.svg")
lnv_section.of_add_item(/*id*/ "documents", /*label*/ "Documents", /*icon*/ "mono:img\doc.svg")
lnv_section.of_add_item(/*id*/ "recherche", /*label*/ "Search", /*icon*/ "mono:img\find.svg")

uo_nav.of_add_header(/*id*/ "config", /*title*/ "Settings") &
      .of_add_item(/*id*/ "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_section, string as_from_id,
//                                        string as_section, string as_id)
choose case as_section + "/" + as_id
    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_section, as_from_id, as_section, as_id).


Properties #

PropertyTypeDefaultPurpose
ib_collapsedbooleanfalsetrue collapses the bar into an icon rail: the labels disappear, the icons stay clickable
ib_auto_widthbooleanfalseOpt-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_reorderablebooleanfalseOpt-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_selectionbooleantrueAsk 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_stylestringfluentVisual style of the component (THEME_STYLE_* constants)
is_theme_modestringlightLight or dark variant (THEME_MODE_* constants)
il_theme_accentlong-1Accent color of this component (-1 = the theme accent)
is_tooltipstring""Simple tooltip shown when hovering the component
is_super_tooltip_titlestring""Title of the rich tooltip (takes precedence over is_tooltip)
is_super_tooltip_textstring""Text of the rich tooltip (rich markup accepted)
is_super_tooltip_imagestring""Image of the rich tooltip

Properties of a section — n_pbt_listbar_section #

PropertyTypeDefaultPurpose
is_titlestring""Section title. Accepts rich text markup. An empty title shows no header row at all: the section becomes a plain invisible grouping
ib_collapsedbooleanfalseAccordion: true collapses the entries of this section. The header stays visible and its chevron rotates

Properties of an entry — n_pbt_listbar_item #

PropertyTypeDefaultPurpose
is_textstring""Entry label, which can be changed on the fly without rebuilding the bar. Accepts rich text markup
is_imagestring""Icon, changeable on the fly (accepted forms: file path, mono:, tint:, DLL resource)
ib_enabledbooleantruefalse grays the entry out and blocks its click
ib_visiblebooleantruefalse hides the entry without removing it from the bar
is_tooltipstring""Simple tooltip shown when hovering the item
is_super_tooltip_titlestring""Title of the item rich tooltip (takes precedence over is_tooltip)
is_super_tooltip_textstring""Text of the item rich tooltip (rich markup accepted)
is_super_tooltip_imagestring""Image of the item rich tooltip

Methods #

On the component #

MethodPurpose
of_add_header (string as_id, string as_text)Adds a section and returns its n_pbt_listbar_section handle, on which entry additions can be chained
of_section (string as_id)Handle of an existing section (created on first access)
of_insert_item (string as_section, string as_id, string as_text, integer ai_index)Inserts an entry at position ai_index within its section
of_insert_item (string as_section, string as_id, 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
of_move_item (string as_section, string as_id, integer ai_index)Moves an existing entry within its section, preserving its state
of_remove_item (string as_section, string as_id)Removes one entry, designated by its section / identifier pair
of_clear ( )Empties the bar: every section and every entry
of_select_item (string as_section, string as_id)Selects an entry — strictly equivalent to the user clicking it: ue_selection_changing is asked first, then ue_selection_changed announces the move
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
of_clear_selection ( )Leaves no entry selected. Announced like any other move
of_selected_key ( )Identifier of the selected entry, "" if there is none. Always the current one: a click and of_select_item both come back through ue_selection_changed
of_selected_section ( )Section holding the selected entry — an entry id is only unique inside its section, so the pair identifies the selection. "" if there is none
of_reset ( )Empties the bar, then brings the component back to its brand-new state
of_set_redraw (boolean)Groups a burst of changes into a single render
of_save_as_png (string) · of_save_as_jpg (string)Exports the rendering as an image

On a section — n_pbt_listbar_section #

MethodPurpose
of_add_item (string as_id, string as_label, string as_icon_file)Adds a normal entry to this section and returns its handle
of_item (string as_id)Handle of an entry in this section (created on first access)

Events #

EventRaised when
ue_selection_changed (string as_from_section, string as_from_id, string as_section, string as_id)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_section, boolean ab_collapsed)The user collapses or expands a section from its header
ue_item_reordered (string as_section, string as_id, integer ai_index)The user finished dragging an entry. ai_index is its new rank inside its section, starting from 1. 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_section, string as_from_id, string as_section, string as_id) → booleanCancelable, 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 #

n_pbt_listbar_section lnv_dossiers, lnv_outils

uo_nav.of_set_redraw(false)

lnv_dossiers = uo_nav.of_add_header("dossiers", "Folders")
lnv_dossiers.of_add_item("recents",  "Recent",  "mono:img\clock.svg")
lnv_dossiers.of_add_item("clients",  "Customers",  "mono:img\user.svg")
lnv_dossiers.of_add_item("archives", "Archives", "mono:img\box.svg")

lnv_outils = uo_nav.of_add_header("outils", "Tools")
lnv_outils.of_add_item("import", "Import", "mono:img\import.svg")
lnv_outils.of_add_item("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_section, boolean ab_collapsed)
of_enregistrer_preference("section_" + as_section, String(ab_collapsed))

Responding to navigation #

// ue_selection_changed event of uo_nav : (string as_from_section, string as_from_id,
//                                        string as_section, string as_id)
// The section is part of the key : two sections may both have a "liste" entry.
choose case as_section
    case "dossiers" ; of_ouvrir_dossier(as_id)
    case "outils"   ; of_lancer_outil(as_id)
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_section, string as_from_id, string as_section, string as_id)
// Returning FALSE keeps the user on the entry being left.
if of_saisie_en_cours(as_from_section, as_from_id) 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 #


← Component reference · Guide contents