Callback System¶
pgwidgets uses a callback model where the browser sends event messages to Python over WebSocket. You register handlers in Python; they fire when the user interacts with widgets in the browser.
Registering Callbacks¶
There are two ways to register a callback on any widget:
on() – handler receives only the callback arguments:
# Sync
btn.on("activated", lambda: print("clicked"))
entry.on("activated", lambda text: print(f"Entered: {text}"))
# Async
await btn.on("activated", on_click)
add_callback() – handler receives the widget as the first argument:
# Sync
btn.add_callback("activated", lambda widget: print(f"{widget} clicked"))
# Async
await btn.add_callback("activated", on_click)
Extra Arguments¶
Both on() and add_callback() accept extra positional and keyword
arguments that are appended to every invocation:
def on_button(label, tag):
label.set_text(f"Button {tag} clicked")
btn_a.on("activated", on_button, status_label, "A")
btn_b.on("activated", on_button, status_label, "B")
Callback Signatures¶
Different callbacks pass different arguments to the handler. Below are the common patterns:
Callback |
Widgets |
Handler receives |
|---|---|---|
|
Button |
(nothing) |
|
CheckBox |
|
|
TextEntry |
|
|
Slider, SpinBox, Dial |
|
|
ComboBox |
|
|
Dialog |
|
|
TabWidget, StackWidget |
|
|
TabWidget, MDIWidget |
|
|
TreeView, TableView |
|
|
Image, Canvas |
|
|
Image, Canvas, Label, … |
|
|
Timer |
(nothing) |
Async Callbacks¶
In the async API, callback handlers can be sync or async functions. Async handlers are automatically awaited:
async def on_click():
await status.set_text("Clicked!")
await btn.on("activated", on_click)
File Transfers (Chunked Protocol)¶
When a user drags and drops files onto a widget (e.g., Image or Canvas with
drop-end), the file data is transferred in chunks to avoid blocking the
WebSocket with large payloads.
The protocol works as follows:
The browser sends a
callbackmessage withtransfer_idand file metadata (names, sizes, MIME types) but no file data.The framework fires a drop-start callback with the metadata so you can show progress UI.
The browser sends
file-chunkmessages with base64-encoded data.The framework fires drop-progress callbacks with transfer status.
When all chunks arrive, the framework reassembles the data and fires the drop-end callback with the complete payload.
drop-start¶
Fires once at the beginning of a file transfer. The handler receives a dict with file metadata:
def on_drop_start(payload):
files = payload["files"] # list of {name, size, type}
print(f"Receiving {len(files)} files...")
widget.on("drop-start", on_drop_start)
drop-progress¶
Fires after each chunk. The handler receives a dict:
def on_progress(info):
pct = info["transferred_bytes"] / info["total_bytes"] * 100
progress_bar.set_value(pct)
if info["complete"]:
print("Transfer complete!")
widget.on("drop-progress", on_progress)
The progress dict contains:
transfer_id– unique ID for this transferfile_index– which file (0-based)chunk_index– which chunk of the current filenum_chunks– total chunks for the current filetransferred_bytes– bytes received so far (all files)total_bytes– total bytes expected (all files)complete– True when all files are fully received
drop-end¶
Fires when all file data has been received. The handler receives the full payload with base64 data URIs:
import base64
def on_drop(payload):
for f in payload["files"]:
name = f["name"]
size = f["size"]
mime = f["type"]
data_uri = f["data"] # "data:<mime>;base64,<data>"
# Decode the file content
b64 = data_uri.split(",", 1)[1]
content = base64.b64decode(b64)
print(f"Received {name}: {len(content)} bytes")
widget.on("drop-end", on_drop)
Example: File Drop Zone¶
drop_label = Widgets.Label("Drop files here")
drop_label.set_color("#e8f0fe", "#4a86c8")
textarea = Widgets.TextArea("")
def on_drop_start(payload):
n = len(payload["files"])
drop_label.set_text(f"Receiving {n} file(s)...")
def on_drop(payload):
f = payload["files"][0]
b64 = f["data"].split(",", 1)[1]
text = base64.b64decode(b64).decode("utf-8", errors="replace")
textarea.set_text(text)
drop_label.set_text(f"Loaded: {f['name']}")
drop_label.on("drop-start", on_drop_start)
drop_label.on("drop-end", on_drop)