Ir al contenido

Un componente propio

Cuando el mismo grupo de controles aparece una y otra vez (una valoración con estrellas, una tarjeta de usuario, una barra de búsqueda), conviértelo en un componente: lo dibujas una vez, le das propiedades y eventos, y lo colocas donde quieras como un control más. Es la idea de las escenas de Godot y de los widget blueprints de Unreal, y es como las apps grandes se mantienen ordenadas.

Objetivo Tiempo Nivel Necesitas
Un Rating de cinco estrellas que puedes poner en cualquier formulario 35 minutos Intermedio · Python Enlace de datos
  • Crear un componente en la Vista de componente (un .pzcomp y su clase en un .py).
  • Añadir una propiedad pública (value) y un estado privado (clicks).
  • Enlazar las propiedades de los controles a las del componente ("solid" if value >= 3 else "ghost").
  • Escribir métodos y lanzar un evento propio con self.emit("change", ...).
  • Probarlo en la ventana de vista previa, ponerlo en un formulario y usarlo desde código Python.
  1. Crea el componente. Ctrl+N → Componente, llámalo rating y pulsa Crear componente. El diseñador crea rating.pzcomp (el dibujo) y rating.py (la clase Rating) y los abre en la Vista de componente: un marco de color de acento en lugar de los botones de ventana, y una insignia Componente.

    El diálogo Nueva con la tarjeta Componente elegida
    Nueva → Componente: una pieza reutilizable, no una ventana.
  2. Dibújalo. Sin nada seleccionado, pon el Ancho del componente en 232 y el Alto en 28 (sección Disposición). Coloca cinco Botones en fila llamados star1 … star5, cada uno con el icono star, sin texto y con variante ghost, y una Etiqueta de texto llamada caption al final.

  3. Añade las propiedades. En la sección Properties del panel del componente pulsa Public y crea value, de tipo int, con valor por defecto 3; luego readonly, de tipo bool. Pulsa Private para clicks, un int que solo ve el componente. La clase de rating.py los recibe como atributos anotados:

    value: Annotated[int, pz.Range(0, 5)] = 3
    readonly: bool = False
    clicks: int = pz.state(0, doc="Times the user picked a rating")
  4. Enlaza las estrellas. Selecciona star3, abre Bindings, elige variant y luego Expression…:

    "solid" if value >= 3 else "ghost"

    Haz lo mismo con las otras estrellas (cada una con su número) y enlaza caption.text a f"{value} / 5". Cambia value en el panel: las estrellas se encienden en el lienzo.

    La sección Bindings que une una propiedad de un control con la propiedad del componente
    Los enlaces mantienen los controles al día con las propiedades del componente.
  5. Añade el evento propio. En Custom events pulsa Event, llámalo change y descríbelo: A star was picked (event.value is the rating).

  6. Escribe la lógica. Añade un método pick (escribe el nombre y pulsa + Method) y haz doble clic en cada estrella para escribir su manejador de clic. En un componente los manejadores son métodos y los controles son self.<nombre>:

    def star1_click(self, event):
    return self.pick(1)
    def pick(self, stars):
    """Lights stars stars and tells the parent."""
    if self.readonly:
    return None
    self.value = stars
    self.clicks += 1
    return self.emit("change", stars)
  7. Pruébalo solo. Pulsa F5: el componente se abre en una ventana de vista previa que lista cada evento propio que lanza. Haz clic en las estrellas y mira llegar change.

    La ventana de vista previa de un componente, con su registro de eventos
    F5 sobre un componente: una vista previa con registro de eventos.
  8. Úsalo en un formulario. Guarda y abre (o crea) un formulario: Rating ya está en las Herramientas, en su categoría. Pon uno, añade una Etiqueta de texto llamada status, selecciona la valoración y, en Eventos, haz doble clic en change:

    def rating1_change(form, event):
    form.status.text = f"You gave it {event.value} stars" # event.value es lo que envió emit()

    Haz doble clic en un componente colocado para abrirlo en su propia pestaña; los cambios aparecen en cada formulario que lo usa.

Un componente ProfileCard que contiene un componente Rating
Los componentes se anidan: esta ProfileCard contiene un Rating, como escenas dentro de escenas en Godot.

Los componentes terminados (incluida una ProfileCard que contiene un Rating, y una TeamCard que hereda de ella) están en examples/designer/components.

examples/designer/components/rating.py
"""Rating: a component designed in Pyzed Design.
The layout is in rating.pzcomp; properties, events and logic are below. Use it like
any component: ``Rating(...)`` in a view, or from the designer's toolbox.
"""
from typing import Annotated
import pyzed as pz
from pyzed.designer import SceneComponent
class Rating(SceneComponent, scene="rating.pzcomp"):
"""Five stars the user can click."""
value: Annotated[int, pz.Range(0, 5)] = 3 # How many stars are lit
readonly: bool = False
clicks: int = pz.state(0, doc="Times the user picked a rating")
events = {"change": "A star was picked (event.value is the rating)"}
def star1_click(self, event):
return self.pick(1)
def star2_click(self, event):
return self.pick(2)
def star3_click(self, event):
return self.pick(3)
def star4_click(self, event):
return self.pick(4)
def star5_click(self, event):
return self.pick(5)
def pick(self, stars):
"""Lights ``stars`` stars and tells the parent."""
if self.readonly:
return None
self.value = stars
self.clicks += 1
return self.emit("change", stars)
examples/designer/components/reviews.py (un formulario que usa dos tarjetas)
"""Event handlers for the Reviews form.
Each function receives the running ``form`` (controls are attributes: ``form.button1.text``)
and an ``event`` with ``event.sender``, ``event.name`` and ``event.value``.
"""
def ada_rated(form, event):
"""Runs when the card of ada is rated."""
event.sender.score = event.value
form.status.text = f"{event.sender.person} now has {event.value} stars"
def grace_rated(form, event):
"""Runs when the card of grace is rated."""
event.sender.score = event.value
form.status.text = f"{event.sender.person} now has {event.value} stars"

Ejecuta la vista previa del componente, o el formulario que lo usa:

Windows, macOS y Linux
uv run --with pyzed-ui pyzed run examples/designer/components/rating.pzcomp
Windows, macOS y Linux
uv run --with pyzed-ui pyzed run examples/designer/components/reviews.pzform

Rating declara un evento propio, change, en events y lo lanza con self.emit("change", stars). El padre lo escucha con on_change=, y su handler recibe un pz.Event cuyo value es la puntuación. Como todo componente, Rating lanza además mount y unmount, y acepta los eventos comunes (on_right_click, on_mouse_enter…).

Un segundo evento propio, que solo se lanza si alguien lo escucha:

class Rating(pz.Component):
value: int = 0
events = {
"change": "Se eligió una estrella (event.value es la puntuación).",
"clear": "Se borró la puntuación con un clic derecho.", # nuevo
}
def render(self):
stars = pz.row([...]) # como en el tutorial
if self.handles("clear"): # solo si el padre pasó on_clear
stars = stars.on(right_click=lambda: self.emit("clear"))
return stars
# El padre, en su view:
Rating(value=self.stars,
on_change=lambda e: setattr(self, "stars", e.value),
on_clear=lambda: setattr(self, "stars", 0), # sin argumentos: se llama sin nada
on_mount=lambda: print("la puntuación está en pantalla"))

Más en crear tus propios eventos.