# listbar — `u_pbt_listbar` #

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

> Barre de navigation latérale : des sections repliables en accordéon contenant des entrées à icône, avec bandeau d'accent sur l'entrée courante et repli en rail d'icônes.

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

## En bref #

Userobject | `u_pbt_listbar`
Classes d'items | `n_pbt_listbar_section` (section) → `n_pbt_listbar_item` (entrée)
Sert à | Remplacer un menu latéral bricolé à coups de boutons par une navigation structurée, thémée et repliable
Options opt-in | `ib_auto_width`, `ib_reorderable`

C'est le seul composant de la bibliothèque à publier `ib_auto_width` : sa largeur naturelle a un sens, puisque le rail replié est nettement plus étroit que la barre déployée. Le cas courant est d'ailleurs déjà couvert sans rien activer — `ib_collapsed = true` rétrécit la barre au rail, et lui rend sa largeur au dépliage.

## Démarrage rapide #

    // event open de la fenetre
    n_pbt_listbar_section lnv_section

    lnv_section = uo_nav.of_add_header(/*id*/ "nav", /*titre*/ "Navigation")
    lnv_section.of_add_item(/*id*/ "accueil",   /*libelle*/ "Accueil",   /*icone*/ "mono:img\home.svg")
    lnv_section.of_add_item(/*id*/ "documents", /*libelle*/ "Documents", /*icone*/ "mono:img\doc.svg")
    lnv_section.of_add_item(/*id*/ "recherche", /*libelle*/ "Recherche", /*icone*/ "mono:img\find.svg")

    uo_nav.of_add_header(/*id*/ "config", /*titre*/ "Parametres") &
          .of_add_item(/*id*/ "preferences", /*libelle*/ "Preferences", /*icone*/ "mono:img\gear.svg")

    // La selection se pose sur l'ENTREE, par son chemin complet
    uo_nav.of_select_item("nav", "accueil")

    // event ue_selection_changed de uo_nav : (string as_from_section, string as_from_id,
    //                                       string as_section, string as_id)
    choose case as_section + "/" + as_id
        case "nav/accueil"       ; of_ouvrir_accueil()
        case "nav/documents"     ; of_ouvrir_documents()
        case "config/preferences"; of_ouvrir_preferences()
    end choose

## Deux niveaux, un chemin obligatoire #

Un identifiant d'entrée n'est unique que dans sa section : il n'existe donc aucun raccourci vers l'entrée. Tout accès passe par la section, ce qui rend l'écriture non ambiguë — voir Hiérarchies.

    // Composant  ->  section        ->  entree      ->  propriete
    uo_nav        .of_section("nav") .of_item("docs") .is_text = "Documents"

Les events portent eux aussi le chemin complet, et l'entrée quittée avec : `ue_selection_changed(as_from_section, as_from_id, as_section, as_id)`.

## Propriétés #

Propriété | Type | Défaut | Rôle
--- | --- | --- | ---
`ib_collapsed` | boolean | `false` | `true` replie la barre en rail d'icônes : les libellés disparaissent, les icônes restent cliquables
`ib_auto_width` | boolean | `false` | Opt-in : idem pour la largeur, y compris dépliée (la barre se cale sur le libellé le plus long). Le repli en rail, lui, se rétrécit déjà tout seul ; `ue_auto_width` suit dans les deux cas
`ib_reorderable` | boolean | `false` | Opt-in : l'utilisateur peut déplacer une entrée à la souris. Le déplacement reste dans sa section — un identifiant d'entrée n'est unique que là, donc traverser risquerait deux clés identiques (déclenche `ue_item_reordered`)
`ib_veto_selection` | boolean | `true` | Demander avant que la sélection ne se déplace (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 — navigation clavier, sélection déplacée en boucle
`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 section — `n_pbt_listbar_section` #

Propriété | Type | Défaut | Rôle
--- | --- | --- | ---
`is_title` | string | `""` | Titre de la section. Accepte le balisage riche. Un titre vide n'affiche aucune ligne d'en-tête : la section devient un simple regroupement invisible
`ib_collapsed` | boolean | `false` | Accordéon : `true` replie les entrées de cette section. L'en-tête reste visible, son chevron pivote

### Propriétés d'une entrée — `n_pbt_listbar_item` #

Propriété | Type | Défaut | Rôle
--- | --- | --- | ---
`is_text` | string | `""` | Libellé de l'entrée, modifiable à chaud sans reconstruire la barre. Accepte le balisage riche
`is_image` | string | `""` | Icône, modifiable à chaud (formes acceptées : chemin, `mono:`, `tint:`, ressource de DLL)
`ib_enabled` | boolean | `true` | `false` grise l'entrée et bloque son clic
`ib_visible` | boolean | `true` | `false` masque l'entrée sans la retirer de la barre
`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 #

### Sur le composant #

Méthode | Rôle
--- | ---
`of_add_header (string as_id, string as_text)` | Ajoute une section et renvoie son handle `n_pbt_listbar_section`, sur lequel s'enchaînent les ajouts d'entrées
`of_section (string as_id)` | Handle d'une section existante (créé au premier accès)
`of_insert_item (string as_section, string as_id, string as_text, integer ai_index)` | Insère une entrée à la position `ai_index` dans sa section
`of_insert_item (string as_section, string as_id, string as_text, string as_image, integer ai_index)` | Idem, avec l’icône de l’entrée : `of_add_item` en prend une, insérer doit pouvoir en prendre une aussi
`of_move_item (string as_section, string as_id, integer ai_index)` | Déplace une entrée existante dans sa section, en conservant son état
`of_remove_item (string as_section, string as_id)` | Retire une entrée désignée par son couple section / identifiant
`of_clear ( )` | Vide la barre : toutes les sections et toutes les entrées
`of_select_item (string as_section, string as_id)` | Sélectionne une entrée — strictement équivalent à un clic de l'utilisateur : `ue_selection_changing` est posée d'abord, puis `ue_selection_changed` annonce le déplacement
`of_get_layout ( )` | Relit la disposition courante en JSON : les sections dans l'ordre, chacune avec ses entrées dans l'ordre et son état replié. À 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_clear_selection ( )` | Ne laisse aucune entrée sélectionnée. Annoncé comme n'importe quel autre déplacement
`of_selected_key ( )` | Identifiant de l'entrée sélectionnée, `""` si aucune. Toujours l'entrée courante : un clic et `of_select_item` repassent tous deux par `ue_selection_changed`
`of_selected_section ( )` | Section qui porte l'entrée sélectionnée — un identifiant d'entrée n'est unique que dans sa section, c'est donc le couple qui désigne la sélection. `""` si aucune
`of_reset ( )` | Vide la barre puis ramène le composant à 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

### Sur une section — `n_pbt_listbar_section` #

Méthode | Rôle
--- | ---
`of_add_item (string as_id, string as_label, string as_icon_file)` | Ajoute une entrée normale à cette section et renvoie son handle
`of_item (string as_id)` | Handle d'une entrée de cette section (créé au premier accès)

## Événements #

Événement | Déclenché quand
--- | ---
`ue_selection_changed (string as_from_section, string as_from_id, string as_section, string as_id)` | La sélection a bougé — par clic ou par `of_select_item`. Mêmes arguments que `ue_selection_changing` : la question et son issue se lisent pareil, et le couple `as_from_*` désigne l'entrée quittée (vide si aucune)
`ue_section_toggled (string as_section, boolean ab_collapsed)` | L'utilisateur plie ou déplie une section par son en-tête
`ue_item_reordered (string as_section, string as_id, integer ai_index)` | L'utilisateur a fini de faire glisser une entrée. `ai_index` est son nouveau rang dans sa section, à partir de 1. Conservez cet ordre pour rendre à l'utilisateur sa barre telle qu'il l'a laissée
`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_section, string as_from_id, string as_section, string as_id)` → boolean | Annulable, posé avant que la sélection ne se déplace. Levé par défaut ; `ib_veto_selection = false` le supprime. Renvoyez `false` pour garder l'utilisateur où il est
`ue_auto_width (long al_width)` | Le composant a recalculé sa largeur idéale — nécessite `ib_auto_width = true`
`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 #

### Menu latéral complet #

    n_pbt_listbar_section lnv_dossiers, lnv_outils

    uo_nav.of_set_redraw(false)

    lnv_dossiers = uo_nav.of_add_header("dossiers", "Dossiers")
    lnv_dossiers.of_add_item("recents",  "Recents",  "mono:img\clock.svg")
    lnv_dossiers.of_add_item("clients",  "Clients",  "mono:img\user.svg")
    lnv_dossiers.of_add_item("archives", "Archives", "mono:img\box.svg")

    lnv_outils = uo_nav.of_add_header("outils", "Outils")
    lnv_outils.of_add_item("import", "Importer", "mono:img\import.svg")
    lnv_outils.of_add_item("export", "Exporter", "mono:img\export.svg")

    uo_nav.of_set_redraw(true)
    uo_nav.of_select_item("dossiers", "recents")

### Rail repliable qui libère la place #

    // Replier en rail d'icones : la barre se retrecit d'elle-meme (et reprend sa
    // largeur quand vous la depliez).
    uo_nav.ib_collapsed = true

    // event ue_auto_width de uo_nav : (long al_width)
    // La barre vient d'adopter sa largeur ideale : recaler ce qui se trouve a droite.
    uo_contenu.x     = uo_nav.x + al_width
    uo_contenu.width = parent.width - uo_contenu.x

### Accordéon : replier une section #

    // Replier la section des archives, rarement utilisee
    uo_nav.of_section("archives").ib_collapsed = true

    // event ue_section_toggled de uo_nav : (string as_section, boolean ab_collapsed)
    of_enregistrer_preference("section_" + as_section, String(ab_collapsed))

### Réagir à la navigation #

    // event ue_selection_changed de uo_nav : (string as_from_section, string as_from_id,
    //                                       string as_section, string as_id)
    // La section fait partie de la cle : deux sections peuvent avoir une entree "liste".
    choose case as_section
        case "dossiers" ; of_ouvrir_dossier(as_id)
        case "outils"   ; of_lancer_outil(as_id)
    end choose

### Refuser un changement de sélection #

    // La question est posee par DEFAUT : rien a activer. Cette ligne fait l'inverse,
    // elle la supprime quand l'arbitrage ne sert pas et que le cout se verrait.
    uo_nav.ib_veto_selection = false

    // event ue_selection_changing de uo_nav :
    //   (string as_from_section, string as_from_id, string as_section, string as_id)
    // Renvoyer FALSE garde l'utilisateur sur l'entree qu'il quitte.
    if of_saisie_en_cours(as_from_section, as_from_id) then
        MessageBox("Saisie", "Terminez la fiche en cours avant de naviguer.")
        return false
    end if
    return true

### Mettre une entrée à jour à chaud #

    n_pbt_listbar_item lnv_entree

    // Le chemin complet est obligatoire : composant -> section -> entree
    lnv_entree = uo_nav.of_section("dossiers").of_item("recents")
    lnv_entree.is_text  = "Recents (12)"
    lnv_entree.is_image = "mono:img\clock_full.svg"

    // Griser ou masquer selon les droits, sans reconstruire la barre
    uo_nav.of_section("outils").of_item("import").ib_enabled = of_a_le_droit("import")
    uo_nav.of_section("outils").of_item("export").ib_visible = gb_mode_expert

### Sélection pilotée depuis le code #

    // Poser la selection ailleurs : le bandeau d'accent suit
    uo_nav.of_select_item("dossiers", "clients")

    // Ou l'effacer completement
    uo_nav.of_clear_selection()

### Reconstruire la barre #

    // of_clear vide sections et entrees ; of_reset y ajoute les defauts du composant
    uo_nav.of_clear()
    of_construire_menu_selon_profil()

## Bonnes pratiques #

- Conservez le handle de section renvoyé par `of_add_header` dans une variable locale : c'est plus lisible que de rappeler `of_section(...)` à chaque entrée.
- Utilisez `mono:` pour les icônes : elles se recolorent avec le thème, clair comme sombre, et restent lisibles une fois la barre repliée en rail.
- Un titre de section vide sert de regroupement discret : les entrées sont espacées sans qu'aucune ligne d'en-tête n'apparaisse.
- `ib_collapsed` rétrécit l'userobject au rail, mais il ne déplace pas vos autres contrôles : traitez `ue_auto_width` pour occuper la place libérée, sinon elle reste vide.
- En mode rail, seules les icônes subsistent : renseignez une info-bulle sur chaque entrée pour que les libellés restent accessibles.
- La question est posée par défaut : un `ue_selection_changing` laissé vide autorise toujours le déplacement, vous n'avez rien à faire. Coupez-la avec `ib_veto_selection = false` là où le clic est répété — navigation clavier, sélection pilotée en boucle — car chaque question coûte un aller-retour vers PowerBuilder.
- `ib_enabled = false` vaut mieux que `ib_visible = false` quand l'entrée redeviendra disponible : le menu ne change pas de forme sous les yeux de l'utilisateur.

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