How do you get a click event from a sub-component #62
-
In the example below the goal is to know which CheckboxItem is clicked on by having its # app.py
from typing import List, Tuple
import rich
from rich.panel import Panel
from rich.table import Table
from textual import events
from textual.app import App
from textual.reactive import Reactive
from textual.widget import Widget
from textual.widgets import Placeholder
class CheckboxItem(Widget):
def __init__(self, parent: Widget, *, value: bool, label: str) -> None:
super().__init__()
self._parent = parent
self.value = value
self.label = label
def __rich_repr__(self) -> rich.repr.Result:
yield "name", self.name
yield "value", self.value
yield "label", self.label
def render(self) -> Table:
grid = Table.grid(padding=2)
grid.add_column()
grid.add_column(justify="right")
grid.add_row("☑️" if self.value else "🟦", self.label)
return grid
async def on_click(self, event: events.Click) -> None:
# Update state in parent.
# Trigger refresh
pass
class TodoList(Widget, can_focus=True):
def __init__(self, items: List[Tuple[bool, str]] = []) -> None:
super().__init__()
self.items = items
def render(self) -> Panel:
grid = Table.grid(padding=1)
grid.add_column()
for value, label in self.items:
grid.add_row(CheckboxItem(self, value=value, label=label))
return Panel(grid, border_style="green")
class Todo(App):
async def on_mount(self, event: events.Mount) -> None:
todos = [(True, "dream about cake"), (False, "bake cake"), (False, "eat cake")]
await self.view.dock(TodoList(todos), edge="left", size=50)
await self.view.dock(Placeholder(), Placeholder(), edge="top")
if __name__ == "__main__":
Todo.run(title="Todo", log="textual.log")
|
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 1 reply
-
These are just guesses but it looks like MouseEvents will bubble by default, so you could try defining a click handler on the parent and triggering a render there. A little inefficient perhaps but could work. Or you could try setting your values to be Reactive Booleans. That could trigger a repaint when they're updated. But that's voodoo so I've really no idea if it will work. |
Beta Was this translation helpful? Give feedback.
-
Sorry there is no docs yet, but you have got the basic idea. It's not enough to add a widget to the output of a From there you can can create a custom message (see messages.py for example) which you can emit from CheckboxItem and handle in TodoList. Alternatively, your CheckboxItem need not be a Widget. It could be a renderable and you can attach an action to the label via Style.on. I got this one working, here's your sample with modifications: from typing import List, Tuple
import rich
import rich.repr
from rich.panel import Panel
from rich.style import Style
from rich.table import Table
from textual import events
from textual.app import App
from textual.reactive import Reactive
from textual.widget import Widget
from textual.widgets import Placeholder
class CheckboxItem:
def __init__(self, parent: Widget, *, value: bool, label: str) -> None:
super().__init__()
self._parent = parent
self.value = value
self.label = label
def __rich_repr__(self) -> rich.repr.Result:
yield "value", self.value
yield "label", self.label
def __rich__(self) -> Table:
grid = Table.grid(padding=2)
grid.add_column()
grid.add_column(justify="right")
grid.add_row(
"☑️" if self.value else "🟦",
self.label,
style=Style.on(click=f"item_clicked({self.label!r})"),
)
return grid
class TodoList(Widget, can_focus=True):
def __init__(self, items: List[Tuple[bool, str]] = []) -> None:
super().__init__()
self.items = items
def render(self) -> Panel:
grid = Table.grid(padding=1)
grid.add_column()
for value, label in self.items:
grid.add_row(CheckboxItem(self, value=value, label=label))
return Panel(grid, border_style="green")
async def action_item_clicked(self, item: str) -> None:
self.console.bell()
self.log("checkbox item", item, "clicked")
class Todo(App):
async def on_mount(self, event: events.Mount) -> None:
todos = [(True, "dream about cake"), (False, "bake cake"), (False, "eat cake")]
await self.view.dock(TodoList(todos), edge="left", size=50)
await self.view.dock(Placeholder(), Placeholder(), edge="top")
if __name__ == "__main__":
Todo.run(title="Todo", log="textual.log") Thanks for being an early adopter! |
Beta Was this translation helpful? Give feedback.
Sorry there is no docs yet, but you have got the basic idea.
It's not enough to add a widget to the output of a
render
method, it needs to be "mounted". AView
class will do this for you, so your TodoList should extend from View. You can then dock your CheckboxItem in the TodoList.on_mountFrom there you can can create a custom message (see messages.py for example) which you can emit from CheckboxItem and handle in TodoList.
Alternatively, your CheckboxItem need not be a Widget. It could be a renderable and you can attach an action to the label via Style.on. I got this one working, here's your sample with modifications: