PBToolboxAI v5 ← Site

3. Base común u_pbt_base #

← Primeros pasos · Índice · Temas →


Todos los componentes visuales heredan de u_pbt_base, que aporta el ciclo de vida, el motor de propiedades, el transporte hacia el componente web y la gestión de errores. Las propiedades en sí — tema y tooltips incluidos — las publica cada componente: su propia página ofrece la lista completa. Nunca se utiliza u_pbt_base directamente — se coloca un componente concreto —, pero todo lo que sigue está disponible en todas partes.


3.1 El motor de propiedades #

Asignar #

Todo valor controlable es una variable de instancia pública, asignada directamente:

uo_progress.id_value   = 42.5
uo_progress.is_label   = "Importación en curso…"
uo_progress.ib_animated = true

El prefijo húngaro indica el tipo: is_ string, ib_ boolean, ii_ integer, il_ long (a menudo un color RGB()), id_ double.

No existe ningún of_set_xxx escalar: una propiedad se establece por asignación. Siguen siendo métodos las adiciones, las eliminaciones y las acciones (of_add_*, of_remove_*, of_select_*, of_reset, of_set_layout…).

Releer #

La lectura devuelve el último valor establecido (caché del lado de PowerBuilder):

if uo_progress.id_value >= 100 then …

Un componente web no puede consultarse de forma síncrona: por tanto, esa caché se actualiza mediante los eventos. Cada vez que el componente mueve una propiedad por sí mismo —el usuario sigue un enlace, amplía con la rueda, pliega la cinta, escribe texto—, el evento que le avisa actualiza la propiedad de paso. La relectura devuelve entonces el estado real, y el nuevo valor ya está puesto cuando se ejecuta el código del evento.

Lo mismo ocurre con los ítems: tras un clic del usuario, of_item(...) relee lo que hay en pantalla: la entrada seleccionada, la sección plegada, el botón marcado.

Una propiedad a la que no acompaña ningún evento sí permanece con el último valor que usted estableció.

Agrupar las modificaciones #

Una ráfaga de asignaciones provoca otras tantas representaciones. of_set_redraw las fusiona en una sola:

uo_grid.of_set_redraw(/*on*/ false)
… veinte asignaciones y of_add_* …
uo_grid.of_set_redraw(/*on*/ true)     // UN solo repintado

Llame siempre a ambas (el true final no es opcional). Una lectura hecha durante la congelación — of_path, una propiedad de item, «¿está libre esta dirección?» — ve lo que la ráfaga acaba de poner: lo pendiente se aplica al modelo antes de responder, sin redibujar nada; el dibujo sigue siendo único, al reanudar. Y un evento que el componente lanza durante una lectura se entrega después, nunca en medio de la llamada que leía.

Por su nombre. Tres funciones públicas de todos los componentes visuales complementan las propiedades tipadas: of_set_property(nombre, valor) fija cualquier propiedad (stretch o is_stretch, en mayúsculas o no). El valor, en texto, se tipa y se comprueba — por el prefijo del nombre cuando lo tiene, si no por lo que el componente guarda bajo esa clave: un booleano acepta true, TRUE, 1, yes, on (y sus contrarios), un número la coma igual que el punto, sea cual sea la configuración regional ("2,5" vale 2,5), un color el long RGB, -1 o #rrggbb; un valor vacío es el neutro del tipo (false, 0, -1 para un color). Devuelve 0 una vez aplicada, -5 con un nombre vacío, con un nombre que el componente no conoce o con un valor que su tipo no puede contener — entonces no se envía nada, y of_get_last_error() dice por qué. of_get_property(nombre) la lee en directo, en texto (un nombre desconocido devuelve "", y of_get_last_error() lo dice), y of_component_name() devuelve el nombre del componente incrustado (video, ribbon…) sea cual sea la clase de su objeto. Es lo que una configuración guardada, o una herramienta genérica, reproduce sin conocer el componente.


3.2 Los items #

Un componente con contenido (pestañas, botones, paneles, mosaicos, secciones…) expone sus elementos mediante handles tipados, obtenidos desde el componente o desde su elemento padre.

Añadir #

La adición se hace en el componente y devuelve un código — 0 una vez añadido, -5 ante una dirección rechazada (vacía, ya ocupada, padre desconocido). El handle del nuevo elemento se obtiene después con el getter de su nivel:

// Anadir la pagina : 0 = anadida, -5 = direccion rechazada
uo_tab.of_add_page(/*key*/ "clients", /*title*/ "Clientes", /*page*/ uo_page_clients)

// El getter del nivel devuelve su handle : poner el icono
uo_tab.of_page(/*key*/ "clients").is_icon = "img\clients.png"

Recuperar y modificar #

of_item(id) — o la factoría del nivel correspondiente — devuelve el handle de un elemento existente; sus propiedades se establecen igual que las de un componente:

uo_toolbar.of_item(/*keys*/ "main/save").ib_enabled = false
uo_tab.of_page(/*key*/ "clients").is_title = "Clientes (128)"

Contar, recorrer, comprobar #

Tres preguntas surgen una y otra vez, y tienen la misma respuesta en cada nivel — tanto en el componente como en cualquier handle:

FunciónResponde
of_count ( ) → longCuántos elementos hay en este nivel ahora mismo — lo que el componente muestra, no lo último que se le envió
of_keys_at ( long al_index ) → stringLa dirección del elemento de rango al_index (desde 1), o "" más allá de uno u otro extremo. La dirección completa, no la clave desnuda: vuelve tal cual a cualquier método que acepte una, que es todo el sentido de recorrer un nivel que no se ha construido
of_has ( string as_key ) → boolean¿Existe este elemento? Un handle se devuelve siempre, incluso para una clave desconocida: es la única forma de preguntarlo
// Local variables
long ll_groups, ll_tiles, li
string ls_group

// Cuantos grupos, y cuantas fichas en el primero
ll_groups = uo_tilesbox.of_count()
ls_group  = uo_tilesbox.of_keys_at(/*index*/ 1)
ll_tiles  = uo_tilesbox.of_count(/*keys*/ ls_group)

// Recorrer lo que uno no ha construido
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

Una hoja responde 0, "" y false: «no contengo nada» es una respuesta verdadera, no una vacía. Puede por tanto recorrer un árbol sin comprobar en cada piso.

En un handle, of_child_keys(ref string as_keys[]) devuelve en una sola lectura las direcciones de todo lo que contiene — las mismas que of_keys_at(1..n) — y su número: recorrer un nivel de mil elementos cuesta una lectura en lugar de mil.

Jerarquías: un identificador solo es único dentro de su elemento padre #

Un componente de varios niveles no expone ningún atajo hacia la hoja: la ruta completa es obligatoria, lo que garantiza que ningún identificador resulte ambiguo.

// Cinta de opciones: pestana > grupo > control > entrada de menu
uo_ribbon.of_item(/*keys*/ "home/clipboard/paste").ib_enabled = false

Los eventos llevan asimismo la ruta completa:

// event ue_clicked de uo_toolbar: (string as_keys)
choose case as_keys
    case "main/save" ; of_save()
end choose

Eventos de item #

No existen eventos de item genéricos en el ancestro: un identificador de hoja por sí solo sería ambiguo en cuanto los items se anidan (una toolbar tiene varias barras, un tilesbox varios grupos…). Por eso cada componente declara sus propios eventos de item, con la ruta completa: ue_item_invoked (as_keys) para la listbar, ue_tile_clicked (as_keys) para el tilesbox, ue_clicked (as_keys) para la toolbar…

Consulte la página del componente: ahí figura la lista exacta.

Un doble clic lanza primero dos clics. En un elemento que se abre con doble clic, llega como dos eventos de clic seguidos del evento de apertura — ue_item_clicked, ue_item_clicked, ue_item_opened en el carousel; ue_card_clicked dos veces y luego ue_card_opened en el kanban — donde un control PowerBuilder nativo lanza Clicked una vez y luego DoubleClicked. Esperar a saber si llega un segundo clic retrasaría cada clic simple: en el evento de clic haga solo lo que soporta hacerse dos veces (seleccionar, mostrar un detalle), y reserve para el evento de apertura lo que debe ocurrir una sola vez.

Un evento cancelable — una pregunta, ue_…_changing, hecha antes de actuar — recibe siempre su respuesta: si su script se detiene por un error de ejecución (una base de datos no disponible, un objeto nulo), el componente no se queda bloqueado esperando; la respuesta por defecto sale de todos modos, y el error sigue su curso habitual.


3.3 Eventos comunes a todos los componentes #

EventoSe activa cuando
ue_ready ( )El componente ha terminado de cargarse; todo lo enviado antes se ha reproducido
ue_runtime_missing ( )El runtime WebView2 está ausente — véase Instalación
ue_bg_color (long al_color)El componente ha calculado el color de fondo de su tema; el userobject ya ha adoptado ese color (backcolor), le corresponde a usted ajustar la ventana si es necesario

Los comandos enviados antes de ue_ready no se pierden: se ponen en cola y se reproducen en orden. Por tanto, puede configurarlo todo ya en el constructor o en el open.

// event ue_bg_color: ajustar la ventana al fondo del componente
parent.backcolor = al_color

3.4 Propiedades y eventos opcionales (opt-in) #

Algunas funcionalidades no están activadas de forma predeterminada: solo las publican los componentes en los que tienen sentido, y hay que solicitarlas.

Altura automática — ib_auto_height #

El componente mide su altura ideal y redimensiona el userobject; el evento ue_auto_height(al_height) le permite reposicionar los controles vecinos.

uo_header.ib_auto_height = true
// event ue_auto_height de uo_header
il_header_height = al_height
of_relayout()          // reposiciona el contenido situado debajo

Publicada por: picture y statictext.

Las bandas no publican esta propiedad — su altura es intrínseca. ribbon y toolbar no se desplazan verticalmente: una altura fija solo puede producir un hueco vacío bajo la banda o contenido truncado (cinta de opciones replegada, barra de herramientas repartida en dos filas…). Por eso se ajustan siempre, sin nada que activar, y publican de todos modos ue_auto_height para que usted recoloque lo que se encuentra debajo.

Anchura automática — ib_auto_width #

Mismo principio para la anchura. La publica únicamente listbar, el único componente cuya anchura natural tiene sentido.

Una listbar plegada en riel se estrecha por sí misma y devuelve la anchura al desplegarse: ib_auto_width solo le sirve si quiere seguir también la anchura desplegada (la barra se ajusta entonces a la etiqueta más larga).

Eventos de ratón ambientales — ib_track_mouse #

Los eventos de ratón de alta frecuencia están cortados en origen: sin suscripción, el componente no los emite (nada atraviesa el puente hacia PowerBuilder).

uo_button.ib_track_mouse = true    // activa ue_mouse_enter / ue_mouse_leave / ue_rclicked

Publicados por: button, picture, statictext.

Los eventos discretos (clic, selección, menú, soltar…) se emiten siempre, sin suscripción.

Nombre accesible — is_accessible_name, is_accessible_description #

Lo que un lector de pantalla (Narrador, NVDA) dice del componente entero, puesto por la aplicación — como el AccessibleName y el AccessibleDescription de un control PowerBuilder. is_accessible_name es el nombre (texto plano; un marcado enriquecido se lee sin sus etiquetas), is_accessible_description la descripción más larga leída después. Vacíos (el valor por defecto), el componente conserva el nombre que se da a sí mismo. Ambos se releen, pasan por of_set_property, y of_reset los vacía. Presentes en todos los componentes visuales.

// lo que dice un lector de pantalla cuando el foco entra en el grafico
uo_chart.is_accessible_name = "Ventas por mes"
uo_chart.is_accessible_description = "Doce barras, de enero a diciembre, en euros"

3.5 Los atajos de teclado #

Un atajo activa un componente esté donde esté el foco dentro de la ventana: el usuario no tiene que volver al botón para accionarlo. Todo componente visual los acepta, sin nada que habilitar.

uo_save.of_register_shortcut(/*chord*/ "Ctrl+S")
uo_refresh.of_register_shortcut(/*chord*/ "F5")

Escribir una combinación de teclas #

La combinación es una cadena libre, normalizada por la biblioteca: las mayúsculas, los espacios y el orden de los modificadores no tienen ninguna importancia. "Ctrl+Shift+S", "ctrl + shift + s" y "SHIFT+CTRL+S" designan el mismo atajo, así que es imposible registrar dos variantes por descuido.

ElementoFormas aceptadas
ModificadoresCtrl (o Control), Alt, Shift — combinables, en cualquier orden
Teclauna letra A–Z, una cifra 0–9, F1 … F24, Enter (o Return), Escape (o Esc), Delete (o Del), Insert, Home, End, PageUp, PageDown; solo con Ctrl o Alt: + (o Plus, el zoom Ctrl++), -, ,, ., Left Right Up Down, Space, Tab, Backspace, las teclas del teclado numérico Num 0 … Num 9, Num +, Num -, Num *, Num /, Num . (como las escribe un menú PowerBuilder: solo responden al teclado numérico, mientras que Ctrl+7 responde a las dos teclas 7)

Esta lista es exhaustiva: una tecla que no figure en ella (Tab, Espacio, una tecla del teclado numérico, un signo de puntuación) no activa ningún atajo.

Una tecla sola es una combinación válida ("F5"). Una cadena vacía retira el atajo del componente.

"Enter" y "Escape" solas no se registran como atajos: ambas teclas quedan reservadas al botón predeterminado y al botón de cancelación (ib_default / ib_cancel del button). Combinadas con un modificador, vuelven a ser combinaciones corrientes ("Ctrl+Enter").

Quién gana en caso de conflicto #

Dos componentes pueden pedir la misma combinación, algo frecuente cuando una ventana aloja varias zonas que tienen cada una su « Guardar ». El arbitraje se resuelve en este orden:

  1. el componente más cercano al foco del teclado se impone: el que tiene el foco, si no el que está en la misma hoja MDI, el mismo panel, la misma página de pestaña que el control enfocado — el atajo de una zona activa nunca queda eclipsado por un vecino;
  2. en empate entre dos componentes (ninguno está más cerca que el otro), ninguno lo recibe: la tecla se queda en la ventana;
  3. entre dos atajos del mismo componente, gana el primero registrado;
  4. las letras Alt de las etiquetas (mnemónicos de un botón) solo se consultan después de todos los atajos explícitos.

La misma regla vale para Intro y Esc: con dos hojas MDI que tienen cada una su botón predeterminado, Intro va al de la hoja en la que se escribe. Intro se queda en cambio en un CommandButton PB con el foco, e Intro o Esc en una DropDownListBox abierta.

Solo compiten los componentes visibles y activos de la ventana en primer plano. Volver a registrar una combinación en un componente que ya tenía una la sustituye sin cambiar su rango: reconfigurar una ventana no altera las prioridades.

Atajos de items #

La sobrecarga de dos argumentos vincula la combinación a un item del componente en lugar de al componente entero; el segundo argumento es el identificador del item; en un componente de varios niveles, su dirección (una toolbar toma "main/save", o "main/export/pdf" para una entrada de menú):

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

Retirar los atajos #

uo_bar.of_clear_shortcuts()      // componente E items

of_reset() y la destrucción del componente llaman a of_clear_shortcuts() por usted: un componente desaparecido nunca conserva una combinación reservada.

La tecla Alt #

Alt sola no se captura: da el foco a la cinta, que muestra sus keytips (véase ribbon). Por tanto, la biblioteca no intercepta las pulsaciones siguientes: es la cinta quien las lee, como si el usuario la hubiera pulsado con el ratón. Esc o un segundo Alt devuelven el foco al control abandonado. Si ninguna cinta de la ventana declara keytips, Alt conserva su comportamiento habitual de Windows.

MiembroEfecto
of_register_shortcut (string as_chord)Declara un atajo para el componente; una cadena vacía lo retira
of_register_shortcut (string as_chord, string as_key)Declara un atajo para un item, designado por su identificador
of_clear_shortcuts ( )Retira todos los atajos del componente, items incluidos

3.6 Poner un componente a cero: of_reset() #

of_reset() devuelve el componente a su estado inicial, como si acabara de cargarse:

uo_grid.of_reset()          // partir de una cuadricula vacia
// ... y luego reconstruir

⚠️ Reutilizar una instancia para mostrar otra cosa sin llamar a of_reset() conserva el estado anterior (un color, un modo, una altura automática). Es la causa más frecuente de un « resto de visualización » inexplicable.


3.7 Diagnóstico #

MiembroEfecto
of_is_created ( ) → booleanEl componente nativo existe (runtime presente, host válido)
of_is_ready ( ) → booleanEl contenido web está cargado (ue_ready ya activado)
of_get_last_error ( ) → stringPor qué falló la última llamada de este componente, tras un valor devuelto < 0: un nombre rechazado por of_set_property, o una llamada de este componente a la DLL — su motivo se lee en el momento del fallo. Nunca el mensaje que otro componente dejó en la DLL; vacío tras una llamada con éxito

Códigos devueltos por los métodos of_*:

Valor devueltoSignificado
≥ 0OK (aplicado o puesto en cola)
-2Componente no creado (runtime ausente, host no válido)
-4Operación fallida (captura, escritura de archivo…)
-5Argumento no válido (identificador vacío, valor fuera de los límites)
-6Runtime WebView2 demasiado antiguo para la función solicitada (impresión)

3.8 Exportar la representación como imagen #

Todo componente sabe exportarse como imagen, tal como se muestra:

uo_tiles.of_save_as_png(/*path*/ "C:\temp\accueil.png")
uo_tiles.of_save_as_jpg(/*path*/ "C:\temp\accueil.jpg")

Para imprimirlo en lugar de exportarlo, véase Imprimir.

Útil para un informe, un archivo adjunto de correo electrónico o el rastro de una incidencia. El componente debe estar creado y su contenido cargado.


3.9 Ciclo de vida #

  1. Construcción: la webview se crea ya en la construcción del userobject — imprescindible para el alojamiento (pestañas, paneles acoplables): una webview creada después de reasignar el elemento padre de su HWND no se muestra.
  2. Cola de espera: sus comandos se ponen en cola mientras ue_ready no se haya activado.
  3. Listo: ue_ready; la cola se reproduce en orden.
  4. Redimensionamiento: automático, el componente sigue el tamaño del userobject.
  5. Destrucción: al cerrar la ventana; la webview se libera, sin ningún proceso huérfano.

Llame a PBT_Warmup() una vez al arrancar la aplicación para que este ciclo resulte imperceptible (Instalación).


3.10 Buenas prácticas #


← Primeros pasos · Índice · Temas →