PBToolboxAI v2 ← Site

4. Themes and appearance #

← Shared foundation · Contents · Language and RTL →


4.1 Themes: two axes #

A theme is made of a style and a mode:

AxisValues
Style (is_theme_style)fluent · metro · office · office2007 · office2003
Mode (is_theme_mode)light · dark

That is ten themes, named <style>-<mode>: fluent-light, fluent-dark, office2007-light, metro-dark…


Set the theme once for the whole application, before the first window opens. It is injected into every component before its first render: no flash of a light style on a dark application.

n_pbt_utils lnv_utils   // autoinstantiate : nothing to create, nothing to destroy
// open event of the application object
PBT_SetDefaultTheme("fluent-dark")
PBT_SetDefaultThemeAccent(RGB(0, 120, 212))   // optional

You can switch on the fly at any time: every component already open is re-themed instantly.

// Light / dark toggle from a button in the application
PBT_SetDefaultTheme("fluent-light")
FunctionEffect
PBT_SetDefaultTheme (string as_nom)Default theme of the process (broadcast to every component)
PBT_GetDefaultTheme ( ) → stringCurrent default theme
PBT_SetDefaultThemeAccent (long al_couleur)Default accent color (-1 = the theme's native accent)
PBT_GetDefaultThemeAccent ( ) → longCurrent default accent

4.3 The theme of one specific component #

A component can depart from the default theme:

uo_editeur.is_theme_style = uo_editeur.THEME_STYLE_OFFICE2007
uo_editeur.is_theme_mode  = uo_editeur.THEME_MODE_DARK
uo_editeur.il_theme_accent = RGB(200, 60, 40)     // -1 = the theme accent
PropertyTypeDefaultPurpose
is_theme_stylestringfluentVisual style (THEME_STYLE_* constants)
is_theme_modestringlightLight or dark variant (THEME_MODE_* constants)
il_theme_accentlong-1Accent color of this component (-1 = the theme accent)

An of_reset() brings the component back to the process default theme.

💡 A single theme for the whole application still looks best. Save local themes for special cases (a deliberately contrasted area, a theme preview).


4.4 Recolouring a component, a group or an item #

Three scopes, the same properties. Nothing to name, nothing to guess.

// The whole component
uo_ruban.il_theme_accent = RGB(0, 120, 90)

// A group: everything inside it follows
uo_ruban.of_tab("accueil").of_group("presse-papiers").il_accent = RGB(0, 120, 90)

// One item
uo_liste.of_item("supprimer").il_text_color = RGB(200, 70, 70)
uo_liste.of_item("supprimer").il_back_color = RGB(255, 235, 235)

// The same two, under the pointer
uo_liste.of_item("supprimer").il_back_color_hover = RGB(255, 220, 220)

// Back to the component's colour
uo_liste.of_item("supprimer").il_text_color = -1
PropertyWhereWhat it recolours
il_theme_accentthe componentits accent, and everything derived from it: the readable text on top, the application background, the tab underline
il_accentan item, group, tab or bar handlewhat that zone paints with the accent, descendants included
il_back_color · il_text_colorsamethe background and the text of the item
il_back_color_hover · il_text_color_hoversamethe same two, under the pointer

-1 restores the colour the component gives, which itself comes from the theme. An item colour survives the component being rebuilt: it is carried by a style rule targeting the item, not by a property set on the node of the moment. of_reset() clears everything.

il_accent only repaints what the zone paints with the accent — a selection, an active underline, a progress bar. A component that never uses it will show nothing: for "this entry in red", il_back_color and il_text_color are the right tools, read by every component with items.

The font of the whole application #

PBT_SetDefaultFont("Segoe UI", 14)

One call dresses every live component and the ones created afterwards — the font is injected before their first paint. An empty family or a size of 0 gives that half back to the theme.


4.5 The component background is reported back to PowerBuilder #

Every component paints its background according to the theme, then reports its color: the userobject adopts that color (backcolor) and raises ue_bg_color, so that the window and the neighboring PowerBuilder controls can match it.

// ue_bg_color event of a component
parent.backcolor = al_color
st_titre.backcolor = al_color

This is what lets you mix PBToolboxAI components and native PowerBuilder controls with no visible seam in dark mode.


4.6 Images and icons #

Everywhere a component expects an image path (button icon, tile, [picture=…]…), four forms are accepted:

FormExampleUsed for
Fileimg\logo.pngThe image as-is (png, jpg, gif, bmp, ico, svg, webp)
DLL resourceimg\packimages.dll:RIBBONAn image packaged in a resource DLL
mono:mono:img\save.svgFlat fill in the theme color: only the shape matters
tint:tint:img\logo_couleur.pngDuotone: the internal shading modulates the theme color

The path.dll:name form loads a resource from an image DLL (packimages.dll style), opened read-only (LOAD_LIBRARY_AS_DATAFILE, no code executed). This saves you from shipping hundreds of loose files.

Instant display: of_icon #

A small glyph passed through of_icon() is embedded in the command (no loading round trip): it shows up on the very first render, without the flicker of an icon loaded afterwards.

n_pbt_utils lnv_utils   // autoinstantiate : nothing to create, nothing to destroy

lnv_bar.of_add_button("save", "Save", lnv_utils.of_icon("mono:img\save.svg"))

For a batch of icons known in advance, of_preload_icons() warms the cache in one go, at startup: the first paint then waits for nothing.

Transparent in practice: beyond a certain size, of_icon returns the original path (the image is then loaded and cached as usual).


4.7 Rich text markup #

Any label of any component accepts BBCode-style markup: tab title, button caption, status bar text, toast message, panel title, tooltip text…

The entries of the built-in menus follow the same rule — a tab's context menu, the ··· list of the tabs that no longer fit, a grid's column menus: the label shown by the menu is the one on the control, markup included.

The text is rendered as text nodes and <span> elements: no HTML injection is possible.

TagEffect
[b] [i] [u] [s] / [strike]Bold, italic, underline, strikethrough
[sub] [super]Subscript, superscript
[red]…[/red] (named colors)Text color (red, green, blue, orange, teal…)
[accent]…[/accent]Accent color of the current theme
[color=#rrggbb] / [color=accent]Text color
[bk=#rrggbb] / [backcolor=accent]Background color
[font=Consolas]Font
[size=14]Absolute size, in points (6 to 200)
[size+=30] / [size-=20]Relative size in % (20% by default)
[picture=path] / [picture=path,width,height]Inline image
[br] / [linebreak] / [br:3]Line break (or n breaks)
[separator]Horizontal rule
[hyperlink=url]…[/hyperlink]Clickable area: the link always opens in the user's browser, in every component. The ue_hyperlink(as_url) event is raised as well, for the components that expose it
[action=id]…[/action]Clickable area → ue_action(as_key) event, styled as a link
[invisibleaction=id]…[/invisibleaction]Clickable area → ue_action, without the link styling
[bullet]…[/bullet]Bullet: list item whose wrapped lines align on the first one instead of running back under the marker (hanging indent). [bullet=-] changes the marker
[foldarea:Title]…[/foldarea]Collapsible block: a clickable header (− / +) above an indented body. The title accepts markup
[foldarea-closed:Title]…[/foldarea]The same block, collapsed when displayed
[[ / ]]Escaping: [[b]] displays [b] without interpreting it
uo_texte.is_text = "Welcome to [b][accent]PBToolboxAI[/accent][/b] [size-=20]v1.0[/size-=20]" &
                   + "[br]See the [hyperlink=https://pbtoolboxai.net]documentation[/hyperlink]."

uo_tab.of_add_page("clients", "[b]Customers[/b] [size-=20](128)[/size-=20]", uo_clients)

uo_st.is_text = "The [[b]] tag makes text [b]bold[/b]"   // displays: The [b] tag makes text bold

Displaying data as-is. A value coming from your database may contain brackets: it would be interpreted. of_escape_markup(), available on every component, doubles them for you — wrap the data, never the markup you wrote yourself.

// Business data may contain brackets : without escaping it is INTERPRETED
// and the bracketed text vanishes.
ls_libelle = "Balance [net]"
uo_st.is_text = "Account : " + ls_libelle                          // shows : Account :
uo_st.is_text = "Account : " + lnv_utils.of_escape_markup(ls_libelle)  // shows : Account : Balance [net]

Text without any tag carries no overhead at all (fast path). Unknown tags are ignored and their content is preserved. A [hyperlink] opens everywhere — a label, a tab title, a status bar panel, a toast, a dialog: the core takes care of it. The ue_action event, on the other hand, is only emitted by interactive text components (statictext); elsewhere, [action] is formatting only.

Only http, https and mailto are opened. A label often carries data coming from your database: handing an arbitrary scheme to the system would turn a label into a program launcher.

A [foldarea] is a block: it takes the full width and folds on a click on its header, with no round trip to PowerBuilder. Blocks nest, and when the component follows the height of its content (ib_auto_height), that height is reported again on every fold. The title is markup too: nothing is bolded for you, [foldarea:[b]Total[/b]] does it.


← Shared foundation · Contents · Language and RTL →

4.8 Animations and the workstation setting #

Windows offers an accessibility setting — Settings > Accessibility > Visual effects > Animation effects — and the components honour it: when it is off, no keyframe and no transition plays. The chart arrives at its place, it does not travel there.

That is the right default, and it is not up for debate: someone who asked their system for less movement meant it. ib_animated = true changes nothing about it.

An application may still insist:

// Declare it once : Function long PBT_SetAnimationPolicy (long al_policy)
//                   Library "pbtoolboxai.dll"
PBT_SetAnimationPolicy(1)   // 1 = always animate, 0 = respect the workstation (default)

The call covers the whole process and may be made at any time: live components follow at once, later ones receive it when they open.

Only set 1 if your application has a real reason to override — a kiosk, a wallboard, a demonstration whose very job is to show what these animations look like. For a business application, leave the default.


4.9 Composing your application's look #

The library ships ten themes and does not let an application define an eleventh: the token vocabulary is internal, and it stays that way. What it offers instead comes down to three levers, which combine — that is how you get "our colours" without writing a theme.

// 1. THE BASE: the shipped theme closest to the target.
PBT_SetDefaultTheme("office-light")

// 2. THE ACCENT: ONE colour dresses every component, those created
//    afterwards included, and everything the theme derives from it.
PBT_SetDefaultThemeAccent(RGB(0, 105, 92))

// 3. THE FONT of the whole application, in one call.
PBT_SetDefaultFont("Segoe UI Semibold", 0)

Put these three lines in the application object's open event: they reach every component before its first paint, so nothing ever flashes.

LeverScopeWhat it changes
PBT_SetDefaultThemethe processstyle and mode: shapes, corners, weights, the whole palette
PBT_SetDefaultThemeAccentthe processthe accent, and what the theme derives from it — readable text over it, tab underline, selection
PBT_SetDefaultFontthe processfamily and size; an empty family or a size of 0 hands that half back to the theme
il_theme_accentone componentits own accent, when a window has to stand apart
il_back_color · il_text_colorone itemone precise entry, in red because it deletes (see 4.4)

What this does not allow #

Redefining the full palette — the surface greys, the borders, the corner radius — is not offered. A theme is a coherent set of some sixty values that answer one another: opening half of them would produce unreadable combinations nobody had checked. If your brand needs more than these three levers, write to us: one more theme inside the library is an option, your theme inside your code is not.

In the demonstration application: ribbon Home > Appearance > Style > Corporate (composed). The entry applies exactly the three calls above — nothing reserved to us. The code is of_apply_corporate.