Skip to content

NumberInput

Usage

import toga

widget = toga.NumberInput(min=1, max=10, step=0.001)
widget.value = 2.718

NumberInput's properties can accept [Decimal][decimal], [int][], [float][], or [str][] containing numbers, but they always return [Decimal][decimal] objects to ensure precision is retained.

Reference

Bases: Widget

Source code in core/src/toga/widgets/numberinput.py
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
class NumberInput(Widget):
    def __init__(
        self,
        id: str | None = None,
        style: StyleT | None = None,
        step: NumberInputT = 1,
        min: NumberInputT | None = None,
        max: NumberInputT | None = None,
        value: NumberInputT | None = None,
        readonly: bool = False,
        on_change: toga.widgets.numberinput.OnChangeHandler | None = None,
        **kwargs,
    ):
        """Create a new number input widget.

        :param id: The ID for the widget.
        :param style: A style object. If no style is provided, a default style will be
            applied to the widget.
        :param step: The amount that any increment/decrement operations will apply to
            the widget's current value.
        :param min: If provided, [`value`][toga.NumberInput.value] will be guaranteed to
            be greater than or equal to this minimum.
        :param max: If provided, [`value`][toga.NumberInput.value] will be guaranteed to
            be less than or equal to this maximum.
        :param value: The initial value for the widget.
        :param readonly: Can the value of the widget be modified by the user?
        :param on_change: A handler that will be invoked when the value of the widget
            changes.
        :param kwargs: Initial style properties.
        """
        # The initial setting of min requires calling get_value(),
        # which in turn interrogates min. Prime those values with
        # an empty starting value
        self._min: Decimal | None = None
        self._max: Decimal | None = None

        self.on_change = None

        super().__init__(id, style, **kwargs)

        self.readonly = readonly
        self.step = step
        self.min = min
        self.max = max
        self.value = value

        self.on_change = on_change

    def _create(self) -> Any:
        return self.factory.NumberInput(interface=self)

    @property
    def readonly(self) -> bool:
        """Can the value of the widget be modified by the user?

        This only controls manual changes by the user (i.e., typing at the
        keyboard). Programmatic changes are permitted while the widget has
        `readonly` enabled.
        """
        return self._impl.get_readonly()

    @readonly.setter
    def readonly(self, value: object) -> None:
        self._impl.set_readonly(value)

    @property
    def step(self) -> Decimal:
        """The amount that any increment/decrement operations will apply to the
        widget's current value. (Not all backends provide increment and
        decrement buttons.)
        """
        return self._step

    @step.setter
    def step(self, step: NumberInputT) -> None:
        try:
            self._step = _clean_decimal(step)
        except (ValueError, TypeError, InvalidOperation) as exc:
            raise ValueError("step must be a number") from exc

        self._impl.set_step(self._step)

        # Re-assigning the min and max value forces the min/max to be re-quantized.
        self.min = self.min
        self.max = self.max

    @property
    def min(self) -> Decimal | None:
        """The minimum bound for the widget's value.

        Returns [`None`][] if there is no minimum bound.

        When setting this property, the current [`value`][toga.NumberInput.value] and
        [`max`][toga.NumberInput.max] will be
        clipped against the new minimum value.
        """
        return self._min

    @min.setter
    def min(self, new_min: NumberInputT | None) -> None:
        try:
            new_min = _clean_decimal(new_min, self.step)

            # Clip widget's value to the new minimum
            if self.value is not None and self.value < new_min:
                self.value = new_min
        except (TypeError, ValueError, InvalidOperation) as exc:
            if new_min is None or new_min == "":
                new_min = None
            else:
                raise ValueError("min must be a number or None") from exc

        # Clip the max value if it's inconsistent with the new min
        if self.max is not None and new_min is not None and new_min > self.max:
            self.max = new_min

        self._min = new_min
        self._impl.set_min_value(new_min)

    @property
    def max(self) -> Decimal | None:
        """The maximum bound for the widget's value.

        Returns [`None`][] if there is no maximum bound.

        When setting this property, the current [`value`][toga.NumberInput.value] and
        [`min`][toga.NumberInput.min] will be
        clipped against the new maximum value.
        """
        return self._max

    @max.setter
    def max(self, new_max: NumberInputT | None) -> None:
        try:
            new_max = _clean_decimal(new_max, self.step)

            # Clip widget's value to the new maximum
            if self.value is not None and self.value > new_max:
                self.value = new_max
        except (TypeError, ValueError, InvalidOperation) as exc:
            if new_max is None or new_max == "":
                new_max = None
            else:
                raise ValueError("max must be a number or None") from exc

        # Clip the min value if it's inconsistent with the new max
        if self.min is not None and new_max is not None and new_max < self.min:
            self.min = new_max

        self._max = new_max
        self._impl.set_max_value(new_max)

    @property
    def value(self) -> Decimal | None:
        """Current value of the widget, rounded to the same number of decimal
        places as [`step`][toga.NumberInput.step], or `None` if no value has been set.

        If this property is set to a value outside of the min/max range, it will be
        clipped.

        While the widget is being edited by the user, it is possible for the UI
        to contain a value which is outside of the min/max range, or has too many
        decimal places. In this case, this property will return a value that has been
        clipped and rounded, and the visible text will be updated to match as soon as
        the widget loses focus.
        """
        # Get the value currently displayed by the widget. This *could*
        # be outside the min/max range.
        value = self._impl.get_value()

        # If the widget has a current value, clip it
        if value is not None:
            if self.min is not None and value < self.min:
                return self.min
            elif self.max is not None and value > self.max:
                return self.max
        return value

    @value.setter
    def value(self, value: NumberInputT | None) -> None:
        try:
            value = _clean_decimal(value, self.step)

            if self.min is not None and value < self.min:
                value = self.min
            elif self.max is not None and value > self.max:
                value = self.max
        except (TypeError, ValueError, InvalidOperation) as exc:
            if value is None or value == "":
                value = None
            else:
                raise ValueError("value must be a number or None") from exc

        self._impl.set_value(value)
        self.refresh()

    @property
    def on_change(self) -> OnChangeHandler:
        """The handler to invoke when the value of the widget changes."""
        return self._on_change

    @on_change.setter
    def on_change(self, handler: toga.widgets.numberinput.OnChangeHandler) -> None:
        self._on_change = wrapped_handler(self, handler)

max property writable

The maximum bound for the widget's value.

Returns [None][] if there is no maximum bound.

When setting this property, the current value and min will be clipped against the new maximum value.

min property writable

The minimum bound for the widget's value.

Returns [None][] if there is no minimum bound.

When setting this property, the current value and max will be clipped against the new minimum value.

on_change property writable

The handler to invoke when the value of the widget changes.

readonly property writable

Can the value of the widget be modified by the user?

This only controls manual changes by the user (i.e., typing at the keyboard). Programmatic changes are permitted while the widget has readonly enabled.

step property writable

The amount that any increment/decrement operations will apply to the widget's current value. (Not all backends provide increment and decrement buttons.)

value property writable

Current value of the widget, rounded to the same number of decimal places as step, or None if no value has been set.

If this property is set to a value outside of the min/max range, it will be clipped.

While the widget is being edited by the user, it is possible for the UI to contain a value which is outside of the min/max range, or has too many decimal places. In this case, this property will return a value that has been clipped and rounded, and the visible text will be updated to match as soon as the widget loses focus.

__init__(id=None, style=None, step=1, min=None, max=None, value=None, readonly=False, on_change=None, **kwargs)

Create a new number input widget.

:param id: The ID for the widget. :param style: A style object. If no style is provided, a default style will be applied to the widget. :param step: The amount that any increment/decrement operations will apply to the widget's current value. :param min: If provided, value will be guaranteed to be greater than or equal to this minimum. :param max: If provided, value will be guaranteed to be less than or equal to this maximum. :param value: The initial value for the widget. :param readonly: Can the value of the widget be modified by the user? :param on_change: A handler that will be invoked when the value of the widget changes. :param kwargs: Initial style properties.

Source code in core/src/toga/widgets/numberinput.py
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
def __init__(
    self,
    id: str | None = None,
    style: StyleT | None = None,
    step: NumberInputT = 1,
    min: NumberInputT | None = None,
    max: NumberInputT | None = None,
    value: NumberInputT | None = None,
    readonly: bool = False,
    on_change: toga.widgets.numberinput.OnChangeHandler | None = None,
    **kwargs,
):
    """Create a new number input widget.

    :param id: The ID for the widget.
    :param style: A style object. If no style is provided, a default style will be
        applied to the widget.
    :param step: The amount that any increment/decrement operations will apply to
        the widget's current value.
    :param min: If provided, [`value`][toga.NumberInput.value] will be guaranteed to
        be greater than or equal to this minimum.
    :param max: If provided, [`value`][toga.NumberInput.value] will be guaranteed to
        be less than or equal to this maximum.
    :param value: The initial value for the widget.
    :param readonly: Can the value of the widget be modified by the user?
    :param on_change: A handler that will be invoked when the value of the widget
        changes.
    :param kwargs: Initial style properties.
    """
    # The initial setting of min requires calling get_value(),
    # which in turn interrogates min. Prime those values with
    # an empty starting value
    self._min: Decimal | None = None
    self._max: Decimal | None = None

    self.on_change = None

    super().__init__(id, style, **kwargs)

    self.readonly = readonly
    self.step = step
    self.min = min
    self.max = max
    self.value = value

    self.on_change = on_change

Bases: Protocol

Source code in core/src/toga/widgets/numberinput.py
71
72
73
74
75
76
77
class OnChangeHandler(Protocol):
    def __call__(self, widget: NumberInput, **kwargs: Any) -> None:
        """A handler to invoke when the value is changed.

        :param widget: The NumberInput that was changed.
        :param kwargs: Ensures compatibility with arguments added in future versions.
        """

__call__(widget, **kwargs)

A handler to invoke when the value is changed.

:param widget: The NumberInput that was changed. :param kwargs: Ensures compatibility with arguments added in future versions.

Source code in core/src/toga/widgets/numberinput.py
72
73
74
75
76
77
def __call__(self, widget: NumberInput, **kwargs: Any) -> None:
    """A handler to invoke when the value is changed.

    :param widget: The NumberInput that was changed.
    :param kwargs: Ensures compatibility with arguments added in future versions.
    """