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:
| Axis | Values |
|---|---|
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…
4.2 The application default theme (recommended) #
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.
// 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")
| Function | Effect |
|---|---|
PBT_SetDefaultTheme (string as_nom) | Default theme of the process (broadcast to every component) |
PBT_GetDefaultTheme ( ) → string | Current default theme |
PBT_SetDefaultThemeAccent (long al_couleur) | Default accent color (-1 = the theme's native accent) |
PBT_GetDefaultThemeAccent ( ) → long | Current 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
| Property | Type | Default | Purpose |
|---|---|---|---|
is_theme_style | string | fluent | Visual style (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) |
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
| Property | Where | What it recolours |
|---|---|---|
il_theme_accent | the component | its accent, and everything derived from it: the readable text on top, the application background, the tab underline |
il_accent | an item, group, tab or bar handle | what that zone paints with the accent, descendants included |
il_back_color · il_text_color | same | the background and the text of the item |
il_back_color_hover · il_text_color_hover | same | the 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:
| Form | Example | Used for |
|---|---|---|
| File | img\logo.png | The image as-is (png, jpg, gif, bmp, ico, svg, webp) |
| DLL resource | img\packimages.dll:RIBBON | An image packaged in a resource DLL |
mono: | mono:img\save.svg | Flat fill in the theme color: only the shape matters |
tint: | tint:img\logo_couleur.png | Duotone: the internal shading modulates the theme color |
- Use
mono:for every monochrome glyph (white or black icons): they recolor themselves automatically, in light mode as well as dark. tint:harmonizes a color icon with the theme while preserving its gradients. Never use it on a white glyph (it would stay white).- With no prefix, a multicolor image is left untouched.
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.
lnv_bar.of_add_button("save", "Save", uo_toolbar.of_icon("mono:img\save.svg"))
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.
| Tag | Effect |
|---|---|
[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_id) 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 : " + uo_st.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.