Skip to content

Selection

Usage

The Selection uses a ListSource to manage the list of options. If items is not specified as a ListSource, it will be converted into a ListSource at runtime.

The simplest instantiation of a Selection is to use a list of strings. If a list of non-string objects are provided, they will be converted into a string for display purposes, but the original data type will be retained when returning the current value. If the string value contains newlines, only the substring up to the first newline will be displayed.

import toga

selection = toga.Selection(items=["Alice", "Bob", "Charlie"])

# Change the selection to "Charlie"
selection.value = "Charlie"

# Which item is currently selected? This will print "Charlie"
print(f"Currently selected: {selection.value}")

A Selection can also be used to display a list of dictionaries, with the accessor detailing which attribute of the dictionary will be used for display purposes. When the current value of the widget is retrieved, a Row object will be returned; this Row object will have all the original data as attributes on the Row. In the following example, the GUI will only display the names in the list of items, but the age will be available as an attribute on the selected item.

import toga

selection = toga.Selection(
    items=[
        {"name": "Alice", "age": 37},
        {"name": "Bob", "age": 42},
        {"name": "Charlie", "age": 24},
    ],
    accessor="name",
)

# Select Bob explicitly
selection.value = selection.items[1]

# What is the age of the currently selected person? This will print 42
print(f"Age of currently selected person: {selection.value.age}")

# Select Charlie by searching
selection.value = selection.items.find(name="Charlie")

Notes

  • On macOS and Android, you cannot change the font of a Selection.
  • On macOS, GTK and Android, you cannot change the text color, background color, or text alignment of labels in a Selection.
  • On GTK, a Selection widget with flexible sizing will expand its width (to the extent possible) to accommodate any changes in content (for example, to accommodate a long label). However, if the content subsequently decreases in width, the Selection widget will not shrink. It will retain the size necessary to accommodate the longest label it has historically contained.
  • On iOS, the size of the Selection widget does not adapt to the size of the currently displayed content, or the potential list of options.

Reference

Bases: Widget

Source code in core/src/toga/widgets/selection.py
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 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
class Selection(Widget):
    def __init__(
        self,
        id: str | None = None,
        style: StyleT | None = None,
        items: ListSourceT | Iterable | None = None,
        accessor: str | None = None,
        value: object | None = None,
        on_change: toga.widgets.selection.OnChangeHandler | None = None,
        enabled: bool = True,
        **kwargs,
    ):
        """Create a new Selection 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 items: Initial [`items`][toga.Selection.items] to display for selection.
        :param accessor: The accessor to use to extract display values from the list of
            items. See [`items`][toga.Selection.items] and
            [`value`][toga.Selection.value] for details on how
            `accessor` alters the interpretation of data in the Selection.
        :param value: Initial value for the selection. If unspecified, the first item in
            `items` will be selected.
        :param on_change: Initial [`on_change`][toga.Selection.on_change] handler.
        :param enabled: Whether the user can interact with the widget.
        :param kwargs: Initial style properties.
        """

        self._items: ListSourceT | ListSource

        self.on_change = None  # needed for _impl initialization

        self._accessor = accessor
        super().__init__(id, style, **kwargs)

        self.items = items
        if value:
            self.value = value

        self.on_change = on_change
        self.enabled = enabled

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

    @property
    def items(self) -> ListSourceT | ListSource:
        """The items to display in the selection.

        When setting this property:

        * A [`Source`][toga.sources.Source] will be used as-is. It must either be a
        [`ListSource`][toga.sources.ListSource], or
          a custom class that provides the same methods.

        * A value of None is turned into an empty ListSource.

        * Otherwise, the value must be an iterable, which is copied into a new
          ListSource using the widget's accessor, or "value" if no accessor was
          specified. Items are converted as shown [here][listsource-item].
        """
        return self._items

    @items.setter
    def items(self, items: ListSourceT | Iterable | None) -> None:
        if getattr(self, "_items", None) is not None:
            self._items.remove_listener(self._impl)

        if self._accessor is None:
            accessors = ["value"]
        else:
            accessors = [self._accessor]

        match items:
            case None:
                self._items = ListSource(accessors=accessors, data=[])
            case Source() if self._accessor is None:
                raise ValueError("Must specify an accessor to use a data source")
            case Source():
                self._items = items
            case _:
                self._items = ListSource(accessors=accessors, data=items)

        self._items.add_listener(self._impl)

        # Temporarily halt notifications
        orig_on_change = self._on_change
        self.on_change = None

        # Clear the widget, and insert all the data rows
        self._impl.source_clear()
        for index, item in enumerate(self.items):
            self._impl.source_insert(index=index, item=item)

        # Restore the original change handler and trigger it.
        self._on_change = orig_on_change
        self.on_change()

        self.refresh()

    def _title_for_item(self, item: Any) -> str:
        """Internal utility method; return the display title for an item"""
        if self._accessor:
            title = getattr(item, self._accessor)
        else:
            title = item.value

        return str(title).split("\n")[0]

    @property
    def value(self) -> object | None:
        """The currently selected item.

        Returns None if there are no items in the selection.

        If an `accessor` was specified when the Selection was constructed, the value
        returned will be Row objects from the ListSource; to change the selection, a Row
        object from the ListSource must be provided.

        If no `accessor` was specified when the Selection was constructed, the value
        returned will be the value stored as the `value` attribute on the Row object.
        When setting the value, the widget will search for the first Row object whose
        `value` attribute matches the provided value. In practice, this means that you
        can treat the selection as containing a list of literal values, rather than a
        ListSource containing Row objects.
        """
        index = self._impl.get_selected_index()
        if index is None:
            return None

        item = self._items[index]
        # If there was no accessor specified, the data values are literals.
        # Dereference the value out of the Row object.
        if item and self._accessor is None:
            return item.value
        return item

    @value.setter
    def value(self, value: object) -> None:
        try:
            if self._accessor is None:
                item = self._items.find({"value": value})
            else:
                item = value

            index = self._items.index(item)
            self._impl.select_item(index=index, item=item)
        except ValueError as exc:
            raise ValueError(
                f"{value!r} is not a current item in the selection"
            ) from exc

    @property
    def on_change(self) -> OnChangeHandler:
        """Handler to invoke when the value of the selection is changed,
        either by the user or programmatically."""
        return self._on_change

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

items property writable

The items to display in the selection.

When setting this property:

  • A Source will be used as-is. It must either be a ListSource, or a custom class that provides the same methods.

  • A value of None is turned into an empty ListSource.

  • Otherwise, the value must be an iterable, which is copied into a new ListSource using the widget's accessor, or "value" if no accessor was specified. Items are converted as shown here.

on_change property writable

Handler to invoke when the value of the selection is changed, either by the user or programmatically.

value property writable

The currently selected item.

Returns None if there are no items in the selection.

If an accessor was specified when the Selection was constructed, the value returned will be Row objects from the ListSource; to change the selection, a Row object from the ListSource must be provided.

If no accessor was specified when the Selection was constructed, the value returned will be the value stored as the value attribute on the Row object. When setting the value, the widget will search for the first Row object whose value attribute matches the provided value. In practice, this means that you can treat the selection as containing a list of literal values, rather than a ListSource containing Row objects.

__init__(id=None, style=None, items=None, accessor=None, value=None, on_change=None, enabled=True, **kwargs)

Create a new Selection 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 items: Initial items to display for selection. :param accessor: The accessor to use to extract display values from the list of items. See items and value for details on how accessor alters the interpretation of data in the Selection. :param value: Initial value for the selection. If unspecified, the first item in items will be selected. :param on_change: Initial on_change handler. :param enabled: Whether the user can interact with the widget. :param kwargs: Initial style properties.

Source code in core/src/toga/widgets/selection.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
def __init__(
    self,
    id: str | None = None,
    style: StyleT | None = None,
    items: ListSourceT | Iterable | None = None,
    accessor: str | None = None,
    value: object | None = None,
    on_change: toga.widgets.selection.OnChangeHandler | None = None,
    enabled: bool = True,
    **kwargs,
):
    """Create a new Selection 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 items: Initial [`items`][toga.Selection.items] to display for selection.
    :param accessor: The accessor to use to extract display values from the list of
        items. See [`items`][toga.Selection.items] and
        [`value`][toga.Selection.value] for details on how
        `accessor` alters the interpretation of data in the Selection.
    :param value: Initial value for the selection. If unspecified, the first item in
        `items` will be selected.
    :param on_change: Initial [`on_change`][toga.Selection.on_change] handler.
    :param enabled: Whether the user can interact with the widget.
    :param kwargs: Initial style properties.
    """

    self._items: ListSourceT | ListSource

    self.on_change = None  # needed for _impl initialization

    self._accessor = accessor
    super().__init__(id, style, **kwargs)

    self.items = items
    if value:
        self.value = value

    self.on_change = on_change
    self.enabled = enabled