從零開始的 electron 開發(fā)-主進程-窗口關閉與托盤處理
窗口關閉與托盤處理
本期主要涉及窗口的關閉處理以及托盤的簡單處理。
先說說本期的一個目標功能實現(xiàn):以網(wǎng)易云音樂為例,在Windows環(huán)境下,我們點擊右上角的關閉,這個時候會出現(xiàn)一個彈窗(以前沒有勾選不在提醒的話)詢問是直接退出還是縮小到系統(tǒng)托盤,選擇后確定才會進行實際關閉處理,當勾選不再提醒后點擊確認后下一次關閉不再提示直接處理,如果是縮小到托盤,對托盤點擊退出才會真正關閉。在Mac環(huán)境下,點擊右上角關閉直接縮小到程序塢,對程序塢右鍵退出或右上角托盤退出或左上角菜單退出,軟件才會真正關閉。
當然,每個軟件都有不同的退出邏輯,這里介紹如何實現(xiàn)上面功能的同時會對electron退出的各種事件進行說明,希望能幫助你找到想要的退出方式。
這里升級了一下版本,本版本為electron:12.0.0

關閉的概念
我們在使用官方例子,打包安裝后會發(fā)現(xiàn)mac和win在關閉上有所不同,mac是直接縮小到程序塢,對程序塢右鍵退出才能關閉,win則是直接關閉軟件,這是為什么呢?
這里我先簡單說一下關閉的概念,很多人把軟件的關閉和窗口的關閉混淆在一起了,我這里把窗口和軟件區(qū)分開說一下:
窗口的關閉:
win:BrowserWindow實例
win.destroy():強制關閉這個窗口,會觸發(fā)win的closed事件,不會觸發(fā)close事件
win.close():關閉窗口,觸發(fā)win的close,closed事件
注意:窗口的關閉不一定會觸發(fā)軟件的關閉,但是通常情況下我們只有一個窗口,如果這個窗口關閉了,會觸發(fā)app的window-all-closed(當所有的窗口都被關閉時觸發(fā))這個事件,在這個事件里我們可以調(diào)用軟件的關閉app.quit(),故大多數(shù)情況下,我們把窗口關閉了,軟件也就退出了。
那么造成這個差異的原因也就浮出水面了:
app.on('window-all-closed', () => {
if (!isMac) {
app.quit()
}
})
軟件的關閉:
app.quit():調(diào)用會先觸發(fā)app的before-quit事件,然后再觸發(fā)所有窗口的關閉事件,窗口全部關閉了(調(diào)用app.quit()關閉窗口是不會觸發(fā)window-all-closed的,會觸發(fā)will-quit),觸發(fā)app的quit事件。但是如果在quit事件前使用event.preventDefault()阻止了默認行為(win的close事件,app的before-quit和will-quit),軟件還是不會關閉。
app.exit():很好理解,最粗暴的強制關閉所有窗口,觸發(fā)app的quit事件,故win的close事件,app的before-quit和will-quit不會被觸發(fā)
總結(jié)一下簡單來說軟件的關閉要滿足兩個條件:
所有窗口都關閉了 調(diào)用了app.quit()
所有窗口關閉觸發(fā)window-all-closed,在window-all-closed里調(diào)用app.quit() 調(diào)用app.quit(),觸發(fā)所有窗口的close事件 app.exit()
進程通信配置
如果我在渲染進程想使用electron的一些方法的話,使用如下
const { ipcRenderer } = require('electron')
ipcRenderer.send('asynchronous-message', 'ping') // 向主進程發(fā)送消息
vue.config.js:
electronBuilder: {
nodeIntegration: true, // 這里設置實際上是設置process.env.ELECTRON_NODE_INTEGRATION的值
preload: 'src/renderer/preload/ipcRenderer.js',
......
}
ipcRenderer.js:
import { ipcRenderer } from 'electron'
window.ipcRenderer = ipcRenderer
主進程:
win = createWindow({
....
webPreferences: {
contextIsolation: false,
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
preload: path.join(__dirname, 'preload.js'),
scrollBounce: isMac
}
}, '', 'index.html')
渲染進程:
if (process.env.IS_ELECTRON) {
window.ipcRenderer.send('asynchronous-message', 'ping')
}
功能實現(xiàn)
一個是我們點擊關閉觸發(fā),此時我們并不想關閉窗口,那么應該使用e.preventDefault()阻止窗口的關閉。 另一個是我們主動使用app.quit()觸發(fā)關閉,這時close事件里就不做處理。
我們的流程為:
主進程檢測關閉─>判斷是否是app.quit()觸發(fā)
──> 否,通知渲染進程關閉消息,渲染進程接收后根據(jù)用戶操作或本地存儲通知主進程將軟件關閉或縮小到托盤
──> 是,關閉軟件
let willQuitApp = false
onAppReady:
win.on('close', (e) => {
console.log('close', willQuitApp)
if (!willQuitApp) {
win.webContents.send('win-close-tips', { isMac })
e.preventDefault()
}
})
我們主動使用`app.quit()`觸發(fā)關閉時把willQuitApp設置為true,然后會觸發(fā)win的close事件,讓窗口關閉掉,達成方法2。
app.on('activate', () => win.show()) // mac點擊程序塢顯示窗口
app.on('before-quit', () => {
console.log('before-quit')
willQuitApp = true
})
<a-modal
v-model:visible="visible"
:destroyOnClose="true"
title="關閉提示"
ok-text="確認"
cancel-text="取消"
@ok="hideModal"
>
<a-radio-group v-model:value="closeValue">
<a-radio :style="radioStyle" :value="1">最小化到托盤</a-radio>
<a-radio :style="radioStyle" :value="2">退出vue-cli-electron</a-radio>
<a-checkbox v-model:checked="closeChecked">不再提醒</a-checkbox>
</a-radio-group>
</a-modal>
import { defineComponent, reactive, ref, onMounted, onUnmounted } from 'vue'
import { LgetItem, LsetItem } from '@/utils/storage'
export default defineComponent({
setup() {
const closeChecked = ref(false)
const closeValue = ref(1)
const visible = ref(false)
const radioStyle = reactive({
display: 'block',
height: '30px',
lineHeight: '30px',
})
onMounted(() => {
window.ipcRenderer.on('win-close-tips', (event, data) => { // 接受主進程的關閉通知
const closeChecked = LgetItem('closeChecked')
const isMac = data.isMac
if (closeChecked || isMac) { // mac和win的區(qū)分處理
event.sender.invoke('win-close', LgetItem('closeValue')) // 當是mac或者勾選了不再提示時向主進程發(fā)送消息
} else {
visible.value = true
event.sender.invoke('win-focus', closeValue.value) // 顯示關閉彈窗并聚焦
}
})
})
onUnmounted(() => {
window.ipcRenderer.removeListener('win-close-tips')
})
async function hideModal() {
if (closeChecked.value) {
LsetItem('closeChecked', true)
LsetItem('closeValue', closeValue.value)
}
await window.ipcRenderer.invoke('win-close', closeValue.value) // 向主進程推送我們選擇的結(jié)果
visible.value = false
}
return {
closeChecked,
closeValue,
radioStyle,
visible,
hideModal
}
}
})
import { ipcMain, app } from 'electron'
import global from '../config/global'
export default function () {
const win = global.sharedObject.win
const isMac = process.platform === 'darwin'
ipcMain.handle('win-close', (event, data) => {
if (isMac) {
if (win.isFullScreen()) { // 全屏狀態(tài)下特殊處理
win.once('leave-full-screen', function () {
win.setSkipTaskbar(true)
win.hide()
})
win.setFullScreen(false)
} else {
win.setSkipTaskbar(true)
win.hide()
}
} else {
if (data === 1) { // win縮小到托盤
win.setSkipTaskbar(true) // 使窗口不顯示在任務欄中
win.hide() // 隱藏窗口
} else {
app.quit() // win退出
}
}
})
ipcMain.handle('win-focus', () => { // 聚焦窗口
if (win.isMinimized()) {
win.restore()
win.focus()
}
})
}

托盤設置
托盤的右鍵點擊退出直接退出,所以直接調(diào)用app.quit()觸發(fā)退出流程
initWindow里win賦值后調(diào)用setTray(win)
import { Tray, nativeImage, Menu, app } from 'electron'
const isMac = process.platform === 'darwin'
const path = require('path')
let tray = null
export default function (win) {
const iconType = isMac ? '16x16.png' : 'icon.ico'
const icon = path.join(__static, `./icons/${iconType}`)
const image = nativeImage.createFromPath(icon)
if (isMac) {
image.setTemplateImage(true)
}
tray = new Tray(image)
let contextMenu = Menu.buildFromTemplate([
{
label: '顯示vue-cli-electron',
click: () => {
winShow(win)
}
}, {
label: '退出',
click: () => {
app.quit()
}
}
])
if (!isMac) {
tray.on('click', () => {
winShow(win)
})
}
tray.setToolTip('vue-cli-electron')
tray.setContextMenu(contextMenu)
}
function winShow(win) {
if (win.isVisible()) {
if (win.isMinimized()) {
win.restore()
win.focus()
} else {
win.focus()
}
} else {
!isMac && win.minimize()
win.show()
win.setSkipTaskbar(false)
}
}
win.on('show', () => {
setTimeout(() => {
win.setOpacity(1)
}, 200)
})
win.on('hide', () => {
win.setOpacity(0)
})
補充
比如托盤的點擊處理win上左擊直接打開軟件,右擊打開菜單,而mac上左擊除了觸發(fā)click外還會打開菜單,如果和win上一樣處理的話有些不太適宜。
這里再補充一個mac上的,mac軟件在全屏時,大多數(shù)軟件都是把縮小這個按鈕給禁用了的,那么electron怎么實現(xiàn)這個呢:
win.on('enter-full-screen', () => {
isMac && app.commandLine.appendSwitch('disable-pinch', true)
})
win.on('leave-full-screen', () => {
isMac && app.commandLine.appendSwitch('disable-pinch', false)
})

評論
圖片
表情
