# 3. Socle commun `u_pbt_base` #

← Prise en main · Sommaire · Thèmes →

Tous les composants visuels héritent de `u_pbt_base`, qui fournit le cycle de vie, le moteur de propriétés, le transport vers le composant web et la gestion des erreurs. Les propriétés elles-mêmes — y compris le thème et les info-bulles — sont publiées par chaque composant : la page du composant en donne la liste complète. Vous n'utilisez jamais `u_pbt_base` directement — vous posez un composant concret — mais tout ce qui suit est disponible partout.

## 3.1 Le moteur de propriétés #

### Affecter #

Toute valeur pilotable est une variable d'instance publique, affectée directement :

    uo_progress.id_value   = 42.5
    uo_progress.is_label   = "Import en cours…"
    uo_progress.ib_animated = true

Le préfixe hongrois indique le type : `is_` string, `ib_` boolean, `ii_` integer, `il_` long (souvent une couleur `RGB()`), `id_` double.

Il n'existe pas de `of_set_xxx` scalaire : une propriété se pose par affectation. Restent des méthodes les ajouts, retraits et actions (`of_add_*`, `of_remove_*`, `of_select_*`, `of_reset`, `of_set_layout`…).

### Relire #

La lecture renvoie la dernière valeur posée (cache côté PowerBuilder) :

    if uo_progress.id_value >= 100 then …

Un composant web ne peut pas être interrogé de façon synchrone : ce cache est donc rafraîchi par les events. Chaque fois que le composant déplace lui-même une propriété — l'utilisateur suit un lien, zoome à la molette, replie le ruban, saisit du texte — l'event qui vous prévient met la propriété à jour au passage. La relire donne alors l'état réel, et la nouvelle valeur est déjà posée quand votre code d'event s'exécute.

La règle vaut aussi pour les items : après un clic de l'utilisateur, `of_item(...)` relit ce qui est à l'écran — l'entrée sélectionnée, la section repliée, le bouton coché.

Une propriété qu'aucun event n'accompagne, elle, reste sur la dernière valeur que vous avez posée.

### Grouper les modifications #

Une rafale d'affectations provoque autant de rendus. `of_set_redraw` les fusionne en un seul :

    uo_grid.of_set_redraw(false)
    … vingt affectations et of_add_* …
    uo_grid.of_set_redraw(true)     // UN seul repaint

Appelez toujours les deux (le `true` final n'est pas optionnel).

## 3.2 Les items #

Un composant à contenu (onglets, boutons, panneaux, tuiles, sections…) expose ses éléments par des handles typés, obtenus depuis le composant ou depuis leur parent.

### Ajouter #

L'ajout renvoie le handle de l'élément créé :

    n_pbt_tab_page lnv_page

    uo_tab.of_add_page("clients", "Clients", uo_page_clients)
    lnv_page = uo_tab.of_item("clients")
    lnv_page.is_icon = "img\clients.png"

### Retrouver et modifier #

`of_item(id)` — ou la fabrique du niveau concerné — renvoie le handle d'un élément existant ; ses propriétés se posent comme celles d'un composant :

    uo_toolbar.of_bar("main").of_item("save").ib_enabled = false
    uo_tab.of_item("clients").is_title = "Clients (128)"

### Hiérarchies : l'identifiant n'est unique que dans son parent #

Un composant à plusieurs niveaux n'expose pas de raccourci vers la feuille : le chemin est obligatoire, ce qui garantit qu'aucun identifiant n'est ambigu.

    // Ruban : onglet > groupe > controle > entree de menu
    uo_ribbon.of_tab("home").of_group("clipboard").of_item("paste").ib_enabled = false

Les events portent eux aussi le chemin complet :

    // event ue_clicked de uo_toolbar : (string as_bar, string as_id)
    choose case as_bar + "/" + as_id
        case "main/save" ; of_enregistrer()
    end choose

### Events d'items #

Il n'existe pas d'events d'items génériques au niveau de l'ancêtre : un identifiant de feuille seul serait ambigu dès que les items sont imbriqués (une barre d'outils a plusieurs barres, un tilesbox plusieurs groupes…). Chaque composant déclare donc ses events d'items, avec le chemin complet : `ue_item_selected (as_section, as_id)` pour la listbar, `ue_tile_clicked (as_group, as_id)` pour le tilesbox, `ue_clicked (as_bar, as_id)` pour la toolbar…

Reportez-vous à la page du composant : c'est là que figure la liste exacte.

## 3.3 Events communs à tous les composants #

Event | Déclenché quand
--- | ---
`ue_ready ( )` | Le composant a fini de charger ; tout ce qui a été envoyé avant a été rejoué
`ue_runtime_missing ( )` | Le runtime WebView2 est absent — voir Installation
`ue_bg_color (long al_color)` | Le composant a calculé sa couleur de fond de thème ; l'userobject a déjà adopté cette couleur (`backcolor`), à vous d'accorder la fenêtre si besoin

> Les commandes envoyées avant `ue_ready` ne sont pas perdues : elles sont mises en file et rejouées dans l'ordre. Vous pouvez donc tout configurer dès le `constructor` ou l'`open`.

    // event ue_bg_color : accorder la fenetre au fond du composant
    parent.backcolor = al_color

## 3.4 Propriétés et events optionnels (opt-in) #

Certaines fonctionnalités ne sont pas activées par défaut : elles ne sont publiées que par les composants où elles ont un sens, et il faut les demander.

### Hauteur automatique — `ib_auto_height` #

Le composant mesure sa hauteur idéale et redimensionne l'userobject ; l'event `ue_auto_height(al_height)` vous permet de repositionner les contrôles voisins.

    uo_entete.ib_auto_height = true

    // event ue_auto_height de uo_entete
    il_hauteur_entete = al_height
    of_relayout()          // repositionne le contenu en dessous

Publiée par : `picture` et `statictext`.

Les bandes ne publient pas cette propriété — leur hauteur est intrinsèque. `ribbon` et `toolbar` ne défilent pas verticalement : une hauteur figée ne peut produire que du vide sous la bande ou du contenu tronqué (ruban replié, barre d'outils passée sur deux rangées…). Elles s'ajustent donc toujours, sans rien à activer, et publient quand même `ue_auto_height` pour que vous repositionniez ce qui se trouve en dessous.

### Largeur automatique — `ib_auto_width` #

Même principe pour la largeur. Publiée uniquement par `listbar`, seul composant dont la largeur naturelle a un sens.

Une listbar repliée en rail se rétrécit d'elle-même et rend la largeur en se dépliant : `ib_auto_width` ne vous sert que si vous voulez suivre aussi la largeur dépliée (la barre se cale alors sur le libellé le plus long).

### Événements souris ambiants — `ib_track_mouse` #

Les events souris de haute fréquence sont coupés à la source : sans abonnement, le composant ne les émet pas (rien ne traverse le pont vers PowerBuilder).

    uo_bouton.ib_track_mouse = true    // active ue_mouse_enter / ue_mouse_leave / ue_rclicked

Publiés par : `button`, `picture`, `statictext`.

Les events discrets (clic, sélection, menu, drop…) sont toujours émis, sans abonnement.

## 3.5 Les raccourcis clavier #

Un raccourci déclenche un composant où que se trouve le focus dans la fenêtre — l'utilisateur n'a pas à revenir sur le bouton pour l'actionner. Tout composant visuel en accepte, sans rien à activer.

    uo_enregistrer.of_register_shortcut("Ctrl+S")
    uo_actualiser.of_register_shortcut("F5")

### Écrire un accord de touches #

L'accord est une chaîne libre, normalisée par la bibliothèque : la casse, les espaces et l'ordre des modificateurs n'ont aucune importance. `"Ctrl+Shift+S"`, `"ctrl + shift + s"` et `"SHIFT+CTRL+S"` désignent le même raccourci — impossible d'en enregistrer deux variantes par mégarde.

Élément | Formes acceptées
--- | ---
Modificateurs | `Ctrl` (ou `Control`), `Alt`, `Shift` — combinables, dans n'importe quel ordre
Touche | une lettre `A`–`Z`, un chiffre `0`–`9`, `F1` … `F24`, `Enter` (ou `Return`), `Escape` (ou `Esc`), `Delete` (ou `Del`), `Insert`, `Home`, `End`, `PageUp`, `PageDown`

Cette liste est exhaustive : une touche absente (Tab, Espace, une touche de pavé numérique, un caractère de ponctuation) ne déclenche aucun raccourci.

Une touche seule est un accord valable (`"F5"`). Une chaîne vide retire le raccourci du composant.

> `"Enter"` et `"Escape"` seules ne s'enregistrent pas comme raccourcis : ces deux touches restent réservées au bouton par défaut et au bouton d'annulation (`ib_default` / `ib_cancel` du button). Combinées à un modificateur, elles redeviennent des accords ordinaires (`"Ctrl+Enter"`).

### Qui gagne en cas de conflit #

Deux composants peuvent demander le même accord — c'est fréquent quand une fenêtre héberge plusieurs zones qui ont chacune leur « Enregistrer ». L'arbitrage se fait dans cet ordre :

1. le composant qui a le focus clavier l'emporte sur tous les autres : le raccourci d'une zone active n'est jamais éclipsé par un voisin ;
2. à défaut, le premier enregistré gagne.

Seuls les composants visibles et actifs de la fenêtre au premier plan concourent. Ré-enregistrer un accord sur un composant qui en avait déjà un le remplace sans changer son rang : reconfigurer une fenêtre ne rebat pas les priorités.

### Raccourcis d'items #

La surcharge à deux arguments rattache l'accord à un item du composant plutôt qu'au composant entier — le second argument est l'identifiant de l'item :

    uo_barre.of_register_shortcut(/*accord*/ "Ctrl+N", /*item*/ "nouveau")
    uo_barre.of_register_shortcut(/*accord*/ "Ctrl+P", /*item*/ "imprimer")

### Retirer les raccourcis #

    uo_barre.of_clear_shortcuts()      // composant ET items

`of_reset()` et la destruction du composant appellent `of_clear_shortcuts()` pour vous : un composant disparu ne garde jamais un accord réservé.

### La touche Alt #

Alt seule n'est pas capturée : elle donne le focus au ruban, qui lève ses keytips (voir ribbon). La bibliothèque n'intercepte donc pas les frappes qui suivent — c'est le ruban qui les lit, comme si l'utilisateur l'avait cliqué. Échap ou un second Alt rendent le focus au contrôle quitté. Si aucun ruban de la fenêtre ne déclare de keytip, Alt garde son comportement Windows habituel.

Membre | Effet
--- | ---
`of_register_shortcut (string as_chord)` | Déclare un raccourci pour le composant ; une chaîne vide le retire
`of_register_shortcut (string as_chord, string as_key)` | Déclare un raccourci pour un item, désigné par son identifiant
`of_clear_shortcuts ( )` | Retire tous les raccourcis du composant, items compris

## 3.6 Remettre un composant à zéro : `of_reset()` #

`of_reset()` ramène le composant à son état neuf, comme s'il venait d'être chargé :

- le contenu est vidé (items, pages, panneaux…) ;
- chaque propriété revient à son défaut (mise en forme, couleurs, mode, libellés) ;
- les surcharges de style et les info-bulles posées sur l'instance sont annulées ;
- le cache de propriétés côté PowerBuilder est vidé (les relectures repartent des défauts) ;
- l'état natif est également remis à plat (menu contextuel, mode d'affichage…).

    uo_grid.of_reset()          // repartir d'une grille vierge
    // ... puis reconstruire

> ⚠️ Réutiliser une instance pour afficher autre chose sans appeler `of_reset()` conserve l'état précédent (une couleur, un mode, une hauteur automatique). C'est la cause la plus fréquente d'un « reste d'affichage » inexpliqué.

## 3.7 Diagnostic #

Membre | Effet
--- | ---
`of_is_created ( )` → boolean | Le composant natif existe (runtime présent, hôte valide)
`of_is_ready ( )` → boolean | Le contenu web est chargé (`ue_ready` déjà levé)
`of_get_last_error ( )` → string | Dernier message d'erreur détaillé de la DLL, après un retour `< 0`

Codes de retour des méthodes `of_*` :

Retour | Signification
--- | ---
`≥ 0` | OK (appliqué, ou mis en file)
`-2` | Composant non créé (runtime absent, hôte invalide)
`-4` | Opération échouée (capture, écriture de fichier…)
`-5` | Argument invalide (identifiant vide, valeur hors bornes)
`-6` | Runtime WebView2 trop ancien pour la fonction demandée (impression)

## 3.8 Exporter le rendu en image #

Tout composant sait s'exporter en image, telle qu'affichée :

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

Utile pour un rapport, une pièce jointe d'e-mail ou une trace d'incident. Le composant doit être créé et son contenu chargé.

## 3.9 Cycle de vie #

1. Construction : le webview est créé dès la construction de l'userobject — indispensable pour l'hébergement (onglets, panneaux dockables) : un webview créé après le reparentage de son HWND ne s'affiche pas.
2. File d'attente : vos commandes sont mises en file tant que `ue_ready` n'est pas levé.
3. Prêt : `ue_ready` ; la file est rejouée dans l'ordre.
4. Redimensionnement : automatique, le composant suit la taille de l'userobject.
5. Destruction : à la fermeture de la fenêtre ; le webview est libéré, aucun processus orphelin.

Appelez `PBT_Warmup()` une fois au démarrage de l'application pour que ce cycle soit imperceptible (Installation).

## 3.10 Bonnes pratiques #

- Posez le thème par défaut et la langue dans l'objet application, avant l'ouverture de la première fenêtre : les composants n'ont alors aucun flash de style.
- Encadrez toute construction volumineuse par `of_set_redraw(false)` / `of_set_redraw(true)`.
- Appelez `of_reset()` avant de réutiliser une instance pour un autre contenu.
- Ne bloquez pas le thread UI par une longue boucle PowerScript entre la création et l'affichage : l'initialisation du webview a besoin de la boucle de messages (voir FAQ).

← Prise en main · Sommaire · Thèmes →

Pour l'imprimer plutôt que l'exporter, voir Imprimer.