【Electron】为啥setAlwaysOnTop没效果

3,122 阅读7分钟

最近开发一个音视频会议的项目,遇到个问题,可以说是 Electron 的一个坑点吧。我大概描述一下:

需求是把某个窗体始终显示在所有应用之上,技术实现就是把某个窗体始终保持置顶效果,在所有应用的最上层。我通过setAlwaysOnTop(true)方法实现置顶效果,但发现这个方法并不是 100% 有效,尤其在 MacOS 系统上,很容易被其他程序覆盖。

于是我把置顶的优先级提高,通过 setAlwaysOnTop(true, 'screen-saver')把窗体的优先级顶到最高,在 MacOS 系统没问题了,window 也可以。

但最近,测试发现置顶窗体有时候会「掉下去」,置顶效果不稳定,我排查了许久,发现每当我打开子窗口的时候,这种情况就很容易出现。我打了断点,父窗体(设置置顶的窗体)一开始是置顶的,但在打开子窗体的时候,窗体属性isAlwaysOnTop()返回了false

一开始:

打开子窗体后,再获取一下父窗体的置顶状态:

这说明父子窗体是会影响窗体的置顶效果。问题找到了,就好办。想要解决问题,就要先了解父子窗体和setAlwaysOnTop

父子窗体

在 Electron 中,父子窗口是一种窗口之间的层级关系,其中子窗口是从父窗口中创建的新窗口。父子窗口之间可以通过一些方法和事件进行通信和交互,从而实现一些复杂的应用场景。

使用场景:

  • 主窗口和子窗口之间的关系,例如在一个应用程序中创建一个主窗口,然后从主窗口中打开多个子窗口,用于展示不同的内容或进行不同的操作。
  • 弹出窗口,例如在一个设置对话框或者消息框中打开一个模态的子窗口,用于进行用户配置或者显示提示信息。
  • 多窗口应用程序,例如在一个多文档编辑器中,可以通过创建多个子窗口来展示和编辑不同的文档。

优点:

  • 父子窗口之间可以方便地进行通信和交互,例如通过IPC(进程间通信)或者Remote模块,可以在父窗口和子窗口之间传递数据和调用方法,从而实现数据共享和功能扩展。
  • 子窗口可以从父窗口中继承一些共享的配置和状态信息,例如主题、语言设置等,从而实现一致的用户体验。
  • 可以方便地管理和控制多个窗口,例如可以通过父窗口来管理所有的子窗口的生命周期、窗口位置和大小等,从而简化应用程序的窗口管理逻辑。

缺点:

  • 父子窗口之间的通信和交互可能需要处理一些复杂的逻辑,例如数据的序列化和反序列化、跨进程通信的性能开销等。
  • 父子窗口之间的层级关系可能会导致一些窗口管理和布局的复杂性,例如子窗口的位置和大小可能受到父窗口的限制,从而需要额外的处理和逻辑。
  • 跨窗口的错误处理和调试可能较为复杂,例如当子窗口出现错误时,如何在父窗口中进行错误处理和调试可能需要额外的处理和逻辑。

总的来说,父子窗口是 Electron 中一种常用的窗口管理方式,可以用于实现复杂的应用场景,但在使用时需要注意处理好窗口之间的通信、层级关系以及错误处理等问题。

setAlwaysOnTop

Electron 中的setAlwaysOnTop(flag[,level][,relativeLevel])方法是用于设置窗口是否始终置顶(即置于其他窗口的上方)的方法。该方法可以用于控制窗口的显示层级,从而在某些特定的应用场景下实现一些特殊的窗口行为。

注意,它的第二个参数level,支持 normal, floating, torn-off-menu, modal-panel, main-menu, status, pop-up-menu, screen-saver

flag 属性为true时,默认值为 floating 。 当flagfalse时,level 会重置为 normal floating status ,窗口会被置于 macOS 上的 Dock 下方和 Windows 上的任务栏下方。 pop-up-menu 到更高级别,窗口显示在 macOS 上的 Dock上方和 Windows 上的任务栏上方。

可以理解为窗体置顶的优先级,

使用场景:

  • 某些应用程序可能需要在其他窗口之上一直显示,例如全局的悬浮窗、通知窗口等。
  • 某些应用程序可能需要在其他窗口之上显示特定的信息,例如实时的系统监控、实时聊天等。
  • 某些应用程序可能需要在其他窗口之上显示特定的控件或工具栏,例如全局的播放控制器、截屏工具等。

优点:

  • 可以在 Electron 应用程序中实现窗口的始终置顶效果,从而窗口能够在其他窗口之上一直保持显示,不被其他窗口遮挡。
  • 可以控制窗口的显示层级,从而在需要窗口在其他窗口之上显示时,提供了一种简便的方式。

缺点:

  • 过度使用始终置顶功能可能会对用户体验造成负面影响,例如过多的弹出窗口可能会干扰用户的操作流程,导致应用程序难以使用。
  • 始终置顶的窗口可能会在某些操作系统或窗口管理器中表现不一致,从而导致窗口的显示效果不符合预期。
  • 需要谨慎使用setAlwaysOnTop()方法,避免滥用,以免对用户造成困扰或干扰其他应用程序的正常使用。

解决冲突

在 Electron 中,窗口的层级关系是通过窗口的level属性来控制的。如果子窗口在父窗口的上层,并且子窗口没有设置始终置顶效果,那么父窗口的level属性可能被子窗口覆盖,从而导致isAlwaysOnTop() 方法返回false

因此,为确保父窗口始终置顶,可以尝试以下方法:

  1. 对子窗口设置始终置顶效果,不过应该考虑平台系统的兼容性问题:
// 在子窗口中设置始终置顶效果
window.setAlwaysOnTop(true);
  1. 子窗口要显示在父窗口之上,可以取消父子窗口的关系,提高子窗口的置顶优先级,一般能解决大部分问题
// 比如设置到 status,level 的值比父窗口的优先级高即可
window.setAlwaysOnTop(true, "status");

总结

作为 Electron 开发者,在设置窗口的置顶效果时,我们应该注意什么呢?我给大家总结了以下几点:

  1. 用户体验:窗口始终置顶可能会对用户体验产生干扰,因此需要谨慎使用。过度使用窗口置顶功能可能会让应用程序显得突兀和不符合界面设计的原则。在设计和实现时,应考虑用户的需求和行为,避免影响用户的正常操作。
  2. 跨平台兼容性:不同操作系统(如 Windows、macOS、Linux)对窗口优先级的处理方式可能存在差异,因此在使用窗口置顶功能时需要进行跨平台的测试和验证,确保在不同操作系统上的行为一致。
  3. 失焦状态:在失焦状态下,窗口置顶效果可能会失效。例如,当窗口失去焦点时(如用户切换到其他应用程序),窗口可能会被其他窗口覆盖,从而失去置顶效果。因此,需要在窗口失焦后重新聚焦窗口,以恢复置顶效果。
  4. 窗口层级:窗口优先级通常通过设置窗口的层级来实现,例如使用 level 参数或 relativeLevel 参数来设置窗口的层级级别。在设置窗口优先级时,需要考虑窗口的层级关系,避免窗口之间的重叠或遮挡,确保置顶窗口能够正常展示。
  5. 安全性:在设置窗口优先级时,需要考虑应用程序的安全性。窗口始终置顶可能会被滥用来进行窃取用户信息、进行窗口劫持等恶意行为。因此,在使用窗口置顶功能时,应确保应用程序没有安全漏洞,并进行适当的权限控制,保护用户的隐私和安全。