# tab — `u_pbt_tab` #

← Référence des composants · Sommaire du guide

> Onglets modernes hébergeant de vrais contrôles PowerBuilder : bandeau repositionnable, icônes par onglet, onglets fermables, réordonnancement à la souris.

> ▶ Le voir en vrai — Application de démonstration, tuile Tab : l'aperçu, le code qui le produit et cette page, côte à côte.

## En bref #

Userobject | `u_pbt_tab`
Classe d'items | `n_pbt_tab_page` (une page = un onglet)
Sert à | Remplacer un `tab` PowerBuilder par un bandeau thémé, tout en conservant vos écrans existants comme pages
Options opt-in | `ib_reorderable`

L'argument clé : une page n'est pas une maquette HTML, c'est un `dragobject` PowerBuilder — un userobject, une DataWindow, un groupe de contrôles, voire un autre composant PBToolboxAI. Le composant se charge du positionnement, du redimensionnement et de l'affichage/masquage au changement d'onglet. Voir Héberger de vrais contrôles PowerBuilder.

## Démarrage rapide #

    // event open de la fenetre : chaque page est un controle PB deja pose sur la fenetre
    uo_onglets.of_add_page("clients",  "Clients",  uo_page_clients)
    uo_onglets.of_add_page("factures", "Factures", uo_page_factures, /*fermable*/ true)

    // Une icone par onglet, posee sur le handle de la page
    uo_onglets.of_item("clients").is_icon  = "mono:img\clients.svg"
    uo_onglets.of_item("factures").is_icon = "mono:img\invoice.svg"

    uo_onglets.of_select_page("clients")

    // event ue_selection_changed de uo_onglets : (string as_from_id, string as_id)
    of_charger_page(as_id)

## Propriétés #

Propriété | Type | Défaut | Rôle
--- | --- | --- | ---
`is_position` | string | `"top"` | Placement du bandeau : `top`, `bottom` (bande horizontale), `start`, `end` (bande verticale). Constantes `POSITION_TOP`, `POSITION_BOTTOM`, `POSITION_START`, `POSITION_END`. `START`/`END` sont logiques et suivent le sens d'écriture
`is_overflow_mode` | string | `"menu"` | Ce que fait une bande trop petite pour ses onglets. `menu` (défaut) garde chaque onglet lisible : ceux qui ne tiennent plus sortent de la bande et le bouton `···` les liste tous, l'onglet actif restant toujours affiché. `compact` les garde tous dans la bande et les laisse se réduire, jusqu'à la place que demande un texte tronqué. Constantes `OVERFLOW_MENU`, `OVERFLOW_COMPACT`
`ib_reorderable` | boolean | `false` | Opt-in : l'utilisateur peut réordonner les onglets en faisant glisser leur en-tête à la souris (déclenche `ue_tab_reordered`)
`ib_veto_selection` | boolean | `true` | Demander avant de changer d'onglet actif (déclenche `ue_selection_changing`, qui peut refuser). Actif par défaut : ne rien scripter laisse toujours passer. Mettez-le à `false` pour supprimer l'aller-retour vers PowerBuilder (~35 ms) là où il se verrait
`ib_veto_close` | boolean | `true` | Demander avant de fermer un onglet fermable (déclenche `ue_tab_closing`, qui peut refuser). Actif par défaut, même règle
`ib_context_menu` | boolean | `true` | Menu contextuel intégré sur un en-tête d'onglet : Fermer, Fermer les autres, Fermer à droite, Fermer tout, grisés quand ils n'ont pas de cible. Actif par défaut. Le clic droit sélectionne d'abord l'onglet visé : si l'application refuse le changement, aucun menu ne s'ouvre. Chaque fermeture passe par `ue_tab_closing`, une question par page : celle qui détient du travail non enregistré refuse seule et reste ouverte pendant que ses voisines partent. À `false` pour afficher votre propre menu depuis `ue_rclicked`
`is_theme_style` | string | `fluent` | Style visuel du composant (constantes `THEME_STYLE_*`)
`is_theme_mode` | string | `light` | Variante claire ou sombre (constantes `THEME_MODE_*`)
`il_theme_accent` | long | `-1` | Couleur d'accent de ce composant (`-1` = accent du thème)
`is_tooltip` | string | `""` | Info-bulle simple affichée au survol du composant
`is_super_tooltip_title` | string | `""` | Titre de l'info-bulle enrichie (prend le pas sur `is_tooltip`)
`is_super_tooltip_text` | string | `""` | Texte de l'info-bulle enrichie (balisage riche accepté)
`is_super_tooltip_image` | string | `""` | Image de l'info-bulle enrichie

### Propriétés d'une page — `n_pbt_tab_page` #

Obtenues par `of_item(id)`, elles se modifient à chaud, sans reconstruire le bandeau.

Propriété | Type | Défaut | Rôle
--- | --- | --- | ---
`is_title` | string | `""` | Libellé de l'onglet. Accepte le balisage riche
`is_icon` | string | `""` | Icône de l'onglet (formes acceptées : chemin, `mono:`, `tint:`, ressource de DLL). Vide = aucune icône
`ib_enabled` | boolean | `true` | `false` grise l'onglet et bloque sa sélection par l'utilisateur
`ib_visible` | boolean | `true` | `false` masque l'onglet sans supprimer la page ; masquer l'onglet actif en active un voisin
`ib_closable` | boolean | `false` | Croix de fermeture sur cet onglet. Rarement connu au moment de l ajout : il passe a `true` des que le document tenu par la page est enregistre, a `false` pendant qu un traitement y tourne
`is_shortcut` | string | `""` | Raccourci clavier qui active cet onglet : `"Ctrl+2"`, `"Alt+F"`, `"F6"`… Il répond où que soit l'utilisateur dans la fenêtre, page hébergée comprise — le raccourci est tenu par la DLL, pas par la barre d'onglets. Sélectionner ainsi est un clic : `ue_selection_changing` est posée, `ue_selection_changed` suit. `""` le retire
`is_tooltip` | string | `""` | Info-bulle simple affichée au survol de l'item
`is_super_tooltip_title` | string | `""` | Titre de l'info-bulle enrichie de l'item (prend le pas sur `is_tooltip`)
`is_super_tooltip_text` | string | `""` | Texte de l'info-bulle enrichie de l'item (balisage riche accepté)
`is_super_tooltip_image` | string | `""` | Image de l'info-bulle enrichie de l'item

## Méthodes #

Méthode | Rôle
--- | ---
`of_add_page (string as_id, string as_title, dragobject ado_page)` | Ajoute un onglet non fermable, sans icône, et héberge le contrôle
`of_add_page (string as_id, string as_title, dragobject ado_page, boolean ab_closable)` | Idem, avec croix de fermeture si `ab_closable`
`of_add_page (string as_id, string as_title, dragobject ado_page, boolean ab_closable, string as_icon)` | Idem, avec l'icône posée dans la foulée (l'onglet s'affiche d'emblée complet)
`of_insert_page (string as_id, string as_title, dragobject ado_page, integer ai_index)` | Ajoute la page puis la déplace à la position `ai_index`
`of_insert_page (string as_id, string as_title, dragobject ado_page, boolean ab_closable, string as_icon, integer ai_index)` | Idem, en disant ce que dit `of_add_page` : si l’onglet se ferme, et son icône
`of_move_page (string as_id, integer ai_index)` | Déplace un onglet existant ; sa page hébergée et la sélection sont conservées
`of_select_page (string as_id)` | Active un onglet — strictement équivalent à un clic utilisateur (passe par `ue_selection_changing` puis `ue_selection_changed`)
`of_remove_page (string as_id)` | Retire l'onglet et rend le contrôle à sa fenêtre d'origine, masqué. Le contrôle reste utilisable et peut être hébergé ailleurs
`of_selected_key ( )` | Identifiant de l'onglet actif, `""` si aucun
`of_get_layout ( )` | Relit la disposition courante en JSON : l'ordre des onglets et la visibilité de chacun. À stocker (fichier, base, registre) et à rendre par `of_set_layout` au démarrage suivant. La même paire porte les mêmes noms sur tous les composants réorganisables
`of_set_layout (string as_layout_json)` | Restaure une disposition lue par `of_get_layout` ou reçue par `ue_layout_changed`. Ce que la disposition ne nomme pas garde sa place à la fin : une disposition d'hier ne doit pas faire disparaître ce qui a été ajouté depuis. L'appliquer ne déclenche aucun event — c'est vous qui l'avez fournie
`of_item (string as_id)` | Handle `n_pbt_tab_page` de la page (créé au premier accès)
`of_refresh_page (string as_id)` | Rafraîchit le rendu d'une page construite hors écran, sans scintillement
`of_relayout ( )` | Republie la zone de page pour que le contrôle hébergé soit repositionné (utile après un affichage différé)
`of_preload_icons (string as_icons[])` | Préchauffe un lot d'icônes au démarrage : un onglet ouvert plus tard affiche son icône instantanément
`of_reset ( )` | Vide le bandeau : chaque page hébergée est rendue à sa fenêtre d'origine, puis le composant revient à son état neuf
`of_set_redraw (boolean)` | Regroupe une rafale de modifications en un seul rendu
`of_save_as_png (string)` · `of_save_as_jpg (string)` | Exporte le rendu en image

> `of_add_page` renvoie un code négatif si l'identifiant est vide, déjà utilisé, ou si le contrôle passé n'est pas valide.

Quand il y a trop d'onglets — ils se réduisent pour tenir dans la bande, mais pas en dessous d'une largeur lisible : l'icône et la croix de fermeture prennent leur place en plus du titre, jamais à sa place. Ceux qui ne tiennent plus sortent de la bande, et un bouton `···` apparaît à son extrémité : il ouvre la liste de tous les onglets visibles, l'actif coché, et le choix bascule dessus. L'onglet actif reste toujours affiché. La bande ne défile pas — ce bouton est le chemin d'accès aux onglets sortis du cadre. Rien à coder, le comportement est automatique.

## Événements #

Événement | Déclenché quand
--- | ---
`ue_selection_changed (string as_from_id, string as_id)` | L'onglet actif a changé — par clic ou par `of_select_page`. Mêmes arguments que `ue_selection_changing` : la question et son issue se lisent pareil, et `as_from_id` est la page quittée
`ue_tab_closed (string as_id)` | L'utilisateur clique la croix d'un onglet fermable. L'onglet est déjà retiré et la page rendue à sa fenêtre
`ue_tab_reordered (string as_id, integer ai_index)` | L'utilisateur a fini de faire glisser un en-tête. `ai_index` est la nouvelle position, à partir de 1
`ue_layout_changed (string as_layout_json)` | L'arrangement a changé — l'utilisateur a réorganisé quelque chose, ou votre propre code l'a fait. Porte la disposition entière, pas seulement ce qui a bougé : la conserver tient en une affectation
`ue_selection_changing (string as_from_id, string as_id)` → boolean | Annulable, posé avant que l'onglet actif ne change. Levé par défaut ; `ib_veto_selection = false` le supprime. Renvoyez `false` pour garder l'utilisateur sur sa page
`ue_tab_closing (string as_id)` → boolean | Annulable, posé avant la fermeture d'un onglet fermable. Levé par défaut ; `ib_veto_close = false` le supprime. Nommé pour faire la paire avec `ue_tab_closed` : closing / closed. Renvoyez `false` pour garder l'onglet (et sa page hébergée) ouvert
`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 : le composant reste vide
`ue_bg_color (long al_color)` | Le composant a calculé sa couleur de fond de thème ; l'userobject l'a déjà adoptée (`backcolor`)

## Exemples #

### Un classeur de pages métier #

    // event open : les userobjects de page sont deja poses sur la fenetre
    uo_onglets.of_set_redraw(false)

    uo_onglets.of_add_page(/*id*/ "identite", /*titre*/ "Identite",  /*page*/ uo_identite)
    uo_onglets.of_add_page(/*id*/ "adresses", /*titre*/ "Adresses",  /*page*/ uo_adresses)
    uo_onglets.of_add_page(/*id*/ "compta",   /*titre*/ "Comptabilite", /*page*/ uo_compta)

    uo_onglets.of_item("identite").is_icon = "mono:img\user.svg"
    uo_onglets.of_item("adresses").is_icon = "mono:img\map.svg"
    uo_onglets.of_item("compta").is_icon   = "mono:img\euro.svg"

    uo_onglets.of_set_redraw(true)
    uo_onglets.of_select_page("identite")

### Documents fermables, ouverts à la demande #

    // Ouvrir un document dans un nouvel onglet fermable
    uo_onglets.of_add_page(/*id*/ ls_cle, /*titre*/ ls_libelle, /*page*/ uo_editeur, &
                           /*fermable*/ true, /*icone*/ "mono:img\doc.svg")
    uo_onglets.of_select_page(ls_cle)

    // event ue_tab_closed de uo_onglets : (string as_id)
    // L'onglet est deja retire ; il reste a liberer vos donnees metier.
    of_liberer_document(as_id)

### Réordonnancement à la souris #

    uo_onglets.ib_reorderable = true

    // event ue_tab_reordered de uo_onglets : (string as_id, integer ai_index)
    // ai_index = nouvelle position, a partir de 1 : on memorise l'ordre choisi.
    of_enregistrer_ordre(as_id, ai_index)

### Bandeau vertical #

    // Bande d'onglets sur le cote gauche, utile quand les libelles sont longs
    uo_onglets.is_position = uo_onglets.POSITION_START

### Griser ou masquer un onglet selon les droits #

    // Le chemin passe toujours par le handle de la page
    uo_onglets.of_item("compta").ib_enabled = of_a_le_droit("compta")
    uo_onglets.of_item("audit").ib_visible  = gb_mode_expert

### Réagir à la sélection #

    // event ue_selection_changed de uo_onglets : (string as_from_id, string as_id)
    choose case as_id
        case "identite" ; uo_identite.of_rafraichir()
        case "adresses" ; uo_adresses.of_rafraichir()
        case "compta"   ; uo_compta.of_rafraichir()
    end choose

### Refuser un changement d'onglet ou une fermeture #

    // Les deux questions sont posees par DEFAUT : rien a activer. Ces lignes font
    // l'inverse, elles les suppriment quand l'arbitrage ne sert pas.
    uo_onglets.ib_veto_selection = false
    uo_onglets.ib_veto_close     = false

    // event ue_selection_changing de uo_onglets : (string as_from_id, string as_id)
    // Renvoyer FALSE garde l'utilisateur sur la page qu'il quitte.
    if of_page_modifiee(as_from_id) then
        if MessageBox("Modifications", "Quitter sans enregistrer ?", Question!, YesNo!) = 2 then
            return false
        end if
    end if
    return true

    // event ue_tab_closing de uo_onglets : (string as_id)
    // Renvoyer FALSE garde l'onglet et sa page hebergee ouverts.
    return not of_page_modifiee(as_id)

### Repartir d'un bandeau vide #

    // of_reset rend CHAQUE page hebergee a sa fenetre d'origine avant de vider
    uo_onglets.of_reset()
    uo_onglets.of_add_page("accueil", "Accueil", uo_accueil)

## Bonnes pratiques #

- Les contrôles hébergés doivent exister avant l'appel : posez-les sur la fenêtre à la conception, le composant se charge de les masquer et de les repositionner.
- Chargez les pages une fois à l'`open`, puis pilotez uniquement la sélection : c'est plus rapide qu'ajouter et retirer des pages en continu.
- Appelez `of_preload_icons` au démarrage si vos onglets portent des icônes : sans cela, la première ouverture d'un onglet masqué montre un court délai avant l'icône.
- Les deux questions sont posées par défaut : un événement laissé vide autorise toujours l'action, vous n'avez rien à faire. Coupez-les avec `ib_veto_selection` / `ib_veto_close` à `false` si l'arbitrage ne vous sert pas, car chaque question coûte un aller-retour vers PowerBuilder.
- Un contrôle hébergé est une fenêtre native : il se dessine au-dessus de la couche web. Aucun effet visuel du composant ne peut passer par-dessus lui.
- N'oubliez pas `of_reset()` avant de reconstruire un bandeau : sans lui, réutiliser un identifiant de page échoue et les contrôles précédents restent affichés.
- Pour des zones redimensionnables et détachables plutôt que des pages exclusives, préférez dockcontainer.

← Référence des composants · Sommaire du guide