Skip to content

DetailedList

Usage

The simplest way to create a DetailedList is to pass a list of dictionaries, with each dictionary containing three keys: icon, title, and subtitle:

import toga

table = toga.DetailedList(
    data=[
        {
           "icon": toga.Icon("icons/arthur"),
           "title": "Arthur Dent",
           "subtitle": "Where's the tea?"
        },
        {
           "icon": toga.Icon("icons/ford"),
           "title": "Ford Prefect",
           "subtitle": "Do you know where my towel is?"
        },
        {
           "icon": toga.Icon("icons/tricia"),
           "title": "Tricia McMillan",
           "subtitle": "What planet are you from?"
        },
    ]
)

If you want to customize the keys used in the dictionary, you can do this by providing an accessors argument to the DetailedList when it is constructed. accessors is a tuple containing the attributes that will be used to provide the icon, title, and subtitle, respectively:

import toga

table = toga.DetailedList(
    accessors=("picture", "name", "quote"),
    data=[
        {
           "picture": toga.Icon("icons/arthur"),
           "name": "Arthur Dent",
           "quote": "Where's the tea?"
        },
        {
           "picture": toga.Icon("icons/ford"),
           "name": "Ford Prefect",
           "quote": "Do you know where my towel is?"
        },
        {
           "picture": toga.Icon("icons/tricia"),
           "name": "Tricia McMillan",
           "quote": "What planet are you from?"
        },
    ]
)

If the value provided by the title or subtitle accessor is None, or the accessor isn't defined, the missing_value will be displayed. Any other value will be converted into a string.

The icon accessor should return an Icon. If it returns None, or the accessor isn't defined, then no icon will be displayed, but space for the icon will remain in the layout.

Items in a DetailedList will respond to a primary and secondary action if the on_primary_action and on_secondary_action handlers are set:

  • On Android, a long press displays a menu with the primary and secondary actions.
  • On iOS, swiping left triggers the primary action, and swiping right triggers the secondary action.
  • On GTK, a right click displays buttons for the primary and secondary actions.
  • On macOS and Windows, a right click displays a context menu with the primary and secondary actions.
  • On Qt, the primary and secondary actions are displayed as standalone buttons.

By default, the primary and secondary action will be labeled as "Delete" and "Action", respectively. These names can be overridden by providing a primary_action and secondary_action argument when constructing the DetailedList. Although the primary action is labeled "Delete" by default, the DetailedList will not perform any data deletion as part of the UI interaction. It is the responsibility of the application to implement any data deletion behavior as part of the on_primary_action handler.

The DetailedList as a whole will also respond to a refresh UI action if an on_refresh handler is set:

  • On Android, iOS and macOS, pulling down at the top of the list triggers a refresh.
  • On Qt, a button bar displays a refresh button.
  • On GTK, a floating refresh button is displayed when scrolled to the top.
  • On Windows, a right click displays a context menu with a refresh option.

Notes

  • The iOS Human Interface Guidelines differentiate between "Normal" and "Destructive" actions on a row. Toga will interpret any action with a name of "Delete" or "Remove" as destructive, and will render the action appropriately.
  • Using DetailedList on Android requires the AndroidX SwipeRefreshLayout widget in your project's Gradle dependencies. Ensure your app declares a dependency on androidx.swiperefreshlayout:swiperefreshlayout:1.1.0 or later.

Reference

Bases: Widget

Source code in core/src/toga/widgets/detailedlist.py
 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
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
class DetailedList(Widget):
    def __init__(
        self,
        id: str | None = None,
        style: StyleT | None = None,
        data: ListSourceT | Iterable | None = None,
        accessors: tuple[str, str, str] = ("title", "subtitle", "icon"),
        missing_value: str = "",
        primary_action: str | None = "Delete",
        on_primary_action: OnPrimaryActionHandler | None = None,
        secondary_action: str | None = "Action",
        on_secondary_action: OnSecondaryActionHandler | None = None,
        on_refresh: OnRefreshHandler | None = None,
        on_select: toga.widgets.detailedlist.OnSelectHandler | None = None,
        **kwargs,
    ):
        """Create a new DetailedList 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 data: Initial [`data`][toga.DetailedList.data] to be displayed in the
            list.
        :param accessors: The accessors to use to retrieve the data for each item, in
            the form (title, subtitle, icon).
        :param missing_value: The text that will be shown when a row doesn't provide a
            value for its title or subtitle.
        :param on_select: Initial [`on_select`][toga.DetailedList.on_select] handler.
        :param primary_action: The name for the primary action.
        :param on_primary_action: Initial
            [`on_primary_action`][toga.DetailedList.on_primary_action] handler.
        :param secondary_action: The name for the secondary action.
        :param on_secondary_action: Initial
            [`on_secondary_action`][toga.DetailedList.on_secondary_action] handler.
        :param on_refresh: Initial [`on_refresh`][toga.DetailedList.on_refresh] handler.
        :param kwargs: Initial style properties.
        """
        # Prime the attributes and handlers that need to exist when the widget is
        # created.
        self._accessors = accessors
        self._missing_value = missing_value
        self._primary_action = primary_action
        self._secondary_action = secondary_action
        self.on_select = None

        self._data: ListSourceT | ListSource = None

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

        self.data = data
        self.on_primary_action = on_primary_action
        self.on_secondary_action = on_secondary_action
        self.on_refresh = on_refresh
        self.on_select = on_select

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

    @property
    def enabled(self) -> Literal[True]:
        """Is the widget currently enabled? i.e., can the user interact with the widget?
        DetailedList widgets cannot be disabled; this property will always return True;
        any attempt to modify it will be ignored.
        """
        return True

    @enabled.setter
    def enabled(self, value: object) -> None:
        pass

    def focus(self) -> None:
        """No-op; DetailedList cannot accept input focus."""
        pass

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

        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. Items are converted as shown [here][listsource-item].
        """
        return self._data

    @data.setter
    def data(self, data: ListSourceT | Iterable | None) -> None:
        if self._data is not None:
            self._data.remove_listener(self._impl)

        if data is None:
            self._data = ListSource(data=[], accessors=self.accessors)
        elif isinstance(data, Source):
            self._data = data
        else:
            self._data = ListSource(data=data, accessors=self.accessors)

        self._data.add_listener(self._impl)
        self._impl.change_source(source=self._data)

    def scroll_to_top(self) -> None:
        """Scroll the view so that the top of the list (first row) is visible."""
        self.scroll_to_row(0)

    def scroll_to_row(self, row: int) -> None:
        """Scroll the view so that the specified row index is visible.

        :param row: The index of the row to make visible. Negative values refer to the
            nth last row (-1 is the last row, -2 second last, and so on).
        """
        if len(self.data) > 1:
            if row >= 0:
                self._impl.scroll_to_row(min(row, len(self.data)))
            else:
                self._impl.scroll_to_row(max(len(self.data) + row, 0))

    def scroll_to_bottom(self) -> None:
        """Scroll the view so that the bottom of the list (last row) is visible."""
        self.scroll_to_row(-1)

    @property
    def accessors(self) -> tuple[str, str, str]:
        """The accessors used to populate the list (read-only)"""
        return self._accessors

    @property
    def missing_value(self) -> str:
        """The text that will be shown when a row doesn't provide a value for its
        title or subtitle.
        """
        return self._missing_value

    @property
    def selection(self) -> Row | None:
        """The current selection of the table.

        Returns the selected Row object, or [`None`][] if no row is currently selected.
        """
        try:
            return self.data[self._impl.get_selection()]
        except TypeError:
            return None

    @property
    def on_primary_action(self) -> OnPrimaryActionHandler:
        """The handler to invoke when the user performs the primary action on a row of
        the DetailedList.

        The primary action is "swipe left" on platforms that use swipe interactions;
        other platforms may manifest this action in other ways (e.g, a context menu).

        If no `on_primary_action` handler is provided, the primary action will be
        disabled in the UI.
        """
        return self._on_primary_action

    @on_primary_action.setter
    def on_primary_action(self, handler: OnPrimaryActionHandler) -> None:
        self._on_primary_action = wrapped_handler(self, handler)
        self._impl.set_primary_action_enabled(handler is not None)

    @property
    def on_secondary_action(self) -> OnSecondaryActionHandler:
        """The handler to invoke when the user performs the secondary action on a row of
        the DetailedList.

        The secondary action is "swipe right" on platforms that use swipe interactions;
        other platforms may manifest this action in other ways (e.g, a context menu).

        If no `on_secondary_action` handler is provided, the secondary action will be
        disabled in the UI.
        """
        return self._on_secondary_action

    @on_secondary_action.setter
    def on_secondary_action(self, handler: OnSecondaryActionHandler) -> None:
        self._on_secondary_action = wrapped_handler(self, handler)
        self._impl.set_secondary_action_enabled(handler is not None)

    @property
    def on_refresh(self) -> OnRefreshHandler:
        """The callback function to invoke when the user performs a refresh action
        (usually "pull down") on the DetailedList.

        If no `on_refresh` handler is provided, the refresh UI action will be
        disabled.
        """
        return self._on_refresh

    @on_refresh.setter
    def on_refresh(self, handler: OnRefreshHandler) -> None:
        self._on_refresh = wrapped_handler(
            self, handler, cleanup=self._impl.after_on_refresh
        )
        self._impl.set_refresh_enabled(handler is not None)

    @property
    def on_select(self) -> OnSelectHandler:
        """The callback function that is invoked
        when a row of the DetailedList is selected."""
        return self._on_select

    @on_select.setter
    def on_select(self, handler: toga.widgets.detailedlist.OnSelectHandler) -> None:
        self._on_select = wrapped_handler(self, handler)

accessors property

The accessors used to populate the list (read-only)

data property writable

The data to display in the table.

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. Items are converted as shown here.

enabled property writable

Is the widget currently enabled? i.e., can the user interact with the widget? DetailedList widgets cannot be disabled; this property will always return True; any attempt to modify it will be ignored.

missing_value property

The text that will be shown when a row doesn't provide a value for its title or subtitle.

on_primary_action property writable

The handler to invoke when the user performs the primary action on a row of the DetailedList.

The primary action is "swipe left" on platforms that use swipe interactions; other platforms may manifest this action in other ways (e.g, a context menu).

If no on_primary_action handler is provided, the primary action will be disabled in the UI.

on_refresh property writable

The callback function to invoke when the user performs a refresh action (usually "pull down") on the DetailedList.

If no on_refresh handler is provided, the refresh UI action will be disabled.

on_secondary_action property writable

The handler to invoke when the user performs the secondary action on a row of the DetailedList.

The secondary action is "swipe right" on platforms that use swipe interactions; other platforms may manifest this action in other ways (e.g, a context menu).

If no on_secondary_action handler is provided, the secondary action will be disabled in the UI.

on_select property writable

The callback function that is invoked when a row of the DetailedList is selected.

selection property

The current selection of the table.

Returns the selected Row object, or [None][] if no row is currently selected.

__init__(id=None, style=None, data=None, accessors=('title', 'subtitle', 'icon'), missing_value='', primary_action='Delete', on_primary_action=None, secondary_action='Action', on_secondary_action=None, on_refresh=None, on_select=None, **kwargs)

Create a new DetailedList 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 data: Initial data to be displayed in the list. :param accessors: The accessors to use to retrieve the data for each item, in the form (title, subtitle, icon). :param missing_value: The text that will be shown when a row doesn't provide a value for its title or subtitle. :param on_select: Initial on_select handler. :param primary_action: The name for the primary action. :param on_primary_action: Initial on_primary_action handler. :param secondary_action: The name for the secondary action. :param on_secondary_action: Initial on_secondary_action handler. :param on_refresh: Initial on_refresh handler. :param kwargs: Initial style properties.

Source code in core/src/toga/widgets/detailedlist.py
 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
def __init__(
    self,
    id: str | None = None,
    style: StyleT | None = None,
    data: ListSourceT | Iterable | None = None,
    accessors: tuple[str, str, str] = ("title", "subtitle", "icon"),
    missing_value: str = "",
    primary_action: str | None = "Delete",
    on_primary_action: OnPrimaryActionHandler | None = None,
    secondary_action: str | None = "Action",
    on_secondary_action: OnSecondaryActionHandler | None = None,
    on_refresh: OnRefreshHandler | None = None,
    on_select: toga.widgets.detailedlist.OnSelectHandler | None = None,
    **kwargs,
):
    """Create a new DetailedList 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 data: Initial [`data`][toga.DetailedList.data] to be displayed in the
        list.
    :param accessors: The accessors to use to retrieve the data for each item, in
        the form (title, subtitle, icon).
    :param missing_value: The text that will be shown when a row doesn't provide a
        value for its title or subtitle.
    :param on_select: Initial [`on_select`][toga.DetailedList.on_select] handler.
    :param primary_action: The name for the primary action.
    :param on_primary_action: Initial
        [`on_primary_action`][toga.DetailedList.on_primary_action] handler.
    :param secondary_action: The name for the secondary action.
    :param on_secondary_action: Initial
        [`on_secondary_action`][toga.DetailedList.on_secondary_action] handler.
    :param on_refresh: Initial [`on_refresh`][toga.DetailedList.on_refresh] handler.
    :param kwargs: Initial style properties.
    """
    # Prime the attributes and handlers that need to exist when the widget is
    # created.
    self._accessors = accessors
    self._missing_value = missing_value
    self._primary_action = primary_action
    self._secondary_action = secondary_action
    self.on_select = None

    self._data: ListSourceT | ListSource = None

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

    self.data = data
    self.on_primary_action = on_primary_action
    self.on_secondary_action = on_secondary_action
    self.on_refresh = on_refresh
    self.on_select = on_select

focus()

No-op; DetailedList cannot accept input focus.

Source code in core/src/toga/widgets/detailedlist.py
121
122
123
def focus(self) -> None:
    """No-op; DetailedList cannot accept input focus."""
    pass

scroll_to_bottom()

Scroll the view so that the bottom of the list (last row) is visible.

Source code in core/src/toga/widgets/detailedlist.py
173
174
175
def scroll_to_bottom(self) -> None:
    """Scroll the view so that the bottom of the list (last row) is visible."""
    self.scroll_to_row(-1)

scroll_to_row(row)

Scroll the view so that the specified row index is visible.

:param row: The index of the row to make visible. Negative values refer to the nth last row (-1 is the last row, -2 second last, and so on).

Source code in core/src/toga/widgets/detailedlist.py
161
162
163
164
165
166
167
168
169
170
171
def scroll_to_row(self, row: int) -> None:
    """Scroll the view so that the specified row index is visible.

    :param row: The index of the row to make visible. Negative values refer to the
        nth last row (-1 is the last row, -2 second last, and so on).
    """
    if len(self.data) > 1:
        if row >= 0:
            self._impl.scroll_to_row(min(row, len(self.data)))
        else:
            self._impl.scroll_to_row(max(len(self.data) + row, 0))

scroll_to_top()

Scroll the view so that the top of the list (first row) is visible.

Source code in core/src/toga/widgets/detailedlist.py
157
158
159
def scroll_to_top(self) -> None:
    """Scroll the view so that the top of the list (first row) is visible."""
    self.scroll_to_row(0)

Bases: Protocol

Source code in core/src/toga/widgets/detailedlist.py
13
14
15
16
17
18
19
20
class OnPrimaryActionHandler(Protocol):
    def __call__(self, widget: DetailedList, row: Any, **kwargs: Any) -> None:
        """A handler to invoke for the primary action.

        :param widget: The DetailedList that was invoked.
        :param row: The current row for the detailed list.
        :param kwargs: Ensures compatibility with arguments added in future versions.
        """

__call__(widget, row, **kwargs)

A handler to invoke for the primary action.

:param widget: The DetailedList that was invoked. :param row: The current row for the detailed list. :param kwargs: Ensures compatibility with arguments added in future versions.

Source code in core/src/toga/widgets/detailedlist.py
14
15
16
17
18
19
20
def __call__(self, widget: DetailedList, row: Any, **kwargs: Any) -> None:
    """A handler to invoke for the primary action.

    :param widget: The DetailedList that was invoked.
    :param row: The current row for the detailed list.
    :param kwargs: Ensures compatibility with arguments added in future versions.
    """

Bases: Protocol

Source code in core/src/toga/widgets/detailedlist.py
23
24
25
26
27
28
29
30
class OnSecondaryActionHandler(Protocol):
    def __call__(self, widget: DetailedList, row: Any, **kwargs: Any) -> None:
        """A handler to invoke for the secondary action.

        :param widget: The DetailedList that was invoked.
        :param row: The current row for the detailed list.
        :param kwargs: Ensures compatibility with arguments added in future versions.
        """

__call__(widget, row, **kwargs)

A handler to invoke for the secondary action.

:param widget: The DetailedList that was invoked. :param row: The current row for the detailed list. :param kwargs: Ensures compatibility with arguments added in future versions.

Source code in core/src/toga/widgets/detailedlist.py
24
25
26
27
28
29
30
def __call__(self, widget: DetailedList, row: Any, **kwargs: Any) -> None:
    """A handler to invoke for the secondary action.

    :param widget: The DetailedList that was invoked.
    :param row: The current row for the detailed list.
    :param kwargs: Ensures compatibility with arguments added in future versions.
    """

Bases: Protocol

Source code in core/src/toga/widgets/detailedlist.py
33
34
35
36
37
38
39
class OnRefreshHandler(Protocol):
    def __call__(self, widget: DetailedList, **kwargs: Any) -> None:
        """A handler to invoke when the detailed list is refreshed.

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

__call__(widget, **kwargs)

A handler to invoke when the detailed list is refreshed.

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

Source code in core/src/toga/widgets/detailedlist.py
34
35
36
37
38
39
def __call__(self, widget: DetailedList, **kwargs: Any) -> None:
    """A handler to invoke when the detailed list is refreshed.

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

Bases: Protocol

Source code in core/src/toga/widgets/detailedlist.py
42
43
44
45
46
47
48
class OnSelectHandler(Protocol):
    def __call__(self, widget: DetailedList, **kwargs: Any) -> None:
        """A handler to invoke when the detailed list is selected.

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

__call__(widget, **kwargs)

A handler to invoke when the detailed list is selected.

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

Source code in core/src/toga/widgets/detailedlist.py
43
44
45
46
47
48
def __call__(self, widget: DetailedList, **kwargs: Any) -> None:
    """A handler to invoke when the detailed list is selected.

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