# listbar — `u_pbt_listbar` #

← Referencia de componentes · Índice de la guía

> Barra de navegación lateral: secciones plegables en acordeón que contienen entradas con icono, con banda de acento sobre la entrada actual y plegado en riel de iconos.

> ▶ Verlo en vivo — Aplicación de demostración, mosaico Listbar: la vista previa, el código que lo genera y esta página, uno al lado del otro.

## De un vistazo #

Userobject | `u_pbt_listbar`
Clases de items | `n_pbt_listbar_section` (sección) → `n_pbt_listbar_item` (entrada)
Sirve para | Sustituir un menú lateral improvisado a base de botones por una navegación estructurada, con tema y plegable
Opciones opt-in | `ib_auto_width`, `ib_reorderable`

Es el único componente de la biblioteca que publica `ib_auto_width`: su anchura natural sí tiene sentido, ya que el riel plegado es bastante más estrecho que la barra desplegada. Además, el caso habitual ya está cubierto sin activar nada — `ib_collapsed = true` estrecha la barra hasta el riel, y le devuelve su anchura al desplegarla.

## Inicio rápido #

    // evento open de la ventana
    n_pbt_listbar_section lnv_section

    lnv_section = uo_nav.of_add_header(/*id*/ "nav", /*titulo*/ "Navegación")
    lnv_section.of_add_item(/*id*/ "accueil",   /*etiqueta*/ "Inicio",   /*icono*/ "mono:img\home.svg")
    lnv_section.of_add_item(/*id*/ "documents", /*etiqueta*/ "Documentos", /*icono*/ "mono:img\doc.svg")
    lnv_section.of_add_item(/*id*/ "recherche", /*etiqueta*/ "Búsqueda", /*icono*/ "mono:img\find.svg")

    uo_nav.of_add_header(/*id*/ "config", /*titulo*/ "Parámetros") &
          .of_add_item(/*id*/ "preferences", /*etiqueta*/ "Preferencias", /*icono*/ "mono:img\gear.svg")

    // La seleccion se aplica a la ENTRADA, por su ruta completa
    uo_nav.of_select_item("nav", "accueil")

    // evento 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

## Dos niveles, una ruta obligatoria #

Un identificador de entrada solo es único dentro de su sección: no existe, por tanto, ningún atajo hacia la entrada. Todo acceso pasa por la sección, lo que hace que la escritura no sea ambigua — véase Jerarquías.

    // Componente ->  seccion        ->  entrada     ->  propiedad
    uo_nav        .of_section("nav") .of_item("docs") .is_text = "Documents"

Los eventos también llevan la ruta completa, y con ella la entrada abandonada: `ue_selection_changed(as_from_section, as_from_id, as_section, as_id)`.

## Propiedades #

Propiedad | Tipo | Predeterminado | Función
--- | --- | --- | ---
`ib_collapsed` | boolean | `false` | `true` pliega la barra en un riel de iconos: las etiquetas desaparecen, los iconos siguen siendo pulsables
`ib_auto_width` | boolean | `false` | Opt-in: lo mismo para la anchura, incluida la desplegada (la barra se ajusta a la etiqueta más larga). El plegado en riel, por su parte, ya se estrecha por sí solo; `ue_auto_width` lo acompaña en ambos casos
`ib_reorderable` | boolean | `false` | Opt-in: el usuario puede desplazar una entrada con el ratón. El desplazamiento permanece dentro de su sección — un identificador de entrada solo es único allí, así que cruzar arriesgaría dos claves idénticas (activa `ue_item_reordered`)
`ib_veto_selection` | boolean | `true` | Preguntar antes de que la selección se desplace (activa `ue_selection_changing`, que puede rechazarla). Activo de forma predeterminada: no programar nada deja pasar siempre. Póngalo a `false` para suprimir la ida y vuelta hasta PowerBuilder (~35 ms) donde se notaría — navegación por teclado, selección desplazada en bucle
`is_theme_style` | string | `fluent` | Estilo visual del componente (constantes `THEME_STYLE_*`)
`is_theme_mode` | string | `light` | Variante clara u oscura (constantes `THEME_MODE_*`)
`il_theme_accent` | long | `-1` | Color de acento de este componente (`-1` = acento del tema)
`is_tooltip` | string | `""` | Tooltip simple mostrado al pasar el ratón por el componente
`is_super_tooltip_title` | string | `""` | Título del tooltip enriquecido (prevalece sobre `is_tooltip`)
`is_super_tooltip_text` | string | `""` | Texto del tooltip enriquecido (se acepta el marcado enriquecido)
`is_super_tooltip_image` | string | `""` | Imagen del tooltip enriquecido

### Propiedades de una sección — `n_pbt_listbar_section` #

Propiedad | Tipo | Predeterminado | Función
--- | --- | --- | ---
`is_title` | string | `""` | Título de la sección. Acepta el texto enriquecido con etiquetas. Un título vacío no muestra ninguna fila de encabezado: la sección se convierte en una simple agrupación invisible
`ib_collapsed` | boolean | `false` | Acordeón: `true` pliega las entradas de esta sección. El encabezado permanece visible y su galón gira

### Propiedades de una entrada — `n_pbt_listbar_item` #

Propiedad | Tipo | Predeterminado | Función
--- | --- | --- | ---
`is_text` | string | `""` | Etiqueta de la entrada, modificable en caliente sin reconstruir la barra. Acepta el texto enriquecido con etiquetas
`is_image` | string | `""` | Icono, modificable en caliente (formas aceptadas: ruta, `mono:`, `tint:`, recurso de DLL)
`ib_enabled` | boolean | `true` | `false` atenúa la entrada y bloquea su clic
`ib_visible` | boolean | `true` | `false` oculta la entrada sin retirarla de la barra
`is_tooltip` | string | `""` | Tooltip simple mostrado al pasar el ratón por el item
`is_super_tooltip_title` | string | `""` | Título del tooltip enriquecido del item (prevalece sobre `is_tooltip`)
`is_super_tooltip_text` | string | `""` | Texto del tooltip enriquecido del item (se acepta el marcado enriquecido)
`is_super_tooltip_image` | string | `""` | Imagen del tooltip enriquecido del item

## Métodos #

### En el componente #

Método | Función
--- | ---
`of_add_header (string as_id, string as_text)` | Añade una sección y devuelve su handle `n_pbt_listbar_section`, sobre el que se encadenan las adiciones de entradas
`of_section (string as_id)` | Handle de una sección existente (creado en el primer acceso)
`of_insert_item (string as_section, string as_id, string as_text, integer ai_index)` | Inserta una entrada en la posición `ai_index` dentro de su sección
`of_insert_item (string as_section, string as_id, string as_text, string as_image, integer ai_index)` | Ídem, con el icono de la entrada: `of_add_item` acepta uno, por lo que insertar también debe poder aceptarlo
`of_move_item (string as_section, string as_id, integer ai_index)` | Desplaza una entrada existente dentro de su sección, conservando su estado
`of_remove_item (string as_section, string as_id)` | Retira una entrada designada por su par sección / identificador
`of_clear ( )` | Vacía la barra: todas las secciones y todas las entradas
`of_select_item (string as_section, string as_id)` | Selecciona una entrada — estrictamente equivalente a un clic del usuario: primero se plantea `ue_selection_changing`, luego `ue_selection_changed` anuncia el desplazamiento
`of_get_layout ( )` | Vuelve a leer la disposición actual en JSON: las secciones en orden, cada una con sus entradas en orden y su estado plegado. Guárdela (archivo, base de datos, registro) y devuélvala con `of_set_layout` en el siguiente arranque. El mismo par lleva los mismos nombres en todos los componentes reorganizables
`of_set_layout (string as_layout_json)` | Restaura una disposición leída con `of_get_layout` o recibida con `ue_layout_changed`. Lo que la disposición no nombra conserva su lugar al final: una disposición de ayer no debe hacer desaparecer lo añadido desde entonces. Aplicarla no genera ningún event — usted la ha proporcionado
`of_clear_selection ( )` | No deja ninguna entrada seleccionada. Se anuncia como cualquier otro desplazamiento
`of_selected_key ( )` | Identificador de la entrada seleccionada, `""` si no hay ninguna. Siempre la actual: un clic y `of_select_item` pasan ambos por `ue_selection_changed`
`of_selected_section ( )` | Sección que contiene la entrada seleccionada — un identificador de entrada solo es único dentro de su sección, así que es el par lo que designa la selección. `""` si no hay ninguna
`of_reset ( )` | Vacía la barra y luego devuelve el componente a su estado inicial
`of_set_redraw (boolean)` | Agrupa una ráfaga de modificaciones en una única representación
`of_save_as_png (string)` · `of_save_as_jpg (string)` | Exporta la representación como imagen

### En una sección — `n_pbt_listbar_section` #

Método | Función
--- | ---
`of_add_item (string as_id, string as_label, string as_icon_file)` | Añade una entrada normal a esta sección y devuelve su handle
`of_item (string as_id)` | Handle de una entrada de esta sección (creado en el primer acceso)

## Eventos #

Evento | Se activa cuando
--- | ---
`ue_selection_changed (string as_from_section, string as_from_id, string as_section, string as_id)` | La selección se ha desplazado — por clic o mediante `of_select_item`. Mismos argumentos que `ue_selection_changing`: la pregunta y su resultado se leen igual, y el par `as_from_*` designa la entrada abandonada (vacío si no hay ninguna)
`ue_section_toggled (string as_section, boolean ab_collapsed)` | El usuario pliega o despliega una sección desde su encabezado
`ue_item_reordered (string as_section, string as_id, integer ai_index)` | El usuario ha terminado de arrastrar una entrada. `ai_index` es su nuevo rango dentro de su sección, a partir de 1. Conserve ese orden para devolver al usuario su barra tal como la dejó
`ue_layout_changed (string as_layout_json)` | La disposición ha cambiado — el usuario ha reorganizado algo, o su propio código. Lleva la disposición entera, no solo lo que se movió: conservarla es una sola asignación
`ue_selection_changing (string as_from_section, string as_from_id, string as_section, string as_id)` → boolean | Cancelable, se plantea antes de que la selección se desplace. Lanzado de forma predeterminada; `ib_veto_selection = false` lo suprime. Devuelva `false` para mantener al usuario donde está
`ue_auto_width (long al_width)` | El componente ha recalculado su anchura ideal — requiere `ib_auto_width = true`
`ue_ready ( )` | El componente ha terminado de cargarse; todo lo enviado antes se ha reproducido
`ue_runtime_missing ( )` | El runtime WebView2 está ausente: el componente permanece vacío
`ue_bg_color (long al_color)` | El componente ha calculado el color de fondo de su tema; el userobject ya lo ha adoptado (`backcolor`)

## Ejemplos #

### Menú lateral completo #

    n_pbt_listbar_section lnv_dossiers, lnv_outils

    uo_nav.of_set_redraw(false)

    lnv_dossiers = uo_nav.of_add_header("dossiers", "Carpetas")
    lnv_dossiers.of_add_item("recents",  "Recientes",  "mono:img\clock.svg")
    lnv_dossiers.of_add_item("clients",  "Clientes",  "mono:img\user.svg")
    lnv_dossiers.of_add_item("archives", "Archivos", "mono:img\box.svg")

    lnv_outils = uo_nav.of_add_header("outils", "Herramientas")
    lnv_outils.of_add_item("import", "Importar", "mono:img\import.svg")
    lnv_outils.of_add_item("export", "Exportar", "mono:img\export.svg")

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

### Riel plegable que libera espacio #

    // Plegar en riel de iconos : la barra se estrecha por si misma (y recupera su
    // anchura cuando usted la despliega).
    uo_nav.ib_collapsed = true

    // evento ue_auto_width de uo_nav : (long al_width)
    // La barra acaba de adoptar su anchura ideal : recolocar lo que se encuentra a su derecha.
    uo_contenu.x     = uo_nav.x + al_width
    uo_contenu.width = parent.width - uo_contenu.x

### Acordeón: plegar una sección #

    // Plegar la seccion de archivos, poco utilizada
    uo_nav.of_section("archives").ib_collapsed = true

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

### Reaccionar a la navegación #

    // evento ue_selection_changed de uo_nav : (string as_from_section, string as_from_id,
    //                                          string as_section, string as_id)
    // La seccion forma parte de la clave : dos secciones pueden tener una entrada "liste".
    choose case as_section
        case "dossiers" ; of_ouvrir_dossier(as_id)
        case "outils"   ; of_lancer_outil(as_id)
    end choose

### Rechazar un cambio de selección #

    // La pregunta se plantea de forma PREDETERMINADA: nada que activar. Esta linea
    // hace lo contrario, la suprime cuando arbitrar no sirve y el coste se notaria.
    uo_nav.ib_veto_selection = false

    // evento ue_selection_changing de uo_nav :
    //   (string as_from_section, string as_from_id, string as_section, string as_id)
    // Devolver FALSE mantiene al usuario en la entrada que abandona.
    if of_saisie_en_cours(as_from_section, as_from_id) then
        MessageBox("Entrada", "Termine la ficha en curso antes de navegar.")
        return false
    end if
    return true

### Actualizar una entrada en caliente #

    n_pbt_listbar_item lnv_entree

    // La ruta completa es obligatoria : componente -> seccion -> entrada
    lnv_entree = uo_nav.of_section("dossiers").of_item("recents")
    lnv_entree.is_text  = "Recientes (12)"
    lnv_entree.is_image = "mono:img\clock_full.svg"

    // Atenuar u ocultar segun los permisos, sin reconstruir la barra
    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

### Selección controlada desde el código #

    // Colocar la seleccion en otro sitio : la banda de acento la sigue
    uo_nav.of_select_item("dossiers", "clients")

    // O borrarla por completo
    uo_nav.of_clear_selection()

### Reconstruir la barra #

    // of_clear vacia secciones y entradas ; of_reset anade ademas los valores predeterminados del componente
    uo_nav.of_clear()
    of_construire_menu_selon_profil()

## Buenas prácticas #

- Conserve el handle de sección devuelto por `of_add_header` en una variable local: resulta más legible que volver a llamar a `of_section(...)` en cada entrada.
- Utilice `mono:` para los iconos: se recolorean con el tema, tanto claro como oscuro, y siguen siendo legibles una vez plegada la barra en riel.
- Un título de sección vacío sirve como agrupación discreta: las entradas quedan espaciadas sin que aparezca ninguna fila de encabezado.
- `ib_collapsed` estrecha el userobject hasta el riel, pero no desplaza sus demás controles: trate `ue_auto_width` para ocupar el espacio liberado, de lo contrario queda vacío.
- En modo riel solo quedan los iconos: indique un tooltip en cada entrada para que las etiquetas sigan siendo accesibles.
- La pregunta se plantea de forma predeterminada: un `ue_selection_changing` dejado vacío autoriza siempre el desplazamiento, no tiene nada que hacer. Desactívela con `ib_veto_selection = false` donde el clic se repite — navegación por teclado, selección pilotada en bucle — porque cada pregunta cuesta una ida y vuelta hasta PowerBuilder.
- `ib_enabled = false` es preferible a `ib_visible = false` cuando la entrada vaya a volver a estar disponible: el menú no cambia de forma ante los ojos del usuario.

← Referencia de componentes · Índice de la guía