# 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_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 #

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 | `true` | Ask 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_close` | boolean | `true` | Ask before closing a closable tab (raises `ue_tab_closing`, which can refuse). On by default, same rule
`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. 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_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 page — `n_pbt_tab_page` #

Obtained through `of_item(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` grays the tab out and prevents users from selecting it
`ib_visible` | boolean | `true` | `false` hides the tab without removing the page; hiding the active tab activates a neighboring one
`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
`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, `ue_selection_changed` follows. `""` 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_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 #

Event | Raised 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)` → boolean | Cancelable, 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)` → boolean | Cancelable, 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 #

- 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 `open` event, then drive the selection only: that is faster than continuously adding and removing pages.
- Call `of_preload_icons` at 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_close` set to `false` if 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.

← Component reference · Guide contents