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:

Service Consolidation

New Service

Merged From

Responsibility

WidgetService

WidgetFinder, WidgetStyling, WidgetUpdate

Widget finding, styling, and value updates

ValueCollectionService

NestedValueCollection, DataclassReconstruction, DataclassUnpacker

Value collection and dataclass operations

SignalService

SignalBlocking, SignalConnection, CrossWindowRegistration

Signal management and cross-window updates

ParameterOpsService

ParameterReset, PlaceholderRefresh

Parameter operations and placeholder management

FormInitService

InitializationServices, InitializationStepFactory, FormBuildOrchestrator, InitialRefreshStrategy

Form initialization and widget building

Standalone services kept as-is:

  • EnabledFieldStylingService - Specific concern for enabled/disabled field styling

  • FlagContextManager - Clean context manager for manager flags

  • FieldChangeDispatcher - 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 parameter

  • find_nested_checkbox(manager, param_name) - Find checkbox in nested manager

  • find_group_box(container, group_box_type) - Find group box within container

  • get_widget_safe(manager, param_name) - Safely get widget from manager

  • make_readonly(widget, color_scheme) - Make widget read-only without greying

  • update_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] parameters

  • DirectDataclassInfo - Direct dataclass parameters

  • GenericInfo - 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 instance

  • ConfigBuilderService - Builds ParameterFormConfig with derived values

  • ServiceFactoryService - Auto-instantiates all manager services

  • FormBuildOrchestrator - Handles async/sync widget creation

  • InitialRefreshStrategy - 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