8.5.8.1. pymodaq_gui.utils.widget_sync package
- 8.5.8.1.1. pymodaq_gui.utils.widget_sync.core module
BaseWidgetSyncBaseWidgetSync.bind()BaseWidgetSync.check_connection_mode()BaseWidgetSync.disable()BaseWidgetSync.enable()BaseWidgetSync.is_enabled()BaseWidgetSync.set_value()BaseWidgetSync.unbind()BaseWidgetSync.unbind_all()BaseWidgetSync.validate_property_connection()BaseWidgetSync.connected_widgetsBaseWidgetSync.connection_countBaseWidgetSync.data_typeBaseWidgetSync.value
DataTypeDictSyncSyncModeValueSync
- 8.5.8.1.2. pymodaq_gui.utils.widget_sync.factories module
8.5.8.1.3. Qt Widget Synchronization
- Basic Usage - Single Values:
>>> from pymodaq_gui.utils.widget_sync import WidgetSync, SyncMode >>> >>> # Create sync for checkboxes >>> sync = WidgetSync.for_checkbox(checkbox1, initial=True) >>> sync.add(checkbox2) >>> sync.add(checkbox3) >>> >>> # Change value programmatically >>> sync.value = False # All checkboxes update
- Dict Synchronization:
>>> # Auto-detects DictSync for dict values >>> color_sync = WidgetSync(initial_value={'r': 128, 'g': 64, 'b': 192}) >>> >>> # Bind different widgets to dict keys >>> color_sync.bind_dict(property_map={ ... 'r': {'widget': r_slider, 'property': 'value'}, ... 'g': {'widget': g_slider, 'property': 'value'}, ... 'b': {'widget': b_slider, 'property': 'value'} ... }) >>> >>> # Change dict value - all sliders update >>> color_sync.value = {'r': 255, 'g': 0, 'b': 0}
- Advanced Usage - Custom Factories:
>>> from pymodaq_gui.utils.widget_sync import WidgetSync, WidgetSyncFactories >>> >>> class MyFactories(WidgetSyncFactories): ... @classmethod ... def for_my_widget(cls, widget, initial=None): ... return cls.for_property(widget, 'myProp', 'mySignal', initial) >>> >>> class MySync(WidgetSync, MyFactories): ... pass >>> >>> sync = MySync.for_my_widget(my_widget)
- class pymodaq_gui.utils.widget_sync.BaseWidgetSync[source]
Bases:
QObjectBase class for widget synchronization.
Provides common functionality for connection management, callbacks, feedback loop prevention, and widget lifecycle management.
Subclasses must implement: set_value(), value property getter/setter
- Attributes:
connected_widgetsGet list of currently connected widgets.
connection_countGet count of active connections.
data_typeGet the data type for this sync.
valueGet current value - must be implemented by subclasses.
Methods
bind(widget[, signal, getter, setter, mode, ...])Bind a widget to this sync.
disable(widget)Disable a widget's connections temporarily.
enable(widget)Enable a widget's connections.
is_enabled(widget)Check if a widget's connections are enabled.
set_value(new_value[, emit])Set value - must be implemented by subclasses.
unbind(widget)Unbind a widget by reference or ID.
Unbind all connected widgets (both regular and property connections).
check_connection_mode
validate_property_connection
value_changed
- bind(widget, signal=None, getter=None, setter=None, mode=None, to_sync_transform=None, from_sync_transform=None, init_from='sync')[source]
Bind a widget to this sync.
For ValueSync: Binds the widget to the single value. For DictSync: Binds the widget to the entire dict value.
IMPORTANT: When binding with FROM_SYNC or BIDIRECTIONAL mode, the widget is immediately initialized with the current sync value. This is different from enable(), which does NOT update the widget value. One can disable this behavior by using the init_from parameter.
- Parameters:
widget (
QWidget) – The widget to connectsignal (
pyqtSignal|None) – The signal to listen to (required for TO_SYNC/BIDIRECTIONAL modes)getter (
Optional[Callable[[],Any]]) – Function to get value from widget: () -> value Required for TO_SYNC and BIDIRECTIONAL modessetter (
Optional[Callable[[Any],None]]) – Function to set value on widget: (value) -> None Required for FROM_SYNC and BIDIRECTIONAL modesmode (
SyncMode|None) – Synchronization mode. If None, auto-inferred from parametersto_sync_transform (
Optional[Callable[[Any],Any]]) – Transform value from widget to sync: (widget_value) -> sync_valuefrom_sync_transform (
Optional[Callable[[Any],Any]]) – Transform value from sync to widget: (sync_value) -> widget_valueinit_from (
Literal['sync','widget',None]) – Initialization source: ‘sync’ (default - widget gets sync’s value), ‘widget’ (sync gets widget’s value), or None (no initialization). When ‘widget’ is used with BIDIRECTIONAL mode, all other connected widgets also receive the widget’s value.
- Raises:
ValueError – If neither getter nor setter provided, or if mode requirements not met
- Return type:
Example
>>> sync = WidgetSync(initial_value=100) >>> sync.bind(slider, signal=slider.valueChanged, ... getter=slider.value, setter=slider.setValue) >>> slider.value() # Returns 100 - widget was initialized!
- disable(widget)[source]
Disable a widget’s connections temporarily.
When disabled, the widget will not receive updates from the sync, and its changes will not affect other widgets. The connections remain in place and can be re-enabled later.
This disables ALL connections for the widget (both regular and property connections).
This is useful for: - Temporarily pausing sync during complex operations - Conditional syncing based on application state - Testing/debugging
- Parameters:
- Raises:
ValueError – If widget is not connected
- Return type:
Example
>>> sync = WidgetSync.for_slider(slider1) >>> sync.add(slider2) >>> sync.disable(slider2) # slider2 stops syncing >>> slider1.setValue(75) # slider2 doesn't update >>> sync.enable(slider2) # Re-enable slider2 >>> slider2.value() # Still has old value until next update
- enable(widget)[source]
Enable a widget’s connections.
When enabled, the widget will sync bidirectionally with other widgets. This enables ALL connections for the widget (both regular and property connections).
- Parameters:
- Raises:
ValueError – If widget is not connected
- Return type:
- is_enabled(widget)[source]
Check if a widget’s connections are enabled.
Returns True only if ALL connections for the widget are enabled.
- Parameters:
- Returns:
True if all connections are enabled, False otherwise
- Return type:
- Raises:
ValueError – If widget is not connected
- set_value(new_value, emit=True)[source]
Set value - must be implemented by subclasses.
- Return type:
- unbind(widget)[source]
Unbind a widget by reference or ID.
Removes ALL connections for the widget (both regular and property connections).
- unbind_all()[source]
Unbind all connected widgets (both regular and property connections).
- Return type:
- validate_property_connection(mode, setter, getter, signal=None, property_key=None)[source]
- Return type:
- property connected_widgets: list[QWidget]
Get list of currently connected widgets.
Returns only widgets that haven’t been deleted. Includes both regular connections and property connections.
- Returns:
List of active widget references (unique widgets)
- Return type:
list[QWidget]
Example
>>> sync = WidgetSync.for_checkbox(cb1) >>> sync.add(cb2) >>> sync.add(cb3) >>> len(sync.connected_widgets) # Returns 3 3
- property connection_count: int
Get count of active connections.
Includes both regular connections and property connections.
- Returns:
Number of currently connected widgets/properties
- Return type:
Example
>>> sync = WidgetSync.for_spinbox(spin1) >>> sync.add(spin2) >>> sync.connection_count # Returns 2 2
- class pymodaq_gui.utils.widget_sync.DataType(*values)[source]
Bases:
EnumSupported data types for widget synchronization.
Each sync instance is associated with a specific data type, ensuring type safety when connecting widgets and transforming values.
- BOOL = <class 'bool'>
- INT = <class 'int'>
- STR = <class 'str'>
- class pymodaq_gui.utils.widget_sync.DictSync(initial_value=None, validator=None)[source]
Bases:
BaseWidgetSyncSynchronize dict values with multiple properties or widgets.
DictSync provides three binding methods:
bind() - Bind widgets that work with the entire dict (e.g., JSON editor, config display)
bind_properties() - Bind multiple properties of ONE widget to dict keys (e.g., ComboBox with both ‘items’ and ‘selection’ properties)
bind_dict() - Bind DIFFERENT widgets to different dict keys (e.g., separate R/G/B sliders for a color dict)
Examples
>>> # Bind multiple properties of one widget >>> sync = DictSync({'items': ['A', 'B'], 'current': 'A'}) >>> sync.bind_properties(combobox, property_map={ ... 'items': {'setter': lambda v: (combo.clear(), combo.addItems(v))}, ... 'current': {'property': 'currentText'} ... }) >>> >>> # Bind different widgets to dict keys >>> color_sync = DictSync({'r': 128, 'g': 64, 'b': 192}) >>> color_sync.bind_dict(property_map={ ... 'r': {'widget': r_slider, 'property': 'value'}, ... 'g': {'widget': g_slider, 'property': 'value'}, ... 'b': {'widget': b_slider, 'property': 'value'} ... })
- Attributes:
valueGet current synced dict value.
Methods
append_to_list(key, item[, emit])Append an item to a list value in the dict.
bind_dict(property_map[, init_from])Bind different widgets to different dict keys.
bind_parameter(parameter, property_map[, ...])Bind multiple properties of a Parameter to different dict keys.
bind_properties(widget, property_map[, ...])Bind multiple properties of ONE widget to different dict keys.
pop_from_list(key[, index, emit])Pop an item from a list value in the dict.
remove_from_list(key, item[, emit])Remove an item from a list value in the dict.
set_value(new_value[, emit])Set dict value with optional emission control.
update_key(key, value[, emit])Update a single key in the dict value.
- append_to_list(key, item, emit=True)[source]
Append an item to a list value in the dict.
- Parameters:
- Raises:
- Return type:
Example
>>> sync = DictSync({'items': ['a', 'b'], 'current': 'a'}) >>> sync.append_to_list('items', 'c') >>> sync.value['items'] # Returns ['a', 'b', 'c']
- bind_dict(property_map, init_from='sync')[source]
Bind different widgets to different dict keys.
Each property in the dict value is controlled by its own widget.
Key Difference from bind_properties(): - bind_properties(): Multiple properties of ONE widget → dict keys - bind_dict(): Multiple different widgets → dict keys (one widget per key)
- Parameters:
property_map (
dict[str,dict[str,Any]]) –Mapping of dict keys to widget configurations. Each key maps to a dict that MUST include:
Required: - ‘widget’: QWidget - The widget for this property
Simple syntax (recommended for Qt properties): - ‘property’: str - Qt property name (auto-generates getter/setter) - ‘signal’: Signal | str (optional, auto-detected if omitted) - ‘mode’: SyncMode (optional, default: BIDIRECTIONAL) - ‘init_from’: InitFrom (optional, overrides global init_from for this property)
Advanced syntax (for custom logic): - ‘signal’: Signal | None (required for TO_SYNC/BIDIRECTIONAL) - ‘getter’: callable () -> value (required for TO_SYNC/BIDIRECTIONAL) - ‘setter’: callable (value) -> None (required for FROM_SYNC/BIDIRECTIONAL) - ‘mode’: SyncMode (optional, default: inferred from getter/setter) - ‘init_from’: InitFrom (optional, overrides global init_from for this property)
init_from (
Literal['sync','widget',None]) – Global default for initialization source. Can be overridden per-property by including ‘init_from’ in the property config dict.
- Raises:
TypeError – If sync value is not a dict
ValueError – If property configuration is invalid or missing ‘widget’ key
- Return type:
- bind_parameter(parameter, property_map, init_from='sync')[source]
Bind multiple properties of a Parameter to different dict keys.
IMPORTANT: Use this method instead of bind_properties() for pyqtgraph Parameters. bind_properties() uses blockSignals() which prevents parameter tree widgets from updating. This method uses callback disconnection instead.
- Parameters:
parameter (
Parameter) – The pyqtgraph Parameter to bindproperty_map (
dict[str,dict[str,Any]]) –Mapping of dict keys to parameter property configurations. Each key maps to a dict with:
Shortcut for parameter value (most common case): - ‘param’: Parameter - Automatically uses sigValueChanged, value(), setValue()
This is equivalent to specifying signal/getter/setter manually
OR Manual specification: - ‘getter’: callable () -> value - Function to get parameter value - ‘setter’: callable (value) -> None - Function to set parameter value - ‘signal’: Signal (optional) - Parameter signal for bidirectional sync
(Note: Parameter signals emit (param, value), this is handled automatically)
’mode’: SyncMode (optional) - BIDIRECTIONAL, TO_SYNC, or FROM_SYNC (default: BIDIRECTIONAL if signal provided, else FROM_SYNC)
init_from (
Literal['sync','widget',None]) – Initialization source: ‘sync’ (default), ‘widget’, or ‘none’
- Raises:
TypeError – If sync value is not a dict
ValueError – If property configuration is invalid
- Return type:
Examples
>>> # Shortcut syntax (recommended for simple value sync) >>> sync = WidgetSync(initial_value={'threshold': 0.5}) >>> sync.bind_parameter( ... threshold_param, ... property_map={'threshold': {'param': threshold_param}} ... )
>>> # Manual syntax (for custom getter/setter or limits) >>> algo_sync = WidgetSync(initial_value={'algorithms': [...], 'algorithm': 'FFT'}) >>> algo_sync.bind_parameter( ... algorithm_param, ... property_map={ ... 'algorithms': { ... 'getter': lambda: algorithm_param.opts['limits'], ... 'setter': algorithm_param.setLimits, ... 'mode': SyncMode.FROM_SYNC, ... }, ... 'algorithm': {'param': algorithm_param} # Shortcut for value ... } ... )
See also
bind_propertiesFor regular Qt widgets (uses blockSignals)
bind_dictFor binding different widgets to different dict keys
- bind_properties(widget, property_map, init_from='sync')[source]
Bind multiple properties of ONE widget to different dict keys.
IMPORTANT: This method is designed for synchronizing multiple properties of a SINGLE widget. All properties control the same widget passed as the first parameter.
- Parameters:
widget (
QWidget) – The widget to bind (all properties control THIS widget)property_map (
dict[str,dict[str,Any]]) –Mapping of dict keys to property configurations. Each key maps to a dict with EITHER:
Simple syntax (recommended for Qt properties): - ‘property’: str - Qt property name (auto-generates getter/setter) - ‘signal’: Signal | str (optional, auto-detected if omitted) - ‘mode’: SyncMode (optional, default: BIDIRECTIONAL) - ‘init_from’: InitFrom (optional, overrides global init_from for this property)
Advanced syntax (for custom logic): - ‘signal’: Signal | None (required for TO_SYNC/BIDIRECTIONAL) - ‘getter’: callable () -> value (required for TO_SYNC/BIDIRECTIONAL) - ‘setter’: callable (value) -> None (required for FROM_SYNC/BIDIRECTIONAL) - ‘mode’: SyncMode (optional, default: inferred from getter/setter) - ‘init_from’: InitFrom (optional, overrides global init_from for this property)
init_from (
Literal['sync','widget',None]) – Global default for initialization source. Can be overridden per-property by including ‘init_from’ in the property config dict.
- Raises:
TypeError – If sync value is not a dict
ValueError – If property configuration is invalid
- Return type:
- pop_from_list(key, index=-1, emit=True)[source]
Pop an item from a list value in the dict.
- Parameters:
- Returns:
The popped item
- Return type:
- Raises:
KeyError – If key doesn’t exist in dict
TypeError – If value at key is not a list
IndexError – If index out of range
Example
>>> sync = DictSync({'items': ['a', 'b', 'c'], 'current': 'a'}) >>> removed = sync.pop_from_list('items', 1) # Returns 'b' >>> sync.value['items'] # Returns ['a', 'c']
- remove_from_list(key, item, emit=True)[source]
Remove an item from a list value in the dict.
- Parameters:
- Raises:
KeyError – If key doesn’t exist in dict
TypeError – If value at key is not a list
ValueError – If item not in list
- Return type:
Example
>>> sync = DictSync({'items': ['a', 'b', 'c'], 'current': 'a'}) >>> sync.remove_from_list('items', 'b') >>> sync.value['items'] # Returns ['a', 'c']
- update_key(key, value, emit=True)[source]
Update a single key in the dict value.
Convenience method that handles copying internally.
- Parameters:
- Return type:
Example
>>> sync = DictSync({'items': ['a', 'b'], 'current': 'a'}) >>> sync.update_key('current', 'b')
- property value: dict
Get current synced dict value.
Returns a shallow copy of the internal dict. Modifying dict keys will not affect the sync, but modifying nested mutable objects (lists, dicts) will affect internal state and should be avoided.
For a fully independent copy, use copy.deepcopy(sync.value).
- class pymodaq_gui.utils.widget_sync.SyncMode(*values)[source]
Bases:
EnumSynchronization modes for widget connections
- BIDIRECTIONAL = 'bidirectional'
- FROM_SYNC = 'from_sync'
- TO_SYNC = 'to_sync'
- class pymodaq_gui.utils.widget_sync.ValueSync(initial_value=None, data_type=None, validator=None)[source]
Bases:
BaseWidgetSyncSynchronize a single value across multiple widgets.
Supports any data type (int, str, bool, float, custom objects). Use bind() to connect widgets.
- Attributes:
valueGet current synced value.
Methods
add(widget[, mode, match, ...])Convenience method to add a widget using auto-detected property sync.
set_value(new_value[, emit])Set value with optional emission control.
- add(widget, mode=SyncMode.BIDIRECTIONAL, match='type', to_sync_transform=None, from_sync_transform=None, init_from='sync')[source]
Convenience method to add a widget using auto-detected property sync.
Automatically detects the property/signal pattern from existing connections.
- Parameters:
widget (
QWidget) – Widget to addmode (
SyncMode) – Sync mode (default: BIDIRECTIONAL)match (
str) – Pattern matching strategy (default: ‘type’): - ‘type’: Match exact widget type (safest) - ‘property’: Match by property/signal namesto_sync_transform (
Optional[Callable[[Any],Any]]) – Transform value from widget to syncfrom_sync_transform (
Optional[Callable[[Any],Any]]) – Transform value from sync to widgetinit_from (
Literal['sync','widget',None]) – Initialization source: ‘sync’ (default), ‘widget’, or None. See bind() for details.
- Raises:
TypeError – If no connection pattern found
ValueError – If match parameter has invalid value
- Return type:
- property value: Any
Get current synced value.
Warning: For mutable values (lists, dicts, custom objects), returns a direct reference to the internal value. Modifying the returned value in-place will affect the sync’s internal state and may cause change detection to fail.
For a fully independent copy of mutable values, use: copy.copy(sync.value) or copy.deepcopy(sync.value)
- class pymodaq_gui.utils.widget_sync.WidgetSync(initial_value: Any = None, data_type: Type | DataType | None = None, validator: Callable[[Any], Any] | None = None)[source]
Bases:
WidgetSyncFactoriesSmart widget synchronization that auto-detects whether to use ValueSync or DictSync.
This is the main class users should use. It automatically: - Uses ValueSync for single values (int, str, bool, float, etc.) - Uses DictSync for dict values - Includes factory methods for common widgets (for_checkbox, for_spinbox, etc.)
- For extending with custom factories:
>>> class MySync(WidgetSync, MyCustomFactories): ... pass
- For direct use of specialized classes:
>>> from pymodaq_gui.utils.widget_sync import ValueSync, DictSync >>> value_sync = ValueSync(initial_value=50) >>> dict_sync = DictSync(initial_value={'a': 1, 'b': 2})
- class pymodaq_gui.utils.widget_sync.WidgetSyncFactories[source]
Bases:
objectMixin class providing factory methods for common Qt widgets.
Users can create their own factory methods by: 1. Inheriting from WidgetSync 2. Adding their own @classmethod factories
Example
- class MyWidgetSync(WidgetSync):
@classmethod def for_my_custom_widget(cls, widget, initial=None):
return cls.for_property(widget, ‘customProperty’, ‘customSignal’, initial)
Methods
for_checkbox(checkbox[, initial, mode, ...])Create a sync for checkbox widgets.
for_combobox(combobox[, initial, use_text, ...])Create a sync for QComboBox widgets.
for_lineedit(lineedit[, initial, mode, ...])Create a sync for QLineEdit widgets.
for_property(widget, property_name[, ...])Create a sync for any Qt property with auto-detection.
for_slider(slider[, initial, mode, validator])Create a sync for QSlider widgets.
for_spinbox(spinbox[, initial, mode, validator])Create a sync for QSpinBox or QDoubleSpinBox widgets.
- classmethod for_checkbox(checkbox, initial=False, mode=None, validator=None)[source]
Create a sync for checkbox widgets.
- Parameters:
checkbox (
QCheckBox) – The first checkbox to syncinitial (bool) – Initial checked state (default: False)
mode (
SyncMode, optional) – Sync mode (default: BIDIRECTIONAL)validator (
collections.abc.Callable, optional) – Optional validator function
- Returns:
A new sync instance
- Return type:
WidgetSync
Example
>>> sync = WidgetSync.for_checkbox(my_checkbox, initial=True) >>> sync.add(another_checkbox)
- classmethod for_combobox(combobox, initial=0, use_text=False, mode=None, validator=None)[source]
Create a sync for QComboBox widgets.
- Parameters:
combobox (
QComboBox) – The first combobox to syncinitial (int | str) – Initial value - index if use_text=False, text if use_text=True (default: 0)
use_text (bool) – If True, sync currentText; if False, sync currentIndex (default: False)
mode (
SyncMode, optional) – Sync mode (default: BIDIRECTIONAL)validator (
collections.abc.Callable, optional) – Optional validator function
- Returns:
A new sync instance
- Return type:
WidgetSync
Examples
>>> # Sync by index (default) >>> sync = WidgetSync.for_combobox(combo1, initial=0) >>> sync.add(combo2)
>>> # Sync by text >>> sync = WidgetSync.for_combobox(combo1, initial="Option A", use_text=True) >>> sync.add(combo2)
- classmethod for_lineedit(lineedit, initial='', mode=None, validator=None)[source]
Create a sync for QLineEdit widgets.
- Parameters:
lineedit (
QLineEdit) – The first line edit to syncinitial (str) – Initial text (default: “”)
mode (
SyncMode, optional) – Sync mode (default: BIDIRECTIONAL)validator (
collections.abc.Callable, optional) – Optional validator function
- Returns:
A new sync instance
- Return type:
WidgetSync
Example
>>> sync = WidgetSync.for_lineedit(edit1, initial="Hello") >>> sync.add(edit2)
- classmethod for_property(widget, property_name, signal_name=None, initial=None, mode=None, data_type=None, validator=None)[source]
Create a sync for any Qt property with auto-detection.
This is the most flexible factory - all others build on this.
- Parameters:
widget (QWidget) – The first widget to sync
property_name (str) – Name of the Qt property (e.g., ‘checked’, ‘value’, ‘text’)
signal_name (str | None) – Name of the change signal. If None, auto-detects from property
initial (Any) – Initial value (if None, uses widget’s current value)
mode (SyncMode | None) – Sync mode (default: BIDIRECTIONAL)
data_type (type | None) – Explicit data type for type checking (default: inferred from initial)
validator (Any) – Optional validator function to correct/constrain values
- Returns:
A new sync instance with the widget connected
- Return type:
WidgetSync
Example
>>> sync = WidgetSync.for_property(my_spinbox, 'value', initial=50) >>> sync.add(other_spinbox) # Add more spinboxes
- classmethod for_slider(slider, initial=0, mode=None, validator=None)[source]
Create a sync for QSlider widgets.
- Parameters:
slider (
QSlider) – The first slider to syncinitial (int) – Initial value (default: 0)
mode (
SyncMode, optional) – Sync mode (default: BIDIRECTIONAL)validator (
collections.abc.Callable, optional) – Optional validator function
- Returns:
A new sync instance
- Return type:
WidgetSync
Example
>>> sync = WidgetSync.for_slider(slider1, initial=75) >>> sync.add(slider2)
- classmethod for_spinbox(spinbox, initial=0, mode=None, validator=None)[source]
Create a sync for QSpinBox or QDoubleSpinBox widgets.
- Parameters:
spinbox (
QSpinBox | QDoubleSpinBox) – The first spinbox to syncinitial (int | float) – Initial value (default: 0)
mode (
SyncMode, optional) – Sync mode (default: BIDIRECTIONAL)validator (
collections.abc.Callable, optional) – Optional validator function
- Returns:
A new sync instance
- Return type:
WidgetSync
Example
>>> sync = WidgetSync.for_spinbox(spinbox1, initial=50) >>> sync.add(spinbox2) >>> sync.add(spinbox3)