PBToolboxAI v4 ← Site

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 #

Userobjectu_pbt_dockcontainer
Item classn_pbt_dock_panel (one panel)
Used forGiving 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 #

PropertyTypeDefaultPurpose
is_mainstring""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_closebooleanfalseOpt-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_stylestring""Visual style of the component (THEME_STYLE_* constants); empty = the application's, followed at every change
is_theme_modestring""Light or dark variant (THEME_MODE_* constants); empty = the application's, followed at every change
il_theme_accentlong-1Accent colour of this component (-1 = the application accent, or the theme's)
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

Docking positions #

The component constants feed as_position in of_add_panel and of_move_panel:

ConstantValueEffect
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).

PropertyTypeDefaultPurpose
is_titlestring""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_titlestring""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_visiblebooleantrueShows 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_pinnedbooleantruetrue = 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_closablebooleantrueCan 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_colorstring""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_colorstring""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_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
il_back_colorlong-1Background 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_colorlong-1Text colour of THIS panel's tab and strip (-1 = the component's)
il_back_color_hoverlong-1Background of THIS panel's tab and strip on hover (-1 = the component's)
il_text_color_hoverlong-1Text colour of THIS panel's tab and strip on hover (-1 = the component's)
il_accentlong-1Accent of THIS panel (-1 = the component's accent); the header follows it when is_header_color is HEADER_COLOR_ACCENT

Methods #

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

EventRaised 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) → booleanCancelable, 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:

GestureEffect
Double-click on the header of a group or on a tabFloats 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 windowDocks the panel back in its place, like the close box of the window — ue_panel_docked
Right-click on the header or on a tabFloat / 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:

KeyEffect
Arrows on a tabMove to the previous / next panel of the group, and select it. A tab strip is a single tab stop
Home / End on a tabFirst / last panel of the group
Arrows on a splitterMove the separation by 2 %, along the splitter's axis only
Page Up / Page Down on a splitterThe same, in 10 % steps
EscapeCloses 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_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_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 #

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.

MembersRoleDetailed in
of_count · of_keys_at · of_hasWalk what the component holds3.2 Items
of_resetPut the component back to zero3.6 Resetting a component: of_reset()
of_register_shortcut · of_clear_shortcutsThe component's keyboard chords3.5 Keyboard shortcuts
of_is_created · of_is_ready · of_get_last_errorWhether it was born, whether it is ready, what failed3.7 Diagnostics
of_save_as_png · of_save_as_jpgExport the rendering as an image3.8 Exporting the rendering as an image
of_set_redrawGroup changes into a single repaint3.10 Best practices
of_preload_iconsIcons shown with no delayInstant display: of_icon
of_set_translationTranslate one of the component's labels5.2 Adapting a label: of_set_translation
of_focus_webviewGive the component the focus6.4 Keyboard and focus
of_print · of_print_to_pdfPrint, or write a PDF6.9 Printing
of_set_property · of_get_property · of_component_nameDriving a property by its name3.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.


← Component reference · Guide contents