Apps, windows, forms and components
Everything you write in Pyzed is one of four kinds of class (or file). Each one has a few methods that Pyzed calls for you at the right moment: this page lists them, says when they run and shows what to put in them.
| You write | It is | Pyzed calls |
|---|---|---|
class MyApp(pz.App) |
The app and its main window. | view(), update(), on_start(), on_key(), subscriptions()… |
class Settings(pz.Window) |
Another window of the same app. | view(), on_open(), on_close(), on_resize()… |
login.pzform + login.py |
A form drawn in Pyzed Design, with its handlers. | form_load(form, event), <control>_<event>(form, event)… |
class Rating(pz.Component) |
A reusable piece of UI with props, state and events. | setup(), render(), on_mount(), on_unmount() |
flowchart LR
Start(["pz.run(MyApp)"]) --> Init["__init__()<br/>initial state"]
Init --> OnStart["on_start()"]
OnStart --> View["view()"]
View -->|widget tree| Draw["Iced draws it"]
Draw -->|click, key, timer…| Event{"event"}
Event -->|callable| Call["your method"]
Event -->|message| Update["update(message)"]
Call --> View
Update --> View
Draw -.->|window closes| Close["on_close()"]
pz.App: the application
Section titled “pz.App: the application”import pyzed as pzfrom pyzed import ui
class Notes(pz.App): title = "Notes" # window options are class attributes size = (720, 480) theme = "One Dark"
def __init__(self): # 1. the initial state: plain attributes self.notes = [] self.draft = "" self.saved_at = None
def on_start(self): # 2. once, when the window is open return pz.Command.focus("draft")
def view(self): # 3. after every change: describe the window return pz.column([ ui.input(self.draft, self.set_draft, id="draft", placeholder="New note", on_submit=self.add), pz.column([ui.list_item(n, icon="sticky-note") for n in self.notes]), ui.muted(f"Saved at {self.saved_at}" if self.saved_at else "Not saved yet"), ], spacing=8, padding=16)
def set_draft(self, text): # 4. callbacks change the state self.draft = text
def add(self): if self.draft.strip(): self.notes.append(self.draft.strip()) self.draft = ""
def update(self, message): # 5. messages that are not callables land here if message == "save": self.saved_at = "now"
def on_key(self, combo): # 6. every key press of the window if combo == "ctrl-s": return self.update("save")
def subscriptions(self): # 7. timers and streams active right now return [pz.every(60, "save")] if self.notes else []
pz.run(Notes)The methods
Section titled “The methods”| Method | When Pyzed calls it | What to put in it | May return |
|---|---|---|---|
__init__() |
Once, when the app is created (pz.run(MyApp) creates it). |
The initial state as attributes. No widgets and no I/O. | — |
view() |
At start and after every event, message or command. | Build and return the widget tree from the state. Never change state here. | A Widget (required). |
update(message) |
When a widget’s handler is a value (a string, a tuple, a dataclass…), or a callable returns one. | Change the state according to the message. | None, a Command, a list of them, a coroutine or another message. |
on_start() |
Once, after the window opens. | Load data, focus a field, start a background task. | A Command, a coroutine or a message. |
on_key(combo) |
On every key press of the window, before the focused widget. combo reads like "ctrl-shift-p". |
Global shortcuts. Returning something (even []) keeps the key from moving the focus. |
Like update. |
on_key_up(key) |
When a key is released ("left", "space"). |
Games and press-and-hold controls. | Like update. |
subscriptions() |
After every update. | Return the timers, streams and frame callbacks that should run now: pz.every, pz.listen, pz.frames. A subscription you stop returning stops. |
A list. |
on_close() |
When the user closes the window. | Ask to save. Return False to keep the window open. |
None or False. |
on_resize(width, height) |
When the window changes size. | Switch layouts (also see self.window_size). |
Like update. |
frame_menus() |
When frame = "custom" draws the title bar. |
The menus on its left side: [ui.Menu("File", [...])]. |
A list or None. |
And the ones you call yourself:
| Method | What it does |
|---|---|
run(reload=None) |
Opens the window and blocks until the app closes (pz.run(MyApp) is the same). reload=True turns on hot reload. |
screenshot(path, size=None) |
Renders the current state into a PNG without a screen (tests, docs). |
resolved_theme() |
The theme in use right now, with pz.set_style applied: self.resolved_theme().accent. |
Useful attributes the runtime keeps up to date: self.window_size, self.window_focused, self.window_maximized,
self.window_position and self.system_dark. The window options (title, size, min_size, resizable,
frame, transparent, icon, splash, tab_focus…) are listed in the App reference.
pz.Window: more windows
Section titled “pz.Window: more windows”A Window is an App without the app-wide parts: it has its own view() and window options, and sends its
messages to the app’s update(). Open it by returning a command:
class Settings(pz.Window): title = "Settings" size = (420, 300)
def on_open(self): # the window is on screen self.draft = self.app.prefs.copy() # self.app is the App that opened it
def view(self): return pz.column([ ui.switch(self.draft["dark"], self.set_dark, label="Dark mode"), ui.button("Save", self.save, variant="solid"), ], spacing=12, padding=16)
def set_dark(self, value): self.draft["dark"] = value
def save(self): self.app.prefs = self.draft return self.close() # a command
def on_close(self): # the user pressed the window's X return None if self.draft == self.app.prefs else False
class Main(pz.App): prefs = {"dark": True}
def view(self): return ui.button("Settings…", lambda: Settings().open())| Method | When | Notes |
|---|---|---|
view(), on_key(), on_key_up(), on_resize(), subscriptions() |
As in App. |
— |
on_open() |
The window opened (self.app is already set). |
May return a command. |
on_close() |
The user asked to close it. | False keeps it open. |
update(message) |
A message produced inside the window. | Forwards to self.app.update by default. |
open(), close(), drag(), minimize(), toggle_maximize() |
You call them. | Each returns a Command: return it from a handler. |
See Multiple windows for positions, modal windows and custom frames.
Designed forms: .pzform + handlers
Section titled “Designed forms: .pzform + handlers”A form you draw in Pyzed Design is saved as login.pzform (the controls and their properties) and
login.py (your code). The code file has no class: each event is a function named
<control>_<event>, and the form’s own events are form_<event>. Every handler gets the running form and
the event:
# login.py: the handlers of login.pzform
def form_load(form, event): # the window opened form.status.text = "" return form.email.focus() # handlers may return commands
def email_change(form, event): # every keystroke: event.value is the new text form.sign_in.enabled = "@" in event.value
def password_submit(form, event): # Enter in the password box return sign_in_click(form, event)
def sign_in_click(form, event): if not form.validate(): # runs the validation rules set in the designer return form.progress.show() return form.open("welcome", data=form.email.text)
def form_key(form, event): # every key press: event.value is the combo if event.value == "escape": return form.close()
def form_close(form, event): # return False to keep the window open if form.password.text: return False| Handler | When | event.value |
|---|---|---|
form_load |
Once, when the window opens. | None |
form_close |
The window is closing; False keeps it open. |
None |
form_key |
Every key press. | The combo, "ctrl-s" |
form_resize |
The window changed size. | (width, height) |
<control>_<event> |
The control raised the event (click, change, submit, tick…). |
Depends on the event: the new text, True/False, the selected item… |
The form object is your way into the window: form.<control>.<property> reads and writes properties,
form.<control>.show() / .hide() / .focus(), form.title, form.data (what form.open(..., data=)
passed), form.validate(), form.close() and form.open("other_form"). The events of every control are
listed in Events and handlers, and on each page of the library.
Run a form from Python, or open it from a code-first app:
from pyzed import designer
designer.run("ui/login.pzform") # the form is the app (same as `pyzed run`)
class Main(pz.App): # or a form as one more window def view(self): return ui.button("Sign in…", lambda: designer.open_window("login", data="ada@example.com"))pz.Component: reusable pieces
Section titled “pz.Component: reusable pieces”A component is a class with props (annotated class attributes, passed by the parent), state (attributes
it keeps between renders) and events (that it raises with self.emit). Calling it returns a widget.
class Stepper(pz.Component): """A number with − and + buttons."""
value: int = 0 # props step: int = 1 min: int = 0 max: int = 10 events = {"change": "The value changed (event.value is the new number)."}
pressed: int = pz.state(0) # state, private to each instance
def setup(self): # once, before the first render self.pressed = 0
def on_mount(self): # after the first render return None
def change(self, delta): self.pressed += 1 new = max(self.min, min(self.max, self.value + delta)) return self.emit("change", new) # calls the parent's on_change
def render(self): # on every render of the parent return pz.row([ ui.icon_button("minus", lambda: self.change(-self.step)), pz.text(str(self.value), size=18), ui.icon_button("plus", lambda: self.change(self.step)), ], spacing=8, align="center")
def on_unmount(self): # it is no longer rendered print(f"pressed {self.pressed} times")
# In any view:Stepper(value=self.copies, max=99, on_change=lambda e: setattr(self, "copies", e.value))| Method | When | Notes |
|---|---|---|
setup() |
Once, before the first render. Props are already set. | Initialize state. |
render() |
Every time the parent’s view() calls the component. |
Return widgets; read self.<prop> and the state. |
on_mount() |
After the first render. | May return a Command or a message. The parent’s on_mount= handler runs too. |
on_unmount() |
When the parent stops rendering it. Its state is dropped. | Stop timers, close files. |
emit(name, value) |
You call it. | Calls the parent’s on_<name> with a pz.Event(value=..., source=self). Return its result. |
handles(name) |
You call it. | Whether the parent passed on_<name>: hide a button nobody listens to. |
provide() |
Before each render. | A dict shared with everything rendered inside (pz.use_context(name)). |
context(name) |
You call it. | The closest value shared with pz.provide or provide(). |
Rating.create(...) |
In tests. | An instance without rendering it: Rating.create(value=3).render(). |
Every component also takes the common events (on_click, on_drag, on_key_down…),
style=, class_=, id=, animate= and key=. See Custom components for children,
context, stylesheets and how components appear in the designer’s toolbox.
Creating your own events
Section titled “Creating your own events”An event is a name plus a value that a piece of UI hands to whoever uses it. There are three ways to make one, from the most structured to the simplest.
1. In a component: events and emit
Section titled “1. In a component: events and emit”Declare the events and raise them with self.emit(name, value). The parent listens with on_<name>=:
class ColorRow(pz.Component): colors: list = () selected: str = "" events = { "pick": "A color was clicked (event.value is the color).", "remove": "The × of a color was pressed (event.value is the color).", }
def render(self): return pz.row([ pz.row([ ui.color_swatch(c, size=28 if c == self.selected else 20, on_press=lambda c=c: self.emit("pick", c)), self.handles("remove") and ui.icon_button("x", lambda c=c: self.emit("remove", c), size="xs"), ], spacing=2) for c in self.colors ], spacing=8)
ColorRow(colors=self.palette, selected=self.color, on_pick=lambda e: setattr(self, "color", e.value), on_remove=lambda e: self.palette.remove(e.value))eventsis a dict{name: description}(the description shows in the designer and the reference) or a tuple of names. Subclasses add to the events they inherit.- The handler gets a
pz.Eventwithtype(the name),valueandsource(the component). A handler without arguments (on_pick=self.refresh) is called with nothing. emitof an undeclared name raises an error, so typos show up at once.- Every component also emits
mountandunmount.
2. In a function: a callback parameter
Section titled “2. In a function: a callback parameter”The components of pyzed.ui are plain functions with on_* parameters. Yours can be too: take the callback as a
parameter and give it to a widget, or wrap it to pass a value.
def tag_list(tags, on_remove=None, on_add=None): """Tags as chips. on_remove(tag) when a chip's × is pressed, on_add() for the + button.""" return pz.row([ *[ui.chip(t, on_remove=on_remove and (lambda t=t: on_remove(t))) for t in tags], on_add and ui.icon_button("plus", on_add, tooltip="Add a tag"), ], spacing=6)
tag_list(self.tags, on_remove=self.tags.remove, on_add=self.new_tag)3. App-wide: messages and pz.post
Section titled “3. App-wide: messages and pz.post”A message is an event for the whole app: any value that reaches update. Use one when the same thing can come
from several places (a button, a shortcut, a menu, a timer, a worker thread):
from dataclasses import dataclass
@dataclassclass Downloaded: # a message with data name: str size: int
def worker(url): # runs in a thread data = fetch(url) pz.post(Downloaded(url, len(data))) # any thread can send a message to update()
class App(pz.App): def update(self, message): match message: case Downloaded(name, size): self.files.append((name, size))In Pyzed Design, a designed component’s custom events are added in its Events card and raised from its
code with self.emit("change", value). See Component designer.
Where next
Section titled “Where next”- Events: the common events of every widget and the
pz.Eventobject. - Events of every component: how many events each component has, and which.
- Imperative, declarative and functional: the same app written three ways.
- App architecture: the view → message → update loop in detail.