在开发桌面应用的时候,我们常常会有这样的需求:让应用的标题栏更符合自己的风格,并且实现一些个性化的窗口控制功能。今天咱们就来讲讲在 Electron 里怎么实现自定义标题栏和窗口控制。

一、Electron 简介

Electron 是个很厉害的工具,它能让咱们用 Web 技术,像 HTML、CSS 和 JavaScript 来开发跨平台的桌面应用。想象一下,你会写网页代码,就能用这些知识来开发桌面软件,是不是很棒?比如说很多知名的软件像 VS Code、Slack 都是用 Electron 开发的。

示例:创建一个简单的 Electron 应用

// 技术栈:Node.js、Javascript
const { app, BrowserWindow } = require('electron')

function createWindow () {
  // 创建浏览器窗口
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  })

  // 加载 index.html 文件
  win.loadFile('index.html')
}

app.whenReady().then(() => {
  createWindow()

  app.on('activate', function () {
    // 在 macOS 上,当单击 dock 图标并且没有其他窗口打开时,
    // 通常在应用程序中重新创建一个窗口。
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed', function () {
  // 在 Windows 和 Linux 上,当所有窗口都关闭时退出应用程序。
  if (process.platform !== 'darwin') app.quit()
})

在这个示例里,我们引入了 Electron 的 appBrowserWindow 模块。当应用准备好的时候,就创建一个宽度 800、高度 600 的窗口,并且加载 index.html 文件。在不同的系统上,窗口关闭和激活的处理逻辑也不一样。

二、实现自定义标题栏

去除默认标题栏

在 Electron 里,默认的标题栏不符合我们的要求,所以要先把它去掉。通过设置 frame 选项为 false,就能去掉默认的标题栏。

// 技术栈:Node.js、Javascript
const { app, BrowserWindow } = require('electron')

function createWindow () {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    frame: false, // 去掉默认标题栏
    webPreferences: {
      nodeIntegration: true
    }
  })

  win.loadFile('index.html')
}

app.whenReady().then(() => {
  createWindow()

  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})

创建自定义标题栏

去掉默认标题栏后,我们要自己创建一个。在 HTML 文件里添加一个 div 作为标题栏,用 CSS 来设置样式。

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Custom Title Bar</title>
  <style>
    /* 标题栏样式 */
    #title-bar {
      height: 30px;
      background-color: #333;
      color: white;
      display: flex;
      align-items: center;
      padding: 0 10px;
    }
  </style>
</head>
<body>
  <div id="title-bar">
    <span>My Custom App</span>
  </div>
  <!-- 其他内容 -->
</body>
</html>

在这个 HTML 文件里,我们创建了一个 div,它的 idtitle-bar,并且给它设置了样式,让它看起来像个标题栏。

三、实现窗口控制功能

实现窗口拖动功能

我们希望自定义的标题栏也能像默认标题栏一样可以拖动窗口。可以在 HTML 文件里添加 JavaScript 代码来实现这个功能。

// 技术栈:Node.js、Javascript
const { ipcRenderer } = require('electron')

// 获取标题栏元素
const titleBar = document.getElementById('title-bar')

// 监听鼠标按下事件
titleBar.addEventListener('mousedown', (e) => {
  // 发送拖动窗口的消息给主进程
  ipcRenderer.send('drag-start', e.offsetX, e.offsetY)
})

在主进程里,我们要处理这个消息。

// 技术栈:Node.js、Javascript
const { app, BrowserWindow, ipcMain } = require('electron')

function createWindow () {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    frame: false,
    webPreferences: {
      nodeIntegration: true
    }
  })

  win.loadFile('index.html')

  // 处理拖动窗口的消息
  ipcMain.on('drag-start', (e, offsetX, offsetY) => {
    win.webContents.executeJavaScript(`
      const { remote } = require('electron')
      const win = remote.getCurrentWindow()
      win.startDrag({ x: offsetX, y: offsetY })
    `)
  })
}

app.whenReady().then(() => {
  createWindow()

  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})

在这个示例里,当用户在标题栏上按下鼠标时,渲染进程会发送一个消息给主进程,主进程接收到消息后,让窗口开始拖动。

实现窗口最小化、最大化和关闭功能

我们还可以在自定义标题栏里添加按钮,实现窗口的最小化、最大化和关闭功能。

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Custom Title Bar</title>
  <style>
    #title-bar {
      height: 30px;
      background-color: #333;
      color: white;
      display: flex;
      align-items: center;
      padding: 0 10px;
    }
    #window-controls {
      margin-left: auto;
    }
    #window-controls button {
      background: none;
      border: none;
      color: white;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <div id="title-bar">
    <span>My Custom App</span>
    <div id="window-controls">
      <button id="minimize-button">-</button>
      <button id="maximize-button">□</button>
      <button id="close-button">×</button>
    </div>
  </div>
  <!-- 其他内容 -->
  <script>
    const { ipcRenderer } = require('electron')

    const minimizeButton = document.getElementById('minimize-button')
    const maximizeButton = document.getElementById('maximize-button')
    const closeButton = document.getElementById('close-button')

    minimizeButton.addEventListener('click', () => {
      ipcRenderer.send('minimize-window')
    })

    maximizeButton.addEventListener('click', () => {
      ipcRenderer.send('maximize-window')
    })

    closeButton.addEventListener('click', () => {
      ipcRenderer.send('close-window')
    })
  </script>
</body>
</html>

在主进程里处理这些消息。

// 技术栈:Node.js、Javascript
const { app, BrowserWindow, ipcMain } = require('electron')

function createWindow () {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    frame: false,
    webPreferences: {
      nodeIntegration: true
    }
  })

  win.loadFile('index.html')

  ipcMain.on('minimize-window', () => {
    win.minimize()
  })

  ipcMain.on('maximize-window', () => {
    if (win.isMaximized()) {
      win.unmaximize()
    } else {
      win.maximize()
    }
  })

  ipcMain.on('close-window', () => {
    win.close()
  })
}

app.whenReady().then(() => {
  createWindow()

  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})

在这个示例里,我们在 HTML 文件里添加了三个按钮,分别是最小化、最大化和关闭按钮。当用户点击这些按钮时,渲染进程会发送相应的消息给主进程,主进程接收到消息后,执行相应的窗口操作。

四、应用场景

个性化设计

很多时候,我们希望应用的界面有独特的风格,自定义标题栏可以让应用的界面更加个性化,符合品牌的形象。比如一些设计类的软件,就需要有独特的界面来吸引用户。

特定功能需求

有些应用可能需要在标题栏上添加一些特殊的功能按钮,像搜索按钮、设置按钮等。通过自定义标题栏,我们可以很方便地实现这些功能。

五、技术优缺点

优点

  • 跨平台兼容性:Electron 可以在 Windows、Mac 和 Linux 等多个平台上运行,所以我们实现的自定义标题栏和窗口控制功能也能在不同的平台上使用。
  • 开发效率高:使用 Web 技术开发,对于熟悉 HTML、CSS 和 JavaScript 的开发者来说,上手很快,能节省开发时间。

缺点

  • 性能问题:因为 Electron 是基于 Chromium 内核的,所以应用的体积可能会比较大,启动速度也可能会受到影响。
  • 安全风险:如果代码编写不当,可能会存在安全漏洞,比如跨站脚本攻击(XSS)等。

六、注意事项

兼容性问题

不同的操作系统对于窗口的处理可能会有所不同,所以在实现自定义标题栏和窗口控制功能时,要考虑到不同系统的兼容性。比如在 macOS 上,窗口的操作按钮位置和 Windows 是不一样的。

安全问题

在处理用户输入和与网络交互时,要注意安全问题。比如对用户输入进行过滤,防止 SQL 注入和 XSS 攻击等。

七、文章总结

通过这篇文章,我们学习了如何在 Electron 里实现自定义标题栏和窗口控制。首先我们去掉了默认的标题栏,然后创建了自己的标题栏,接着实现了窗口的拖动、最小化、最大化和关闭功能。同时,我们也了解了这个技术的应用场景、优缺点和注意事项。希望这篇文章能帮助大家在 Electron 开发中实现个性化的窗口界面。