---
title: "Multiventana — Docs de zenit Zig UI"
description: "Crea ventanas nativas aisladas para editores, vistas previas o paneles de herramientas, todas movidas por un único bucle de eventos que despacha por…"
url: https://zenit.z.express/es/docs/advanced/multi-window
language: es
alternate_en: https://zenit.z.express/docs/advanced/multi-window.md
alternate_zh: https://zenit.z.express/zh/docs/advanced/multi-window.md
alternate_ja: https://zenit.z.express/ja/docs/advanced/multi-window.md
alternate_ko: https://zenit.z.express/ko/docs/advanced/multi-window.md
alternate_fr: https://zenit.z.express/fr/docs/advanced/multi-window.md
alternate_de: https://zenit.z.express/de/docs/advanced/multi-window.md
project: zenit v0.1.0-alpha (Zig 0.15.2, macOS)
source: https://github.com/version-next/zenit
---

# Aplicaciones multiventana

Crea ventanas nativas aisladas para editores, vistas previas o paneles de herramientas, todas movidas por un único bucle de eventos que despacha por window\_id.

## Cuándo usarlo

Las aplicaciones de una sola ventana siguen usando `App`; su API no cambia. Pasa a `MultiWindowApp` (también exportado como `zenit_app.Application`) solo cuando las ventanas necesiten su propio ciclo de vida, enrutamiento de entrada, superficie GPU o árbol de accesibilidad.

## Crear ventanas

`main.zig`

```zig
const std = @import("std");
const ui = @import("ui");
const zenit_app = @import("zenit_app");

fn mountEditor(cx: *ui.Cx, scope: *ui.Scope) anyerror!*ui.Node {
    _ = scope;
    return ui.box(cx, .{ .width = .fill(), .height = .fill() }, .{});
}

fn mountPreview(cx: *ui.Cx, scope: *ui.Scope) anyerror!*ui.Node {
    _ = scope;
    return ui.box(cx, .{ .width = .fill(), .height = .fill() }, .{});
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    var application = zenit_app.MultiWindowApp.init(gpa.allocator(), .{});
    defer application.deinit();

    // createWindowWith = create + mount in one transaction: if mount fails,
    // every native / GPU resource of that window is torn down again.
    const editor = try application.createWindowWith(.{
        .window = .{ .width = 900, .height = 700, .title = "Editor" },
    }, mountEditor);

    _ = try application.createWindowWith(.{
        .window = .{ .width = 480, .height = 700, .title = "Preview" },
    }, mountPreview);

    _ = application.activateWindow(editor.windowId());
    try application.run(); // returns after the last window closes or quit()
}
```

Cada llamada a `createWindow*` devuelve un `*App` y acepta la misma configuración que un `App` de una sola ventana (`window`, `console`, `frame_pacing`, …). `createWindow` crea sin montar, así que llamas tú a `app.mount(mountFn)`; `createWindowWith` hace ambas cosas en una sola transacción. El ejemplo `zig build multi-window` del repositorio muestra las dos.

> NOTE
> 
> **Como máximo 16 ventanas.** `MultiWindowApp.max_windows` es 16: un límite explícito para el registro de ciclo de vida sin asignaciones. Por encima, `createWindow*` devuelve `error.TooManyWindows`.

## Garantías de aislamiento

PER-WINDOW STACK

Un evento entra exactamente en una ventana, elegida por window\_id; el modelo compartido está fuera y la aplicación notifica a cada ventana de forma explícita.

**Independiente por ventana**

Ventana nativa, Cx (con su propio grafo reactivo), contexto de fuentes, superficie Metal / renderer / cola del dispositivo, IME y ruta de accesibilidad.

**Eventos aislados**

Los eventos de puntero, teclado, IME y arrastre se enrutan por el window\_id nativo; los comandos de menú van a la key window capturada al dispararse, no a la ventana que casualmente los sacó de la cola.

**Cierre independiente**

Cerrar una ventana libera solo sus propios recursos mientras las demás siguen renderizando; run() retorna cuando se cierra la última.

[Video](https://zenit.z.express/media/devtools-console.mp4?v=05e8c163e3)

Una aplicación real de dos ventanas: el target observado y DevTools tienen cada uno su propio Cx y su ventana. El harness cambia la ventana de herramientas a Console y la filtra mientras el target sigue vivo.

## Ciclo de vida

| API | Qué hace |
| --- | --- |
| `run()` | Ejecuta el bucle hasta que se cierra la última ventana o se llama a quit() |
| `tick()` | Una ronda de pump + frame, para controlar el bucle tú mismo |
| `closeWindow(id)` | Cierra una ventana; dentro de un callback se encola hasta un límite seguro |
| `quit()` | Sale de toda la aplicación; deinit() cierra en orden las ventanas restantes |
| `window(id)` | Busca una ventana viva por id; null si ya se cerró |
| `activateWindow(id)` | Convierte una ventana en la activa |
| `setMenuModel` / `bindMenuCommand` | El modelo de menú es global al proceso; los callbacks de comandos se vinculan por ventana |

`close.zig`

```zig
const preview_id = preview.windowId(); // preview: *App from createWindowWith

// Safe from inside an input / menu / render callback: the close is queued
// and committed at the loop's iteration boundary.
_ = application.closeWindow(preview_id);

// Look windows up by id instead of holding *App across a close.
if (application.window(preview_id)) |app| {
    _ = app; // still alive
}
```

> WARNING
> 
> **\*App deja de ser válido tras cerrar.** En cuanto se destruye una ventana, cualquier puntero `*App` que tuvieras de ella queda colgante. Guarda el id de la ventana y búscala con `application.window(id)` cuando la necesites.

## Compartir estado

El modelo compartido entre ventanas pertenece a la aplicación; cada ventana guarda solo su propio estado de vista. Cada Cx tiene su propio grafo reactivo, así que no dejes que el Effect de una ventana lea un Signal del Scope de otra, y nunca compartas punteros a Node ni recursos de Scope entre Cx.

-   **El modelo vive fuera de las ventanas.** Guarda los datos en un struct simple o en un store de nivel de aplicación que sobreviva a cualquier ventana.
    
-   **Cada ventana refleja lo que necesita.** Una ventana crea Signals en su propio Scope; cuando el modelo cambia, la aplicación escribe explícitamente el reflejo de cada ventana.
    
-   **Da de baja al cerrar.** Tras cerrarse una ventana, el modelo no debe conservar punteros a sus Signals.
    

> TIP
> 
> **DevTools también es una ventana.** El panel completo de Elements / Components / Console / Performance puede vivir en una segunda ventana y observar el Cx de otra; para echar un vistazo rápido a los límites del layout, el overlay de una línea es más ligero. Consulta [DevTools](https://zenit.z.express/es/docs/advanced/devtools).
