Module m45wxcontrols.accessible_spin

Accessible spinbutton class

Classes

class AccessibleSpinCtrl (parent, label_text, initial_val, min_val, max_val, inc)
Expand source code
class AccessibleSpinCtrl(wx.BoxSizer):
    """
    Accessible floating-point spin control composed of wx widgets.

    The control exposes a labeled text field as the primary interaction point
    and keeps a wx.SpinButton in sync for mouse users. Arrow keys on the text
    field adjust the value directly, which makes the widget easier to use with
    screen readers.
    """

    def __init__(self, parent, label_text, initial_val, min_val, max_val, inc):
        """
        Initialize accessible spin control.

        Args:
            parent: Parent wx window that owns the child controls.
            label_text: Label shown next to the text field and used as its
                accessible name.
            initial_val: Initial numeric value displayed in the control.
            min_val: Minimum allowed value.
            max_val: Maximum allowed value.
            inc: Increment used by the spin button and Up/Down arrow keys.
        """
        super().__init__(wx.HORIZONTAL)
        
        self.min_val = min_val
        self.max_val = max_val
        self.inc = inc
        
        # 1. The Label
        self.label = wx.StaticText(parent, label=label_text)
        self.Add(self.label, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
        
        # 2. The TextCtrl (The primary interaction point)
        self.text_ctrl = wx.TextCtrl(parent, value=f"{initial_val:.1f}")
        self.text_ctrl.SetName(label_text)
        self.Add(self.text_ctrl, 1, wx.EXPAND | wx.ALL, 5)
        
        # 3. The SpinButton (Visible for mouse users)
        self.spin_btn = wx.SpinButton(parent, style=wx.SP_VERTICAL)
        self.spin_btn.SetRange(int(min_val / inc), int(max_val / inc))
        self.spin_btn.SetValue(int(initial_val / inc))
        self.spin_btn.SetCanFocus(False)
        self.Add(self.spin_btn, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
        
        # Bindings
        self.spin_btn.Bind(wx.EVT_SPIN, self.on_spin)
        self.text_ctrl.Bind(wx.EVT_TEXT, self.on_text_entry)
        
        # Accessibility: Allow Up/Down arrow keys directly in the TextCtrl
        self.text_ctrl.Bind(wx.EVT_KEY_DOWN, self.on_key_down)

    def Bind(self, event_type, handler, *args, **kwargs):
        """
        Proxy method to allow the dialog to bind to changes.
        We map any binding attempt to our internal controls.

        Args:
            event_type: Ignored wx event binder supplied by the caller.
            handler: Callable to bind to text and spin changes.
            *args: Additional positional arguments forwarded to wx.Bind.
            **kwargs: Additional keyword arguments forwarded to wx.Bind.
        """
        # We bind to both text changes and spin changes
        self.text_ctrl.Bind(wx.EVT_TEXT, handler, *args, **kwargs)
        self.spin_btn.Bind(wx.EVT_SPIN, handler, *args, **kwargs)

    def _adjust_value(self, steps):
        """
        Internal helper to increment/decrement the value.

        Args:
            steps: Number of increments to apply. Negative values decrement.
        """
        try:
            current = float(self.text_ctrl.GetValue())
            new_val = current + (steps * self.inc)
            # Clamp value
            new_val = max(self.min_val, min(self.max_val, new_val))
            
            # Update both controls
            formatted_val = f"{new_val:.1f}"
            self.text_ctrl.SetValue(formatted_val) 
            # Note: SetValue triggers NVDA to read the new content
            
            self.spin_btn.SetValue(int(new_val / self.inc))
        except ValueError:
            pass

    def on_spin(self, event):
        """
        Handle spin button changes and mirror the value into the text field.

        Args:
            event: wx spin event containing the current spin position.
        """
        new_val = event.GetPosition() * self.inc
        self.text_ctrl.ChangeValue(f"{new_val:.1f}")
        event.Skip() # CRITICAL: Allows the event to propagate to the dialog

    def on_text_entry(self, event):
        """
        Handle manual text edits and keep the spin button position in sync.

        Args:
            event: wx text event emitted by the internal text control.
        """
        try:
            val = float(self.text_ctrl.GetValue())
            self.spin_btn.SetValue(int(val / self.inc))
        except ValueError:
            pass
        event.Skip() # CRITICAL: Allows the event to propagate to the dialog

    def on_key_down(self, event):
        """
        Handle keyboard increments from the internal text control.

        Args:
            event: wx key event. Up and Down adjust the value; all other keys
                are passed through.
        """
        key = event.GetKeyCode()
        if key in (wx.WXK_UP, wx.WXK_DOWN):
            steps = 1 if key == wx.WXK_UP else -1
            self._adjust_value(steps)
            
            # Manually fire a text event so the dialog knows something changed
            # since _adjust_value uses SetValue/ChangeValue
            cmd_event = wx.CommandEvent(wx.wxEVT_TEXT, self.text_ctrl.GetId())
            cmd_event.SetString(self.text_ctrl.GetValue())
            self.text_ctrl.GetEventHandler().ProcessEvent(cmd_event)
        else:
            event.Skip()

    def GetValue(self):
        """
        Return the current numeric value.

        Returns:
            float: Parsed text control value, or 2.0 if the text is not a
            valid number.
        """
        try:
            return float(self.text_ctrl.GetValue())
        except ValueError:
            return 2.0

Accessible floating-point spin control composed of wx widgets.

The control exposes a labeled text field as the primary interaction point and keeps a wx.SpinButton in sync for mouse users. Arrow keys on the text field adjust the value directly, which makes the widget easier to use with screen readers.

Initialize accessible spin control.

Args

parent
Parent wx window that owns the child controls.
label_text
Label shown next to the text field and used as its accessible name.
initial_val
Initial numeric value displayed in the control.
min_val
Minimum allowed value.
max_val
Maximum allowed value.
inc
Increment used by the spin button and Up/Down arrow keys.

Ancestors

  • wx._core.BoxSizer
  • wx._core.Sizer
  • wx._core.Object
  • sip.wrapper
  • sip.simplewrapper

Methods

def Bind(self, event_type, handler, *args, **kwargs)
Expand source code
def Bind(self, event_type, handler, *args, **kwargs):
    """
    Proxy method to allow the dialog to bind to changes.
    We map any binding attempt to our internal controls.

    Args:
        event_type: Ignored wx event binder supplied by the caller.
        handler: Callable to bind to text and spin changes.
        *args: Additional positional arguments forwarded to wx.Bind.
        **kwargs: Additional keyword arguments forwarded to wx.Bind.
    """
    # We bind to both text changes and spin changes
    self.text_ctrl.Bind(wx.EVT_TEXT, handler, *args, **kwargs)
    self.spin_btn.Bind(wx.EVT_SPIN, handler, *args, **kwargs)

Proxy method to allow the dialog to bind to changes. We map any binding attempt to our internal controls.

Args

event_type
Ignored wx event binder supplied by the caller.
handler
Callable to bind to text and spin changes.
*args
Additional positional arguments forwarded to wx.Bind.
**kwargs
Additional keyword arguments forwarded to wx.Bind.
def GetValue(self)
Expand source code
def GetValue(self):
    """
    Return the current numeric value.

    Returns:
        float: Parsed text control value, or 2.0 if the text is not a
        valid number.
    """
    try:
        return float(self.text_ctrl.GetValue())
    except ValueError:
        return 2.0

Return the current numeric value.

Returns

float
Parsed text control value, or 2.0 if the text is not a

valid number.

def on_key_down(self, event)
Expand source code
def on_key_down(self, event):
    """
    Handle keyboard increments from the internal text control.

    Args:
        event: wx key event. Up and Down adjust the value; all other keys
            are passed through.
    """
    key = event.GetKeyCode()
    if key in (wx.WXK_UP, wx.WXK_DOWN):
        steps = 1 if key == wx.WXK_UP else -1
        self._adjust_value(steps)
        
        # Manually fire a text event so the dialog knows something changed
        # since _adjust_value uses SetValue/ChangeValue
        cmd_event = wx.CommandEvent(wx.wxEVT_TEXT, self.text_ctrl.GetId())
        cmd_event.SetString(self.text_ctrl.GetValue())
        self.text_ctrl.GetEventHandler().ProcessEvent(cmd_event)
    else:
        event.Skip()

Handle keyboard increments from the internal text control.

Args

event
wx key event. Up and Down adjust the value; all other keys are passed through.
def on_spin(self, event)
Expand source code
def on_spin(self, event):
    """
    Handle spin button changes and mirror the value into the text field.

    Args:
        event: wx spin event containing the current spin position.
    """
    new_val = event.GetPosition() * self.inc
    self.text_ctrl.ChangeValue(f"{new_val:.1f}")
    event.Skip() # CRITICAL: Allows the event to propagate to the dialog

Handle spin button changes and mirror the value into the text field.

Args

event
wx spin event containing the current spin position.
def on_text_entry(self, event)
Expand source code
def on_text_entry(self, event):
    """
    Handle manual text edits and keep the spin button position in sync.

    Args:
        event: wx text event emitted by the internal text control.
    """
    try:
        val = float(self.text_ctrl.GetValue())
        self.spin_btn.SetValue(int(val / self.inc))
    except ValueError:
        pass
    event.Skip() # CRITICAL: Allows the event to propagate to the dialog

Handle manual text edits and keep the spin button position in sync.

Args

event
wx text event emitted by the internal text control.