---
title: "Multi-fenêtre — Docs zenit Zig UI"
description: "Créez des fenêtres natives isolées pour des éditeurs, des aperçus ou des panneaux d’outils, toutes pilotées par une seule boucle d’événements qui distribue…"
url: https://zenit.z.express/fr/docs/advanced/multi-window
language: fr
alternate_en: https://zenit.z.express/docs/advanced/multi-window.md
alternate_zh: https://zenit.z.express/zh/docs/advanced/multi-window.md
alternate_es: https://zenit.z.express/es/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_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
---

# Applications multifenêtres

Créez des fenêtres natives isolées pour des éditeurs, des aperçus ou des panneaux d’outils, toutes pilotées par une seule boucle d’événements qui distribue par window\_id.

## Quand l’utiliser

Les applications à fenêtre unique continuent d’utiliser `App` ; son API ne change pas. Passez à `MultiWindowApp` (aussi exporté sous `zenit_app.Application`) uniquement lorsque les fenêtres ont besoin de leur propre cycle de vie, routage des entrées, surface GPU ou arbre d’accessibilité.

## Créer des fenêtres

`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()
}
```

Chaque appel à `createWindow*` renvoie un `*App` et accepte la même configuration qu’un `App` à fenêtre unique (`window`, `console`, `frame_pacing`, …). `createWindow` crée sans monter : vous appelez vous-même `app.mount(mountFn)` ; `createWindowWith` fait les deux en une seule transaction. L’exemple `zig build multi-window` du dépôt illustre les deux.

> NOTE
> 
> **16 fenêtres au maximum.** `MultiWindowApp.max_windows` vaut 16 — une borne explicite pour le registre de cycle de vie sans allocation. Au-delà, `createWindow*` renvoie `error.TooManyWindows`.

## Garanties d’isolation

PER-WINDOW STACK

Un événement entre dans exactement une fenêtre, choisie par window\_id ; le modèle partagé reste à l’extérieur et l’application notifie chaque fenêtre explicitement.

**Propre à chaque fenêtre**

Fenêtre native, Cx (avec son propre graphe réactif), contexte de polices, surface Metal / renderer / file du device, IME et route d’accessibilité.

**Événements isolés**

Les événements de pointeur, clavier, IME et glisser sont routés par window\_id natif ; les commandes de menu vont à la key window capturée au déclenchement, pas à la fenêtre qui les a dépilées par hasard.

**Destruction indépendante**

Fermer une fenêtre ne détruit que ses propres ressources, les autres continuent de s’afficher ; run() rend la main quand la dernière se ferme.

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

Une vraie application à deux fenêtres : la cible observée et DevTools ont chacune leur propre Cx et leur fenêtre. Le harness passe la fenêtre d’outils sur Console et la filtre pendant que la cible reste active.

## Cycle de vie

| API | Rôle |
| --- | --- |
| `run()` | Fait tourner la boucle jusqu’à la fermeture de la dernière fenêtre ou l’appel de quit() |
| `tick()` | Un tour de pump + frame, pour piloter la boucle vous-même |
| `closeWindow(id)` | Ferme une fenêtre ; depuis un callback, c’est mis en file jusqu’à une frontière sûre |
| `quit()` | Quitte toute l’application ; deinit() détruit les fenêtres restantes dans l’ordre |
| `window(id)` | Trouve une fenêtre vivante par id ; null une fois fermée |
| `activateWindow(id)` | Rend une fenêtre active |
| `setMenuModel` / `bindMenuCommand` | Le modèle de menu est global au processus ; les callbacks de commande se lient par fenêtre |

`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 est invalide après fermeture.** Dès qu’une fenêtre est détruite, tout pointeur `*App` que vous déteniez sur elle devient pendant. Conservez l’id de la fenêtre et retrouvez-la avec `application.window(id)` au besoin.

## Partager l’état

Le modèle partagé entre fenêtres appartient à l’application ; chaque fenêtre ne garde que son propre état de vue. Chaque Cx a son propre graphe réactif : ne laissez donc pas l’Effect d’une fenêtre lire un Signal du Scope d’une autre, et ne partagez jamais de pointeurs Node ni de ressources de Scope entre Cx.

-   **Le modèle vit en dehors des fenêtres.** Conservez les données dans une struct simple ou un store au niveau de l’application qui survit à chaque fenêtre.
    
-   **Chaque fenêtre reflète ce dont elle a besoin.** Une fenêtre crée des Signals dans son propre Scope ; quand le modèle change, l’application écrit explicitement dans le miroir de chaque fenêtre.
    
-   **Désinscrivez à la fermeture.** Une fois la fenêtre fermée, le modèle ne doit plus garder de pointeurs vers ses Signals.
    

> TIP
> 
> **DevTools est aussi une fenêtre.** Le panneau complet Elements / Components / Console / Performance peut vivre dans une seconde fenêtre et observer le Cx d’une autre ; pour un simple coup d’œil aux limites de layout, l’overlay en une ligne est plus léger. Voir [DevTools](https://zenit.z.express/fr/docs/advanced/devtools).
