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_editor)
// The explorer, docked at the start of the document, 260 pixels wide
uo_dock.of_add_panel(/*key*/ "explorer", /*position*/ uo_dock.POSITION_START, /*relative_to*/ "document", /*size*/ 260, /*title*/ "Explorer", /*content*/ uo_tree)
// The properties, docked at the end of the document, 300 pixels wide
uo_dock.of_add_panel(/*key*/ "properties", /*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, and on which no panel is stacked. Set before that panel is added, it is kept and applied by its of_add_panel; "" removes the main panel |
ib_veto_close | boolean | false | Opt-in: ask before a panel closes — the cross of its header, Close / Close others in the menu, the cross of a sliding panel and of_close_panel (raises ue_panel_closing, which can refuse; a refused order returns -4). Off by default, like every cancelable event of the library: set it to true to keep open a panel that holds unsaved work |
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 |
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_panel(key).
| Property | Type | Default | Purpose |
|---|---|---|---|
is_title | string | "" | Title shown on the panel tab; empty (no title given), the tab shows the key and the main panel has no header — giving it a title later makes it appear. 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. The main panel cannot be hidden; hiding a floating panel closes its window, and it comes back docked in its place |
ib_pinned | boolean | true | true = pinned in place; false = the whole group of the panel (its tabs included) collapses to strips on the edge. A strip slides its panel out on hover (after 400 ms) or at once on a click; opened on hover it folds back when the mouse leaves it while the focus is not inside, opened by a click it stays until a click elsewhere. the pin button of the header raises ue_panel_pinned for every panel of the group, and so does setting ib_pinned from code (an order raises it like the gesture) |
ib_closable | boolean | true | Can the user close this panel (header cross, sliding panel cross, Close / Close others in the menu)? Set at the add by of_add_panel(…, ab_closable), changeable at any time: the cross and the menu entries follow at once. of_close_panel refuses (-5) a panel that is not closable |
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 |
il_back_color | long | -1 | Background of THIS panel's tab in a stack, and of its strip on the edge once unpinned (-1 = the component's, which follows the theme). The header of a panel alone takes is_header_color |
il_text_color | long | -1 | Text colour of THIS panel's tab and strip (-1 = the component's) |
il_back_color_hover | long | -1 | Background of THIS panel's tab and strip on hover (-1 = the component's) |
il_text_color_hover | long | -1 | Text colour of THIS panel's tab and strip on hover (-1 = the component's) |
il_accent | long | -1 | Accent of THIS panel (-1 = the component's accent); the header follows it when is_header_color is HEADER_COLOR_ACCENT |
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). Returns 0 once applied, -5 on an empty key, a key already taken or holding / or a vertical bar, a control already hosted, an unknown as_relative_to or a stack (POSITION_STACK) on the main panel, or an unknown position — nothing is touched then, -6 in demo mode once the dock holds its three panels (the control stays where it was, visible), -2 when the component is not created |
of_add_panel (…, boolean ab_closable) | Same thing, with ab_closable = false for a panel without a close button. Returns 0 once applied, -5 on an empty key, a key already taken or holding / or a vertical bar, a control already hosted, an unknown as_relative_to or a stack (POSITION_STACK) on the main panel, or an unknown position — nothing is touched then, -6 in demo mode once the dock holds its three panels (the control stays where it was, visible), -2 when the component is not created |
of_select_panel (string as_key) | Brings a panel into view: to the front of its stack, its sliding panel opened when its group is collapsed, its window raised when it floats. When a docked panel comes to the front of its stack, ue_panel_selected follows, as for a click on its tab. Returns 0 once applied, -5 on an unknown key or a HIDDEN panel (showing it is the job of ib_visible), -2 when the component is not created |
of_move_panel (string as_key, string as_target, string as_position) | Rearranges from code, exactly as if the user had dragged the tab. Returns 0 once applied, -5 on an unknown key, an unknown target (as_target) an unknown position, a stack on the main panel or a stack OF the main panel, -2 when the component is not created |
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 (1 = first, 0 or less = last). Only a stack is an ordered list: the index is ignored for POSITION_START / END / TOP / BOTTOM. Returns 0 once applied, -5 on an unknown key, an unknown target (as_target) an unknown position, a stack on the main panel or a stack OF the main panel, -2 when the component is not created |
of_float_panel (string as_key) | Floats the panel into a real floating window that can be moved and resized; a panel detached before gets the place of its window back. Closing it docks the panel back (ue_panel_docked). Not the main panel (is_main): it is the document zone the others dock around, it builds its own header, and it can be neither detached, hidden nor closed. ue_panel_floated follows, as for the header button, a double-click on the header or on the tab, or Float in the right-click menu. Returns 0 once applied, -5 on an unknown key, the main panel or a HIDDEN panel, -2 when the component is not created |
of_close_panel (string as_key) | Closes the panel like its cross: it is hidden (ib_visible = true shows it again) and ue_panel_closed follows. With ib_veto_close = true, ue_panel_closing is asked first. Returns 0 once applied, -4 when your ue_panel_closing refused (the panel stays), -5 on an unknown key, the main panel, a hidden panel or one that is not closable (ib_closable = false), -2 when the component is not created |
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. Returns 0 once applied, -5 on an invalid argument (empty key, wrong address), -2 when the component is not created |
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. Returns 0 once applied, -5 on an invalid argument (empty key, wrong address), -2 when the component is not created |
of_remove_panel (string as_key) | Removes the panel and returns the control to its original window, in its place, at its size and with the visibility it had before of_add_panel. Returns 0 once applied, -5 on an invalid argument (empty key, wrong address), -2 when the component is not created |
of_get_layout ( ) | Returns the current layout (split tree, ratios, tabs, visibility, floating panels and the place of their window) 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 — or all of them, if it knows none — joins a group next to the others, never the main panel. Floating panels get their window back at its place, brought back onto a screen when theirs is gone; a layout that names no main panel keeps the current one. Returns 0 once applied, -5 on a text that is not a layout (not JSON, or no tree): the dock is then left as it is, -2 when the component is not created |
of_panel (string as_key) | 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. Returns 0, -5 when the key names no panel, -2 when the component is not created |
of_relayout ( ) | Recomputes and republishes every area so that the hosted controls are repositioned. Returns 0 once applied, -2 when the component is not created |
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. 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 |
Events #
| Event | Raised when |
|---|---|
ue_panel_selected (string as_key) | A docked panel comes to the front of its group: click on its tab or its header, arrows of the tab strip, or of_select_panel (an order of your code raises it too) |
ue_panel_closed (string as_key) | A panel is closed: the cross of its header, Close or Close others in the right-click menu of the header or the tab, the cross of a sliding panel, or of_close_panel (an order of your code raises it too). The panel is hidden (ib_visible = true shows it again); with ib_veto_close = true, only once ue_panel_closing has allowed it |
ue_panel_closing (string as_key) → boolean | Cancelable, asked before a panel closes — the cross of its header, Close or Close others in the menu (one question per panel), the cross of a sliding panel, or of_close_panel — and only when ib_veto_close = true (off by default). Return false to keep the panel open (unsaved work): ue_panel_closed does not follow, and of_close_panel returns -4 |
ue_panel_pinned (string as_key, boolean ab_pinned) | A panel is pinned (true) or unpinned into auto-hide (false): pin button of the header, dock button of the sliding panel, or ib_pinned set by your code (an order raises it too) |
ue_panel_floated (string as_key) | A panel is floated into a floating window: header button, a double-click on the header or on the tab, Float in the right-click menu, or of_float_panel (an order of your code raises it too). A restored layout (of_set_layout) raises nothing |
ue_panel_docked (string as_key) | The USER docks a floating panel back: closing its window, or a double-click on its title bar — the panel goes back to its place |
ue_layout_changed (string as_json) | The layout has changed (drag, resize, docking, a floating window moved or resized, but also a panel closed, hidden, selected, renamed, or another main panel). 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) |
Mouse #
Beyond dragging tabs and splitters, three gestures act on a panel — never on the main panel:
| Gesture | Effect |
|---|---|
| Double-click on the header of a group or on a tab | Floats the panel (for the header: the active panel of the group) into a floating window — ue_panel_floated |
| Double-click on the title bar of a floating window | Docks the panel back in its place, like the close box of the window — ue_panel_docked |
| Right-click on the header or on a tab | Float / Close / Close others menu (the other closable panels of the same group). An entry that can do nothing is greyed out; with ib_veto_close = true, each close asks ue_panel_closing — one question per panel |
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(/*key*/ "document", /*position*/ "", /*relative_to*/ "", /*size*/ 0, /*title*/ "Document", /*content*/ uo_editor)
uo_dock.of_add_panel(/*key*/ "explorer", /*position*/ uo_dock.POSITION_START, /*relative_to*/ "document", /*size*/ 260, /*title*/ "Explorer", /*content*/ uo_tree)
uo_dock.of_add_panel(/*key*/ "properties", /*position*/ uo_dock.POSITION_END, /*relative_to*/ "document", /*size*/ 300, /*title*/ "Properties", /*content*/ uo_props)
uo_dock.of_add_panel(/*key*/ "output", /*position*/ uo_dock.POSITION_BOTTOM, /*relative_to*/ "document", /*size*/ 200, /*title*/ "Output", /*content*/ uo_console)
// A second panel stacked as a tab onto "properties"
uo_dock.of_add_panel(/*key*/ "help", /*position*/ uo_dock.POSITION_STACK, /*relative_to*/ "properties", /*size*/ 0, /*title*/ "Help", /*content*/ uo_help)
// The document is the central area
uo_dock.is_main = "document"
Remembering and restoring the user's layout #
// window close event : save what the user has arranged
string ls_layout
ls_layout = uo_dock.of_get_layout()
of_save_setting(/*name*/ "main_dock", /*value*/ ls_layout)
// open event : RE-CREATE the panels first, restore afterwards
string ls_layout
// First the panels : the of_add_panel calls above
of_create_panels()
// Then the layout saved at the last closing, if there is one
ls_layout = of_read_setting(/*name*/ "main_dock")
if ls_layout <> "" then uo_dock.of_set_layout(/*json*/ ls_layout)
The order matters:
of_set_layoutcreates 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_save_setting(/*name*/ "main_dock", /*value*/ as_json)
Driving a panel through its handle #
// Local variables
n_pbt_dock_panel lnv_panel
// The properties panel : a title that counts the changes, folded onto its edge
lnv_panel = uo_dock.of_panel(/*key*/ "properties")
lnv_panel.is_title = "Properties (3 changed)"
lnv_panel.ib_pinned = false // collapses to a strip on the edge
// Hide a panel reserved for administrators
uo_dock.of_panel(/*key*/ "audit").ib_visible = gb_administrator
Drawing attention to a background panel #
// A background task wrote to the console : signal it without stealing the focus
uo_dock.of_notify_panel(/*key*/ "output")
Rearranging from code #
// Move a panel as if the user had dragged its tab
uo_dock.of_move_panel(/*key*/ "output", /*target*/ "document", /*position*/ "bottom")
// Stack it as a tab onto another panel
uo_dock.of_move_panel(/*key*/ "help", /*target*/ "properties", /*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(/*key*/ "properties")
// ue_panel_docked event of uo_dock : (string as_key)
// The user closed the floating window : the panel is back.
of_log(/*msg*/ "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(/*key*/ as_key)
Refusing to close a panel that holds unsaved work #
// window open event : ask before a panel closes (off by default)
uo_dock.ib_veto_close = true
// ue_panel_closing event of uo_dock : (string as_key) returns boolean
// The editor still holds unsaved changes : its panel stays open.
if as_key = "editor" then
if ib_editor_modified then return false
end if
return true
// Your code asks the same question : -4 when it is refused
if uo_dock.of_close_panel(/*key*/ "editor") = -4 then
st_status.Text = "Save the document before closing the editor."
end if
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_changedrather than only on close: an abnormal shutdown then loses nothing. of_set_layoutcomes after theof_add_panelcalls, never before.- Use
of_notify_panelfor 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.
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.