Textualize / textual

The lean application framework for Python. Build sophisticated user interfaces with a simple Python API. Run your apps in the terminal and a web browser.
https://textual.textualize.io/
MIT License
25.55k stars 786 forks source link

Strange column width behaviour when updating `DataTable` on `*Selected` event #4470

Open TomJGooding opened 6 months ago

TomJGooding commented 6 months ago

This issue was originally posted in discussion #4463 by @luked42

There seems to be some strange behaviour with column widths when trying to update a DataTable on a *Selected event.

Here's an example based on the original post. When updating the table with the binding, the column widths work as expected. But if you swap this to update the table when a row is selected, it seems the column width isn't updated until the next refresh (when a row is hovered/highlighted, etc).

from textual.app import App, ComposeResult
from textual.widgets import DataTable

class MainWindow(App):
    BINDINGS = [("space", "update_table")]

    def compose(self) -> ComposeResult:
        self.ideas = ["a"]
        yield DataTable()

    def on_mount(self) -> None:
        table = self.query_one(DataTable)
        table.add_columns(*("foo", "bar"))
        table.cursor_type = "row"

        self.redraw()

    # def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
    def action_update_table(self) -> None:
        self.ideas.append(f"{self.ideas[-1] * 2}")
        self.redraw()

    def redraw(self) -> None:
        table = self.query_one(DataTable)
        table.clear()
        for data in self.ideas:
            table.add_row(*(data, "-"), key=str(data))

if __name__ == "__main__":
    app = MainWindow()
    app.run()
github-actions[bot] commented 6 months ago

We found the following entry in the FAQ which you may find helpful:

Feel free to close this issue if you found an answer in the FAQ. Otherwise, please give us a little time to review.

This is an automated reply, generated by FAQtory

TomJGooding commented 6 months ago

Just to note in case it is relevant, there are also a couple of existing issues relating to column widths:

luked42 commented 5 months ago

I've had some time to look a bit into this. Still getting familiar with the Textual framework so apologies for any oversights here.

From what I'm seeing / intuiting

Flow of code

  1. Selector key is hit
  2. self._set_hover_cursor(False) is called from action_select_cursor _data_table.py:2580. This ultimately queues a refresh of the current region
  3. main.py handles the redraw, clearing the table and readding rows (including the newly added row)
  4. The refresh/render line from step 2 is called, the column width (set to the max required for all rows) has not yet been updated yet, so DataTable renders using the old column width. Important to note here that we get called to render the lines that existed previous to step 3 (in terms of indices)
  5. _on_idle _data_table.py:1706 is now called where we have need to update dimensions. in _update_dimensions _data_table.py:1275 the column width is updated, additionally the virtual_size is updated to include the new row
  6. Compositor now calls for the extra line that was just added to be rendered as well. The previous rows how however not updated in this cycle

Minor points to note

Lapshin commented 2 days ago

Hi @luked42 , @TomJGooding !

I also faced this issue and after some research, I found a workaround. Just change the line:

    def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
        self.ideas.append(f"{self.ideas[-1] * 2}")
-       self.redraw()
+       self.call_after_refresh(self.redraw)

I don't know if this is a good solution but I'm happy with it