Replies: 5 comments 4 replies
-
No, it isn't currently possible to highlight multiple cells/rows/columns AFAIK. |
Beta Was this translation helpful? Give feedback.
-
I'm interested in this. I'll work towards a solution and post it here. Basically, I want to make it possible to select/unselect rows with keybingings, for example:
|
Beta Was this translation helpful? Give feedback.
-
OK, as promised, here's my implementation of a data table with selectable rows: """Data tables with selectable rows."""
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING, ClassVar, Iterable, Iterator
from textual.binding import Binding
from textual.coordinate import Coordinate
from textual.widgets import DataTable
from textual.widgets.data_table import CellDoesNotExist, RowKey
if TYPE_CHECKING:
from textual.app import App
@dataclass
class Checkbox:
"""A checkbox, added to rows to make them selectable."""
checked: bool = False
def __str__(self) -> str:
return "■" if self.checked else ""
def __rich__(self) -> str:
return "[b]■[/]" if self.checked else ""
def check(self) -> None:
"""Uncheck the checkbox."""
self.checked = True
def uncheck(self) -> None:
"""Uncheck the checkbox."""
self.checked = False
def toggle(self) -> bool:
"""Toggle the checkbox."""
self.checked = not self.checked
return self.checked
@dataclass
class SelectableRow:
"""A selectable row."""
table: SelectableRowsDataTable
key: RowKey
@property
def app(self) -> App:
"""Textual application."""
return self.table.app
@property
def _data(self) -> list:
return self.table.get_row(self.key)
@property
def data(self) -> list:
"""Row data (without checkbox)."""
return self._data[1:]
@property
def index(self) -> int:
"""Row index."""
return self.table.get_row_index(self.key)
@property
def checkbox(self) -> Checkbox:
"""Row checkbox."""
return self._data[0]
def select(self) -> None:
"""Select this row."""
self.checkbox.check()
def unselect(self) -> None:
"""Unselect this row."""
self.checkbox.uncheck()
def toggle_select(self) -> bool:
"""Toggle-select this row."""
return self.checkbox.toggle()
@property
def selected(self) -> bool:
"""Whether this row is selected."""
return self.checkbox.checked
def remove(self) -> None:
"""Remove row from the table."""
self.table.remove_row(self.key)
@property
def previous(self) -> SelectableRow:
"""Previous row (up)."""
new_coord = Coordinate(self.index - 1, 0)
key = self.table.coordinate_to_cell_key(new_coord).row_key
return self.__class__(table=self.table, key=key)
@property
def next(self) -> SelectableRow: # noqa: A003
"""Next row (down)."""
new_coord = Coordinate(self.index + 1, 0)
key = self.table.coordinate_to_cell_key(new_coord).row_key
return self.__class__(table=self.table, key=key)
class SelectableRowsDataTable(DataTable):
"""Data table with selectable rows."""
ROW = SelectableRow
BINDINGS: ClassVar = [
Binding("space", "toggle_select_row", "Toggle select", show=False),
Binding("ctrl+a, *", "toggle_select_all", "Toggle select all", show=False),
Binding("exclamation_mark", "reverse_select", "Reverse select", show=False),
Binding("shift+up", "toggle_select_up", "Expand select up", show=False),
Binding("shift+down", "toggle_select_down", "Expand select down", show=False),
]
# --------------------------------------------------
# Textual methods.
# --------------------------------------------------
def add_rows(self, rows: Iterable[Iterable]) -> list[RowKey]:
"""Add rows.
Automatically insert a column with checkboxes in position 0.
"""
return super().add_rows((Checkbox(), *row) for row in rows)
def clear(self, columns: bool = True) -> SelectableRowsDataTable: # noqa: FBT001,FBT002
"""Clear rows and optionally columns.
When clearing columns, automatically re-add a column for checkboxes.
"""
super().clear(columns)
if columns:
self.add_column("", key="checkbox")
return self
# --------------------------------------------------
# Binding actions.
# --------------------------------------------------
def action_toggle_select_row(self) -> None:
"""Toggle-select current row."""
try:
row = self.current_row
except CellDoesNotExist:
return
row.toggle_select()
self.force_refresh()
def action_toggle_select_all(self) -> None:
"""Toggle-select all rows."""
rows = list(self.selectable_rows)
if all(row.selected for row in rows):
for row in rows:
row.unselect()
else:
for row in rows:
row.select()
self.force_refresh()
def action_reverse_select(self) -> None:
"""Reverse selection."""
for row in self.selectable_rows:
row.toggle_select()
self.force_refresh()
def action_toggle_select_up(self) -> None:
"""Toggle selection up."""
try:
row = self.current_row
previous_row = row.previous
except CellDoesNotExist:
pass
else:
previous_row.toggle_select()
self.move_cursor(row=previous_row.index)
self.force_refresh()
def action_toggle_select_down(self) -> None:
"""Toggle selection down."""
try:
row = self.current_row
next_row = row.next
except CellDoesNotExist:
pass
else:
next_row.toggle_select()
self.move_cursor(row=next_row.index)
self.force_refresh()
# --------------------------------------------------
# Additional methods/properties.
# --------------------------------------------------
def force_refresh(self) -> None:
"""Force refresh table."""
# HACK: Without such increment, the table is refreshed
# only when focus changes to another column.
self._update_count += 1
self.refresh()
@property
def current_row(self) -> SelectableRow:
"""Currently selected row."""
key = self.coordinate_to_cell_key(self.cursor_coordinate).row_key
return self.ROW(table=self, key=key)
@property
def selectable_rows(self) -> Iterator[SelectableRow]:
"""Rows, as selectable ones."""
for key in self.rows:
yield self.ROW(table=self, key=key)
@property
def selected_rows(self) -> Iterator[SelectableRow]:
"""Selected rows."""
for row in self.selectable_rows:
if row.selected:
yield row Both classes (row and table) can be further customized, for example: from thing import Thing
class CustomRow(SelectableRow):
@property
def thing(self) -> Thing:
for val in self.data:
if isinstance(val, Thing):
return val
raise ValueError("No thing in row data")
class CustomDataTable(SelectableRowsDataTable):
ROW = CustomRow |
Beta Was this translation helpful? Give feedback.
-
Based on my findings on #3606 (reply in thread) I'm going to see about implementing this directly in existing |
Beta Was this translation helpful? Give feedback.
-
Okay provisional implementation here: Caveat: it only allows selection of a contiguous range of rows, you couldn't e.g. pick the first and fifth. Feedback welcome! |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Is it possible to select multiple rows in a DataTable? For example, can we do a shift-down to select multiple rows?
Beta Was this translation helpful? Give feedback.
All reactions