Skip to content

Example Gallery

A collection of example applications showcasing QtPie features.


Counter - The Hero Example

The canonical counter example demonstrating reactive state and automatic UI updates.

from qtpie import entrypoint, make, state, widget
from qtpy.QtWidgets import QLabel, QPushButton, QWidget

@entrypoint
@widget
class Counter(QWidget):
    count: int = state(0)
    label: QLabel = make(QLabel, bind="Count: {count}")
    button: QPushButton = make(QPushButton, "+1", clicked="increment")

    def increment(self) -> None:
        self.count += 1  # That's it. Label updates automatically.

What it demonstrates: - state() for reactive state management - Format string binding with bind="Count: {count}" - Signal connections via clicked="increment" - Automatic UI updates when state changes

Run it: Save the code and run with python counter.py


Hello World - Three Ways

QtPie offers multiple approaches for different needs.

Function Entry Point

The simplest possible QtPie app - a function that returns a widget.

from qtpie import entrypoint
from qtpy.QtWidgets import QLabel

@entrypoint
def main():
    return QLabel("Hello, World!")

Widget Class Entry Point

For apps that need interactivity and state.

from qtpie import entrypoint, make, widget
from qtpy.QtWidgets import QLabel, QPushButton, QWidget

@entrypoint
@widget
class MyWidget(QWidget):
    text: QLabel = make(QLabel, "Hello, World!")
    button: QPushButton = make(QPushButton, "Click Me", clicked="on_click")

    def on_click(self) -> None:
        self.text.setText("Button Clicked!")

App Class Entry Point

For apps that need full control over initialization.

from typing import override
from qtpie import App, entrypoint
from qtpy.QtWidgets import QLabel

@entrypoint
class MyApp(App):
    @override
    def create_window(self):
        return QLabel("Hello from App!")

What it demonstrates: - Three different entry point patterns - Choose the right level of complexity for your needs - All three use @entrypoint to handle app lifecycle


Form with Data Binding

A form that automatically binds input fields to a model.

from dataclasses import dataclass
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QLabel, QLineEdit, QSlider, QWidget
from qtpie import Widget, entrypoint, make, widget

@dataclass
class Dog:
    name: str = ""
    age: int = 0

@entrypoint
@widget(layout="form")
class DogEditor(QWidget, Widget[Dog]):
    name: QLineEdit = make(QLineEdit, form_label="Name")
    age: QSlider = make(
        QSlider,
        Qt.Orientation.Horizontal,
        form_label="Age",
        minimum=0,
        maximum=20
    )
    info: QLabel = make(QLabel, bind="Name: {name}, Age: {age}")

What it demonstrates: - Widget[Dog] for model-based widgets - Automatic two-way binding to model fields - layout="form" for form-style layouts - form_label parameter for field labels - Format binding with multiple variables: bind="Name: {name}, Age: {age}"

How it works: - Fields named name and age auto-bind to model.name and model.age - Changing the widgets updates the model automatically - The info label watches both fields and updates when either changes


Form with Validation

Real-time validation with visual feedback.

from dataclasses import dataclass
from qtpy.QtWidgets import QLabel, QLineEdit, QWidget
from qtpie import Widget, entrypoint, make, widget

@dataclass
class User:
    name: str = ""
    email: str = ""

@entrypoint
@widget(layout="form")
class UserEditor(QWidget, Widget[User]):
    name: QLineEdit = make(QLineEdit, form_label="Name")
    email: QLineEdit = make(QLineEdit, form_label="Email")
    name_error: QLabel = make(QLabel, bind="{validation_for('name')}")
    email_error: QLabel = make(QLabel, bind="{validation_for('email')}")
    valid_status: QLabel = make(QLabel, bind="Valid: {is_valid()}")

    def setup(self) -> None:
        # Add validation rules
        self.add_validator("name", lambda v: "Name required" if not v else None)
        self.add_validator("email", lambda v: "Invalid email" if "@" not in v else None)

        # Style error labels
        self.name_error.setStyleSheet("color: red;")
        self.email_error.setStyleSheet("color: red;")

What it demonstrates: - add_validator() for field validation - validation_for(field) to get errors for one field - is_valid() to check overall form validity - Binding validation state to labels - Custom setup in setup() lifecycle hook

How validation works: - Validators are functions that return None (valid) or error message (invalid) - validation_for() returns a list of error messages for a field - is_valid() returns True only when all fields pass validation - All validation results are reactive - UI updates automatically


Window with Menus

A main window with a menu bar.

from qtpy.QtGui import QAction
from qtpy.QtWidgets import QMainWindow, QMenu, QTextEdit
from qtpie import action, make, menu, separator, window

@action("&New")
class NewAction(QAction):
    pass

@action("&Open")
class OpenAction(QAction):
    pass

@action("&Save")
class SaveAction(QAction):
    pass

@action("E&xit")
class ExitAction(QAction):
    pass

@menu("&File")
class FileMenu(QMenu):
    new: NewAction = make(NewAction, triggered="on_new")
    open_file: OpenAction = make(OpenAction, triggered="on_open")
    save: SaveAction = make(SaveAction, triggered="on_save")
    sep1: QAction = separator()
    exit_app: ExitAction = make(ExitAction, triggered="on_exit")

    def on_new(self) -> None:
        print("New file")

    def on_open(self) -> None:
        print("Open file")

    def on_save(self) -> None:
        print("Save file")

    def on_exit(self) -> None:
        self.parent().close()

@window(title="Text Editor", size=(800, 600))
class EditorWindow(QMainWindow):
    file_menu: FileMenu = make(FileMenu)
    central_widget: QTextEdit = make(QTextEdit)

What it demonstrates: - @window decorator for QMainWindow - @menu decorator for QMenu with auto-title from class name - @action decorator for QAction with text - separator() for menu separators - Auto-adding menus to menu bar - Auto-adding actions to menus - central_widget field auto-set as central widget - Signal connections for menu actions

How menus work: - QMenu fields in a window are automatically added to the menu bar - QAction fields in a menu are automatically added to the menu - Separators are inserted in declaration order - "Menu" suffix is stripped from class name for auto-title


Counter with Up/Down Buttons

A counter with increment and decrement.

from qtpie import entrypoint, make, state, widget
from qtpy.QtWidgets import QLabel, QPushButton, QWidget

@entrypoint
@widget
class Counter(QWidget):
    count: int = state(0)
    label: QLabel = make(QLabel, bind="Count: {count}")
    up: QPushButton = make(QPushButton, "+", clicked="increment")
    down: QPushButton = make(QPushButton, "-", clicked="decrement")
    reset: QPushButton = make(QPushButton, "Reset", clicked="reset")

    def increment(self) -> None:
        self.count += 1

    def decrement(self) -> None:
        self.count -= 1

    def reset(self) -> None:
        self.count = 0

What it demonstrates: - Multiple buttons controlling the same state - Simple state manipulation with +=, -=, and = - All connected widgets update automatically


Format String Expressions

Advanced format string bindings with expressions.

from qtpie import entrypoint, make, state, widget
from qtpy.QtWidgets import QLabel, QLineEdit, QSpinBox, QWidget

@entrypoint
@widget(layout="form")
class Calculator(QWidget):
    price: float = state(10.0)
    quantity: int = state(1)
    discount: float = state(0.0)

    price_input: QSpinBox = make(
        QSpinBox,
        form_label="Price",
        bind="price",
        maximum=10000
    )
    qty_input: QSpinBox = make(
        QSpinBox,
        form_label="Quantity",
        bind="quantity",
        minimum=1,
        maximum=100
    )
    discount_input: QSpinBox = make(
        QSpinBox,
        form_label="Discount %",
        bind="discount",
        maximum=100
    )

    subtotal: QLabel = make(QLabel, bind="Subtotal: ${price * quantity:.2f}")
    discount_amount: QLabel = make(
        QLabel,
        bind="Discount: -${price * quantity * discount / 100:.2f}"
    )
    total: QLabel = make(
        QLabel,
        bind="Total: ${price * quantity * (1 - discount / 100):.2f}"
    )

What it demonstrates: - Math expressions in format strings: {price * quantity} - Format specifiers: {price:.2f} for decimal precision - Multiple variables in one expression - Complex calculations update automatically


Nested State Binding

Binding to nested object properties.

from dataclasses import dataclass
from qtpie import entrypoint, make, state, widget
from qtpy.QtWidgets import QLabel, QLineEdit, QSpinBox, QWidget

@dataclass
class Dog:
    name: str = ""
    age: int = 0

@entrypoint
@widget(layout="form")
class DogEditor(QWidget):
    dog: Dog = state(Dog(name="Buddy", age=3))

    name_edit: QLineEdit = make(QLineEdit, form_label="Name", bind="dog.name")
    age_spin: QSpinBox = make(QSpinBox, form_label="Age", bind="dog.age")
    greeting: QLabel = make(QLabel, bind="Hello, {dog.name}!")
    info: QLabel = make(QLabel, bind="{dog.name} is {dog.age} years old")

What it demonstrates: - state(Dog(...)) for complex object state - Nested property binding: bind="dog.name" - Format strings with nested paths: {dog.name} - Two-way binding to nested properties


Form with Dirty Tracking

Track unsaved changes in a form.

from dataclasses import dataclass
from qtpie import Widget, entrypoint, make, widget
from qtpy.QtWidgets import QLabel, QLineEdit, QPushButton, QWidget

@dataclass
class User:
    name: str = ""
    email: str = ""

@entrypoint
@widget(layout="form")
class UserEditor(QWidget, Widget[User]):
    name: QLineEdit = make(QLineEdit, form_label="Name")
    email: QLineEdit = make(QLineEdit, form_label="Email")

    dirty_status: QLabel = make(
        QLabel,
        bind="{'Unsaved changes!' if is_dirty() else 'All saved'}"
    )
    save_button: QPushButton = make(
        QPushButton,
        "Save",
        clicked="on_save",
        bind_prop={"enabled": "is_dirty()"}
    )

    def on_save(self) -> None:
        # Save changes
        print(f"Saving: {self.model.name}, {self.model.email}")
        # Mark as clean
        self.reset_dirty()

What it demonstrates: - is_dirty() to check if any field has changed - reset_dirty() to mark all fields as clean - bind_prop to bind widget properties (button enabled state) - Ternary expressions in bindings - Conditional UI based on state


Text Editor with Undo/Redo

A text editor with undo and redo buttons.

from qtpie import Widget, entrypoint, make, widget
from qtpy.QtWidgets import QLineEdit, QPushButton, QWidget

@entrypoint
@widget(undo=True)  # Enable undo/redo tracking
class Editor(QWidget):
    text: str = ""

    text_edit: QLineEdit = make(QLineEdit, bind="text")
    undo_btn: QPushButton = make(
        QPushButton,
        "Undo",
        clicked="do_undo",
        bind_prop={"enabled": "can_undo('text')"}
    )
    redo_btn: QPushButton = make(
        QPushButton,
        "Redo",
        clicked="do_redo",
        bind_prop={"enabled": "can_redo('text')"}
    )

    def do_undo(self) -> None:
        self.undo("text")

    def do_redo(self) -> None:
        self.redo("text")

What it demonstrates: - @widget(undo=True) to enable undo/redo - undo(field) and redo(field) methods - can_undo(field) and can_redo(field) for button states - Property binding to disable buttons when undo/redo unavailable

Undo options: - undo=True - enable with defaults - undo_max=50 - max history depth (default: 20) - undo_debounce_ms=500 - debounce rapid changes (default: 300ms)


Multiple State Fields

A form with multiple independent state fields.

from qtpie import entrypoint, make, state, widget
from qtpy.QtWidgets import QLabel, QLineEdit, QSpinBox, QWidget

@entrypoint
@widget(layout="form")
class UserForm(QWidget):
    # Independent state fields
    first_name: str = state("John")
    last_name: str = state("Doe")
    age: int = state(0)
    active: bool = state(False)

    # Input widgets
    first_edit: QLineEdit = make(QLineEdit, form_label="First", bind="first_name")
    last_edit: QLineEdit = make(QLineEdit, form_label="Last", bind="last_name")
    age_spin: QSpinBox = make(QSpinBox, form_label="Age", bind="age")

    # Display widget
    full_name: QLabel = make(QLabel, bind="{first_name} {last_name}")

What it demonstrates: - Multiple state() fields in one widget - Each state field is independent - Format binding can reference multiple state fields - All state changes trigger automatic UI updates


See Also