PBToolboxAI v1 ← Site

tab — u_pbt_tab #

← Component reference · Guide contents

Modern tabs hosting real PowerBuilder controls: repositionable tab strip, per-tab icons, closable tabs, mouse reordering.

See it live — Demo application, Tab tile: the preview, the code behind it and this page, side by side.


At a glance #

Userobjectu_pbt_tab
Item classn_pbt_tab_page (one page = one tab)
Used forReplacing a PowerBuilder tab with a themed strip while keeping your existing screens as pages
Opt-in optionsib_reorderable

The key point: a page is not an HTML mockup, it is a PowerBuilder dragobject — a userobject, a DataWindow, a group of controls, or even another PBToolboxAI component. The component takes care of positioning, resizing and showing/hiding it when the tab changes. See Hosting real PowerBuilder controls.


Quick start #

// window open event : each page is a PB control already placed on the window
uo_onglets.of_add_page("clients",  "Customers",  uo_page_clients)
uo_onglets.of_add_page("factures", "Invoices", uo_page_factures, /*closable*/ true)

// One icon per tab, set on the page handle
uo_onglets.of_item("clients").is_icon  = "mono:img\clients.svg"
uo_onglets.of_item("factures").is_icon = "mono:img\invoice.svg"

uo_onglets.of_select_page("clients")
// ue_selection_changed event of uo_onglets : (string as_from_id, string as_id)
of_charger_page(as_id)

Properties #

PropertyTypeDefaultPurpose
is_positionstring"top"Placement of the strip: top, bottom (horizontal strip), start, end (vertical strip). Constants POSITION_TOP, POSITION_BOTTOM, POSITION_START, POSITION_END. START/END are logical and follow the writing direction
is_overflow_modestring"menu"What a band too small for its tabs does. menu (default) keeps every tab readable: those that no longer fit leave the band and the ··· button lists them all, the active tab always staying in view. compact keeps them all in the band and lets them shrink, down to the room an ellipsis needs. Constants OVERFLOW_MENU, OVERFLOW_COMPACT
ib_reorderablebooleanfalseOpt-in: users can reorder the tabs by dragging their header with the mouse (raises ue_tab_reordered)
ib_veto_selectionbooleantrueAsk before the active tab changes (raises ue_selection_changing, which can refuse). On by default: scripting nothing always lets the change happen. Set it to false to drop the round trip to PowerBuilder (~35 ms) where it would show
ib_veto_closebooleantrueAsk before closing a closable tab (raises ue_tab_closing, which can refuse). On by default, same rule
ib_context_menubooleantrueBuilt-in context menu on a tab header: Close, Close others, Close to the right, Close all, greyed out when they have no target. On by default. The right-click selects the tab first: if the application refuses the change, no menu opens. Every close goes through ue_tab_closing, one question per page: the one holding unsaved work refuses on its own and stays open while its neighbours go. Set to false to show your own menu from ue_rclicked
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 page — n_pbt_tab_page #

Obtained through of_item(id), they can be changed on the fly, without rebuilding the strip.

PropertyTypeDefaultPurpose
is_titlestring""Caption of the tab. Accepts rich text markup
is_iconstring""Icon of the tab (accepted forms: path, mono:, tint:, DLL resource). Empty = no icon
ib_enabledbooleantruefalse grays the tab out and prevents users from selecting it
ib_visiblebooleantruefalse hides the tab without removing the page; hiding the active tab activates a neighboring one
ib_closablebooleanfalseClose cross on this tab. Rarely known when the page is added: it becomes true the moment the document it holds is saved, false while a job runs inside it
is_shortcutstring""Keyboard chord that activates this tab: "Ctrl+2", "Alt+F", "F6"… It answers wherever the user is in the window, the hosted page included — the chord is held by the DLL, not by the tab strip. Selecting this way is a click: ue_selection_changing is asked, ue_selection_changed follows. "" removes it
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 #

MethodPurpose
of_add_page (string as_id, string as_title, dragobject ado_page)Adds a non-closable tab, without an icon, and hosts the control
of_add_page (string as_id, string as_title, dragobject ado_page, boolean ab_closable)Same, with a close cross if ab_closable
of_add_page (string as_id, string as_title, dragobject ado_page, boolean ab_closable, string as_icon)Same, with the icon set in the same call (the tab appears complete right away)
of_insert_page (string as_id, string as_title, dragobject ado_page, integer ai_index)Adds the page then moves it to position ai_index
of_insert_page (string as_id, string as_title, dragobject ado_page, boolean ab_closable, string as_icon, integer ai_index)Same, saying what of_add_page says : whether the tab closes, and its icon
of_move_page (string as_id, integer ai_index)Moves an existing tab; its hosted page and the selection are preserved
of_select_page (string as_id)Activates a tab — strictly equivalent to a user click (goes through ue_selection_changing then ue_selection_changed)
of_remove_page (string as_id)Removes the tab and hands the control back to its original window, hidden. The control remains usable and can be hosted elsewhere
of_selected_key ( )Identifier of the active tab, "" if none
of_get_layout ( )Reads the current arrangement back as JSON: the order of the tabs and the visibility of each. 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_item (string as_id)n_pbt_tab_page handle of the page (created on first access)
of_refresh_page (string as_id)Refreshes the rendering of a page built off-screen, without flicker
of_relayout ( )Republishes the page area so that the hosted control is repositioned (useful after a deferred display)
of_preload_icons (string as_icons[])Warms up a batch of icons at startup: a tab opened later shows its icon instantly
of_reset ( )Empties the strip: every hosted page is handed back to its original window, then the component returns to its pristine 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

of_add_page returns a negative code if the identifier is empty, already in use, or if the control passed in is not valid.

When there are too many tabs — they shrink to fit the band, but never below a readable width: the icon and the closing cross take their room on top of the title, never out of it. Those that no longer fit are taken out of the band, and a ··· button appears at its end: it opens the list of every visible tab, the active one ticked, and the choice switches to it. The active tab always stays on screen. The band does not scroll — that button is the way to the tabs beyond the fold. Nothing to code, the behaviour is automatic.


Events #

EventRaised when
ue_selection_changed (string as_from_id, string as_id)The active tab has changed — by click or through of_select_page. Same arguments as ue_selection_changing: the question and its outcome read the same way, and as_from_id is the page left
ue_tab_closed (string as_id)The user clicks the cross of a closable tab. The tab is already removed and the page handed back to its window
ue_tab_reordered (string as_id, integer ai_index)The user has finished dragging a header. ai_index is the new position, starting from 1
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_id, string as_id) → booleanCancelable, raised before the active tab changes. Raised by default; ib_veto_selection = false removes it. Return false to keep the user on their page
ue_tab_closing (string as_id) → booleanCancelable, raised before a closable tab is closed. Raised by default; ib_veto_close = false removes it. Named to pair with ue_tab_closed: closing / closed. Return false to keep the tab (and its hosted page) open
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 binder of business pages #

// open event : the page userobjects are already placed on the window
uo_onglets.of_set_redraw(false)

uo_onglets.of_add_page(/*id*/ "identite", /*title*/ "Identity",  /*page*/ uo_identite)
uo_onglets.of_add_page(/*id*/ "adresses", /*title*/ "Addresses",  /*page*/ uo_adresses)
uo_onglets.of_add_page(/*id*/ "compta",   /*title*/ "Accounting", /*page*/ uo_compta)

uo_onglets.of_item("identite").is_icon = "mono:img\user.svg"
uo_onglets.of_item("adresses").is_icon = "mono:img\map.svg"
uo_onglets.of_item("compta").is_icon   = "mono:img\euro.svg"

uo_onglets.of_set_redraw(true)
uo_onglets.of_select_page("identite")

Closable documents, opened on demand #

// Open a document in a new closable tab
uo_onglets.of_add_page(/*id*/ ls_cle, /*title*/ ls_libelle, /*page*/ uo_editeur, &
                       /*closable*/ true, /*icon*/ "mono:img\doc.svg")
uo_onglets.of_select_page(ls_cle)
// ue_tab_closed event of uo_onglets : (string as_id)
// The tab is already removed ; all that is left is releasing your business data.
of_liberer_document(as_id)

Mouse reordering #

uo_onglets.ib_reorderable = true
// ue_tab_reordered event of uo_onglets : (string as_id, integer ai_index)
// ai_index = new position, starting from 1 : we store the chosen order.
of_enregistrer_ordre(as_id, ai_index)

Vertical strip #

// Tab strip along the left side, useful when the captions are long
uo_onglets.is_position = uo_onglets.POSITION_START

Graying out or hiding a tab according to permissions #

// The path always goes through the page handle
uo_onglets.of_item("compta").ib_enabled = of_a_le_droit("compta")
uo_onglets.of_item("audit").ib_visible  = gb_mode_expert

Reacting to the selection #

// ue_selection_changed event of uo_onglets : (string as_from_id, string as_id)
choose case as_id
    case "identite" ; uo_identite.of_rafraichir()
    case "adresses" ; uo_adresses.of_rafraichir()
    case "compta"   ; uo_compta.of_rafraichir()
end choose

Refusing a tab change or a close #

// Both questions are asked by DEFAULT: nothing to enable. These lines do the
// opposite, removing them where arbitration is useless.
uo_onglets.ib_veto_selection = false
uo_onglets.ib_veto_close     = false
// ue_selection_changing event of uo_onglets : (string as_from_id, string as_id)
// Returning FALSE keeps the user on the page being left.
if of_page_modifiee(as_from_id) then
    if MessageBox("Changes", "Leave without saving ?", Question!, YesNo!) = 2 then
        return false
    end if
end if
return true
// ue_tab_closing event of uo_onglets : (string as_id)
// Returning FALSE keeps the tab and its hosted page open.
return not of_page_modifiee(as_id)

Starting again from an empty strip #

// of_reset hands EVERY hosted page back to its original window before clearing
uo_onglets.of_reset()
uo_onglets.of_add_page("accueil", "Home", uo_accueil)

Best practices #


← Component reference · Guide contents