# Lista virtual

> Una lista que solo construye las filas visibles, para millones de elementos. Componente de Pyzed para GUIs de escritorio en Python, con ejemplos y API.

Source: https://albakore.github.io/pyzed/es/library/virtual-list/

:::tip[En pocas palabras]
Una lista que sigue siendo rápida con millones de filas, porque solo dibuja lo que ves.
:::

Una lista con scroll para miles o millones de elementos. Solo se construyen las filas visibles: `render(elemento)` se llama cuando un elemento entra en la vista, así que la lista cuesta lo mismo sea cual sea su longitud. Todas las filas tienen la misma `item_height`. `pz.virtual_column` es la pieza de debajo (filas por índice, dentro de cualquier `scrollable`), y la [Tabla](https://albakore.github.io/pyzed/es/library/table/) también la usa para sus filas.

`ui.virtual_list` · Categoría: [Listas y menús](https://albakore.github.io/pyzed/es/library/#listas-y-menús) · [Referencia completa →](https://albakore.github.io/pyzed/es/reference/ui/#virtual_list)

**Código:**

```python
files = [f"src/module_{i:06}.py" for i in range(1_000_000)]
pz.container(ui.virtual_list(
    files, lambda f: ui.list_item(f, icon="file-code", selected=f.endswith("02.py")),
    height=240,
), width=420)
```

:::note[Nuevo]
Este componente se añadió hace poco. Cuéntanos qué tal te funciona en el [gestor de incidencias](https://github.com/albakore/pyzed/issues).
:::

## Cuándo usarlo

- Para logs, resultados de búsqueda, listas de archivos o cualquier lista que pueda pasar de unos cientos de filas.
- Para listas cortas, una `pz.column` de [Filas](https://albakore.github.io/pyzed/es/library/list-item/) es más sencilla.

## Uso

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

class Logs(pz.App):
    def __init__(self):
        self.lines = [f"[{i:06}] request handled" for i in range(500_000)]

    def view(self):
        return ui.virtual_list(self.lines, lambda line: pz.text(line, font="mono", size=12),
                               item_height=20, id="logs")
```

## 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.virtual_list([f"row {i:,}" for i in range(1_000_000)],
                             lambda r: ui.list_item(r, icon="file"), height=150), width=260)
```

```python
pz.container(ui.virtual_list([f"[{i:06}] request handled" for i in range(100_000)],
                             lambda line: pz.text(line, font="mono", size=12), item_height=20, height=150),
             width=260)
```

```python
pz.container(ui.virtual_list(list(range(10_000)), lambda i: ui.card(ui.label(f"Card {i}"), padding=8),
                             item_height=40, spacing=6, height=150), width=260)
```

## Ejemplos

### Filas por índice

`pz.virtual_column(count, item, item_height=...)` llama a `item(índice)`, útil cuando las filas salen de un array, un cursor de base de datos o un cálculo. Se desplaza como cualquier `scrollable`: la fila `i` empieza en `i * (item_height + spacing)`.

**Código:**

```python
pz.container(pz.scrollable(
    pz.virtual_column(10_000_000, lambda i: pz.text(f"{i:>10,}  {i * i:>22,}", font="mono", size=12),
                      item_height=22),
    height=150,
), width=420)
```

## Eventos

Lista virtual tiene **1 evento 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_scroll` | `(info)` | Callable que recibe el viewport del scroll, como en `pz.scrollable`. |

**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 Logs(pz.App):
    def __init__(self):
        self.lines = [f"[{i:06}] request handled" for i in range(100_000)]
        self.top = 0

    def scrolled(self, info):            # on_scroll: offsets and sizes of the viewport
        self.top = int(info["y"] // 20)

    def view(self):
        return pz.column([
            ui.label(f"Line {self.top + 1} of {len(self.lines)}"),
            ui.virtual_list(self.lines, lambda line: pz.text(line, font="mono", size=12),
                            item_height=20, height=200, on_scroll=self.scrolled),
        ], spacing=6)
```

**Declarativo:**

```python
from dataclasses import dataclass

@dataclass
class Scrolled:
    info: dict

class Logs(pz.App):
    lines = [f"[{i:06}] request handled" for i in range(100_000)]
    top = 0

    def update(self, message):
        match message:
            case Scrolled(info):
                self.top = int(info["y"] // 20)

    def view(self):
        return ui.virtual_list(self.lines, lambda line: pz.text(line, size=12), item_height=20,
                               height=200, on_scroll=Scrolled)
```

**Funcional:**

```python
from dataclasses import dataclass

LINES = [f"[{i:06}] request handled" for i in range(100_000)]

@dataclass(frozen=True)
class State:
    top: int = 0

def view(state, set_state):
    return pz.column([
        ui.label(f"Line {state.top + 1}"),
        ui.virtual_list(LINES, lambda line: pz.text(line, size=12), item_height=20, height=200,
                        on_scroll=lambda info: set_state(top=int(info["y"] // 20))),
    ], spacing=6)
```

## Consejos

:::tip
- Las filas se vuelven a construir cuando vuelven a la vista, así que mantén `render` ligero o envuélvelo en `@pz.memo`.
- `items` debería ser una lista u otra secuencia: otros iterables se copian a una lista en cada `view()`.
:::

## Propiedades y API

### `ui.virtual_list`

```python
virtual_list(
    items,
    render,
    *,
    item_height=26,
    spacing=0,
    width='fill',
    height='fill',
    padding=None,
    overscan=4,
    id=None,
    on_scroll=None,
    theme=None,
)
```

Una lista con scroll que solo construye las filas visibles, para miles o millones de elementos. `render(elemento)` devuelve el widget de un elemento y se llama cuando entra en la vista. Todas las filas tienen la misma altura.

| Propiedad | Por defecto | Descripción |
| --- | --- | --- |
| `items` | *obligatorio* | Los elementos: una lista o cualquier secuencia (otros iterables se convierten en lista). |
| `render` | *obligatorio* | Función que recibe un elemento y devuelve su widget. |
| `item_height` | `26` | Altura de cada fila, en píxeles. |
| `spacing` | `0` | Separación entre hijos, en píxeles. |
| `width` | `"fill"` | Ancho: píxeles, `"fill"`, `"shrink"` o `"fill:N"` (proporción). |
| `height` | `"fill"` | Alto: píxeles, `"fill"`, `"shrink"` o `"fill:N"` (proporción). |
| `padding` | `None` | Relleno interior: `8`, `(vertical, horizontal)` o `(arriba, derecha, abajo, izquierda)`. |
| `overscan` | `4` | Filas extra construidas por encima y por debajo de las visibles. |
| `id` | `None` | Identificador estable para enfocarlo o desplazarlo con un `Command`. |
| `theme` | `None` | Tema a usar en lugar del actual (`pyzed.theme.current()`). |

Eventos: `on_scroll` ([eventos](#eventos)).

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

### `pz.virtual_column`

```python
virtual_column(count, item, *, item_height, spacing=0.0, overscan=4, width=None)
```

Una columna de `count` filas de la misma altura que solo construye las filas visibles: `item(índice)` se llama cuando la fila entra en la vista, y las que salen se descartan. Una columna de un millón de filas cuesta lo mismo que una de cincuenta. Ponla dentro de un `scrollable` (se virtualiza contra el área visible del más cercano, o de la ventana).

| Propiedad | Por defecto | Descripción |
| --- | --- | --- |
| `count` | *obligatorio* | Número de filas. |
| `item` | *obligatorio* | Función que recibe el índice de una fila y devuelve su widget. |
| `item_height` | *obligatorio* | Altura de cada fila, en píxeles. |
| `spacing` | `0.0` | Espacio entre filas, en píxeles. |
| `overscan` | `4` | Filas extra construidas por encima y por debajo de las visibles, para que el scroll rápido no deje huecos. |
| `width` | `None` | Ancho: píxeles, `"fill"`, `"shrink"` o `"fill:N"` (proporción). |

[Referencia completa →](https://albakore.github.io/pyzed/es/reference/widgets/#virtual_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.
- [Tabla](https://albakore.github.io/pyzed/es/library/table/): Una tabla de datos con cabecera fija, ordenación y selección.
