当前位置: 首页 > news >正文

Electron自定义菜单栏及Mac最大化无效的问题解决

Electron自定义菜单栏及Mac最大化无效的问题解决

electron的应用打包后会有一个系统标题栏,在win中包含最小化、最大化、关闭、file等其他功能按钮。这个系统标题栏的局限很大,首先就是这个标题栏是固定的像素高度,没办法做到响应式,其次就是这个菜单栏的功能没法拓展,如果需要添加其他功能就比较捉襟见肘了。

针对这个问题我们可以隐藏这个系统标题栏然后在页面中自己去编写一个菜单栏,这一点官方也给出了实例👉自定义标题栏 | Electron,接下来我们就叙述一下如何创建一个自定义菜单栏并解决一些可能出现的问题。

自定义菜单栏

1.隐藏自带菜单栏

要创建自定义菜单栏第一步就是隐藏掉electron自带的系统标题栏。在隐藏之前还是提一下关于调整系统菜单栏的样式:

titleBarOverlay: {color: '#0ff', // 自定义背景色symbolColor: '#fff', // 自定义符号颜色height: 24, // 自定义高度
}

可以看到能做的处理有限,远比不上使用html+css能做到的样式体验,所以我们需要隐藏掉这个菜单栏:

autoHideMenuBar: true, // 是否自动隐藏菜单栏

可以简单展示一下创建窗口的相关参数:

import { BrowserWindow } from 'electron';mainWindow = new BrowserWindow({width: 1400,height: 800,useContentSize: true,autoHideMenuBar: true, // 是否自动隐藏菜单栏resizable: false, // 是否允许用户手动调整大小transparent: false, // 是否开启透明背景maximizable: false, //禁止双击放大frame: false, // 去掉顶部操作栏// titleBarStyle: 'hidden', // 隐藏标题栏// titleBarOverlay: {//   color: '#0ff', // 自定义背景色//   symbolColor: '#fff', // 自定义符号颜色//   height: 24, // 自定义高度// },webPreferences: {preload: path.join(__dirname, 'preload.mjs'),},});
2.封装自定义菜单栏组件

接下来就是封装一个自定义菜单栏去替代系统的菜单栏,这里我只以最小化、全屏、关闭等操作为例。

// SysTitleBar.tsxconst SysTitleBar = (props: SysTitleBarProps) => {return (<div className={styles['system-title-bar']}><div className={styles['title-bar-left']}>{leftContent}</div><div className={styles['title-bar-center']}>{centerContent}</div><div className={styles['title-bar-right']}>{rightContent}<div className={styles['sys-menu-icon-box']}><MinimizeIcon className={styles['sys-menu-icon']} onClick={minimizeHandle} /><FullScreenIconclassName={styles['sys-menu-icon']}onClick={() => {toggleMaximize(true);}}/><CloseIcon className={styles['sys-menu-icon']} /></div></div></div>);
};

创建好了以后我们在入口页面中引入

// app.tsx<div className="app-container"><SysTitleBar /><div className="app-content"><Outlet /></div>
</div>

当然了,如果布局不一样可以自行调整,这一块就按设计稿去决定怎么展示菜单栏就好了。

3.实现系统菜单栏功能

现在我们创建了自定义的菜单栏,但是现在它还是个花架子,只能看不能用,所以我们需要给他补充功能。

3.1 可拖拽

当务之急我们需要完成的第一个功能就是让这个菜单栏可以像系统菜单栏一样鼠标按住拖拽。这里用的是app-region: drag来告诉electron哪些地方时可以拖拽的。

.system-title-bar{app-region: drag;
}

到这里还没有结束,因为你这么设置以后,组件内部的所有元素都是可拖拽的了,这样点击等事件根本没办法触发,所以我们需要将这些元素排除为非可编辑区域。

.title-bar-right{app-region: no-drag;
}
3.2 最小化

最小化的功能依靠于electron提供的minimize方法,所以我们只需要通知主进程调用这个方法即可。得益于我们刚刚将自定义菜单栏的右边设置为非可拖拽区域,所以我们可以给最小化的图标添加一个点击事件并绑定一个minimizeHandle方法来告诉electron去调用minimize方法

// SysTitleBar.tsxconst minimizeHandle = () => {window.ipcRenderer?.minimize();
};// preload.ts
import { ipcRenderer, contextBridge } from 'electron';contextBridge.exposeInMainWorld('ipcRenderer', {minimize: () => ipcRenderer.send('window-minimize'),
});// main.ts
import { BrowserWindow, ipcMain } from 'electron';ipcMain.on('window-minimize', (event) => {const win = BrowserWindow.fromWebContents(event.sender);win?.minimize();
});
3.3 全屏

这里除了需要将软件全屏外,还有一个恢复原始大小的功能。图标的切换大家自己做处理就好,这里仅展示功能实现。还是按上面最小化的方式去实现,具体的方法及使用的API如下:

//preload.tstoggleMaximize: () => ipcRenderer.send('toggle-window-maximize'),// main.ts
ipcMain.on('toggle-window-maximize', (event) => {const win = BrowserWindow.fromWebContents(event.sender);if (win?.isMaximized()) { // 判断当前是否是全屏状态win.unmaximize(); // 取消全屏} else {win?.maximize(); // 全屏}
});
3.4 关闭软件

方式同上,使用的是创建的window实例上的close方法,具体使用方式如下:

// main.ts
ipcMain.on('window-close', (event) => {const win = BrowserWindow.fromWebContents(event.sender);win?.close();
});

到这里基本的自定义菜单栏就OK了,自定义菜单栏也是好处多多,例如需要再关闭之前做什么操作,我们完全可以在用户点击关闭按钮时先执行我们的操作然后再通知electron去关闭。所以我们在开发应用时基本都是需要自定义菜单栏实现。

Mac的最大化无效

这里说一下我这里的环境,electron的版本是30.0,系统是win11。上述功能在我自己电脑上运行时没有问题,在其他同事电脑上也是正常。但是测试突然给我踢了一个bug说是最大化无效。这就有点难搞了,因为我比较熟悉win所以我没有选Mac,难不成以前兼容ie的噩梦又出现了?

调试

首先还是需要找出问题,于是紧急给旁边同事派了根烟让他休息半小时,我紧急调试一下。根据测试发现是判断当前是否是全屏的方法**win.isMaximized()**一直返回false。去查了下发现是因为Mac和win的全屏逻辑不一样,electron的issue也提到了这个问题,这里我就不贴了感兴趣的自己可以去翻翻看。

我看了下issue给出的建议是全屏的状态有我们去控制不依赖electron的API,然后我试了下,发现没啥用,win?.maximize()这个方法不生效,看来问题不出在这,还得接着找。
于是我又去找了下原因,发现罪魁祸首是创建窗口时的一个属性——resizable。这里我不希望用户能够拖拽改变软件窗口大小,所以这里设置为fasle。但是在Mac环境下如果resizable为false的情况下,Mac没办法做到全屏,因为Mac的全屏就是resize页面到屏幕的四周,所以上面的方法就没用了。

解决

然后我就在想,可不可以在全屏的时候将resizable设置为true,方法调用完毕以后再设置为false

ipcMain.on('toggle-window-maximize', (event, flag: boolean) => {const win = BrowserWindow.fromWebContents(event.sender);if (!win) return;// 临时打开 resizable,否则 macOS 最大化不生效const wasResizable = win.isResizable();// 如果页面传过来的全屏判断值为trueif (flag) {// 开启resizeableif (!wasResizable) win.setResizable(true);// 这里可以不判断直接调用if (!win.isMaximized()) win.maximize();} else {if (win.isMaximized()) win.unmaximize();// 关闭resizeablesetTimeout(() => {win?.setResizable(false);}, 100);}
});

🚩之所以在调用win.maximize()以后不关闭resizeable是因为如果在调用win.maximize()以后关闭resizeable会导致windows环境下调用win.unmaximize()方法无法回到初始比例的现象,应该是会导致electron的页面基准计算错误出现这个问题

http://www.lryc.cn/news/619339.html

相关文章:

  • XML头部声明发送者信息的实现方法
  • C# 微软依赖注入 (Microsoft.Extensions.DependencyInjection) 详解
  • CV 医学影像分类、分割、目标检测,之【肝脏分割】项目拆解
  • windows常用的快捷命令
  • 机器学习实战·第三章 分类(2)
  • docker 容器内编译onnxruntime
  • git clone 支持在命令行临时设置proxy
  • CV 医学影像分类、分割、目标检测,之【腹腔多器官语义分割】项目拆解
  • 何解决PyCharm中pip install安装Python报错ModuleNotFoundError: No module named ‘json’问题
  • Video_AVI_Packet(2)
  • 基于RTSP|RTMP低延迟视频链路的多模态情绪识别系统构建与实现
  • 日志数据链路的 “搬运工”:Flume 分布式采集的组件分工与原理
  • 进阶向:Python编写自动化邮件发送程序
  • Jenkins一直无法启动,怎么办?
  • 论文分享 | Flashboom:一种声东击西攻击手段以致盲基于大语言模型的代码审计
  • 守拙以致远:个人IP的长青之道|创客匠人
  • Hive 创建事务表的方法
  • 自建知识库,向量数据库 体系建设(四)之文本向量与相似度计算——仙盟创梦IDE
  • java中list的api详细使用
  • 无人机航拍数据集|第15期 无人机人员目标检测YOLO数据集4923张yolov11/yolov8/yolov5可训练
  • pt-online-schema-change 全解析:MySQL 表结构变更的安全之道
  • clickhouse集群的安装与部署
  • Vue3 使用 echarts 甘特图(GanttChart)
  • Java -- Vector底层结构-- ArrayList和LinkedList的比较
  • C++主流string的使用
  • 工业元宇宙:迈向星辰大海的“玄奘之路”
  • C++ 类和对象4---(初始化列表,类型转化,static成员)
  • nuxt相比于vue的优点
  • java-泛型接口
  • C++多态:理解面向对象的“一个接口,多种实现”