PBToolboxAI v4 ← 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 — voit ce que la rafale vient de poser : ce qui attend est appliqué avant la réponse, et le gel continue ensuite.

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, la valeur en texte : true/false, chiffres, le long RGB d'une couleur ; rend 0 une fois appliquée, -5 sur un nom vide), of_get_property(nom) la lit en direct, en texte, 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 renvoie le handle de l'élément créé :

n_pbt_tab_page lnv_page

uo_tab.of_add_page(/*key*/ "clients", /*title*/ "Clients", /*page*/ uo_page_clients)
lnv_page = uo_tab.of_page(/*key*/ "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_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
// 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.

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_selected (as_section, as_key) pour la listbar, ue_tile_clicked (as_group, as_key) pour le tilesbox, ue_clicked (as_bar, as_key) pour la toolbar…

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


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.


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

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 ( ) → stringDernier message d'erreur détaillé de la DLL, après un retour < 0

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 →