3. Shared foundation u_pbt_base #
← Getting started · Contents · Themes →
Every visual component inherits from u_pbt_base, which provides the life cycle, the property engine, the transport to the web component and error handling. The properties themselves — theme and tooltips included — are published by each component: its own page lists them all. You never use u_pbt_base directly — you drop a concrete component onto your window — but everything described below is available everywhere.
3.1 The property engine #
Assigning #
Every value you can drive is a public instance variable, assigned directly:
uo_progress.id_value = 42.5
uo_progress.is_label = "Import in progress…"
uo_progress.ib_animated = true
The Hungarian prefix tells you the type: is_ string, ib_ boolean, ii_ integer, il_ long (often an RGB() color), id_ double.
There is no scalar of_set_xxx: a property is set by assignment. What remains methods are additions, removals and actions (of_add_*, of_remove_*, of_select_*, of_reset, of_set_layout…).
Reading back #
Reading returns the current value (a cache on the PowerBuilder side):
if uo_progress.id_value >= 100 then …
A web component cannot be queried synchronously, so that cache is refreshed by the events. Whenever the component moves a property by itself — the user follows a link, zooms with the wheel, folds the ribbon, types — the event that tells you about it updates the property on the way. Reading it back then gives the real state, and the new value is already in place when your event code runs.
The same holds for items: after a user click, of_item(...) reads back what is on screen — the selected entry, the folded section, the checked button.
A property that no event accompanies stays on the last value you set.
Batching changes #
A burst of assignments triggers just as many renders. of_set_redraw merges them into a single one:
uo_grid.of_set_redraw(false)
… vingt affectations et of_add_* …
uo_grid.of_set_redraw(true) // ONE single repaint
Always call both (the closing true is not optional).
3.2 Items #
A component with content (tabs, buttons, panels, tiles, sections…) exposes its elements through typed handles, obtained from the component itself or from their parent.
Adding #
Adding returns the handle of the element that was created:
n_pbt_tab_page lnv_page
uo_tab.of_add_page("clients", "Customers", uo_page_clients)
lnv_page = uo_tab.of_item("clients")
lnv_page.is_icon = "img\clients.png"
Retrieving and modifying #
of_item(id) — or the factory for the level concerned — returns the handle of an existing element; its properties are set exactly like a component's:
uo_toolbar.of_bar("main").of_item("save").ib_enabled = false
uo_tab.of_item("clients").is_title = "Customers (128)"
Hierarchies: an identifier is only unique within its parent #
A multi-level component provides no shortcut down to the leaf: the full path is required, which guarantees that no identifier is ambiguous.
// Ribbon: tab > group > control > menu entry
uo_ribbon.of_tab("home").of_group("clipboard").of_item("paste").ib_enabled = false
The events carry the full path as well:
// ue_clicked event of uo_toolbar: (string as_bar, string as_id)
choose case as_bar + "/" + as_id
case "main/save" ; of_enregistrer()
end choose
Item events #
There are no generic item events on the ancestor: a leaf identifier alone would be ambiguous as soon as items are nested (a toolbar has several bars, a tilesbox several groups…). Each component therefore declares its own item events, carrying the full path: ue_item_selected (as_section, as_id) for the listbar, ue_tile_clicked (as_group, as_id) for the tilesbox, ue_clicked (as_bar, as_id) for the toolbar…
See the component page: that is where the exact list lives.
3.3 Events common to every component #
| Event | Raised when |
|---|---|
ue_ready ( ) | The component has finished loading; everything sent beforehand has been replayed |
ue_runtime_missing ( ) | The WebView2 runtime is missing — see Installation |
ue_bg_color (long al_color) | The component has computed its theme background color; the userobject has already adopted it (backcolor), it is up to you to match the window if needed |
Commands sent before
ue_readyare not lost: they are queued and replayed in order. You can therefore configure everything as early as theconstructoror theopen.
// ue_bg_color event: match the window to the component background
parent.backcolor = al_color
3.4 Optional properties and events (opt-in) #
Some features are not enabled by default: they are only published by the components where they make sense, and you have to ask for them.
Automatic height — ib_auto_height #
The component measures its ideal height and resizes the userobject; the ue_auto_height(al_height) event lets you reposition the neighboring controls.
uo_entete.ib_auto_height = true
// ue_auto_height event of uo_entete
il_hauteur_entete = al_height
of_relayout() // reposition the content below
Published by: picture and statictext.
Bands do not publish this property — their height is intrinsic. ribbon and toolbar do not scroll vertically: a fixed height can only produce empty space under the band or truncated content (a collapsed ribbon, a toolbar wrapped onto two rows, and so on). They therefore adjust at all times, with nothing to enable, and still publish ue_auto_height so that you can reposition whatever sits below.
Automatic width — ib_auto_width #
Same principle for the width. Published only by listbar, the one component whose natural width means something.
A collapsed listbar shrinks to the rail on its own and gives the width back when it expands: ib_auto_width is only useful if you also want to follow the expanded width (the bar then sizes itself to the longest label).
Ambient mouse events — ib_track_mouse #
High-frequency mouse events are cut off at the source: without a subscription, the component does not emit them at all (nothing crosses the bridge to PowerBuilder).
uo_bouton.ib_track_mouse = true // enables ue_mouse_enter / ue_mouse_leave / ue_rclicked
Published by: button, picture, statictext.
Discrete events (click, selection, menu, drop…) are always emitted, with no subscription needed.
3.5 Keyboard shortcuts #
A shortcut triggers a component wherever the focus sits in the window — the user does not have to go back to the button to fire it. Every visual component accepts them, with nothing to enable.
uo_enregistrer.of_register_shortcut("Ctrl+S")
uo_actualiser.of_register_shortcut("F5")
Writing a key chord #
The chord is a free-form string, normalized by the library: case, spaces and modifier order are irrelevant. "Ctrl+Shift+S", "ctrl + shift + s" and "SHIFT+CTRL+S" all mean the same shortcut — you cannot register two variants of it by mistake.
| Element | Accepted forms |
|---|---|
| Modifiers | Ctrl (or Control), Alt, Shift — combinable, in any order |
| Key | a letter A–Z, a digit 0–9, F1 … F24, Enter (or Return), Escape (or Esc), Delete (or Del), Insert, Home, End, PageUp, PageDown |
This list is exhaustive: a key that is not in it (Tab, Space, a numeric keypad key, a punctuation character) fires no shortcut.
A single key is a valid chord ("F5"). An empty string removes the shortcut from the component.
"Enter"and"Escape"on their own cannot be registered as shortcuts: those two keys stay reserved for the default button and the cancel button (ib_default/ib_cancelof the button). Combined with a modifier, they become ordinary chords again ("Ctrl+Enter").
Who wins in case of conflict #
Two components may ask for the same chord — a common situation when a window hosts several areas that each have their own "Save". The arbitration goes like this:
- the component that holds the keyboard focus wins over all the others: the shortcut of an active area is never eclipsed by a neighbour;
- failing that, the first one registered wins.
Only the visible and enabled components of the foreground window take part. Re-registering a chord on a component that already had one replaces it without changing its rank: reconfiguring a window does not reshuffle the priorities.
Item shortcuts #
The two-argument overload attaches the chord to an item of the component rather than to the whole component — the second argument is the item identifier:
uo_barre.of_register_shortcut(/*chord*/ "Ctrl+N", /*item*/ "nouveau")
uo_barre.of_register_shortcut(/*chord*/ "Ctrl+P", /*item*/ "imprimer")
Removing shortcuts #
uo_barre.of_clear_shortcuts() // component AND items
of_reset() and destroying the component call of_clear_shortcuts() for you: a component that is gone never keeps a chord reserved.
The Alt key #
Alt on its own is not captured: it gives the focus to the ribbon, which raises its keytips (see ribbon). The library therefore does not intercept the keystrokes that follow — the ribbon reads them, as if the user had clicked it. Escape or a second Alt return the focus to the control that was left. If no ribbon in the window declares a keytip, Alt keeps its usual Windows behaviour.
| Member | Effect |
|---|---|
of_register_shortcut (string as_chord) | Declares a shortcut for the component; an empty string removes it |
of_register_shortcut (string as_chord, string as_key) | Declares a shortcut for an item, designated by its identifier |
of_clear_shortcuts ( ) | Removes every shortcut from the component, items included |
3.6 Resetting a component: of_reset() #
of_reset() returns the component to its as-new state, as if it had just been loaded:
- the content is cleared (items, pages, panels…);
- every property goes back to its default (formatting, colors, mode, labels);
- the style overrides and tooltips set on the instance are cancelled;
- the property cache on the PowerBuilder side is emptied (read-backs start again from the defaults);
- the native state is flattened as well (context menu, display mode…).
uo_grid.of_reset() // start again from an empty grid
// ... then rebuild
⚠️ Reusing an instance to display something else without calling
of_reset()keeps the previous state (a color, a mode, an automatic height). This is the single most common cause of an unexplained "leftover" on screen.
3.7 Diagnostics #
| Member | Effect |
|---|---|
of_is_created ( ) → boolean | The native component exists (runtime present, host valid) |
of_is_ready ( ) → boolean | The web content is loaded (ue_ready already raised) |
of_get_last_error ( ) → string | Last detailed error message from the DLL, after a < 0 return code |
Return codes of the of_* methods:
| Return | Meaning |
|---|---|
≥ 0 | OK (applied, or queued) |
-2 | Component not created (runtime missing, host invalid) |
-4 | Operation failed (capture, file write…) |
-5 | Invalid argument (empty identifier, value out of range) |
-6 | WebView2 runtime too old for the requested feature (printing) |
3.8 Exporting the rendering as an image #
Every component can export itself as an image, exactly as displayed:
uo_pivot.of_save_as_png("C:\temp\tableau.png")
uo_pivot.of_save_as_jpg("C:\temp\tableau.jpg")
To print it rather than export it, see Printing.
Handy for a report, an e-mail attachment or an incident trace. The component must be created and its content loaded.
3.9 Life cycle #
- Construction: the webview is created as soon as the userobject is constructed — essential for hosting (tabs, dockable panels): a webview created after its HWND has been reparented does not display.
- Queue: your commands are queued for as long as
ue_readyhas not been raised. - Ready:
ue_ready; the queue is replayed in order. - Resizing: automatic, the component follows the size of the userobject.
- Destruction: when the window closes; the webview is released, with no orphan process left behind.
Call PBT_Warmup() once when the application starts so that this cycle goes unnoticed (Installation).
3.10 Best practices #
- Set the default theme and the language in the application object, before the first window opens: the components then show no style flash.
- Wrap any bulk build in
of_set_redraw(false)/of_set_redraw(true). - Call
of_reset()before reusing an instance for different content. - Do not block the UI thread with a long PowerScript loop between creation and display: initializing the webview needs the message loop (see FAQ).