Electron-Vite (二)自定义标题栏

201 阅读4分钟

在 Electron-Vite (一)快速构建桌面应用 中我们讲解了怎么快速构建一个Electron应用,并且怎么在主进程和渲染进程之间进行通信

那在本章节中,我们重点讲解一下怎么自定义Electron的标题栏

相信大多数开发过Electron应用的工程师都会抛弃原有的标题栏,原生的标题栏无法高度自定义,所以我们在构建的过程中,就要隐藏掉原生的,自己设计开发符合要求的标题栏

那接下来我们开始

第一步:隐藏原生标题栏

我们来看下面这张图,如图所示,这是原生的Electron的标题栏

接下来我们先隐藏原生的标题栏

我们来到主进程下面的index.ts文件

然后mainWindow的配置内容修改如下

  // Create the browser window.
  mainWindow = new BrowserWindow({
    width: 900,
    height: 670,
    show: false,
    autoHideMenuBar: true,

    //隐藏标题栏
    frame: false,
    titleBarStyle: 'hidden',

    ...(process.platform === 'linux' ? { icon } : {}),
    webPreferences: {
      preload: join(__dirname, '../preload/index.js'),
      sandbox: false
    }
  })

然后我们重启项目,就会发现标题栏消失了

第二步:设计标题栏页面

我们先回到渲染进程里面的App.vue文件里面

将代码改成下面样子

<template>
  <div class="title-container"></div>
</template>
<script setup lang="ts"></script>
<style>
body {
  margin: 0;
  padding: 0;
}
.title-container {
  width: 100%;
  height: 30px;
  background-color: rgb(226, 241, 255);
  -webkit-app-region: drag; /* 允许拖拽 */
}
</style>

然后我们运行项目后,页面就会呈现现在的样子

鼠标在蓝色的标头区域,可以进行拖动,双击也可以进行放大缩小

然后我们 给我们的标题栏填充一些内容

<template>
  <div class="title-container">
    <div class="left">
      <span>软件Icon</span>
    </div>
    <div class="right">
      <div class="btn-item">隐藏</div>
      <div class="btn-item">放大</div>
      <div class="btn-item">关闭</div>
    </div>
  </div>
</template>
<script setup lang="ts"></script>
<style>
body {
  margin: 0;
  padding: 0;
}
.title-container {
  width: 100%;
  height: 30px;
  background-color: rgb(226, 241, 255);
  -webkit-app-region: drag; /* 允许拖拽 */
  display: flex;
  justify-content: space-between;
}
.right {
  display: flex;
  justify-content: right;
  -webkit-app-region: no-drag;
  gap: 10px;
}
.btn-item {
  cursor: pointer;
}
</style>

效果展示

这时我们就会发现只有放大按钮,没有缩小按钮,这时我们就有一个疑问,放大和缩小按钮是谁来控制,当我们软件最大化后,我们怎么知道软件已经最大化了,反之,我们又怎么知道软件最小化了

第三步:最大化和最小化按钮的切换

让我们回到主进程的index.ts里面

在这里加一段代码

  //监听窗口尺寸
  mainWindow.on('resize', () => {
    const isMax = mainWindow.isMaximized()
    if (isMax) {
      mainWindow.webContents.send('winSizeChange', { size: 'max' })
    } else {
      mainWindow.webContents.send('winSizeChange', { size: 'min' })
    }
  })

这段代码的意思是我们要开始监听mainWindow窗口的尺寸,如果窗口发生了改变,就会立即触发回调函数

isMaximized()方法就是能够判断当前窗口是否是最大化

mainWindow.webContents.send('winSizeChange', { size: 'max' }),这一段代码是主进程向渲染进程发消息,通知我的窗口已发生了变化,并把当前窗口的尺寸大小传过去

然后我们再次回到App.vue里面

将内容修改成下面样子

<template>
  <div class="title-container">
    <div class="left">
      <span>软件Icon</span>
    </div>
    <div class="right">
      <div class="btn-item">隐藏</div>
      <div v-if="sizeType === 'min'" class="btn-item">放大</div>
      <div v-if="sizeType === 'max'" class="btn-item">缩小</div>
      <div class="btn-item">关闭</div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'

//当前窗口尺寸的类型
const sizeType = ref<'max' | 'min'>('min')
onMounted(() => {
  window.electron.ipcRenderer.on('winSizeChange', (event, type) => {
    sizeType.value = type.size
  })
})
</script>
<style>
body {
  margin: 0;
  padding: 0;
}
.title-container {
  width: 100%;
  height: 30px;
  background-color: rgb(226, 241, 255);
  -webkit-app-region: drag; /* 允许拖拽 */
  display: flex;
  justify-content: space-between;
}
.right {
  display: flex;
  justify-content: right;
  -webkit-app-region: no-drag;
  gap: 10px;
}
.btn-item {
  cursor: pointer;
}
</style>

我们再来看界面

没有占满屏幕时

占满屏幕后

第四步:串接功能

还是在App.vue里面,代码如下

<template>
  <div class="title-container">
    <div class="left">
      <span>软件Icon</span>
    </div>
    <div class="right">
      <div class="btn-item" @click="handleClick('hide')">隐藏</div>
      <div v-if="sizeType === 'min'" class="btn-item" @click="handleClick('max')">放大</div>
      <div v-if="sizeType === 'max'" class="btn-item" @click="handleClick('min')">缩小</div>
      <div class="btn-item" @click="handleClick('close')">关闭</div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'

//当前窗口尺寸的类型
const sizeType = ref<'max' | 'min'>('min')
onMounted(() => {
  window.electron.ipcRenderer.on('winSizeChange', (event, type) => {
    sizeType.value = type.size
  })
})

const handleClick = (type: 'hide' | 'min' | 'max' | 'close'): void => {
  window.electron.ipcRenderer.send('winAction', { type })
}
</script>
<style>
body {
  margin: 0;
  padding: 0;
}
.title-container {
  width: 100%;
  height: 30px;
  background-color: rgb(226, 241, 255);
  -webkit-app-region: drag; /* 允许拖拽 */
  display: flex;
  justify-content: space-between;
}
.right {
  display: flex;
  justify-content: right;
  -webkit-app-region: no-drag;
  gap: 10px;
}
.btn-item {
  cursor: pointer;
}
</style>

四个功能按钮经过click事件,向主进程发送消息,消息窗口是winAction,主进程监听这个消息窗口用来获取消息内容,然后再判断type类型

然后我们回到monitorEvent.ts里面

代码效果如下

import { ipcMain } from 'electron'
import { mainWindow } from '.'

export const monitorEvent = (): void => {
  ipcMain.on('toMain', (e, data) => {
    console.log(data)
  })

  ipcMain.on('winAction', (e, data) => {
    const type = data.type
    if (type === 'hide') {
      //最小化窗口
      mainWindow.minimize()
    } else if (type === 'min') {
      //取消最大化
      mainWindow.unmaximize()
    } else if (type === 'max') {
      //最大化
      mainWindow.maximize()
    } else if (type === 'close') {
      //关闭窗口
      mainWindow.close()
    }
  })
}

最后运行项目,就会发现标题栏的功能都完整齐全了