# 如何在 controller 中集中管理导航

> 将 EventsProcessor 栈的 push/pop 封装到专属 controller 中，实现可测试且解耦的导航。

Source: https://crossfly.dev/zh/docs/cookbook/nav/controller-de-navegacao — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# 如何在 controller 中集中管理导航

**目的。** 将 `new X().show()` 和 `hide()` 散布在各个屏幕中，会使 UI 与导航图耦合。一个 **controller** 集中管理 push/pop，提供一个用于日志/守卫的统一入口，并支持"关闭直到根屏幕"。它仅仅编排 `EventsProcessor` 的真实栈。

## 使用的 API
- `Window.show()` / `Window.hide()` — 栈上的 push/pop（controller 只是委托给它们）。
- `EventsProcessor.topWindow()` — **静态**方法，返回栈顶窗口（`instance.windows.peek()`）。
- `EventsProcessor.instance.getLastWindow()` — **实例**方法；返回栈的真实顶部（如果为空则返回 `null`）。当需要检查栈中是否有内容而不抛出异常时使用。

> 在 `EventsProcessor` 的源码中确认的名称：`topWindow()`（静态）和 `getLastWindow()`（实例）。`topWindow()` 执行 `windows.peek()`，如果栈为空则抛出异常；`getLastWindow()` 在这种情况下返回 `null`——防御性检查时优先使用后者。

## 完整示例（复制、粘贴并运行）
```java
package com.exemplo;

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;

/** 可执行主屏：使用 Nav 控制器压入/弹出屏幕。 */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      add(new Label("Tela raiz"));
      add(new Button("Abrir detalhe").onAction(e -> Nav.push(new OutraTela())));  // 通过控制器 push
   }
}

/** 栈内屏幕：通过控制器关闭（pop）。 */
class OutraTela extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      add(new Label("Detalhe"));
      add(new Button("Voltar").onAction(e -> Nav.pop()));  // 通过控制器 pop
   }
}

/** 控制器：对 EventsProcessor 真实栈的薄封装。 */
final class Nav
{
   private Nav() {}

   /** push：入栈并显示。 */
   public static void push(Window w)
   {
      w.show();
   }

   /** pop：关闭栈顶窗口（若有）。 */
   public static void pop()
   {
      Window top = EventsProcessor.instance.getLastWindow();  // null 安全
      if (top != null)
         top.hide();
   }

   /** 当前栈顶窗口（可能为 null）。 */
   public static Window current()
   {
      return EventsProcessor.instance.getLastWindow();
   }
}
```

> 下面的片段只展示**核心部分**。要运行它，请将其放入上面的 `MainWindow` 骨架中。

## 最小片段
```java
// 已在源码核对：EventsProcessor.topWindow()（静态）与 instance.getLastWindow()（栈空时返回 null）
public final class Nav
{
   private Nav() {}

   /** push：入栈并显示。 */
   public static void push(Window w)
   {
      w.show();
   }

   /** pop：关闭栈顶窗口（若有）。 */
   public static void pop()
   {
      Window top = EventsProcessor.instance.getLastWindow();  // null 安全
      if (top != null)
         top.hide();
   }

   /** 反复 pop 直到只剩 1 个窗口（根窗口）。 */
   public static void popToRoot()
   {
      Window top;
      // 只要栈中窗口多于一个，就消掉栈顶
      while ((top = EventsProcessor.instance.getLastWindow()) != null
             && top != EventsProcessor.topWindow()) { top.hide(); }
      // （若要"回到根"，每次 hide() 弹出一个；请按你的应用调整停止条件）
   }

   /** 当前栈顶窗口（可能为 null）。 */
   public static Window current()
   {
      return EventsProcessor.instance.getLastWindow();
   }
}

// 在屏幕中的用法：
Nav.push(new DetalheTela());
// ...
Nav.pop();
```

最佳实践：

- **不要**重新实现栈：`EventsProcessor` 已经是唯一的事实来源。controller 是 `show()`/`hide()` 之上的一个薄门面。
- 导航守卫（例如："有未保存的更改吗？"）在这里很自然：在 `pop()` 之前检查，如有必要，弹出一个确认 `Alert`。
- 当栈可能为空时要小心 `topWindow()`——它执行 `peek()` 并会抛出异常。对于防御性代码请使用 `getLastWindow()`。

## 深入了解
- [窗口栈](/docs/ui/navigation/the-window-stack) · [在屏幕间导航](/docs/ui/navigation/navigate-between-screens)
- 姊妹配方：[打开和关闭一个屏幕](/docs/cookbook/nav/abrir-tela)
