Skip to content

bind()

The bind() function creates manual two-way bindings between observables and Qt widgets. This is a low-level function - most users should use the bind parameter in make() instead.

When to Use bind()

You typically don't need bind() directly. Use it when:

  • Creating bindings outside of @widget classes
  • Building custom binding logic
  • Manually managing observable connections

For normal widget binding, use make() with the bind parameter:

from qtpie import widget, make
from qtpy.QtWidgets import QLineEdit, QWidget

@widget
class Editor(QWidget):
    name: QLineEdit = make(QLineEdit, bind="name")  # Preferred!

This page documents the low-level bind() function for advanced usage.

Basic Usage

Simple Manual Binding

from dataclasses import dataclass
from observant import ObservableProxy
from qtpie import bind
from qtpy.QtWidgets import QLineEdit

@dataclass
class Model:
    name: str = "Test"

proxy = ObservableProxy(Model(), sync=True)
edit = QLineEdit()

# Manual binding
bind(proxy.observable(str, "name"), edit)

Now changes flow both ways: - proxy.observable(str, "name").set("Updated") → widget shows "Updated" - User types "New" → proxy.name becomes "New"

Default Property

Each widget type has a default property that bind() uses automatically:

from qtpie import bind
from qtpy.QtWidgets import QLineEdit, QSpinBox, QCheckBox

# Uses "text" for QLineEdit
bind(name_observable, line_edit)

# Uses "value" for QSpinBox
bind(age_observable, spin_box)

# Uses "checked" for QCheckBox
bind(active_observable, check_box)

Explicit Property

Specify a different property with the prop parameter:

from dataclasses import dataclass
from observant import ObservableProxy
from qtpie import bind
from qtpy.QtWidgets import QComboBox

@dataclass
class Model:
    selected: str = "Option A"

proxy = ObservableProxy(Model(), sync=True)
combo = QComboBox()
combo.addItems(["Option A", "Option B", "Option C"])

# Bind to currentText instead of default (currentIndex)
bind(proxy.observable(str, "selected"), combo, "currentText")

Widget Bindings

Input Widgets (Two-Way)

Input widgets support bidirectional binding:

from dataclasses import dataclass
from observant import ObservableProxy
from qtpie import bind
from qtpy.QtWidgets import QLineEdit, QSpinBox, QCheckBox

@dataclass
class Person:
    name: str = ""
    age: int = 0
    active: bool = False

proxy = ObservableProxy(Person(), sync=True)

name_edit = QLineEdit()
age_spin = QSpinBox()
active_check = QCheckBox()

bind(proxy.observable(str, "name"), name_edit)
bind(proxy.observable(int, "age"), age_spin)
bind(proxy.observable(bool, "active"), active_check)

Changes flow both ways: - Model → Widget: Observable changes update the widget - Widget → Model: User input updates the observable

Display Widgets (One-Way)

Some widgets only support model → widget binding (read-only):

from dataclasses import dataclass
from observant import ObservableProxy
from qtpie import bind
from qtpy.QtWidgets import QLabel, QProgressBar

@dataclass
class Status:
    message: str = "Ready"
    progress: int = 0

proxy = ObservableProxy(Status(), sync=True)

label = QLabel()
bar = QProgressBar()

bind(proxy.observable(str, "message"), label)
bind(proxy.observable(int, "progress"), bar)

# Only model → widget works
proxy.observable(str, "message").set("Loading...")  # Updates label
proxy.observable(int, "progress").set(50)  # Updates progress bar

Built-in Widget Support

QtPie includes bindings for many common Qt widgets:

Text Widgets

Widget Default Property Bidirectional
QLineEdit text Yes
QTextEdit text Yes
QPlainTextEdit text Yes
QLabel text No (read-only)

Numeric Widgets

Widget Default Property Bidirectional
QSpinBox value Yes
QDoubleSpinBox value Yes
QSlider value Yes
QDial value Yes
QProgressBar value No (read-only)

Selection Widgets

Widget Default Property Bidirectional
QCheckBox checked Yes
QRadioButton checked Yes
QComboBox currentText Yes
QListWidget currentRow Yes

Date/Time Widgets

Widget Default Property Bidirectional
QDateEdit date Yes
QTimeEdit time Yes
QDateTimeEdit dateTime Yes

Other Widgets

Widget Default Property Bidirectional
QFontComboBox currentFont Yes
QKeySequenceEdit keySequence Yes

Multiple Widgets, Same Observable

Multiple widgets can bind to the same observable:

from dataclasses import dataclass
from observant import ObservableProxy
from qtpie import bind
from qtpy.QtWidgets import QLineEdit, QLabel

@dataclass
class Model:
    name: str = ""

proxy = ObservableProxy(Model(), sync=True)

edit1 = QLineEdit()
edit2 = QLineEdit()
label = QLabel()

name_obs = proxy.observable(str, "name")
bind(name_obs, edit1)
bind(name_obs, edit2)
bind(name_obs, label)

All three widgets stay synchronized: - User types "Alice" in edit1 → edit2 and label show "Alice" - User types "Bob" in edit2 → edit1 and label show "Bob" - proxy.observable(str, "name").set("Charlie") → all three show "Charlie"

Advanced Features

No Infinite Loops

bind() automatically prevents infinite update loops:

from dataclasses import dataclass
from observant import ObservableProxy
from qtpie import bind
from qtpy.QtWidgets import QSpinBox

@dataclass
class Counter:
    value: int = 0

proxy = ObservableProxy(Counter(), sync=True)
spin = QSpinBox()

bind(proxy.observable(int, "value"), spin)

# These don't cause infinite loops:
spin.setValue(10)  # Updates observable once
proxy.observable(int, "value").set(20)  # Updates widget once

Initial Sync

When you call bind(), it immediately syncs the current observable value to the widget:

from dataclasses import dataclass
from observant import ObservableProxy
from qtpie import bind
from qtpy.QtWidgets import QLineEdit

@dataclass
class Model:
    name: str = "Alice"

proxy = ObservableProxy(Model(), sync=True)
edit = QLineEdit()

# Before binding, widget is empty
assert edit.text() == ""

# After binding, widget shows current value
bind(proxy.observable(str, "name"), edit)
assert edit.text() == "Alice"

Prefer make() Over bind()

In most cases, use the bind parameter in make() instead of calling bind() directly:

Instead of This

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

@dataclass
class Dog:
    name: str = ""

@widget
class DogEditor(QWidget, Widget[Dog]):
    name_edit: QLineEdit = make(QLineEdit)

    def setup(self) -> None:
        # Manual binding - verbose!
        bind(self.record_observable_proxy.observable(str, "name"), self.name_edit)

Do This

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

@dataclass
class Dog:
    name: str = ""

@widget
class DogEditor(QWidget, Widget[Dog]):
    # Short form - clean and simple!
    name_edit: QLineEdit = make(QLineEdit, bind="name")

Much cleaner! The Widget[Dog] base class creates the record_observable_proxy automatically, and bind="name" expands to bind="record_observable_proxy.name" behind the scenes.

API Reference

def bind(
    observable: IObservable[Any],
    widget: QObject,
    prop: str | None = None,
) -> None

Parameters

  • observable: The observable to bind (from ObservableProxy)
  • widget: The Qt widget to bind to
  • prop: The widget property name. If None, uses the default for the widget type

Returns

None. The function sets up the binding as a side effect.

Raises

  • ValueError: If no binding is registered for the widget type and property combination

See Also