-
Notifications
You must be signed in to change notification settings - Fork 818
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Table header columns width mismatch #4404
Comments
See the following advice about threaded workers https://textual.textualize.io/guide/workers/#posting-messages I suspect fixing that will resolve your issue. |
Yep, that fixed the problem! Sorry for the noise. |
Don't forget to star the repository! Follow @textualizeio for Textual updates. |
That's quite a bit of code to wade through, but I don't understand why you have so many workers and async handlers? |
I tried with only one worker (the threaded one), but then wrapped the remaining two callbacks into async ones to see if that helps (it didn't). I just updated the code example so it no longer has the extra workers. UPD: also fixed the formatting issues |
Threaded workers cannot be forced to cancel (a limit of threads). Which means that you need to manually check if the worker is cancelled. I've made a couple of tweaks to your code, and I can't reproduce that issue now: import json
from textual import work
from textual.app import App, ComposeResult
from textual.message import Message
from textual.screen import Screen
from textual.widgets import Header, Footer, DataTable, Markdown
from textual.binding import Binding
from textual.worker import get_current_worker
import time, datetime
class AccountsListScreen(Screen[None]):
BINDINGS = [
("ctrl+r", "refresh", "Refresh"),
]
CSS = """
Markdown {
margin: 0 0 0 0;
padding: 1 1 0 1;
border: solid $panel-lighten-3;
}
"""
account: str | None = None
accounts: dict[str, dict] = {}
def compose(self) -> ComposeResult:
yield Header()
yield DataTable(cursor_type="row")
yield Markdown(id="detail")
yield Footer()
def on_mount(self) -> None:
self.sub_title = "Accounts"
table = self.query_one(DataTable)
table.add_columns("Name", "Instance", "Status", "Created at", "Updated at")
self.refresh_accounts()
def action_refresh(self) -> None:
self.refresh_accounts()
def on_data_table_row_highlighted(self, row: DataTable.RowSelected) -> None:
self.account = row.row_key.value
detail = self.query_one("#detail")
if self.account:
acc = self.accounts[self.account].copy()
acc["created_at"] = acc["created_at"].strftime("%Y-%m-%d %H:%M:%S")
acc["updated_at"] = acc["updated_at"].strftime("%Y-%m-%d %H:%M:%S")
MD = f"""
* Name: {acc["name"]}
* Instance: {acc["instance"]}
* Status: {acc["status"]}
* Created at: {acc["created_at"]}
* Updated at: {acc["updated_at"]}
### Params
```json
{json.dumps(acc, indent=2)}
```
""".strip()
detail.update(MD)
detail.border_title = self.account
else:
detail.update("")
detail.border_title = ""
class DBReturn(Message):
def __init__(self, accounts) -> None:
self.accounts = accounts
super().__init__()
# @work(exclusive=True)
def refresh_accounts(self) -> None:
table = self.query_one(DataTable)
# if table.loading:
# return
table.loading = True
self.get_accounts()
@work(exclusive=True, thread=True)
def get_accounts(self) -> None:
time.sleep(1) # synchronous DB call
accounts = [
{
"name": f"Test Account {i}",
"instance": "default",
"status": "active",
"created_at": datetime.datetime.now(),
"updated_at": datetime.datetime.now(),
}
for i in range(10)
]
# self.app.call_from_thread(self.on_accounts_list_screen_dbreturn, accounts)
if not get_current_worker().is_cancelled:
self.post_message(self.DBReturn(accounts))
# @work(exclusive=True)
def on_accounts_list_screen_dbreturn(self, accounts):
table = self.query_one(DataTable)
table.clear()
self.account = None
self.accounts = {}
for acc in accounts.accounts:
table.add_row(
acc["name"],
acc["instance"],
acc["status"],
acc["created_at"].strftime("%Y-%m-%d %H:%M:%S"),
acc["updated_at"].strftime("%Y-%m-%d %H:%M:%S"),
key=acc["name"],
)
self.accounts[acc["name"]] = acc
self.account = self.account or acc["name"]
table.border_title = f"{len(accounts.accounts)} accounts"
table.loading = False
table.focus()
class BackofficeApp(App):
MODES = {
"accounts": AccountsListScreen,
}
BINDINGS = [
("q", "quit()", "Quit"),
Binding("f12", "take_screenshot()", "Take screenshot", show=False),
]
CSS = """
DataTable {
border: solid $accent;
}
"""
def on_mount(self) -> None:
self.title = "Title"
self.switch_mode("accounts")
def action_take_screenshot(self) -> None:
filename = self.save_screenshot()
self.notify(f"Saved in {filename}", title="Screenshot saved")
app = BackofficeApp()
if __name__ == "__main__":
app.run() |
Here's a quick MRE for the issue described above when rapidly updating the from textual import on
from textual.app import App, ComposeResult
from textual.widgets import DataTable, Markdown
EXAMPLE_MARKDOWN = """\
## Try continuously scrolling the table by holding the down key
This is an example of Textual's `Markdown` widget.
Markdown syntax and extensions are supported.
- Typography *emphasis*, **strong**, `inline code` etc.
- Headers
- Lists (bullet and ordered)
- Syntax highlighted code blocks
- Tables!
"""
class ExampleApp(App):
CSS = """
DataTable, Markdown {
height: 50%;
}
"""
def compose(self) -> ComposeResult:
yield DataTable(cursor_type="row")
yield Markdown()
def on_mount(self) -> None:
table = self.query_one(DataTable)
table.add_column("Column")
for i in range(100):
table.add_row(f"Row #{i}")
@on(DataTable.RowHighlighted)
def update_markdown(self, event: DataTable.RowHighlighted) -> None:
markdown = self.query_one(Markdown)
markdown.update(f"# Row #{event.cursor_row}\n{EXAMPLE_MARKDOWN}")
if __name__ == "__main__":
app = ExampleApp()
app.run() |
Apparently, the async def on_data_table_row_highlighted(self, row):
...
await detail.update(MD)
def refresh_accounts(self):
table = self.query_one(DataTable)
table.loading = True
self.get_accounts()
@work(exclusive=True, thread=True)
def get_accounts(self):
...
self.post_message(self.DBReturn(accounts))
async def on_accounts_list_screen_dbreturn(self, accounts):
table = self.query_one(DataTable)
table.clear()
...
table.loading = False
table.focus() Does it look sane? But the parallelism model is confusing to me. I thought that plain (non-async) functions (e.g. |
Don't forget to star the repository! Follow @textualizeio for Textual updates. |
Is the header column width mismatch a known issue? Happens intermittently and goes away on terminal defocus. Seems to be caused by this method on my Screen subclass that updates the Static widget under the table:
textual diagnose
The text was updated successfully, but these errors were encountered: