前言
electron默认沿用系统UI,并没有提供很多接口供使用者定制样式,如果想要完全自定义的样式,目前我能想到的方案只能是通过前端自定义样式,然后通过进程通信来实现系统基础功能:最大/小化、关闭、拖动窗口等。
效果图:
一、窗口自定义
通过前面系列文章我们可以了解到,窗口是通过实例化BrowserWindow对象创建的,实例化时会传入一些窗口的参数。
要实现窗口自定义,就必须把窗口默认样式都屏蔽。几个关键的参数如下:
transparent: true,
backgroundColor:'rgba(0,0,0,0)',
frame:false,
参数含义很好理解,窗体透明无边框,参数详解请查询官网。
把系统自带的窗体样式去掉后,我们会得到一个只有主体的窗口,这个主体就是前端(vue)渲染的窗口,我们可以通过控制它,来实现任何样式的窗口。
但是这会带来一个问题,那就是窗口对象提供的很多便捷功能无法使用了,所以如果需要做最大化、最小化、拖动窗口等功能,只能通过进程通信,前端发送指令,主进程接收指令后,完成相应功能。具体原理请参考本系列前面关于进程通信的文章。
这里简单列一个示例代码,以最大化为例:
//vue代码部分,在某个dom元素上监听事件
<img class="title-icon" src="@/assets/img/maxsize.png" alt="最大化" @click="handleMaxSize()">
function handleMaxSize(){
renderApi.handleMaxSize()
}
//preload.js中定义通信的api,下面是我项目中所有渲染进程到主进程的通信
const handleSendPageName= (pageName) => ipcRenderer.send('send-page-name', pageName) //渲染进程主动到主进程
const handleMinSize= () => ipcRenderer.send('send-min-size') //渲染进程主动到主进程
const handleMaxSize= () => ipcRenderer.send('send-max-size') //渲染进程主动到主进程
const handleRestore= () => ipcRenderer.send('send-restore') //渲染进程主动到主进程
const handleRelaunch= () => ipcRenderer.send('user-click-Dock-Icon') //渲染进程主动到主进程
const handleCloseWin=()=>{
ipcRenderer.send('send-auto-close')
}
contextBridge.exposeInMainWorld('renderApi', {
//监听渲染进程事件
handleGetStoreFiles,
handleSendPageName,
handleMinSize,
handleMaxSize,
handleCloseWin,
handleRestore,
handleRelaunch
})
//主进程main.js中接收对应的通信
ipcMain.on('send-max-size', () => {
if(win.isMaximized()){
win.unmaximize()
}else{
win.maximize()
}
})
至此,模拟窗口最大化功能的全部过程就打通了。
二、窗口拖拽功能
这里值得注意的是,拖拽窗口不止是要配置参数,还要给对应dom元素增加类。
比如说我想实现拖动类名为“c-drag”的元素时,拖动窗口移动,大致代码如下:
<div class="c-drag">
</div>
.c-drag{
-webkit-app-region: drag;
}
-webkit-app-region: drag是electron提供的css样式,具体可查询官网。
这是一个比较小众的知识点,网上资料目前较少,这里记录一下。
三、不同窗口设置不同大小
这一部分逻辑略微复杂。
窗口大小的设置一定是在主进程中设置,如果仅仅依靠vue部分控制显示区域大小,不显示区域设置为透明,虽然视觉上可以实现不同的窗体大小,但是这是一种伪实现,因为透明部分只是人眼看不到而已,鼠标点击、拖拽等功能仍然存在,就会对软件用户造成困扰。
在主进程中设置窗口大小,最重要的就是进入不同页面时,要主动向主进程发送指令,并告诉主进程,我现在进入登录页了,我现在进入正常页了,我现在进入xx页……
主进程接收指令后,根据参数,控制窗口的大小即可。
在我的项目中,各页面有一个统一的路由跳转方法,所以我在跳转路由后,同时将活跃页面的name通handleSendPageName发送给主进程。代码如下:
function turnToPage_menu(name) {
console.log(name)
turnToPage(router, name)
renderApi.handleSendPageName(activeName.value) //发送pageName到主进程,以此判断窗口大小
}
主进程接收到指令后,根据参数,决定窗口设置为多大,代码:
ipcMain.on('send-page-name', (event, pageName) => {
console.log('setWindowSize',pageName)
const loginSize={
width:500,
height:580
}
const pageSize={
width:1000,
height: 950
}
if (pageName && pageName == 'normalLogin') {
win.setSize(loginSize.width, loginSize.height)
win.center()
win.setMenuBarVisibility(false)
} else {
if(this.judgePageSet(win,pageSize,loginSize)){
win.setSize(pageSize.width, pageSize.height)
win.center()
win.setMenuBarVisibility(true)
}
}
})
至此不同页面实现不同大小的窗口功能,就实现了。
其实在我们项目中,还有另一种需求场景,那就是当通过注册表把软件注册到系统右键后,上传文件时,右下角有一个简易窗口,窗口高度根据上传文件数量来计算。这就需要判断命令行参数、获取文件下载地址等操作,更加复杂,但是应用场景应该不多,有兴趣的同学可以私聊,此处不再赘述。
四、右键菜单自定义
我并没有在实际项目中真正实现过右键菜单的自定义,但是道理和窗口是相通的,如果electron提供的右键菜单样式无法满足要求,那就舍弃框架提供的便捷菜单,通过进程间通信,手动实现。
总结
- 舍弃系统窗口所有功能,利用通信机制,模拟实现需要的窗口功能。
- 判断页面发送的参数,为不同页面的窗口设置不同的样式。