Skip to content

Column

Usage

Columns are abstractions that allow you to specify how the data in a Table or Tree widget should be displayed. Each column object is responsible for taking a row from the data source and providing text, icon and other display elements suitable for the Table and Tree widgets to use.

The protocol, ColumnT, describes what custom Column implementations need to provide so that they can be used by the widget. Toga provides the Column and AccessorColumn as implementations of the ColumnT protocol. The Column class is a base class suitable for custom sub-classing, while the AccessorColumn is used by default in the Table and Tree widgets.

Column objects are usually immutable, and can be shared between widgets freely if desired.

Accessor columns

AccessorColumn objects are designed to work with the ListSource and TreeSource objects: each AccessorColumn holds the heading text and an attribute name, or "accessor", that is used to get values from each row of the source to use in the column.

The Table and Tree widgets will automatically create AccessorColumn objects from column headings, but they can also be created manually if desired. Each column object expects a heading and an accessor as arguments, but will automatically generate an accessor from the heading if needed.

table = Table(
    columns=[
        AccessorColumn("Title", "title"),
        AccessorColumn("Year"),
        "Rating",  # equivalent to AccessorColumn("Rating")
    ]
)

The accessors are created automatically from the headings, by:

  1. Converting the heading to lower case
  2. Removing any character that can't be used in a Python identifier
  3. Replacing all whitespace with _
  4. Prepending _ if the first character is a digit

The value provided by an accessor is interpreted as follows:

  • If the value is a Widget, that widget will be displayed in the cell. Note that this is currently a beta API: see the Notes section.
  • If the value is a [tuple][], it must have two elements: an icon, and a second element which will be interpreted as one of the options below. A tuple of any other length will raise an error.
  • If the value is None, then missing_value will be displayed.
  • Any other value will be converted into a string. If an icon has not already been provided in a tuple, it can also be provided using the value's icon attribute.

Icon values must either be an Icon, which will be displayed on the left of the cell, or None to display no icon.

Custom columns

You can define your own subclasses that can override the way that text and icons are computed to provide custom formatting of text. Any object which implements the ColumnT protocol can be used. This protocol requires:

  • a read-only heading property that is the column heding text or None for no heading text.;
  • a value method that takes a row object and gives the value for the column in that row.
  • a text method that takes a row object and an optional default value and gives the text for the column to display in that row, or None if no text is to be displayed.
  • an icon method that takes a row object and gives the icon for the column to display in that row, or None if no icon is to be displayed.
  • a widget method that takes a row object and gives the widget for the column to use in that row, or None if no widget is to be used (this is experimental and is only supported on macOS at present).

For example, we could subclass AccessorColumn to make column that takes a value which is a list of strings and formats it as a comma-separated list as follows:

class ListStrColumn(AccessorColumn):

    def text(self, row, default=None):
        value = self.value(row)
        if value is None:
            return default
        else:
            return ", ".join(value)

table = Table(
    columns=[
        "Title",
        ListStrColumn("Genre"),
    ]
)

so a row providing the value ["Drama", "Action"] would be displayed in the table cell as "Drama, Action".

Custom columns can even override the default way of looking up values to allow such things as combining values from multiple attributes, looking up values by index rather than attribute, or using a method or function on the row to get the display values. The Column class provides a convenient minimal base class for implementing custom columns.

class TotalCostColumn(Column):

    def value(self, row):
        return row.item_cost * row.quantity

    def text(self, row, default=None):
        value = self.value(row)
        return f"${value:.2d}"

table = Table(
    columns=[
        "Product",
        "Quantity",
        "Item Cost",
        TotalCostColumn("Total Cost"),
    ]
)

Reference

Bases: Protocol, Generic[Value]

Protocol that Column types must adhere to.

Source code in core/src/toga/sources/columns.py
13
14
15
16
17
18
19
20
21
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
@runtime_checkable
class ColumnT(Protocol, Generic[Value]):
    """Protocol that Column types must adhere to."""

    @property
    @abstractmethod
    def heading(self) -> str:
        """The heading text for this column."""

    @abstractmethod
    def value(self, row: Any) -> Value | None:
        """Get a value from the row of a Source.

        :param row: A row object from the underlying Source.
        :returns: The value associated with this column, or
            None if no value.
        """

    @abstractmethod
    def text(self, row: Any, default: str | None = None) -> str | None:
        """Get the text to display for the row in this column.

        :param row: A row object from the underlying Source.
        :param default: A default value if the text cannot be determined.
        :returns: The text to display, or None if no Text.
        """

    @abstractmethod
    def icon(self, row: Any) -> Icon | None:
        """Get the icon to display for the row in this column.

        :param row: A row object from the underlying Source.
        :returns: The icon to display, or None if no Icon.
        """

    def widget(self, row: Row[Value]) -> Widget | None:
        """Get a widget from the Row or Node of a ListSource or TreeSource.

        If the value is a widget, it is returned, otherwise None is returned

        :param row: A row object from the underlying Source.
        :returns: The Widget to use, or None if no Widget.
        """

heading abstractmethod property

The heading text for this column.

icon(row) abstractmethod

Get the icon to display for the row in this column.

:param row: A row object from the underlying Source. :returns: The icon to display, or None if no Icon.

Source code in core/src/toga/sources/columns.py
40
41
42
43
44
45
46
@abstractmethod
def icon(self, row: Any) -> Icon | None:
    """Get the icon to display for the row in this column.

    :param row: A row object from the underlying Source.
    :returns: The icon to display, or None if no Icon.
    """

text(row, default=None) abstractmethod

Get the text to display for the row in this column.

:param row: A row object from the underlying Source. :param default: A default value if the text cannot be determined. :returns: The text to display, or None if no Text.

Source code in core/src/toga/sources/columns.py
31
32
33
34
35
36
37
38
@abstractmethod
def text(self, row: Any, default: str | None = None) -> str | None:
    """Get the text to display for the row in this column.

    :param row: A row object from the underlying Source.
    :param default: A default value if the text cannot be determined.
    :returns: The text to display, or None if no Text.
    """

value(row) abstractmethod

Get a value from the row of a Source.

:param row: A row object from the underlying Source. :returns: The value associated with this column, or None if no value.

Source code in core/src/toga/sources/columns.py
22
23
24
25
26
27
28
29
@abstractmethod
def value(self, row: Any) -> Value | None:
    """Get a value from the row of a Source.

    :param row: A row object from the underlying Source.
    :returns: The value associated with this column, or
        None if no value.
    """

widget(row)

Get a widget from the Row or Node of a ListSource or TreeSource.

If the value is a widget, it is returned, otherwise None is returned

:param row: A row object from the underlying Source. :returns: The Widget to use, or None if no Widget.

Source code in core/src/toga/sources/columns.py
48
49
50
51
52
53
54
55
def widget(self, row: Row[Value]) -> Widget | None:
    """Get a widget from the Row or Node of a ListSource or TreeSource.

    If the value is a widget, it is returned, otherwise None is returned

    :param row: A row object from the underlying Source.
    :returns: The Widget to use, or None if no Widget.
    """

Bases: ColumnT[Value], Generic[Value]

An implementation the ColumnT protocol for easy subclassing.

This abstract base class provides default implementations of the ColumnT: the value, icon and widget methods all return None, and the text method returns the str() of the value, or the default string if the value is None. The constructor takes the heading text and makes it available as a property.

Unmodified, the column will display the default text value in each cell.

Subclasses should override the value method at a minimum, and other methods as needed.

Source code in core/src/toga/sources/columns.py
 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
class Column(ColumnT[Value], Generic[Value]):
    """An implementation the ColumnT protocol for easy subclassing.

    This abstract base class provides default implementations of the ColumnT:
    the value, icon and widget methods all return None, and the text method
    returns the str() of the value, or the default string if the value is None.
    The constructor takes the heading text and makes it available as a property.

    Unmodified, the column will display the default text value in each cell.

    Subclasses should override the value method at a minimum, and other methods
    as needed.
    """

    def __init__(self, heading: str | None):
        self._heading = heading

    @property
    def heading(self) -> str:
        """The heading text for this column."""
        return self._heading if self._heading is not None else ""

    def value(self, row: Any) -> Value | None:
        """Get a value from the row of a Source.

        The base implementation always returns None.

        :param row: A row object from the underlying Source.
        :returns: The value associated with this column, or
            None if no value.
        """
        return None

    def text(self, row: Any, default: str | None = None) -> str | None:
        """Get the text to display for the row in this column.

        This returns the str() of the value unless the value is
        None, in which case it returns the default.

        :param row: A row object from the underlying Source.
        :param default: A default value if the text cannot be determined.
        :returns: The text to display, or None if no Text.
        """
        value = self.value(row)
        return str(value) if value is not None else default

    def icon(self, row: Any) -> Icon | None:
        """Get the icon to display for the row in this column.

        The default

        :param row: A row object from the underlying Source.
        :returns: The icon to display, or None if no Icon.
        """
        return None

    def widget(self, row: Any) -> Widget | None:
        """Get a widget from the Row or Node of a ListSource or TreeSource.

        If the value is a widget, it is returned, otherwise None is returned

        :param row: A row object from the underlying Source.
        :returns: The Widget to use, or None if no Widget.
        """
        return None

heading property

The heading text for this column.

icon(row)

Get the icon to display for the row in this column.

The default

:param row: A row object from the underlying Source. :returns: The icon to display, or None if no Icon.

Source code in core/src/toga/sources/columns.py
104
105
106
107
108
109
110
111
112
def icon(self, row: Any) -> Icon | None:
    """Get the icon to display for the row in this column.

    The default

    :param row: A row object from the underlying Source.
    :returns: The icon to display, or None if no Icon.
    """
    return None

text(row, default=None)

Get the text to display for the row in this column.

This returns the str() of the value unless the value is None, in which case it returns the default.

:param row: A row object from the underlying Source. :param default: A default value if the text cannot be determined. :returns: The text to display, or None if no Text.

Source code in core/src/toga/sources/columns.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
def text(self, row: Any, default: str | None = None) -> str | None:
    """Get the text to display for the row in this column.

    This returns the str() of the value unless the value is
    None, in which case it returns the default.

    :param row: A row object from the underlying Source.
    :param default: A default value if the text cannot be determined.
    :returns: The text to display, or None if no Text.
    """
    value = self.value(row)
    return str(value) if value is not None else default

value(row)

Get a value from the row of a Source.

The base implementation always returns None.

:param row: A row object from the underlying Source. :returns: The value associated with this column, or None if no value.

Source code in core/src/toga/sources/columns.py
80
81
82
83
84
85
86
87
88
89
def value(self, row: Any) -> Value | None:
    """Get a value from the row of a Source.

    The base implementation always returns None.

    :param row: A row object from the underlying Source.
    :returns: The value associated with this column, or
        None if no value.
    """
    return None

widget(row)

Get a widget from the Row or Node of a ListSource or TreeSource.

If the value is a widget, it is returned, otherwise None is returned

:param row: A row object from the underlying Source. :returns: The Widget to use, or None if no Widget.

Source code in core/src/toga/sources/columns.py
114
115
116
117
118
119
120
121
122
def widget(self, row: Any) -> Widget | None:
    """Get a widget from the Row or Node of a ListSource or TreeSource.

    If the value is a widget, it is returned, otherwise None is returned

    :param row: A row object from the underlying Source.
    :returns: The Widget to use, or None if no Widget.
    """
    return None

Bases: Column[Value], Generic[Value]

This is a column which implements accessor semantics.

The value of a cell in an AccessorColumn is found by getting the value of the attribute on the row whose name matches the accessor. This value can be:

This requires at least one of the heading and the accessor to be supplied to the init method. If given just a heading, it generates the accessor from the heading.

The value provided by an accessor is interpreted as follows:

  • If the value is a Widget, that widget will be displayed in the cell. Note that this is currently a beta API: see the Notes section.
  • If the value is a [tuple][], it must have two elements: an icon, and a second element which will be interpreted as one of the options below.
  • Any other value will be converted into a string. If an icon has not already been provided in a tuple, it can also be provided using the value's icon attribute.

Icon values must either be an Icon, which will be displayed on the left of the cell, or None to display no icon.

Source code in core/src/toga/sources/columns.py
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 AccessorColumn(Column[Value], Generic[Value]):
    """This is a column which implements accessor semantics.

    The value of a cell in an AccessorColumn is found by getting the value of
    the attribute on the row whose name matches the `accessor`. This value
    can be:

    This requires at least one of the heading and the accessor to be
    supplied to the init method. If given just a heading, it generates
    the accessor from the heading.

    The value provided by an accessor is interpreted as follows:

    - If the value is a [Widget][], that widget will be displayed in the cell.
      Note that this is currently a beta API: see the Notes section.
    - If the value is a [`tuple`][], it must have two elements: an icon, and a
      second element which will be interpreted as one of the options below.
    - Any other value will be converted into a string. If an icon has not already
      been provided in a tuple, it can also be provided using the value's `icon`
      attribute.

    Icon values must either be an [Icon][], which will be displayed on the left
    of the cell, or `None` to display no icon.
    """

    def __init__(
        self,
        heading: str | None = None,
        accessor: str | None = None,
    ):
        if accessor is None:
            if heading is not None:
                accessor = to_accessor(heading)
            else:
                raise ValueError(
                    "Cannot create a column without either headings or accessors"
                )
        super().__init__(heading)
        self._accessor = accessor

    def __eq__(self, other):
        if type(other) is type(self):
            return self._heading == other._heading and self._accessor == other._accessor
        return NotImplemented

    def __repr__(self):
        cls_name = self.__class__.__name__
        return f"{cls_name}(heading={self.heading!r}, accessor={self.accessor!r})"

    def __hash__(self):
        return hash((self.__class__, self._heading, self._accessor))

    @property
    def accessor(self):
        return self._accessor

    def value(self, row: Row[Value]) -> Value | None:
        """Get a value from the Row or Node of a ListSource or TreeSource.

        :param row: A Row object from the underlying Source.
        :returns: The value associated with this column's accessor, or
            None if no value.
        """
        return getattr(row, self.accessor, None)

    def text(self, row: Row[Value], default: str | None = None) -> str | None:
        """Get text from the Row or Node of a ListSource or TreeSource.

        If the value is a tuple, it must be of length two; the second item is assumed to
        be text.

        If the value is None, and a default is supplied, the default is returned.

        All other values are converted to a string by calling str().

        :param row: A row object from the underlying Source.
        :param default: A default value if the resulting value is otherwise None.
        :returns: The text to associated with this column's accessor, or None if no
            text.
        """
        match value := self.value(row):
            case Widget():
                return default
            case tuple((_, None)):
                return default
            case tuple((_, value)):
                return str(value)
            case tuple():
                raise ValueError("Data tuples must have length 2")
            case None:
                return default
            case _:
                return str(value)

    def icon(self, row: Row[Value]) -> Icon | None:
        """Get text from the Row or Node of a ListSource or TreeSource.

        If the value is a tuple, it must be of length 2, and the first item is assumed
        to be an Icon. A tuple of any other length will raise a ValueError. Otherwise if
        the item has an `icon` attribute, that is assumed to be the icon.

        :param row: A row object from the underlying Source.
        :returns: The Icon to associated with this column's accessor, or None if no
            Icon.
        """
        match value := self.value(row):
            case Widget():
                return None
            case tuple((icon, _)):
                return icon
            case tuple():
                raise ValueError("Data tuples must have length 2")
            case _:
                return getattr(value, "icon", None)

    def widget(self, row: Row[Value]) -> Widget | None:
        """Get a widget from the Row or Node of a ListSource or TreeSource.

        If the value is a widget, it is returned, otherwise None is returned

        :param row: A row object from the underlying Source.
        :returns: The Widget to associated with this column's accessor, or
            None if no Widget.
        """
        if isinstance(value := self.value(row), Widget):
            return value
        else:
            return None

    @classmethod
    def columns_from_headings_and_accessors(
        cls,
        headings: Iterable[str] | None = None,
        accessors: Iterable[str] | None = None,
    ) -> list[ColumnT]:
        """Get a list of columns from lists of headings and accessors.

        :param headings: A list of heading titles, or None if no headings.
        :param accessors: A list of accessor names, or None if accessors are
            to be generated from the headings automatically.

        :returns: A list of AccessorColumns matching the order of headers and
            accessors.
        :raises ValueError: If neither headings nor accessor liss are supplied.
        """
        if headings is not None:
            headings = [heading.splitlines()[0] for heading in headings]
            accessors = build_accessors(headings, accessors)
            return [
                cls(heading, accessor)
                for heading, accessor in zip(headings, accessors, strict=False)
            ]
        elif accessors is not None:
            accessors = list(accessors)
            return [cls(None, accessor) for accessor in accessors]
        else:
            raise ValueError(
                "Cannot create columns without either headings or accessors."
            )

columns_from_headings_and_accessors(headings=None, accessors=None) classmethod

Get a list of columns from lists of headings and accessors.

:param headings: A list of heading titles, or None if no headings. :param accessors: A list of accessor names, or None if accessors are to be generated from the headings automatically.

:returns: A list of AccessorColumns matching the order of headers and accessors. :raises ValueError: If neither headings nor accessor liss are supplied.

Source code in core/src/toga/sources/columns.py
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
@classmethod
def columns_from_headings_and_accessors(
    cls,
    headings: Iterable[str] | None = None,
    accessors: Iterable[str] | None = None,
) -> list[ColumnT]:
    """Get a list of columns from lists of headings and accessors.

    :param headings: A list of heading titles, or None if no headings.
    :param accessors: A list of accessor names, or None if accessors are
        to be generated from the headings automatically.

    :returns: A list of AccessorColumns matching the order of headers and
        accessors.
    :raises ValueError: If neither headings nor accessor liss are supplied.
    """
    if headings is not None:
        headings = [heading.splitlines()[0] for heading in headings]
        accessors = build_accessors(headings, accessors)
        return [
            cls(heading, accessor)
            for heading, accessor in zip(headings, accessors, strict=False)
        ]
    elif accessors is not None:
        accessors = list(accessors)
        return [cls(None, accessor) for accessor in accessors]
    else:
        raise ValueError(
            "Cannot create columns without either headings or accessors."
        )

icon(row)

Get text from the Row or Node of a ListSource or TreeSource.

If the value is a tuple, it must be of length 2, and the first item is assumed to be an Icon. A tuple of any other length will raise a ValueError. Otherwise if the item has an icon attribute, that is assumed to be the icon.

:param row: A row object from the underlying Source. :returns: The Icon to associated with this column's accessor, or None if no Icon.

Source code in core/src/toga/sources/columns.py
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
def icon(self, row: Row[Value]) -> Icon | None:
    """Get text from the Row or Node of a ListSource or TreeSource.

    If the value is a tuple, it must be of length 2, and the first item is assumed
    to be an Icon. A tuple of any other length will raise a ValueError. Otherwise if
    the item has an `icon` attribute, that is assumed to be the icon.

    :param row: A row object from the underlying Source.
    :returns: The Icon to associated with this column's accessor, or None if no
        Icon.
    """
    match value := self.value(row):
        case Widget():
            return None
        case tuple((icon, _)):
            return icon
        case tuple():
            raise ValueError("Data tuples must have length 2")
        case _:
            return getattr(value, "icon", None)

text(row, default=None)

Get text from the Row or Node of a ListSource or TreeSource.

If the value is a tuple, it must be of length two; the second item is assumed to be text.

If the value is None, and a default is supplied, the default is returned.

All other values are converted to a string by calling str().

:param row: A row object from the underlying Source. :param default: A default value if the resulting value is otherwise None. :returns: The text to associated with this column's accessor, or None if no text.

Source code in core/src/toga/sources/columns.py
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
def text(self, row: Row[Value], default: str | None = None) -> str | None:
    """Get text from the Row or Node of a ListSource or TreeSource.

    If the value is a tuple, it must be of length two; the second item is assumed to
    be text.

    If the value is None, and a default is supplied, the default is returned.

    All other values are converted to a string by calling str().

    :param row: A row object from the underlying Source.
    :param default: A default value if the resulting value is otherwise None.
    :returns: The text to associated with this column's accessor, or None if no
        text.
    """
    match value := self.value(row):
        case Widget():
            return default
        case tuple((_, None)):
            return default
        case tuple((_, value)):
            return str(value)
        case tuple():
            raise ValueError("Data tuples must have length 2")
        case None:
            return default
        case _:
            return str(value)

value(row)

Get a value from the Row or Node of a ListSource or TreeSource.

:param row: A Row object from the underlying Source. :returns: The value associated with this column's accessor, or None if no value.

Source code in core/src/toga/sources/columns.py
181
182
183
184
185
186
187
188
def value(self, row: Row[Value]) -> Value | None:
    """Get a value from the Row or Node of a ListSource or TreeSource.

    :param row: A Row object from the underlying Source.
    :returns: The value associated with this column's accessor, or
        None if no value.
    """
    return getattr(row, self.accessor, None)

widget(row)

Get a widget from the Row or Node of a ListSource or TreeSource.

If the value is a widget, it is returned, otherwise None is returned

:param row: A row object from the underlying Source. :returns: The Widget to associated with this column's accessor, or None if no Widget.

Source code in core/src/toga/sources/columns.py
240
241
242
243
244
245
246
247
248
249
250
251
252
def widget(self, row: Row[Value]) -> Widget | None:
    """Get a widget from the Row or Node of a ListSource or TreeSource.

    If the value is a widget, it is returned, otherwise None is returned

    :param row: A row object from the underlying Source.
    :returns: The Widget to associated with this column's accessor, or
        None if no Widget.
    """
    if isinstance(value := self.value(row), Widget):
        return value
    else:
        return None