# Tabla

> Una tabla de datos con cabecera fija, ordenación y selección. Componente de Pyzed para GUIs de escritorio en Python, con ejemplos y API.

Source: https://albakore.github.io/pyzed/es/library/table/

:::tip[En pocas palabras]
Filas y columnas de datos, como una hoja de cálculo que ordenas pulsando una columna.
:::

Una tabla de datos con cabecera fija, ordenación opcional y selección de filas. Las columnas son objetos `ui.Column` cuyo `key` es una clave de dict, un atributo o una función de la fila; `render` convierte un valor en cualquier widget. La tabla ordena las filas ella misma por la columna cuyo **título** es `sort_by`: solo guardas ese título y la dirección.

`ui.table` · Categoría: [Datos](https://albakore.github.io/pyzed/es/library/#datos) · [Referencia completa →](https://albakore.github.io/pyzed/es/reference/ui/#table)

**Código:**

```python
rows = [
    {"name": "render.rs", "lang": "Rust", "lines": 1840},
    {"name": "app.py", "lang": "Python", "lines": 512},
    {"name": "theme.py", "lang": "Python", "lines": 730},
    {"name": "docs.yml", "lang": "YAML", "lines": 64},
]
pz.container(ui.table(
    [ui.Column("File", "name"), ui.Column("Language", "lang", width=120),
     ui.Column("Lines", "lines", width=80, align="end")],
    rows, sort_by="Lines", descending=True, on_sort=on_sort, selected=rows[1], striped=True,
), width=520)
```

## Cuándo usarlo

- Para registros con varios campos comparables: archivos, usuarios, procesos, resultados.
- Para una sola columna de elementos, una lista de [Filas](https://albakore.github.io/pyzed/es/library/list-item/) es más ligera.

## Uso

```python
import pyzed as pz
from pyzed import ui

class Files(pz.App):
    def __init__(self):
        self.rows = [{"name": "app.py", "lines": 512}, {"name": "theme.py", "lines": 730}]
        self.sort_by, self.descending, self.selected = "Lines", True, None

    def sort(self, title):
        self.descending = not self.descending if self.sort_by == title else False
        self.sort_by = title

    def view(self):
        return ui.table(
            [ui.Column("File", "name"), ui.Column("Lines", "lines", width=80, align="end")],
            self.rows, sort_by=self.sort_by, descending=self.descending, on_sort=self.sort,
            on_row_press=lambda row: setattr(self, "selected", row), selected=self.selected,
        )
```

## Variantes

:::note[Sobre estas capturas]
Cada captura la graba pyzed mismo, fuera de pantalla a 2x, en One Dark o One Light según el tema de este sitio, y recortada al componente para que se vea a su tamaño real. El puntero pasa por encima, pulsa o escribe en él; un componente que no reacciona al puntero recorre algunos temas. Con movimiento reducido ves el primer fotograma. Abre **Código** bajo una captura para ver el fragmento que la dibuja; los nombres libres como `on_save` son tus propios callbacks.
:::

### Opciones

```python
pz.container(ui.table([ui.Column("File", "name"), ui.Column("Lines", "lines", width=80, align="end")],
                      [{"name": "app.py", "lines": 512}, {"name": "theme.py", "lines": 730}, {"name": "ui.py", "lines": 1290}]), width=320)
```

```python
pz.container(ui.table([ui.Column("File", "name"), ui.Column("Lines", "lines", width=80, align="end")],
                      [{"name": "app.py", "lines": 512}, {"name": "theme.py", "lines": 730}, {"name": "ui.py", "lines": 1290}], striped=True), width=320)
```

```python
rows = [{"name": "app.py", "lines": 512}, {"name": "theme.py", "lines": 730}, {"name": "ui.py", "lines": 1290}]
pz.container(ui.table([ui.Column("File", "name"), ui.Column("Lines", "lines", width=80, align="end")], rows, selected=rows[1]), width=320)
```

```python
pz.container(ui.table([ui.Column("File", "name"), ui.Column("Lines", "lines", width=80, align="end")],
                      [{"name": "app.py", "lines": 512}, {"name": "theme.py", "lines": 730}, {"name": "ui.py", "lines": 1290}], sort_by="Lines", descending=True,
                      on_sort=on_sort), width=320)
```

```python
pz.container(ui.table([ui.Column("File", "name"), ui.Column("Lines", "lines", width=80, align="end")],
                      [{"name": "app.py", "lines": 512}, {"name": "theme.py", "lines": 730}, {"name": "ui.py", "lines": 1290}], row_height=24), width=320)
```

### Celdas

```python
rows = [{"job": "build", "state": "passed"}, {"job": "lint", "state": "failed"}]
tint = {"passed": "success", "failed": "error"}
pz.container(ui.table([ui.Column("Job", "job"),
                       ui.Column("State", "state", width=110, render=lambda s: ui.badge(s, tint=tint[s]))],
                      rows), width=320)
```

```python
people = [("Ana", 31), ("Luis", 27)]
pz.container(ui.table([ui.Column("Name", lambda p: p[0]),
                       ui.Column("Age", lambda p: p[1], width=70, align="end")], people), width=320)
```

### Estados y eventos

`on_row_double_press(fila)` abre una fila y `on_row_context(fila)` muestra un menú contextual.

```python
rows = [{"name": "app.py", "size": 4}, {"name": "ui.py", "size": 12}, {"name": "README.md", "size": 2}]
ui.table(["name", "size"], rows, selected=[rows[0], rows[2]], on_row_press=on_press,
         on_row_double_press=on_open, on_row_context=on_context, height=130)
```

```python
ui.table(["name", "size"], [], empty="No files yet", height=100)
```

## Ejemplos

### Celdas personalizadas

`render` recibe el valor y devuelve un widget, aquí una insignia.

**Código:**

```python
rows = [{"job": "build", "state": "passed"}, {"job": "lint", "state": "failed"},
        {"job": "docs", "state": "running"}]
tint = {"passed": "success", "failed": "error", "running": "warning"}
pz.container(ui.table(
    [ui.Column("Job", "job"),
     ui.Column("State", "state", width=120, render=lambda s: ui.badge(s, tint=tint[s]))],
    rows,
), width=400)
```

## Eventos

Tabla tiene **4 eventos propios**, en la tabla de abajo. Como todo widget, también tiene los **18 eventos comunes** (`click`, `double_click`, `right_click`, `mouse_enter`, `drag`, `key_down`, `resize`…), que se conectan con `.on(...)`: mira [Eventos](https://albakore.github.io/pyzed/es/guides/events/) y [Eventos de cada componente](https://albakore.github.io/pyzed/es/library/events/).

| Evento | El handler recibe | Cuándo |
| --- | --- | --- |
| `on_sort` | `(title)` | Callable que recibe el título pulsado. |
| `on_row_press` | `(row)` | Callable que recibe la fila pulsada. |
| `on_row_double_press` | `(row)` | Recibe la fila con un doble clic (abrir, editar…). |
| `on_row_context` | `(row)` | Recibe la fila con un clic derecho (un menú contextual). |

**El handler recibe** es lo que Pyzed le pasa a tu función: `()` es nada. Cualquier handler también puede ser un mensaje (un valor que no se puede llamar), que va a `update()`.

### Manejar los eventos

Los mismos eventos escritos de tres formas. **Imperativa**: métodos de la app que cambian `self`. **Declarativa**: los handlers producen mensajes y un único `update()` decide. **Funcional**: una función pura dibuja un estado inmutable y pide cambios con `set_state(...)`. Mira [Imperativo, declarativo y funcional](https://albakore.github.io/pyzed/es/guides/code-styles/).

**Imperativo:**

```python
class Files(pz.App):
    def __init__(self):
        self.rows = [{"name": "app.py", "lines": 512}, {"name": "theme.py", "lines": 730}]
        self.sort_by, self.descending = "Lines", True
        self.selected, self.opened = None, None

    def sort(self, title):               # on_sort: the clicked column title
        self.descending = not self.descending if self.sort_by == title else False
        self.sort_by = title

    def view(self):
        key = "name" if self.sort_by == "File" else "lines"
        rows = sorted(self.rows, key=lambda r: r[key], reverse=self.descending)
        return ui.table(
            [ui.Column("File", "name"), ui.Column("Lines", "lines", width=80, align="end")], rows,
            sort_by=self.sort_by, descending=self.descending, on_sort=self.sort, selected=self.selected,
            on_row_press=lambda row: setattr(self, "selected", row),         # click
            on_row_double_press=lambda row: setattr(self, "opened", row),    # double click
            on_row_context=lambda row: print("menu for", row["name"]),       # right click
        )
```

**Declarativo:**

```python
from dataclasses import dataclass

@dataclass
class Sort:
    title: str

@dataclass
class Select:
    row: dict

class Files(pz.App):
    rows = [{"name": "app.py", "lines": 512}, {"name": "theme.py", "lines": 730}]
    sort_by, descending, selected = "Lines", True, None

    def update(self, message):
        match message:
            case Sort(title):
                self.descending = not self.descending if title == self.sort_by else False
                self.sort_by = title
            case Select(row):
                self.selected = row

    def view(self):
        return ui.table([ui.Column("File", "name"), ui.Column("Lines", "lines", width=80)], self.rows,
                        sort_by=self.sort_by, descending=self.descending, selected=self.selected,
                        on_sort=Sort, on_row_press=Select)
```

**Funcional:**

```python
from dataclasses import dataclass

ROWS = ({"name": "app.py", "lines": 512}, {"name": "theme.py", "lines": 730})

@dataclass(frozen=True)
class State:
    sort_by: str = "Lines"
    descending: bool = True
    selected: str = ""

def view(state, set_state):
    key = "name" if state.sort_by == "File" else "lines"
    rows = sorted(ROWS, key=lambda r: r[key], reverse=state.descending)
    return ui.table(
        [ui.Column("File", "name"), ui.Column("Lines", "lines", width=80)], rows,
        sort_by=state.sort_by, descending=state.descending,
        selected=next((r for r in rows if r["name"] == state.selected), None),
        on_sort=lambda t: set_state(sort_by=t, descending=not state.descending if t == state.sort_by else False),
        on_row_press=lambda row: set_state(selected=row["name"]))
```

## Propiedades y API

### `ui.table`

```python
table(
    columns,
    rows,
    *,
    sort_by=None,
    descending=False,
    on_sort=None,
    on_row_press=None,
    selected=None,
    striped=False,
    row_height=30,
    height=None,
    on_row_double_press=None,
    on_row_context=None,
    empty=None,
    theme=None,
)
```

Una tabla de datos con encabezado fijo, orden opcional y selección de filas. El orden se hace aquí cuando `sort_by` nombra una columna: pulsar un encabezado llama a `on_sort(título)` y tú cambias `sort_by`/`descending` en tu estado. Las filas están virtualizadas: solo se construyen las visibles, así que cientos de miles de filas se desplazan con fluidez.

| Propiedad | Por defecto | Descripción |
| --- | --- | --- |
| `columns` | *obligatorio* | `ui.Column` o claves simples. |
| `rows` | *obligatorio* | Dicts u objetos. |
| `sort_by` | `None` | Título de la columna por la que se ordena. |
| `descending` | `False` | Orden descendente. |
| `selected` | `None` | La fila a resaltar, o una lista o conjunto de filas (selección múltiple). |
| `striped` | `False` | Filas alternas sombreadas. |
| `row_height` | `30` | Alto de cada fila. |
| `height` | `None` | Alto: píxeles, `"fill"`, `"shrink"` o `"fill:N"` (proporción). |
| `empty` | `None` | Texto o widget que se muestra en lugar de las filas cuando no hay ninguna. |
| `theme` | `None` | Tema a usar en lugar del actual (`pyzed.theme.current()`). |

Eventos: `on_sort`, `on_row_press`, `on_row_double_press`, `on_row_context` ([eventos](#eventos)).

[Referencia completa →](https://albakore.github.io/pyzed/es/reference/ui/#table)

### `ui.Column`

```python
@dataclass
class Column(title, key, width='fill', align='start', render=None, sortable=True)
```

Una columna de `ui.table`. `key` es una clave de dict, un nombre de atributo o una función de la fila; `render` (opcional) convierte el valor en un widget. `width` es `"fill"`, un número o una porción como `"fill:2"`.

| Propiedad | Por defecto | Descripción |
| --- | --- | --- |
| `title` | *obligatorio* | Encabezado (y nombre para ordenar). |
| `key` | *obligatorio* | Cómo obtener el valor de cada fila. |
| `width` | `"fill"` | Ancho: píxeles, `"fill"`, `"shrink"` o `"fill:N"` (proporción). |
| `align` | `"start"` | `"start"`, `"center"` o `"end"`. |
| `render` | `None` | Función valor → widget. |
| `sortable` | `True` | Si el encabezado se puede pulsar para ordenar. |

[Referencia completa →](https://albakore.github.io/pyzed/es/reference/ui/#column)

## Relacionados

- [Fila de lista](https://albakore.github.io/pyzed/es/library/list-item/): Una fila de lista con icono, texto y un hueco al final.
- [Lista virtual](https://albakore.github.io/pyzed/es/library/virtual-list/): Una lista que solo construye las filas visibles, para millones de elementos.
- [Cifra](https://albakore.github.io/pyzed/es/library/stat/): Una cifra de dashboard con variación y minigráfico.
- [Grupo de ajustes](https://albakore.github.io/pyzed/es/library/settings-group/): Un bloque con título de filas de ajustes, para pantallas de ajustes.
