# dockcontainer — `u_pbt_dockcontainer` #

← Component reference · Guide contents

> Visual Studio style dockable panels: drag-and-drop docking, tab stacking, resizable splitters, floating windows, auto-hide — plus saving and restoring the layout.

> ▶ See it live — Demo application, Dock container tile: the preview, the code behind it and this page, side by side.

## At a glance #

Userobject | `u_pbt_dockcontainer`
Item class | `n_pbt_dock_panel` (one panel)
Used for | Giving a PowerBuilder window the ergonomics of a modern IDE: users arrange their own panels, and the layout is still there at the next session

Just like tab, every panel hosts a real PowerBuilder control (`dragobject`): a userobject, a DataWindow, or another PBToolboxAI component. See Hosting real PowerBuilder controls.

## Quick start #

    // window open event : the panel controls are already placed on it
    uo_dock.of_add_panel(/*key*/ "document", /*position*/ "", /*relative_to*/ "", &
                         /*size*/ 0, /*title*/ "Document", /*content*/ uo_editeur)

    uo_dock.of_add_panel(/*key*/ "explorateur", /*position*/ uo_dock.POSITION_START, &
                         /*relative_to*/ "document", /*size*/ 260, &
                         /*title*/ "Explorer", /*content*/ uo_arbre)

    uo_dock.of_add_panel(/*key*/ "proprietes", /*position*/ uo_dock.POSITION_END, &
                         /*relative_to*/ "document", /*size*/ 300, &
                         /*title*/ "Properties", /*content*/ uo_props)

    // The central area : the docking cannot close, float or hide it
    uo_dock.is_main = "document"

## Properties #

Property | Type | Default | Purpose
--- | --- | --- | ---
`is_main` | string | `""` | Key of the main panel: the central document area, which can neither be hidden nor floated. The panel must already exist
`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

### Docking positions #

The component constants feed `as_position` in `of_add_panel` and `of_move_panel`:

Constant | Value | Effect
--- | --- | ---
`POSITION_START` | `"start"` | The new panel docks on the reading start side of the reference panel (on the left in left-to-right writing)
`POSITION_END` | `"end"` | … on the reading end side
`POSITION_TOP` | `"top"` | … above it
`POSITION_BOTTOM` | `"bottom"` | … below it
`POSITION_STACK` | `"stack"` | The panel is stacked as a tab onto the reference panel. This is the value used when `as_position` is empty

### Properties of a panel — `n_pbt_dock_panel` #

Obtained through `of_item(key)`.

Property | Type | Default | Purpose
--- | --- | --- | ---
`is_title` | string | `""` | Title shown on the panel tab. Accepts rich text markup
`is_short_title` | string | `""` | Short form of the title, for the places where the room is scarce: the edge rail of a pinned panel (always — it is a sliver) and a stacked tab, but only once the full title no longer fits, which is measured rather than guessed. The panel header keeps the full title. `""` = no short form
`ib_visible` | boolean | `true` | Shows or hides this panel
`ib_pinned` | boolean | `true` | `true` = pinned in place; `false` = the panel collapses to a strip on the edge and only slides out on hover (auto-hide)
`is_header_color` | string | `""` | Header colour of this panel. Empty = the theme decides; `HEADER_COLOR_ACCENT` follows the theme accent (and keeps following it after a theme change, which a fixed code would not); otherwise a `#rrggbb`. The readable text colour comes with it
`is_border_color` | string | `""` | Border colour of this panel, same grammar. It outlines the frame and the header, which each carry their own: colouring only one leaves a visible seam. It follows the panel into its fly-out
`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_panel (string as_key, string as_position, string as_relative_to, integer ai_size, string as_title, dragobject ado_content)` | Adds a closable panel and hosts the control. `as_relative_to` = key of the panel it docks against (empty = the root); `ai_size` = initial size in pixels (`0` = automatic)
`of_add_panel (…, boolean ab_closable)` | Same thing, with `ab_closable = false` for a panel without a close button
`of_select_panel (string as_key)` | Brings a panel to the front of its tab group
`of_move_panel (string as_key, string as_target, string as_position)` | Rearranges from code, exactly as if the user had dragged the tab
`of_move_panel (string as_key, string as_target, string as_position, integer ai_index)` | Same thing, giving the panel's rank inside the target stack (0 = first, `-1` = last). Only a stack is an ordered list: the index is ignored for `POSITION_START` / `END` / `TOP` / `BOTTOM`
`of_float_panel (string as_key)` | Floats the panel into a real floating window that can be moved and resized. Closing it docks the panel back (`ue_panel_docked`)
`of_notify_panel (string as_key)` | Signals new content: if the panel is not visible, its tab shows a count of unseen items, cleared when the panel is selected
`of_clear_notify (string as_key)` | Clears that counter without showing the panel. Selecting the tab was the only other way, which is wrong when your code knows the content itself is gone
`of_remove_panel (string as_key)` | Removes the panel and returns the control to its original window
`of_get_layout ( )` | Returns the current layout (split tree, ratios, tabs, visibility) as a string, ready to be stored in a file, a database or the registry
`of_set_layout (string as_json)` | Restores a layout obtained from `of_get_layout`. The panels must have been re-created before the call; any panel missing from the layout is attached to the first group
`of_item (string as_id)` | `n_pbt_dock_panel` handle of the panel (created on first access)
`of_refresh_panel (string as_key)` | Refreshes the rendering of a panel built off-screen, without flicker
`of_relayout ( )` | Recomputes and republishes every area so that the hosted controls are repositioned
`of_reset ( )` | Empties the container: every hosted control is returned to its original window, the panel handles are released, and the stored layout is cleared
`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

## Events #

Event | Raised when
--- | ---
`ue_panel_selected (string as_key)` | A panel becomes the active panel of its group
`ue_panel_closed (string as_key)` | The user closes a panel
`ue_panel_pinned (string as_key, boolean ab_pinned)` | A panel is pinned (`true`) or unpinned into auto-hide (`false`)
`ue_panel_floated (string as_key)` | A panel is floated into a floating window
`ue_panel_docked (string as_key)` | A floating panel is docked back
`ue_layout_changed (string as_json)` | The layout has changed (drag, resize, docking). The parameter carries the new layout, ready to be saved
`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`)

## Keyboard #

A panel's content is a real PowerBuilder control: the keyboard there belongs to your code, as in any window. What the component takes care of is its own chrome — headers, tabs, splitters:

Key | Effect
--- | ---
Arrows on a tab | Move to the previous / next panel of the group, and select it. A tab strip is a single tab stop
Home / End on a tab | First / last panel of the group
Arrows on a splitter | Move the separation by 2 %, along the splitter's axis only
Page Up / Page Down on a splitter | The same, in 10 % steps
Escape | Closes the sliding panel opened from an edge button

A splitter is a focusable `separator` that announces its position; the header buttons (float, hide, close) carry a name, and the active panel of a group is marked `aria-selected`. A keyboard move is reported through `ue_layout_changed` exactly like a mouse drag.

## Examples #

### An IDE-style window #

    // open event : every content is a real PB control placed on the window
    uo_dock.of_add_panel("document", "", "", 0, "Document", uo_editeur)
    uo_dock.of_add_panel("explorateur", uo_dock.POSITION_START,   "document", 260, "Explorer", uo_arbre)
    uo_dock.of_add_panel("proprietes",  uo_dock.POSITION_END,  "document", 300, "Properties",  uo_props)
    uo_dock.of_add_panel("sortie",      uo_dock.POSITION_BOTTOM, "document", 200, "Output",      uo_console)

    // A second panel stacked as a tab onto "proprietes"
    uo_dock.of_add_panel("aide", uo_dock.POSITION_STACK, "proprietes", 0, "Help", uo_aide)

    uo_dock.is_main = "document"

### Remembering and restoring the user's layout #

    // window close event : save what the user has arranged
    string ls_disposition
    ls_disposition = uo_dock.of_get_layout()
    of_enregistrer_preference("dock_principal", ls_disposition)

    // open event : RE-CREATE the panels first, restore afterwards
    string ls_disposition

    of_creer_les_panneaux()          // the of_add_panel calls above

    ls_disposition = of_lire_preference("dock_principal")
    if ls_disposition <> "" then uo_dock.of_set_layout(ls_disposition)

> The order matters: `of_set_layout` creates no panel, it only rearranges the ones that already exist.

### Tracking changes continuously #

    // ue_layout_changed event of uo_dock : (string as_json)
    // Saved right away, without waiting for the window to close.
    of_enregistrer_preference("dock_principal", as_json)

### Driving a panel through its handle #

    n_pbt_dock_panel lnv_panneau

    lnv_panneau = uo_dock.of_item("proprietes")
    lnv_panneau.is_title  = "Properties (3 changed)"
    lnv_panneau.ib_pinned = false        // collapses to a strip on the edge

    // Hide a panel reserved for administrators
    uo_dock.of_item("audit").ib_visible = gb_administrateur

### Drawing attention to a background panel #

    // A background task wrote to the console : signal it without stealing the focus
    uo_dock.of_notify_panel("sortie")

### Rearranging from code #

    // Move a panel as if the user had dragged its tab
    uo_dock.of_move_panel(/*key*/ "sortie", /*target*/ "document", /*position*/ "bottom")

    // Stack it as a tab onto another panel
    uo_dock.of_move_panel(/*key*/ "aide", /*target*/ "proprietes", /*position*/ "stack")

### Floating a panel onto a second monitor #

    // Native floating window : the user can drop it on another monitor
    uo_dock.of_float_panel("proprietes")

    // ue_panel_docked event of uo_dock : (string as_key)
    // The user closed the floating window : the panel is back.
    of_journaliser("Panel docked back : " + as_key)

### Closing a panel cleanly #

    // ue_panel_closed event of uo_dock : (string as_key)
    // Also remove the panel from the container to release the hosted control.
    uo_dock.of_remove_panel(as_key)

## Best practices #

- Always designate a main panel with `is_main`: without a central area, users can hide everything and end up staring at an empty window.
- Save the layout on `ue_layout_changed` rather than only on close: an abnormal shutdown then loses nothing.
- `of_set_layout` comes after the `of_add_panel` calls, never before.
- Use `of_notify_panel` for background tasks: it is less intrusive than a dialog box and the information stays visible.
- A hosted control is a native window: it draws above the web layer, so no visual effect of the component can appear over it.
- Call `of_reset()` before any rebuild: without it, the previously hosted controls stay reparented into the emptied container and reusing a key fails.
- For simple exclusive pages, with no docking and no floating, tab is lighter.

← Component reference · Guide contents