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 #
| Userobject | u_pbt_tab |
| Item class | n_pbt_tab_page (one page = one tab) |
| Used for | Replacing a PowerBuilder tab with a themed strip while keeping your existing screens as pages |
| Opt-in options | ib_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_tabs.of_add_page(/*key*/ "customers", /*title*/ "Customers", /*page*/ uo_page_customers)
uo_tabs.of_add_page(/*key*/ "invoices", /*title*/ "Invoices", /*page*/ uo_page_invoices, /*closable*/ true)
// One icon per tab, set on the page handle
uo_tabs.of_page(/*key*/ "customers").is_icon = "mono:img\customers.svg"
uo_tabs.of_page(/*key*/ "invoices").is_icon = "mono:img\invoice.svg"
// Show the first tab
uo_tabs.of_select_page(/*key*/ "customers")
// ue_selection_changed event of uo_tabs : (string as_from_key, string as_key)
of_load_page(as_key)
Properties #
| Property | Type | Default | Purpose |
|---|---|---|---|
is_position | string | "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_mode | string | "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_reorderable | boolean | false | Opt-in: users can reorder the tabs by dragging their header with the mouse (raises ue_tab_reordered) |
ib_veto_selection | boolean | false | Ask before the active tab changes — a gesture of the user (click, keyboard, shortcut, list of tabs) and of_select_page: raises ue_selection_changing, which can refuse; a refused of_select_page returns -4. Off by default, like every cancelable event: set it to true to be able to refuse (unsaved work). Each question of a gesture costs a round trip to PowerBuilder (~35 ms) |
ib_veto_close | boolean | false | Ask before the user closes a tab (cross, middle-click, context menu, Delete): raises ue_tab_closing, which can refuse. Off by default: set it to true to keep a page holding unsaved work |
ib_context_menu | boolean | true | Built-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. With ib_veto_close, 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, a right-click on a tab selects it then raises ue_tab_rclicked: open your own menu there |
is_theme_style | string | "" | Visual style of the component (THEME_STYLE_* constants); empty = the application's, followed at every change |
is_theme_mode | string | "" | Light or dark variant (THEME_MODE_* constants); empty = the application's, followed at every change |
il_theme_accent | long | -1 | Accent colour of this component (-1 = the application accent, or the theme's) |
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 page — n_pbt_tab_page #
Obtained through of_page(id), they can be changed on the fly, without rebuilding the strip.
| Property | Type | Default | Purpose |
|---|---|---|---|
is_title | string | "" | Caption of the tab. Accepts rich text markup |
is_icon | string | "" | Icon of the tab (accepted forms: path, mono:, tint:, DLL resource). Empty = no icon |
ib_enabled | boolean | true | false greys the tab out: no click, key, shortcut or of_select_page makes it active, and it has no close cross. Disabling the active tab leaves it active and its page usable: disable the page itself if it must stop answering (your code knows whether it should) |
ib_visible | boolean | true | false hides the tab without removing the page; hiding the active tab activates its neighbour (the one on the right, otherwise the one on the left); hiding the last visible tab hides the page and raises ue_selection_changed with an empty key |
ib_closable | boolean | false | Close 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. A middle-click on a closable tab closes it too, like its cross |
is_shortcut | string | "" | 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 when ib_veto_selection is on, ue_selection_changed follows; a disabled or hidden tab ignores it. "" removes it |
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 #
| Method | Purpose | |
|---|---|---|
of_add_page (string as_key, string as_title, dragobject ado_page) | Adds a non-closable tab, without an icon, and hosts the control. Returns 0 once applied, -5 when the key is empty, contains a / or a ` | , is already taken, or the page is missing or already hosted (by this tab under another key, or by another component): nothing is added then, -2` when the component is not created |
of_add_page (string as_key, string as_title, dragobject ado_page, boolean ab_closable) | Same, with a close cross if ab_closable. Returns 0 once applied, -5 when the key is empty, contains a / or a ` | , is already taken, or the page is missing or already hosted (by this tab under another key, or by another component): nothing is added then, -2` when the component is not created |
of_add_page (string as_key, 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). Returns 0 once applied, -5 when the key is empty, contains a / or a ` | , is already taken, or the page is missing or already hosted (by this tab under another key, or by another component): nothing is added then, -2` when the component is not created |
of_insert_page (string as_key, string as_title, dragobject ado_page, integer ai_index) | Adds the page then moves it to position ai_index (1 = the first). Returns 0 once applied, -5 for the same refusals as of_add_page, -2 when the component is not created | |
of_insert_page (string as_key, 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. Returns 0 once applied, -5 for the same refusals as of_add_page, -2 when the component is not created | |
of_move_page (string as_key, integer ai_index) | Moves an existing tab to position ai_index (1 = the first); its hosted page and the selection are preserved. Returns 0 once applied, -5 when the key names no page, -2 when the component is not created | |
of_select_page (string as_key) | Activates a tab, like a click on it: with ib_veto_selection on, ue_selection_changing is asked first; then ue_selection_changed follows, as for a click (right after your script, from the event queue), and of_selected_key() read right after already answers the new key. The tab already active: nothing is asked nor raised. Returns 0 once applied (or already active), -4 when your ue_selection_changing refused (nothing moves), -5 when the key names no page or the page cannot be shown (hidden, disabled, past the demo cap) — including when no page at all is active afterwards, -2 when the component is not created | |
of_remove_page (string as_key) | Removes the tab and hands the control back to its original window, hidden. The control remains usable and can be hosted elsewhere. Does not raise ue_tab_closed: your code did it. If it was the active page, its neighbour becomes active. Returns 0 once applied, -5 when the key names no page, -2 when the component is not created | |
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 layout event — you supplied it; if it hides the active tab, its neighbour becomes active and ue_selection_changed says so. Returns 0 once applied, -5 when the text is empty or not valid JSON, -2 when the component is not created | |
of_page (string as_key) | n_pbt_tab_page handle of the page (created on first access) | |
of_refresh_page (string as_key) | Refreshes the rendering of a page built off-screen, without flicker. Returns 0, -5 when the key names no page, -2 when the component is not created | |
of_relayout ( ) | Republishes the page area so that the hosted control is repositioned (useful after a deferred display). Returns 0 once applied, -2 when the component is not created | |
of_preload_icons (string as_icons[]) | Warms up a batch of icons at startup: a tab opened later shows its icon instantly. Returns 0 once applied, -2 when the component is not created | |
of_reset ( ) | Empties the strip: every hosted page is handed back to its original window, then the component returns to its pristine 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 |
of_add_pagereturns a negative code if the identifier is empty, already in use, or if the control passed in is not valid or already hosted.
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.
Keyboard and mouse — Ctrl+Tab and Ctrl+PageDown go to the next tab, Ctrl+Shift+Tab and Ctrl+PageUp to the previous one: only the tabs that can become active count (hidden and greyed ones are skipped), and the walk wraps around. These keys answer when the keyboard is in the tab strip or in a page it hosts — a PowerBuilder control of the page as well as another component placed in it; from a control outside the tab, they stay with the window. It is a gesture of the user: ue_selection_changing is asked when ib_veto_selection is on, then ue_selection_changed follows. A middle-click on a closable tab closes it, exactly like its cross: the same ue_tab_closing question when ib_veto_close is on, then ue_tab_closed; on a greyed or non-closable tab it does nothing. Nothing to code.
Events #
| Event | Raised when |
|---|---|
ue_selection_changed (string as_from_key, string as_key) | The active tab has changed, through a gesture (a click, the keyboard including Ctrl+Tab, a shortcut, the list of tabs), through of_select_page (an order of your code raises it too, like SelectTab raises SelectionChanged) or because a tab left (closed, removed, hidden). Same arguments as ue_selection_changing: the question and its outcome read the same way, and as_from_key is the page left — even when it no longer exists. An empty as_key means no tab can be shown any more: every page is hidden |
ue_tab_closed (string as_key) | The user closed a tab: its cross, a middle-click on it, the context menu or the Delete key. The tab is already removed and the page handed back to its window. of_remove_page never raises it: your code did it |
ue_tab_reordered (string as_key, integer ai_index) | The user has finished dragging a header. ai_index is the new position, starting from 1, like of_move_page |
ue_layout_changed (string as_layout_json) | The layout to store changed: the user dragged a header, or your code moved a tab (of_move_page, of_insert_page) or hid / showed one (ib_visible). of_set_layout does not raise it (you supplied that layout), nor does adding, closing or removing a tab — the order of the others does not change. Carries the whole layout, not just what moved: persisting it is one assignment |
ue_selection_changing (string as_from_key, string as_key) → boolean | Cancelable, raised before the active tab changes — a gesture of the user or of_select_page — when ib_veto_selection is true (off by default). Return false to stay on the page: nothing moves, and of_select_page returns -4 |
ue_tab_closing (string as_key) → boolean | Cancelable, raised before the user closes a tab, when ib_veto_close is true (off by default). Named to pair with ue_tab_closed: closing / closed. Return false to keep the tab (and its hosted page) open |
ue_tab_rclicked (string as_key) | Right-click on a tab header while ib_context_menu is false: the tab is selected first (through ue_selection_changing when it is armed — a refused selection raises nothing), then this event gives its key. Open your own menu here: PopMenu(PointerX(), PointerY()) of your window. A disabled tab raises nothing; one event per right-click |
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_tabs.of_set_redraw(/*on*/ false)
// One tab per page userobject
uo_tabs.of_add_page(/*key*/ "identity", /*title*/ "Identity", /*page*/ uo_identity)
uo_tabs.of_add_page(/*key*/ "addresses", /*title*/ "Addresses", /*page*/ uo_addresses)
uo_tabs.of_add_page(/*key*/ "accounting", /*title*/ "Accounting", /*page*/ uo_accounting)
// One icon per tab, set on the page handle
uo_tabs.of_page(/*key*/ "identity").is_icon = "mono:img\user.svg"
uo_tabs.of_page(/*key*/ "addresses").is_icon = "mono:img\map.svg"
uo_tabs.of_page(/*key*/ "accounting").is_icon = "mono:img\euro.svg"
// One render for everything, then the first tab is shown
uo_tabs.of_set_redraw(/*on*/ true)
uo_tabs.of_select_page(/*key*/ "identity")
Closable documents, opened on demand #
// Open a document in a new closable tab
uo_tabs.of_add_page(/*key*/ ls_key, /*title*/ ls_label, /*page*/ uo_editor, /*closable*/ true, /*icon*/ "mono:img\doc.svg")
uo_tabs.of_select_page(/*key*/ ls_key)
// ue_tab_closed event of uo_tabs : (string as_key)
// The tab is already removed ; all that is left is releasing your business data.
of_free_document(as_key)
Mouse reordering #
// The user may drag a tab to a new place
uo_tabs.ib_reorderable = true
// ue_tab_reordered event of uo_tabs : (string as_key, integer ai_index)
// ai_index = new position, starting from 1 (like of_move_page) : we store the chosen order.
of_save_order(as_key, ai_index)
Vertical strip #
// Tab strip along the left side, useful when the captions are long
uo_tabs.is_position = uo_tabs.POSITION_START
Graying out or hiding a tab according to permissions #
// The path always goes through the page handle
uo_tabs.of_page(/*key*/ "accounting").ib_enabled = of_is_allowed("accounting")
uo_tabs.of_page(/*key*/ "audit").ib_visible = gb_expert_mode
Reacting to the selection #
// ue_selection_changed event of uo_tabs : (string as_from_key, string as_key)
choose case as_key
case "identity" ; uo_identity.of_refresh()
case "addresses" ; uo_addresses.of_refresh()
case "accounting" ; uo_accounting.of_refresh()
end choose
Refusing a tab change or a close #
// Both questions must be TURNED ON: without these lines the component asks
// nothing and changes page or closes at once.
uo_tabs.ib_veto_selection = true
uo_tabs.ib_veto_close = true
// ue_selection_changing event of uo_tabs : (string as_from_key, string as_key)
// Returning FALSE keeps the user on the page being left.
if of_page_modified(as_from_key) 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_tabs : (string as_key)
// Returning FALSE keeps the tab and its hosted page open.
return not of_page_modified(as_key)
Starting again from an empty strip #
// of_reset hands EVERY hosted page back to its original window before clearing
uo_tabs.of_reset()
uo_tabs.of_add_page(/*key*/ "home", /*title*/ "Home", /*page*/ uo_home)
Best practices #
- The hosted controls must exist before the call: place them on the window at design time, the component takes care of hiding and repositioning them.
- Load the pages once in the
openevent, then drive the selection only: that is faster than continuously adding and removing pages. - Call
of_preload_iconsat startup if your tabs carry icons: without it, the first time a hidden tab is opened there is a short delay before the icon appears. - Both questions are asked by default: an event left empty always allows the action, you have nothing to do. Switch them off with
ib_veto_selection/ib_veto_closeset tofalseif arbitration is useless to you, since every question costs a round trip to PowerBuilder. - A hosted control is a native window: it draws on top of the web layer. No visual effect of the component can pass over it.
- Do not forget
of_reset()before rebuilding a strip: without it, reusing a page identifier fails and the previous controls stay on screen. - For resizable and detachable areas rather than exclusive pages, prefer dockcontainer.
- On the keyboard, the tab strip follows its axis: Left/Right (swapped in a right-to-left language), Up/Down in the
start/endpositions, Home/End, Enter or Space to activate, Delete to close a closable tab. Disabled tabs and those moved out of the band are skipped.
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.