PBToolboxAI v5 ← Site

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(/*on*/ false)
… vingt affectations et of_add_* …
uo_grid.of_set_redraw(/*on*/ true)     // UN seul repaint

Appelez toujours les deux (le true final n'est pas optionnel). Une lecture faite pendant le gel — of_path, une propriété d'item, « cette adresse est-elle libre ? » — voit ce que la rafale vient de poser : ce qui attend est appliqué au modèle avant la réponse, sans rien redessiner ; le dessin reste unique, à la reprise. Et un événement que le composant lève pendant une lecture est livré après elle, jamais au milieu de l'appel qui lisait.

Par leur nom. Trois fonctions publiques de tous les composants visuels doublent les propriétés typées : of_set_property(nom, valeur) pose n'importe quelle propriété (stretch ou is_stretch, en majuscules ou non). La valeur, en texte, est typée et vérifiée — par le préfixe du nom quand il en a un, sinon par ce que le composant tient sous cette clé : un booléen accepte true, TRUE, 1, yes, on (et leurs contraires), un nombre la virgule comme le point quels que soient les paramètres régionaux ("2,5" vaut 2,5), une couleur le long RGB, -1 ou #rrggbb ; une valeur vide vaut le neutre du type (false, 0, -1 pour une couleur). Elle rend 0 une fois appliquée, -5 sur un nom vide, sur un nom que le composant ne connaît pas ou sur une valeur que son type ne peut pas tenir — rien n'est envoyé alors, et of_get_last_error() dit pourquoi. of_get_property(nom) la lit en direct, en texte (un nom inconnu rend "", et of_get_last_error() le dit), et of_component_name() rend le nom du composant embarqué (video, ribbon…) quelle que soit la classe de votre objet. C'est ce qu'une configuration enregistrée, ou un outil générique, rejoue sans connaître le composant.


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 se fait sur le composant et rend un code — 0 une fois ajouté, -5 sur une adresse refusée (vide, déjà prise, parent introuvable). Le handle de l'élément créé s'obtient ensuite par le getter de son niveau :

// Ajouter la page : 0 = ajoutee, -5 = adresse refusee
uo_tab.of_add_page(/*key*/ "clients", /*title*/ "Clients", /*page*/ uo_page_clients)

// Le getter du niveau rend son handle : poser l'icone
uo_tab.of_page(/*key*/ "clients").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_item(/*keys*/ "main/save").ib_enabled = false
uo_tab.of_page(/*key*/ "clients").is_title = "Clients (128)"

Compter, parcourir, vérifier #

Trois questions reviennent sans cesse, et elles ont la même réponse à chaque niveau — sur le composant comme sur n'importe quel handle :

FonctionRépond
of_count ( ) → longCombien d'éléments à ce niveau en ce moment — ce que le composant montre, pas ce qu'on lui a envoyé en dernier
of_keys_at ( long al_index ) → stringL'adresse de l'élément de rang al_index (à partir de 1), ou "" au-delà de l'un ou l'autre bout. L'adresse complète, pas la clé nue : elle repart telle quelle dans n'importe quelle méthode qui en prend une, ce qui est tout l'intérêt de parcourir un niveau qu'on n'a pas construit
of_has ( string as_key ) → booleanCet élément existe-t-il ? Un handle est toujours rendu, même pour une clé inconnue : c'est la seule façon de poser la question
// Local variables
long ll_groups, ll_tiles, li
string ls_group

// Combien de groupes, et combien de tuiles dans le premier
ll_groups = uo_tilesbox.of_count()
ls_group  = uo_tilesbox.of_keys_at(/*index*/ 1)
ll_tiles  = uo_tilesbox.of_count(/*keys*/ ls_group)

// Parcourir ce qu'on n'a pas construit soi-meme
for li = 1 to uo_tilesbox.of_count()
	ls_group = uo_tilesbox.of_keys_at(/*index*/ li)
	if uo_tilesbox.of_has(/*keys*/ ls_group + "/report") then
		uo_tilesbox.of_tile(/*keys*/ ls_group + "/report").ib_enabled = false
	end if
next

Une feuille répond 0, "" et false : « je ne contiens rien » est une réponse vraie, pas une réponse vide. Vous pouvez donc descendre un arbre sans tester à chaque étage.

Sur un handle, of_child_keys(ref string as_keys[]) rend en une seule lecture les adresses de tout ce qu'il contient — les mêmes que of_keys_at(1..n) — et leur nombre : parcourir un niveau de mille éléments coûte une lecture au lieu de mille.

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_item(/*keys*/ "home/clipboard/paste").ib_enabled = false

Les events portent eux aussi le chemin complet :

// event ue_clicked de uo_toolbar : (string as_keys)
choose case as_keys
    case "main/save" ; of_save()
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_invoked (as_keys) pour la listbar, ue_tile_clicked (as_keys) pour le tilesbox, ue_clicked (as_keys) pour la toolbar…

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

Un double-clic lève d'abord deux clics. Sur un élément qui s'ouvre au double-clic, il arrive comme deux événements de clic suivis de l'événement d'ouverture — ue_item_clicked, ue_item_clicked, ue_item_opened sur le carousel ; ue_card_clicked deux fois puis ue_card_opened sur le kanban — là où un contrôle PowerBuilder natif lève Clicked une fois puis DoubleClicked. Attendre de savoir si un second clic vient retarderait chaque clic simple : ne faites dans l'événement de clic que ce qui supporte d'être fait deux fois (sélectionner, afficher un détail), et réservez à l'événement d'ouverture ce qui ne doit arriver qu'une fois.

Un event annulable — une question, ue_…_changing, posée avant d'agir — reçoit toujours sa réponse : si son script s'arrête sur une erreur d'exécution (une base indisponible, un objet nul), le composant ne reste pas bloqué dans l'attente ; la réponse par défaut part quand même, et l'erreur suit son cours habituel.


3.3 Events communs à tous les composants #

EventDé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_header.ib_auto_height = true
// event ue_auto_height de uo_header
il_header_height = 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_button.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.

Nom accessible — is_accessible_name, is_accessible_description #

Ce qu'un lecteur d'écran (Narrateur, NVDA) dit du composant entier, posé par l'application — comme l'AccessibleName et l'AccessibleDescription d'un contrôle PowerBuilder. is_accessible_name est le nom (texte brut ; un balisage riche est lu sans ses balises), is_accessible_description la description plus longue lue après lui. Vides (le défaut), le composant garde le nom qu'il se donne lui-même. Les deux se relisent, passent par of_set_property, et of_reset les vide. Présentes sur tous les composants visuels.

// ce que dit un lecteur d'ecran quand le focus entre dans le graphique
uo_chart.is_accessible_name = "Sales by month"
uo_chart.is_accessible_description = "Twelve bars, January to December, in euros"

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_save.of_register_shortcut(/*chord*/ "Ctrl+S")
uo_refresh.of_register_shortcut(/*chord*/ "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émentFormes acceptées
ModificateursCtrl (ou Control), Alt, Shift — combinables, dans n'importe quel ordre
Toucheune lettre A–Z, un chiffre 0–9, F1 … F24, Enter (ou Return), Escape (ou Esc), Delete (ou Del), Insert, Home, End, PageUp, PageDown ; avec Ctrl ou Alt seulement : + (ou Plus, le zoom Ctrl++), -, ,, ., Left Right Up Down, Space, Tab, Backspace, les touches du pavé numérique Num 0 … Num 9, Num +, Num -, Num *, Num /, Num . (comme un menu PowerBuilder les écrit : elles ne répondent qu'au pavé, alors que Ctrl+7 répond aux deux 7)

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 le plus proche du focus clavier l'emporte : celui qui a le focus lui-même, sinon celui qui est dans la même feuille MDI, le même panneau, la même page d'onglet que le contrôle focalisé — le raccourci d'une zone active n'est jamais éclipsé par un voisin ;
  2. à égalité entre deux composants (aucun n'est plus proche que l'autre), aucun ne le reçoit : la touche reste à la fenêtre ;
  3. entre deux raccourcis d'un même composant, le premier enregistré gagne ;
  4. les lettres Alt des libellés (mnémoniques d'un bouton) ne sont consultées qu'après tous les raccourcis explicites.

La même règle vaut pour Entrée et Échap : avec deux feuilles MDI qui ont chacune leur bouton par défaut, Entrée va à celui de la feuille où l'on tape. Entrée reste en revanche à un CommandButton PB qui a le focus, et Entrée ou Échap à une DropDownListBox ouverte.

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 ; sur un composant à plusieurs niveaux, son adresse (une toolbar prend "main/save", ou "main/export/pdf" pour une entrée de menu) :

uo_bar.of_register_shortcut(/*chord*/ "Ctrl+N", /*keys*/ "new")
uo_bar.of_register_shortcut(/*chord*/ "Ctrl+P", /*keys*/ "print")

Retirer les raccourcis #

uo_bar.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.

MembreEffet
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é :

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 #

MembreEffet
of_is_created ( ) → booleanLe composant natif existe (runtime présent, hôte valide)
of_is_ready ( ) → booleanLe contenu web est chargé (ue_ready déjà levé)
of_get_last_error ( ) → stringPourquoi le dernier appel de ce composant a échoué, après un retour < 0 : un nom refusé par of_set_property, ou un appel à la DLL de ce composant — sa raison est lue au moment de l'échec. Jamais le message qu'un autre composant a laissé dans la DLL ; vide après un appel réussi

Codes de retour des méthodes of_* :

RetourSignification
≥ 0OK (appliqué, ou mis en file)
-2Composant non créé (runtime absent, hôte invalide)
-4Opération échouée (capture, écriture de fichier…)
-5Argument invalide (identifiant vide, valeur hors bornes)
-6Runtime 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_tiles.of_save_as_png(/*path*/ "C:\temp\accueil.png")
uo_tiles.of_save_as_jpg(/*path*/ "C:\temp\accueil.jpg")

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

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 #


← Prise en main · Sommaire · Thèmes →