# 6. Cross-cutting features #

← Language and RTL · Contents · FAQ →

This chapter covers the services available whatever the component: tooltips, hosting PowerBuilder controls, drag and drop from Windows Explorer, keyboard focus, image export, and the non-visual utility objects.

## 6.1 Simple and rich tooltips #

### Component tooltip #

    uo_bouton.is_tooltip = "Save the file (Ctrl+S)"

The text accepts rich text markup: `[b]`, `[br]`, `[color=…]`, `[picture=…]`…

### Rich tooltip (title + text + image) #

For Office ribbon style contextual help:

    uo_bouton.is_super_tooltip_title = "Save"
    uo_bouton.is_super_tooltip_text  = "Writes your changes to the server." &
                                       + "[br][br][size-=15]Shortcut: Ctrl+S[/size-=15]"
    uo_bouton.is_super_tooltip_image = "img\help_save.png"

The three properties are independent: a title alone, or a title plus text with no image, are both valid. As soon as one `is_super_tooltip_*` property is set, it takes precedence over `is_tooltip`.

### Item tooltip #

Every item class (`n_pbt_item` and its descendants) carries the same four properties:

    uo_toolbar.of_bar("main").of_item("save").is_tooltip = "Save"

    uo_tab.of_item("clients").is_super_tooltip_title = "Customers"
    uo_tab.of_item("clients").is_super_tooltip_text  = "128 records, last synced at 09:12"

### Behavior #

- The tooltip is a themed native window, identical across all components (it is not confined by the boundaries of the webview).
- It does not appear when your application does not have the focus: hovering over a background window triggers nothing.
- It disappears when you switch to another application, and after 10 seconds at the latest.
- `of_reset()` clears the tooltips set on the instance and on its items.

## 6.2 Hosting real PowerBuilder controls #

Container components do not display HTML in place of your screens: they host real PowerBuilder controls (userobjects, DataWindows, control groups), reparented as Win32 child windows. Your existing screens are reused as they are.

    // Tabs: each page is a PB userobject
    uo_tab.of_add_page("clients",   "Customers",   uo_page_clients)
    uo_tab.of_add_page("factures",  "Invoices",  uo_page_factures, /*closable*/ true)

    // Dockable panels: same principle
    uo_dock.is_main = "doc"                       // center area
    uo_dock.of_add_panel("doc",      uo_dock.POSITION_STACK, "", 0,   "Document",    uo_editeur)
    uo_dock.of_add_panel("explorer", uo_dock.POSITION_START, "", 240, "Explorer", uo_arbre)
    uo_dock.of_add_panel("props",    uo_dock.POSITION_END,   "", 260, "Properties",  uo_props)

What is handled for you:

- the positioning and resizing of the hosted control when the tab or panel changes size;
- showing / hiding it when the active tab or panel changes;
- floating, docked or auto-hidden panels (dockcontainer);
- clean destruction when the window closes.

> ⚠️ A hosted control is a native window: it is painted on top of the web layer. That is by design (your DataWindow stays crisp and fast), but it means no web effect — shadow, transparency, animation — can be drawn over it.

## 6.3 Receiving files from Windows Explorer #

A component can become a drop target for files dragged from Windows Explorer. The library installs a native drop target: you receive the full paths, which an HTML drop cannot give you.

    uo_editeur.ib_allow_drop = true

    // ue_drop_files event of uo_editeur: (string as_files[])
    long ll_i

    for ll_i = 1 to UpperBound(as_files)
        of_ouvrir_fichier(as_files[ll_i])
    next

Event | Raised when
--- | ---
`ue_drag_enter ( )` | A file drag enters the component
`ue_drag_leave ( )` | It leaves without dropping
`ue_drop_files (string as_files[])` | The files are dropped — full paths

The component provides the hover visual feedback itself (the drop zone is highlighted). Published by the components where dropping makes sense: `statictext`, `codeeditor`.

## 6.4 Keyboard and focus #

A PBToolboxAI component takes part in the keyboard navigation of your window just like a native control:

- the Tab key reaches it in the window tab order;
- keystrokes (arrows, Enter, Esc, typing) are handled by the component that has the focus;
- window shortcuts (Enter = default button, Esc = cancel) keep working even when the focus is inside a component.

To explicitly give the focus to a component's content (for example after opening a search panel):

    uo_editeur.of_focus_webview()

## 6.5 Exporting the rendering as an image #

A reminder from the shared foundation: every component can be exported exactly as displayed.

    uo_pivot.of_save_as_png("C:\temp\tableau.png")
    uo_pivot.of_save_as_jpg("C:\temp\tableau.jpg")

Handy for attaching a chart to an email, feeding a report, or documenting a user incident.

## 6.6 Dialog boxes and notifications #

Two services that require no control on the window:

    // Themed modal dialog box (synchronous return)
    n_pbt_messagebox lnv_mb

    lnv_mb = create n_pbt_messagebox
    lnv_mb.is_title   = "Delete"
    lnv_mb.is_message = "Permanently delete [b]12 files[/b]?"
    lnv_mb.is_icon    = lnv_mb.ICON_QUESTION
    lnv_mb.of_add_button("Delete", /*default*/ false, /*cancel*/ false)
    lnv_mb.of_add_button("Cancel",   /*default*/ true,  /*cancel*/ true)

    if lnv_mb.of_show(Handle(this)) = 1 then of_supprimer()
    destroy lnv_mb

    // Non-blocking "toast" notification in a screen corner
    n_pbt_toaster lnv_toast

    lnv_toast = create n_pbt_toaster
    lnv_toast.is_title = "Import complete"
    lnv_toast.is_text  = "1,240 rows imported."
    lnv_toast.is_kind  = lnv_toast.KIND_SUCCESS
    lnv_toast.of_show()
    destroy lnv_toast

Full details: messagebox and toaster.

## 6.7 Non-visual utility objects #

Shipped in the same PBL, with no webview and no rendering: they are simple wrappers around the DLL. Create them, use them, destroy them.

### Base64 encoding — `u_pbt_base64` #

    u_pbt_base64 lnv_b64
    string ls_encode

    lnv_b64 = create u_pbt_base64
    ls_encode = lnv_b64.of_encode("Hello")       // text -> UTF-8 -> Base64
    ls_texte  = lnv_b64.of_decode(ls_encode)     // "" if the input is invalid
    destroy lnv_b64

### Hashes — `u_pbt_hash` #

    u_pbt_hash lnv_hash

    lnv_hash = create u_pbt_hash
    ls_empreinte = lnv_hash.of_sha256("password")                // lowercase hex
    ls_fichier   = lnv_hash.of_file("SHA256", "C:\livraison.zip")
    destroy lnv_hash

Method | Purpose
--- | ---
`of_string (string as_algo, string as_texte)` | Hash of a string — `MD5`, `SHA1`, `SHA256`, `SHA384`, `SHA512`
`of_file (string as_algo, string as_chemin)` | Hash of a file's contents (`""` if unreadable)
`of_md5` · `of_sha1` · `of_sha256` · `of_sha512` | Shorthands

### Regular expressions — `u_pbt_regex` #

ECMAScript syntax, which PowerScript does not provide.

    u_pbt_regex lnv_re
    string ls_trouves[]

    lnv_re = create u_pbt_regex

    if lnv_re.of_is_match("^[\w.]+@[\w.]+\.\w{2,}$", ls_email) then …

    ls_annee = lnv_re.of_match("(\d{4})-(\d{2})-(\d{2})", ls_date, /*group*/ 1)
    ls_propre = lnv_re.of_replace("\s+", ls_saisie, " ")
    ll_nb = lnv_re.of_match_all("[A-Z]{2}\d{6}", ls_texte, ls_trouves)

    destroy lnv_re

Method | Purpose
--- | ---
`of_is_match (pattern, text [, ab_ignore_case | al_flags])` | Does the text match?
`of_match (pattern, text, ai_groupe [, al_flags])` | First match (`0` = whole match, `1..` = capturing group)
`of_match_all (pattern, text [, ai_groupe, al_flags], ref as_res[])` | All matches; returns the count
`of_replace (pattern, text, replacement)` | Replaces every occurrence (`$1`… allowed)

Options can be combined: `FLAG_IGNORE_CASE` (1), `FLAG_SINGLELINE` (2, `.` also matches line breaks).

### Unique identifiers — `u_pbt_guid` #

    u_pbt_guid lnv_guid

    lnv_guid = create u_pbt_guid
    ls_id = lnv_guid.of_new()          // 1b4e28ba-2fa1-11d2-883f-0016d3cca427
    ls_id = lnv_guid.of_new_braces()   // {1b4e28ba-...}
    ls_id = lnv_guid.of_new_plain()    // 1b4e28ba2fa111d2883f0016d3cca427
    destroy lnv_guid

### ZIP archives — `u_pbt_zip` #

    u_pbt_zip lnv_zip

    lnv_zip = create u_pbt_zip
    lnv_zip.of_compress("C:\temp\livraison.zip", "C:\appli\export")   // file OR folder
    lnv_zip.of_extract("C:\temp\livraison.zip",  "C:\appli\import")   // folder created if missing
    destroy lnv_zip

Both methods return a `boolean`.

## 6.8 Library information #

    // DLL version
    string ls_version

    ls_version = Space(32)
    PBT_GetVersion(ls_version, 32)

Global function | Purpose
--- | ---
`PBT_GetVersion (ref string, long)` | Library version
`PBT_CheckRuntime (ref string, long)` | Version of the installed WebView2 runtime (`≤ 0` = missing)
`PBT_GetLastErrorMessage (ref string, long)` | Last error message for the process
`PBT_LicenseStatus ( )` | License state — see License

## 6.9 Printing #

Every component knows how to print itself, as displayed, with no intermediate DataWindow.

    // Silent: a PDF on disk, nothing to click
    uo_grille.of_print_to_pdf("C:\etats\ventes.pdf")

    // Landscape, for a wide grid
    uo_grille.of_print_to_pdf(/*fichier*/ "C:\etats\ventes.pdf", /*paysage*/ true)

    // With a dialog: the user picks their printer and sees the preview
    uo_grille.of_print()

    // Straight to the system print dialog
    uo_grille.of_print(/*dialogue systeme*/ true)

Method | Purpose
--- | ---
`of_print_to_pdf (string as_path)` · `of_print_to_pdf (string, boolean ab_landscape)` | Writes a PDF without showing anything. Returns only once the file is written (`0` = done)
`of_print ( )` · `of_print (boolean ab_system_dialog)` | Opens the print dialog. Returns as soon as the dialog is up: what the user does with it is theirs

> ⚠️ What is printed is what is RENDERED. A virtualized grid prints only the rows it holds: for a complete report, first switch to a layout that shows them all (pagination), or export the data rather than the rendering.

Return code `-6` means the WebView2 runtime is too old to print (1.0.1108 for the PDF, 1.0.1587 for the dialog). Everything else follows the common return codes.

← Language and RTL · Contents · FAQ →