人人都会AI编程

10.2 父子窗口、模态窗口、窗口层级控制

更新时间:2026-07-11

桌面应用与网页的一个显著区别在于,窗口不再是平面的选项卡,而是可以在屏幕上自由叠加、独立拖拽、互相约束的立体空间。Electron 提供了三种核心的窗口关系机制:父子窗口、模态窗口和层级控制。它们在用户交互中扮演着不同角色,但都有一个共同目的——让窗口之间的行为符合用户的直觉。

10.2.1 父子窗口:建立从属关系

父子窗口是最基础的窗口关系类型。在 Electron 中,通过 parent 选项指定一个窗口成为另一个窗口的子窗口:

// 主进程中创建父窗口和子窗口
const parentWin = new BrowserWindow({
  width: 800,
  height: 600,
  webPreferences: { /* ... */ }
});

const childWin = new BrowserWindow({
  width: 400,
  height: 300,
  parent: parentWin,        // 指定父窗口
  webPreferences: { /* ... */ }
});

这种从属关系带来几点实际的行为变化:

  • 子窗口始终显示在父窗口之上。即使你点击父窗口的标题栏,子窗口也不会被遮挡。
  • 最小化或关闭父窗口时,子窗口会跟随执行同样的操作。比如父窗口最小化到任务栏,子窗口也会一并隐藏。
  • macOS 上子窗口没有独立的程序坞图标,它只是父窗口的附属物,在 Exposé 和任务切换中会与父窗口视为一个整体。
  • 子窗口不能单独被 app.focus() 带到最前,它的激活总是与父窗口相关联。

这种关系在实际场景中非常有用。例如,一个“首选项”或“查找替换”的浮动面板,用户希望它永远浮在主窗口之上,但又不要独立占据任务栏空间,这时设置 parent 就是最合适的做法。

需要注意的限制

  • 并非所有平台的行为完全一致。在 Linux 的某些桌面环境中,父子窗口的层级约束可能不如 Windows/macOS 严格,建议在目标系统上测试。
  • 子窗口可以使用 modal 选项进一步变为模态,但两者是独立的属性。不设 modal 的父子窗口,用户仍然可以与父窗口正常交互(比如在父窗口中编辑内容),只是子窗口保持在视觉上层。

10.2.2 模态窗口:阻断父窗口交互

模态窗口是桌面应用中常见的交互模式,它会阻止用户与父窗口(或整个应用)的任何交互,直到该窗口被关闭。典型的场景是“确认删除”、“选择文件保存路径”或“登录验证”。

在 Electron 中创建模态窗口非常简单,只需在子窗口的选项中设置 modal: true,同时通常也指定 parent

const modal = new BrowserWindow({
  width: 350,
  height: 200,
  parent: mainWindow,
  modal: true,
  resizable: false,
  webPreferences: { /* ... */ }
});

modal.loadFile('confirm-dialog.html');

启用 modal: true 后:

  • 父窗口的所有输入事件被拦截:无法点击父窗口内的按钮,无法滚动父窗口的页面,甚至无法用快捷键聚焦父窗口的控件。
  • 父窗口仍可移动和关闭:虽然内容区被锁定,但父窗口的标题栏操作(拖动、最小化、关闭)仍然可用。这意味着用户依然可以通过父窗口的关闭按钮关闭整个应用(包括模态对话框)。
  • macOS 上的“工作表”模式:如果在 macOS 上设置了 modal: trueparent,Electron 默认会以“工作表”(Sheet)的形式将模态窗口从父窗口的标题栏下方滑出,而不是弹出独立窗口。这是 macOS 系统的标准模态交互,如果你希望仍以独立窗口形式出现,需要显式设置 type 为非默认值或使用其他窗口类型。

实用经验

  • 模态窗口应该保持简洁小巧。如果内容复杂(比如一个完整的设置向导),考虑用普通非模态窗口或内嵌在父窗口中的侧边面板,避免阻塞用户操作。
  • 务必提供明确的关闭方式(按钮、ESC 键、点击遮罩区)。浏览器端的 alert/confirm 在 Electron 中虽然可用,但它们会阻塞整个渲染进程,不推荐使用。自定义的模态窗口更灵活,也更符合桌面应用的设计语言。
  • 模态窗口结束后的数据传递,可以通过 IPC 或直接调用父窗口暴露的方法完成,与普通窗口通信方式完全相同。

10.2.3 窗口层级控制:精细管理窗口 Z 序

除了明确的父子关系和模态,Electron 还提供了更精细的层级控制能力,允许你动态调整窗口在屏幕上的前后顺序。这些功能在需要全局悬浮窗、迷你播放器或始终置顶的工具类应用中非常关键。

(1)alwaysOnTop 常居顶层

BrowserWindow 构造选项中的 alwaysOnTop: true 可以让窗口始终位于所有应用窗口的最上层,即使用户切换到其他应用,它也会浮动在屏幕上。

const miniPlayer = new BrowserWindow({
  width: 300,
  height: 150,
  alwaysOnTop: true,
  frame: false,          // 配合无边框实现悬浮小窗效果
  transparent: true,     // 可选,实现背景透明
});

动态切换常居顶层:

// 开启置顶
miniPlayer.setAlwaysOnTop(true);

// 关闭置顶,变为普通窗口层级
miniPlayer.setAlwaysOnTop(false);

// 设置层级(可选参数 level)
// 'normal' / 'floating' / 'torn-off-menu' / 'modal-panel' / 'main-menu' / 'status' / 'pop-up-menu' / 'screen-saver'
miniPlayer.setAlwaysOnTop(true, 'floating');

第二个参数 level 是平台相关的,仅在 macOS 上明确区分不同层级,Windows 上通常只有置顶和非置顶两种状态。多数情况下直接用 true/false 就足够了。

(2)focusable 与窗口激活行为

focusable 选项控制窗口是否可以获取键盘焦点。创建一个始终置顶但不抢夺焦点的悬浮工具窗:

const toolPane = new BrowserWindow({
  width: 200,
  height: 600,
  alwaysOnTop: true,
  focusable: false,    // 不抢焦点
  webPreferences: { /* ... */ }
});

这个设置常用于“辅助工具面板”,用户可以在主窗口继续打字或操作,而工具窗保持可见但不干扰输入。

(3)moveAbove / moveTop / moveBelow 动态调整层叠顺序

对于复杂的多窗口应用(如 IDE 的自由布局窗口),有时需要动态调整某个窗口与其他窗口的相对叠放次序:

// 将窗口 winA 移动到 winB 的上方
winA.moveAbove(winB.getMediaSourceId()); // 注意:参数需要是 MediaSourceId,实际用法有变化

// 在最新 Electron 中,可以使用:
// win.moveTop() 将窗口移到最顶层(不设置 alwaysOnTop 的情况下,只在本应用窗口范围内)
win.moveTop();

不过要注意,这类函数在不同版本中参数可能有所变化,建议查阅对应 Electron 版本文档。最常用的还是 alwaysOnTopparent 机制,大多数窗口排列需求都能通过它们满足。

(4)skipTaskbar:控制任务栏显示

有时一些小工具窗口不希望出现在任务栏里(例如截图工具的选择区、便签、颜色拾取器),可以在构造选项中设置 skipTaskbar: true

const picker = new BrowserWindow({
  width: 400,
  height: 300,
  skipTaskbar: true,
  // ...
});

这样用户在 Alt+Tab 切换时不会看到这个窗口,它更像个轻量级的“控件”而非完整窗口。

10.2.4 实际场景组合运用

真实项目中的窗口管理往往是多种属性的组合。举几个常见的例子:

  • 应用首选项对话框:新建带 parentmodal: true 窗口,居中显示,固定大小,点击外部或按 ESC 关闭。
  • 迷你播放器 / 悬浮笔记alwaysOnTop: true + frame: false + skipTaskbar: true + 可拖拽区域。用户在工作时始终可看到播放信息,但不被任务栏和切换列表打扰。
  • 多文档编辑器的浮动搜索栏:非模态、指定了 parent 的子窗口,可自由移动但始终浮在父窗口之上。允许用户在主窗口内编辑的同时,在搜索栏中修改关键词。

注意性能与体验

  • 创建大量窗口会增加内存开销(每个窗口都是一个独立的渲染进程,除非共享同一个 session 并手动合并)。对于频繁使用的辅助面板,考虑复用而非反复创建销毁。
  • 父子窗口与模态窗口的遮挡策略在不同操作系统下的表现可能存在细微差异,一定要在目标平台上进行实测,特别是 macOS 的 Sheet 样式有时需要微调。
  • 对于 Android / iOS 等移动端(虽然 Electron 不直接支持),概念就完全不同了,因此层级设计仅适用于桌面端交互。

掌握好父子窗口、模态窗口和层级控制,就等于掌握了 Electron 多窗口交互的命脉。它们可以组合出从简单对话框到复杂 IDE 工作区的各种窗口布局,让你的应用从“能运行”进化为“用起来舒服”。