UI Services Architecture
Consolidated service layer for ParameterFormManager operations.
The Problem: Monolithic Form Manager
The ParameterFormManager class originally contained all logic for parameter forms in a single 2600+ line class: widget finding, value collection, signal management, parameter operations, form initialization, and styling. This monolithic design made the code hard to test, extend, and maintain. Changes to one concern (e.g., widget styling) required understanding the entire class.
The Solution: Service-Oriented Architecture
The UI services extract specialized responsibilities into focused service classes, each handling one concern. Services are grouped by related functionality (widget operations, value collection, signal management, parameter operations, form initialization) while maintaining clean interfaces. This enables testing individual services in isolation and makes the codebase easier to understand and extend.
Overview
The UI services provide a clean separation of concerns for the ParameterFormManager. Originally implemented as 17+ separate service files, these have been consolidated into 5 cohesive services plus 2 base classes, reducing complexity while maintaining all functionality.
Service Consolidation
The services were restructured following the principle of grouping related functionality:
New Service |
Merged From |
Responsibility |
|---|---|---|
|
WidgetFinder, WidgetStyling, WidgetUpdate |
Widget finding, styling, and value updates |
|
NestedValueCollection, DataclassReconstruction, DataclassUnpacker |
Value collection and dataclass operations |
|
SignalBlocking, SignalConnection, CrossWindowRegistration |
Signal management and cross-window updates |
|
ParameterReset, PlaceholderRefresh |
Parameter operations and placeholder management |
|
InitializationServices, InitializationStepFactory, FormBuildOrchestrator, InitialRefreshStrategy |
Form initialization and widget building |
Standalone services kept as-is:
EnabledFieldStylingService- Specific concern for enabled/disabled field stylingFlagContextManager- Clean context manager for manager flagsFieldChangeDispatcher- Unified event-driven field change handling (see Field Change Dispatcher Architecture)ParameterServiceABC,EnumDispatchService- Base classes for type-safe dispatch
WidgetService
Consolidated service for widget finding, styling, and value updates.
from pyqt_reactive.services.widget_service import WidgetService
# Find widgets
checkbox = WidgetService.find_optional_checkbox(manager, param_name)
widget = WidgetService.get_widget_safe(manager, param_name)
# Style widgets
WidgetService.make_readonly(widget, color_scheme)
WidgetService.apply_dimming(widget, opacity=0.5)
# Update widget values (instance method)
service = WidgetService()
service.update_widget_value(widget, value, param_name, manager=manager)
Key methods:
find_optional_checkbox(manager, param_name)- Find optional checkbox for a parameterfind_nested_checkbox(manager, param_name)- Find checkbox in nested managerfind_group_box(container, group_box_type)- Find group box within containerget_widget_safe(manager, param_name)- Safely get widget from managermake_readonly(widget, color_scheme)- Make widget read-only without greyingupdate_widget_value(widget, value, param_name, ...)- Update widget with signal blocking
ValueCollectionService
Handles value collection from nested managers and dataclass operations.
It works hand-in-hand with ObjectState:
PFM uses ObjectState as the single source of truth for parameters and nested states.
Live context collection walks ObjectStateRegistry (not PFMs) and uses
get_user_modified_values/overlays.Cancel/save flows rely on ObjectState baselines (mark_saved/restore_saved).
from pyqt_reactive.services.value_collection_service import ValueCollectionService
service = ValueCollectionService()
# Collect nested value with type-safe dispatch
value = service.collect_nested_value(manager, param_name, nested_manager)
# Unpack dataclass fields to instance attributes
ValueCollectionService.unpack_to_self(target, source, prefix="config_")
Uses discriminated union dispatch based on ParameterInfo types:
OptionalDataclassInfo- Optional[Dataclass] parametersDirectDataclassInfo- Direct dataclass parametersGenericInfo- Generic/primitive parameters
SignalService
Manages Qt signal blocking, connection, and cross-window registration.
from pyqt_reactive.services.signal_service import SignalService
# Block signals (context manager)
with SignalService.block_signals(checkbox):
checkbox.setChecked(True)
# Block multiple widgets
with SignalService.block_signals(widget1, widget2):
widget1.setValue(1)
widget2.setValue(2)
# Connect all signals for a manager
SignalService.connect_all_signals(manager)
# Cross-window registration
with SignalService.cross_window_registration(manager):
dialog.exec()
ParameterOpsService
Handles parameter reset and placeholder refresh operations.
from pyqt_reactive.services.parameter_ops_service import ParameterOpsService
service = ParameterOpsService()
# Reset parameter with type-safe dispatch
service.reset_parameter(manager, param_name)
# Refresh placeholders with live context from other windows
service.refresh_with_live_context(manager)
# Refresh all placeholders in a form
service.refresh_all_placeholders(manager)
Uses discriminated union dispatch for reset operations:
_reset_OptionalDataclassInfo- Reset Optional[Dataclass] with checkbox sync_reset_DirectDataclassInfo- Reset direct dataclass via nested manager_reset_GenericInfo- Reset generic field with context-aware value
FormInitService
Orchestrates form initialization with metaprogrammed services.
from pyqt_reactive.services.form_init_service import (
FormBuildOrchestrator,
InitialRefreshStrategy,
ParameterExtractionService,
ConfigBuilderService,
ServiceFactoryService
)
# Extract parameters using metaprogrammed service
extracted = ParameterExtractionService.build(object_instance, exclude_params, initial_values)
# Build config using metaprogrammed service
form_config = ConfigBuilderService.build(field_id, extracted, context_obj, color_scheme, parent_manager, service)
# Create all services using metaprogrammed factory
services = ServiceFactoryService.build()
# Build widgets with unified async/sync path
orchestrator = FormBuildOrchestrator()
orchestrator.build_widgets(manager, content_layout, param_infos, use_async=True)
# Execute initial refresh strategy
InitialRefreshStrategy.execute(manager)
Key components:
ParameterExtractionService- Extracts parameters from object instanceConfigBuilderService- Builds ParameterFormConfig with derived valuesServiceFactoryService- Auto-instantiates all manager servicesFormBuildOrchestrator- Handles async/sync widget creationInitialRefreshStrategy- Enum-driven initial placeholder refresh
Type-Safe Dispatch Pattern
Services use discriminated union dispatch via ParameterServiceABC:
class ParameterOpsService(ParameterServiceABC):
def _get_handler_prefix(self) -> str:
return '_reset_'
def _reset_OptionalDataclassInfo(self, info, manager) -> None:
# Handle Optional[Dataclass] reset
...
def _reset_DirectDataclassInfo(self, info, manager) -> None:
# Handle direct Dataclass reset
...
def _reset_GenericInfo(self, info, manager) -> None:
# Handle generic field reset
...
This pattern eliminates if/elif type-checking smell with polymorphic dispatch
based on the concrete ParameterInfo type.
Architecture Benefits
The consolidated architecture provides:
Reduced File Count: 17 files → 9 files (including base classes)
Cohesive Grouping: Related functionality in single services
Consistent Patterns: All services use same ABC-based dispatch
Clear Responsibilities: Each service has well-defined scope
Easier Discovery: Developers find functionality in fewer places
Maintainability: Changes localized to single service files
See Also
Field Change Dispatcher Architecture - Unified event-driven field change handling
Service Layer Architecture - Framework-agnostic service layer patterns
Parameter Form Service Architecture - ParameterFormService architecture
Parameter Form Lifecycle Management - Form lifecycle management