前段時間,我用electron-vue開發了一款跨平臺(目前支持Mac和Windows)的免費開源的圖牀上傳應用——PicGo,在開發過程當中踩了很多的坑,不只來自應用的業務邏輯自己,也來自electron自己。在開發這個應用過程當中,我學了很多的東西。由於我也是從0開始學習electron,因此不少經歷應該也能給初學、想學electron開發的同窗們一些啓發和指示。故而寫一份Electron的開發實戰經歷,用最貼近實際工程項目開發的角度來闡述。但願能幫助到你們。javascript
預計將會從幾篇系列文章或方面來展開:css
PicGo
是採用electron-vue
開發的,因此若是你會vue
,那麼跟着一塊兒來學習將會比較快。若是你的技術棧是其餘的諸如react
、angular
,那麼純按照本教程雖然在render端(能夠理解爲頁面)的構建可能學習到的東西很少,不過在main端(electron的主進程)應該仍是能學習到相應的知識的。html
若是以前的文章沒閱讀的朋友能夠先從以前的文章跟着看。前端
從上一篇文章結尾部分咱們運行成功的一個electron-vue的DEMO來直觀看看這兩個進程的粗淺認識:vue
能夠看到Main進程管理的是這個app窗口(BrowserWindow),而Renderer進程負責的就是咱們熟悉的頁面UI渲染。不過實際上,它們遠遠不只如此。下面一張圖可以把它們所支持、管理的electron或者原生的模塊大體列出來:java
圖中列出來的大部分模塊都是咱們會在開發過程當中用到的。react
它們有各自的模塊,也有共有的模塊好比clipboard
等。還有一部分是Main進程裏的模塊,不過能夠經過remote
模塊,讓renderer進程也能使用。好比Menu
好比shell
等。linux
瞭解一下哪些模塊在哪些進程裏,哪些模塊能夠經過remote
模塊讓renderer進程也能使用是有必要的,這樣咱們後續開發的時候才能正確的使用。webpack
上面的模塊可能有些從名字裏並不能看出做用是啥,不要緊,後續的內容會慢慢涉及。nginx
上面說到了Main進程一個顯著的做用就是建立app的窗口。咱們來看看這個是怎麼實現的。
import { app, BrowserWindow } from 'electron' // 從electron引入app和BrowserWindow
let mainWindow
const winURL = process.env.NODE_ENV === 'development'
? `http://localhost:9080` // 開發模式的話走webpack-dev-server的url
: `file://${__dirname}/index.html`
function createWindow () { // 建立窗口
/** * Initial window options */
mainWindow = new BrowserWindow({
height: 563,
useContentSize: true,
width: 1000
}) // 建立一個窗口
mainWindow.loadURL(winURL) // 加載窗口的URL -> 來自renderer進程的頁面
mainWindow.on('closed', () => {
mainWindow = null
})
}
app.on('ready', createWindow) // app準備好的時候建立窗口
複製代碼
暫且先無論渲染進程裏的頁面長什麼樣,在app準備好的時候打開一個窗口只須要調用一個建立BrowserWindow
的方法便可。
main進程裏的開發有點當年寫jQuery
的樣子,比較多的是事件驅動型的寫法。
首先須要注意的是app的模塊。這個模塊是electron應用的骨架。它掌管着整個應用的生命週期鉤子,以及不少其餘事件鉤子。
app的經常使用生命週期鉤子以下:
will-finish-launching
在應用完成基本啓動進程以後觸發ready
當electron完成初始化後觸發window-all-closed
全部窗口都關閉的時候觸發,在windows和linux裏,全部窗口都退出的時候一般是應用退出的時候before-quit
退出應用以前的時候觸發will-quit
即將退出應用的時候觸發quit
應用退出的時候觸發而咱們一般會在ready
的時候執行建立應用窗口、建立應用菜單、建立應用快捷鍵等初始化操做。而在will-quit
或者quit
的時候執行一些清空操做,好比解綁應用快捷鍵。
特別的,在非macOS
的系統下,一般一個應用的全部窗口都退出的時候,也是這個應用退出之時。因此能夠配合window-all-closed
這個鉤子來實現:
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') { // 當操做系統不是darwin(macOS)的話
app.quit() // 退出應用
}
})
複製代碼
除了上面說的生命週期鉤子以外,還有一些經常使用的事件鉤子:
active
(僅macOS)當應用處於激活狀態時browser-window-created
當一個BrowserWindow被建立的時候browser-window-focus
當一個BrowserWindow處於激活狀態的時候這些鉤子須要配合一些具體場景來作出具體的操做。好比當一個BrowserWindow處於激活狀態的時候修改窗口的title值。
固然,app這個模塊除了上述的一些事件鉤子以外,還有一些很經常使用的方法:
app.quit()
用於退出應用app.getPath(name)
用於獲取一些系統目錄,對於存放應用的配置文件等頗有用app.focus()
用於激活應用,不一樣系統激活邏輯不同這些事件和方法都是怎麼知道的呢?固然是官方文檔了。不過並不須要一開始就通讀一遍官方的api文檔。官方的api文檔更多的做用是用來查閱,當你要開發到某個功能的時候再去查它可否有對應的api、怎麼使用。
BrowserWindow模塊用於建立最多見的應用窗口。對於不一樣系統,建立的窗口的默認樣式也不太同樣。下面來看看macOS和windows的窗口在外觀上的區別:
mac版的
windows版的
能夠看到兩者在窗口頂部的操做區(最小化、最大化、關閉)和標題的位置以及菜單的位置仍是有明顯的不一樣的。它們跟系統原生的窗口是一致的。不過若是你想要美化一下也是沒問題的。好比:
mac版的PicGo
和windows的PicGo
其中mac版用了系統的操做區,而windows則沒有用系統的操做區,而是用圖標模擬的。不過一樣的地方是都未使用系統默認的titlebar
。這個以後會結合renderer
進程來講。
讓咱們來看看建立一個BrowserWindow的經常使用配置:
let window
function createWindow () {
window = new BrowserWindow({
height: 900, // 高
width: 400, // 寬
show: false, // 建立後是否顯示
frame: false, // 是否建立frameless窗口
fullscreenable: false, // 是否容許全屏
center: true, // 是否出如今屏幕居中的位置
backgroundColor: '#fff' // 背景色,用於transparent和frameless窗口
titleBarStyle: 'xxx' // 標題欄的樣式,有hidden、hiddenInset、customButtonsOnHover等
resizable: false, // 是否容許拉伸大小
transparent: true, // 是不是透明窗口(僅macOS)
vibrancy: 'ultra-dark', // 窗口模糊的樣式(僅macOS)
webPreferences: {
backgroundThrottling: false // 當頁面被置於非激活窗口的時候是否中止動畫和計時器
}
// ... 以及其餘可選配置
})
window.loadURL(url)
window.on('closed', () => { window = null })
}
複製代碼
窗口的長寬天然沒必要說,須要指定。其中須要注意的幾個比較重要的就是,frame
這個選項,默認是true
。若是選擇了false
則會建立一個frameless
窗口,建立一個沒有頂部工具欄、沒有border的窗口。這個也是咱們在windows系統下自定義頂部欄的基礎。
像上述PicGo的主窗口的配置,就是經過以下的配置實現的:
const createSettingWindow = () => {
const options = {
height: 450,
width: 800,
show: false,
frame: true,
center: true,
fullscreenable: false,
resizable: false,
title: 'PicGo',
vibrancy: 'ultra-dark',
transparent: true,
titleBarStyle: 'hidden',
webPreferences: {
backgroundThrottling: false
}
}
if (process.platform === 'win32') { // 針對windows平臺作出不一樣的配置
options.show = true // 建立即展現
options.frame = false // 建立一個frameless窗口
options.backgroundColor = '#3f3c37' // 背景色
}
settingWindow = new BrowserWindow(options)
settingWindow.loadURL(settingWinURL)
settingWindow.on('closed', () => {
settingWindow = null
})
}
複製代碼
跟app
模塊同樣,BrowserWindow
也有不少經常使用的事件鉤子:
closed
當窗口被關閉的時候focus
當窗口被激活的時候show
當窗口展現的時候hide
當窗口被隱藏的時候maxmize
當窗口最大化時minimize
當窗口最小化時...
固然,也依然有不少實用的方法:
BrowserWindow.getFocusedWindow()
[靜態方法]獲取激活的窗口win.close()
[實例方法,下同]關閉窗口win.focus()
激活窗口win.show()
顯示窗口win.hide()
隱藏窗口win.maximize()
最大化窗口win.minimize()
最小化窗口win.restore()
從最小化窗口恢復...
針對不一樣的業務邏輯你須要對窗口進行不同的操做。這個須要跟你的項目需求相匹配。好比上述說到的,windows的頂部的操做區(放大、縮小、關閉按鈕)就能夠經過icon模擬+實例方法來實現。
一開始看這個名字你可能並不知道這個是個什麼東西。能夠把它理解爲不一樣系統的任務欄裏的圖標組件吧。
好比在macOS裏,Tray
配合上圖標以後就是頂部欄裏的應用圖標了:
好比在windows裏,Tray
配合上圖標以後就是windows右下角的應用圖標了:
須要注意的是,windows和macOS裏,圖標的大小都是16*16
px。macOS下頂部欄的圖標一般都是走黑白
路線,因此能夠爲兩種系統分別準備不一樣的圖標。PicGo
裏Tray
的生成代碼大體以下:
function createTray () {
const menubarPic = process.platform === 'darwin' ? `${__static}/menubar.png` : `${__static}/menubar-nodarwin.png`
tray = new Tray(menubarPic) // 指定圖片的路徑
// ... 其餘代碼
}
複製代碼
注意上述代碼裏有一個${__static}
的變量。該變量是electron-vue
爲咱們暴露出來的項目根目錄下的static
文件夾的路徑。經過這個路徑,在開發和生產階段都能很好的定位你的靜態資源所在的目錄。是個很方便的變量。
固然Tray
並不僅是一個圖標而無其餘做用了。Tray支持不少有用的事件。其中最關鍵的兩個是click
和right-click
。分別對應鼠標左鍵點擊和鼠標右鍵點擊事件。
因此須要咱們去適配不一樣操做系統下用戶的操做習慣。
對應於PicGo而言,在macOS系統下左鍵點擊會出現一個menubar的小窗口,右鍵點擊會出現配置菜單。而在windows下,左鍵點擊會直接出現主窗口,(由於在windows下無小窗口的必要),右鍵點擊會出現配置菜單。它們在PicGo裏的實現以下:
function createTray () {
const menubarPic = process.platform === 'darwin' ? `${__static}/menubar.png` : `${__static}/menubar-nodarwin.png`
tray = new Tray(menubarPic)
const contextMenu = // ...菜單
tray.on('right-click', () => { // 右鍵點擊
window.hide() // 隱藏小窗口
tray.popUpContextMenu(contextMenu) // 打開菜單
})
tray.on('click', () => { // 左鍵點擊
if (process.platform === 'darwin') { // 若是是macOS
toggleWindow() // 打開或關閉小窗口
} else { // 若是是windows
window.hide() // 隱藏小窗口
if (settingWindow === null) { // 若是主窗口不存在就建立一個
createSettingWindow()
settingWindow.show()
} else { // 若是主窗口在,就顯示並激活
settingWindow.show()
settingWindow.focus()
}
}
})
}
複製代碼
對於macOS而言,Tray還有一個很棒的特性——能夠拖拽文件到Tray的icon上,會觸發以下事件:
drop
當任何東西拖拽到icon上時drop-files
當文件被拖拽到icon上時drop-text
當文本被拖拽到icon上時drop-enter
當剛拖拽到icon上時drop-leave
當拖拽事件離開icon時drop-end
當拖拽事件結束時就像PicGo實現的拖拽圖片到Tray的icon上時實現圖片上傳的功能,就是用到了上述的一些事件:
尤爲注意到在拖拽上的時候和拖拽結束後的時候icon是不同的。在PicGo裏是這樣實現的,很簡單:
tray.on('drag-enter', () => {
tray.setImage(`${__static}/upload.png`)
})
tray.on('drag-end', () => {
tray.setImage(`${__static}/menubar.png`)
})
複製代碼
而Tray
另外一個重要的做用就是開啓菜單項。這個將結合下一節Menu
一塊兒說明。
electron威力強大的Menu組件,既可以生成系統菜單項,也能實現綁定應用經常使用快捷鍵的功能。
先來看看什麼是系統菜單項:
macOS
windows
主要分兩種。
第一種菜單能夠經過Menu.setApplicationMenu()
來實現。
第二種菜單能夠經過兩個步驟來展現:
1. 建立菜單:
const contextMenu = Menu.buildFromTemplate([...])
複製代碼
2. 展現菜單:
tray.on('right-click', () => { // 右鍵點擊tray的時候
tray.popUpContextMenu(contextMenu) // 彈出菜單
})
複製代碼
這裏咱們只介紹了Menu
自己。其實組成Menu
的是一個一個的MenuItem
。它們有不少類型:
以及不少角色:
一般來講,配置的菜單項基本從類型裏來組合。好比PicGo的菜單項:
這裏面就有normal、submenu、checkbox和radio四種類型。其中默認是normal。
角色的話一般對應的是一些常見的行爲。好比quit
是退出app,好比minimize
是最小化,好比copy
是複製。不過須要注意的是,若是你沒有在建立app菜單裏指定這些操做的快捷鍵的話,那麼一些常見的快捷操做就沒法在你的app裏使用了。好比ctrl+c
或者command+c
複製這個操做,若是你沒有經過Menu.setApplicationMenu()
來設定這個快捷鍵的話,那麼在你的electron應用裏就沒法執行復制的操做了。PicGo在早期版本里也犯了這個錯誤。當時的問題是我在開發模式下是沒有問題的,可是在生產模式下就沒法進行復制粘貼操做。後來查了一下緣由,發現原來在開發模式下,electron會置入默認的一些快捷操做菜單,如圖:
因此在生產模式若是我沒有置入這些快捷鍵的話,使用者就沒法使用了。這個是大坑。
說了這麼多,來看看生成app的菜單的代碼長啥樣:
注意,若是在開發模式下直接只使用以下快捷鍵的話,一些調試快捷鍵好比
F12
或者command+shift+i
打開控制檯的操做就沒法使用了。因此在開發模式下不須要建立這些快捷鍵菜單。
const createMenu = () => {
if (process.env.NODE_ENV !== 'development') {
const template = [{
label: 'Edit',
submenu: [
{ label: 'Undo', accelerator: 'CmdOrCtrl+Z', selector: 'undo:' },
{ label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:' },
{ type: 'separator' },
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:' },
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' },
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' },
{ label: 'Select All', accelerator: 'CmdOrCtrl+A', selector: 'selectAll:' },
{
label: 'Quit',
accelerator: 'CmdOrCtrl+Q',
click () {
app.quit()
}
}
]
}]
menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
}
}
複製代碼
能夠經過accelerator
指定你想要的快捷鍵。諸如Shift
、Ctrl
、Cmd
等鍵位縮寫。若是是組合鍵,就加上+
。尤爲注意到,由於macOS和windows鍵位的差別,因此有一個很好用的鍵位縮寫CmdOrCtrl
,即若是是在macOS上就是Cmd
,在windows上就是Ctrl
。
而後再來看看Tray的「右鍵」菜單的生成:
const contextMenu = Menu.buildFromTemplate([
{
label: '關於',
click () {
dialog.showMessageBox({
title: 'PicGo',
message: 'PicGo',
detail: `Version: ${pkg.version}\nAuthor: Molunerfinn\nGithub: https://github.com/Molunerfinn/PicGo`
})
}
},
{
label: '打開詳細窗口',
click () {
if (settingWindow === null) {
createSettingWindow()
settingWindow.show()
} else {
settingWindow.show()
settingWindow.focus()
}
}
},
{
label: '選擇默認圖牀',
type: 'submenu',
submenu: [
{
label: '微博圖牀',
type: 'radio',
checked: db.read().get('picBed.current').value() === 'weibo',
click () {
db.read().set('picBed.current', 'weibo')
.write()
}
},
{
label: '七牛圖牀',
type: 'radio',
checked: db.read().get('picBed.current').value() === 'qiniu',
click () {
db.read().set('picBed.current', 'qiniu')
.write()
}
},
{
label: '騰訊雲COS',
type: 'radio',
checked: db.read().get('picBed.current').value() === 'tcyun',
click () {
db.read().set('picBed.current', 'tcyun')
.write()
}
},
{
label: '又拍雲圖牀',
type: 'radio',
checked: db.read().get('picBed.current').value() === 'upyun',
click () {
db.read().set('picBed.current', 'upyun')
.write()
}
}
]
},
{
label: '打開更新助手',
type: 'checkbox',
checked: db.get('picBed.showUpdateTip').value(),
click () {
const value = db.read().get('picBed.showUpdateTip').value()
db.read().set('picBed.showUpdateTip', !value).write()
}
},
{
role: 'quit',
label: '退出'
}
])
tray.on('right-click', () => {
tray.popUpContextMenu(contextMenu)
})
複製代碼
注意,菜單項的點擊事件能夠直接經過click
屬性來指定。上面咱們是先經過了Menu.buildFromTemplate()
這個方法建立了菜單,而後再在右鍵點擊Tray
圖標的時候將其彈(PopUp)出來。
固然也有其餘構建菜單的方法。能夠經過Menu實例的append
方法來加入Menu Item
。以下例:
const menu = new Menu()
menu.append(new MenuItem({ label: 'Cut', accelerator: 'CmdOrCtrl+X' }))
menu.append(new MenuItem({ type: 'separator' })) // 分割線
menu.append(new MenuItem({ label: 'Helper', type: 'checkbox', checked: true }))
複製代碼
基本上有了上述的幾個基本模塊,咱們的一個應用的骨架是基本搭建好了,擁有窗口、任務欄應用圖標和菜單項。其餘的Main進程的模塊,並非必須的,當會用到的時候將在以後的文章裏逐步說起。下一節咱們未來看renderer進程的開發。
對於electron-vue
而言,renderer進程其實大部分就是在寫咱們平時常寫的前端頁面罷了。不過相對於平時在瀏覽器裏寫的頁面,在electron裏寫頁面的時候你還能用到很多非瀏覽器端的模塊,好比fs
,好比electron經過remote
模塊暴露給renderer進程的模塊。接下去咱們來看看renderer進程有哪些須要注意的地方。
往常咱們在寫Vue的時候都比較喜歡開啓路由的history
模式,由於這樣在瀏覽器的地址欄上看起來比較好看——沒有hash的#
號,就如同請求後端的url通常。然而須要注意的是,history
模式須要後端服務器的支持。
可能不少朋友平時開發的時候沒有感受,那是由於vue-cli裏在開發模式下啓動的webpack-dev-server
幫你實現了服務端的history-fallback
的特性。因此在實際部署的時候,至少都須要在你的web服務器程序諸如nginx
、apache
等配置相關的規則,讓前端路由返回給vue-router
去處理。
而electron裏也是如此。在開發模式下,因爲使用的是webpack-dev-server
開啓的服務器,因此BrowserWindow
加載的是來自於相似``http://localhost:9080這樣的地址的頁面。而在生產模式下,倒是使用的
file://的協議,好比
file://${__dirname}/index.html`來指定窗口加載的頁面。
所以,從上面的表述你也能明白了。假如我有一個子路由地址爲child
。若是不啓用Hash模式,在開發模式下沒啥問題,http://localhost:9080/child
,可是在生產模式下,file://${__dirname}/index.html/child
倒是沒法匹配的一條路徑。所以在electron下,vue-router
請不要使用history
模式,而使用默認的hash
模式。
那麼上面的問題就迎刃而解,變爲file://${__dirname}/index.html#child
便可。
PicGo里加載的頁面路由規則以下,從中你也能看出我使用的是hash
模式。
const winURL = process.env.NODE_ENV === 'development'
? `http://localhost:9080`
: `file://${__dirname}/index.html`
const settingWinURL = process.env.NODE_ENV === 'development'
? `http://localhost:9080/#setting/upload`
: `file://${__dirname}/index.html#setting/upload`
複製代碼
在上面講BrowserWindow
的時候,我說到有時爲了應用的美觀,並不想讓咱們的應用窗口採用系統默認的titlebar
,而想用本身寫的來實現。這樣的話就在建立你的BrowserWindow
的配置里加上一句
titleBarStyle: 'hidden'
複製代碼
這樣就好了。而後你就能夠自行在renderer進程的頁面裏模擬一個頂部的titlebar
了,好比上面提到的PicGo
的titlebar
的樣子。實際上代碼也很簡單:
<div class="fake-title-bar">
PicGo - {{ version }}
<div class="handle-bar" v-if="os === 'win32'"> <!-- 若是是windows系統 就加上模擬的操做按鈕-->
<i class="el-icon-minus" @click="minimizeWindow"></i>
<i class="el-icon-close" @click="closeWindow"></i>
</div>
</div>
複製代碼
而後把這個titlebar的position置頂便可。
不過在平時的使用中,咱們要注意,通常咱們鼠標按住titlebar的時候是能夠拖動窗口的。可是若是咱們在不加可拖拽的屬性以前,咱們本身寫的titlebar是不具有這樣的特性的。要加上這個特性也很簡單:
.fake-title-bar {
-webkit-app-region drag
}
複製代碼
只需一條CSS,便可讓你的titlebar能夠拖拽。
不過在windows下,操做區的按鈕(縮小、放大、關閉)長按應該是不能拖拽的,因此還須要:
.handle-bar {
-webkit-app-region no-drag
}
複製代碼
變成no-drag
,這樣就實現了咱們本身生成應用的titlebar了。
一般咱們用Chrome的時候,有個特性是好比你往Chrome裏拖入一個pdf,它就會自動用內置的pdf閱讀器打開。你往Chrome裏拖入一張圖片,它就會打開這張圖片。因爲咱們的electron應用的BrowserWindow
其實內部也是一個瀏覽器,因此這樣的特性依然存在。而這也是不少人沒有注意的地方。也就是當你開發完一個electron應用以後,往裏拖入一張圖片,一個pdf等等,若是不是一個可拖拽區域(好比PicGo的上傳區),那麼它就不該該打開這張圖、這個pdf,而是將其排除在外。
因此咱們將在全局監聽drag
和drop
事件,當用戶拖入一個文件可是又不是拖入可拖拽區域的時候,應該將其屏蔽掉。由於全部的頁面都應該要有這樣的特性,因此我寫了一個vue的mixin
:
export default {
mounted () {
this.disableDragEvent()
},
methods: {
disableDragEvent () {
window.addEventListener('dragenter', this.disableDrag, false)
window.addEventListener('dragover', this.disableDrag)
window.addEventListener('drop', this.disableDrag)
},
disableDrag (e) {
const dropzone = document.getElementById('upload-area') // 這個是可拖拽的上傳區
if (dropzone === null || !dropzone.contains(e.target)) {
e.preventDefault()
e.dataTransfer.effectAllowed = 'none'
e.dataTransfer.dropEffect = 'none'
}
}
},
beforeDestroy () {
window.removeEventListener('dragenter', this.disableDrag, false)
window.removeEventListener('dragover', this.disableDrag)
window.removeEventListener('drop', this.disableDrag)
}
}
複製代碼
這樣在全局引入這個mixin便可。
remote模塊是electron爲了讓一些本來在Main進程裏運行的模塊也能在renderer進程裏運行而建立的。如下說幾個咱們會用到的。
在electron-vue
裏內置了vue-electron
這個模塊,能夠在vue裏很方便的使用諸如this.$electron.remote.xxx
來使用remote的模塊。
shell
模塊的官方說明是:Manage files and URLs using their default applications.
也就是使用文件或者URL的默認應用。一般咱們能夠用其讓默認圖片應用打開一張圖片、讓默認瀏覽器打開一個url。
若是咱們想在renderer進程裏點擊一個按鈕而後在默認瀏覽器裏打開一個url的話就能夠這樣:
<button @click="openURL"></button>
<script> export default { methods: { openURL () { this.$electron.remote.shell.openExternal('https://github.com/Molunerfinn/PicGo') } } } </script>
複製代碼
是否是很方便?
更多詳細的shell的用法能夠參考文檔。
有的時候咱們會有打開原生的對話框的需求。好比PicGo
的版本信息:
macOS
windows
這個時候就能夠經過dialog
這個模塊來實現了。邏輯跟上面同樣也是點擊一個按鈕打開一個dialog:
openDialog () {
this.$electron.remote.dialog.showMessageBox({
title: 'PicGo',
message: 'PicGo',
detail: `Version: ${pkg.version}\nAuthor: Molunerfinn\nGithub: https://github.com/Molunerfinn/PicGo`
})
}
複製代碼
更多詳細的dialog的用法能夠參考文檔。
使用Menu
可能不少人可以理解。可是爲何要使用BrowserWindow
呢?由於須要定位你打開Menu
的窗口。
在PicGo裏,有一個點擊按鈕打開Menu的操做,大體以下:
buildMenu () {
const template = [...]
this.menu = Menu.buildFromTemplate(template)
},
openDialog () {
this.menu.popup(remote.getCurrentWindow) // 獲取當前打開Menu的窗口
}
複製代碼
這裏的menu.popup
就須要你指定一下打開這個menu的窗口。它將自動定位你點擊的位置而彈出。
在Vue裏,若是是非父子組件通訊,很經常使用的是經過Bus Event
來實現的。而electron裏的不一樣進程間的通訊其實也很相似,是經過ipcMain
和ipcRenderer
來實現的。其中ipcMain
是在main
進程裏使用的,而ipcRenderer
是在renderer
進程裏使用的。
官網的例子其實很簡潔明瞭了,我放出來:
// In main process.
const {ipcMain} = require('electron')
ipcMain.on('asynchronous-message', (event, arg) => {
console.log(arg) // prints "ping"
event.sender.send('asynchronous-reply', 'pong')
})
ipcMain.on('synchronous-message', (event, arg) => {
console.log(arg) // prints "ping"
event.returnValue = 'pong'
})
複製代碼
// In renderer process (web page).
const {ipcRenderer} = require('electron')
console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong"
ipcRenderer.on('asynchronous-reply', (event, arg) => {
console.log(arg) // prints "pong"
})
ipcRenderer.send('asynchronous-message', 'ping')
複製代碼
其中ipcMain
只有監聽來自ipcRenderer
的某個事件後才能返回給ipcRenderer
值。而ipcRenderer
既能夠收,也能夠發。
那麼問題就來了,如何讓ipcMain
主動發送消息呢?或者說讓main進程主動發送消息給ipcRenderer
。
首先要明確的是,ipcMain
沒法主動發消息給ipcRenderer
。由於ipcMain只有.on()
方法沒有.send()
的方法。因此只能用其餘方法來實現。有辦法麼?有的,用webContents
。
webContents
實際上是BrowserWindow
實例的一個屬性。也就是若是咱們須要在main
進程裏給某個窗口某個頁面發送消息,則必須經過win.webContents.send()
方法來發送。
代碼大體以下:
// In main process
let win = new BrowserWindow({...})
win.webContents.send('img-files', imgs)
複製代碼
// In renderer process
ipcRenderer.on('img-files', (event, files) => {
console.log(files)
})
複製代碼
因此必須指定要發送的窗口,才能將信息準確送達。
本文詳細地講述了electron裏Main
進程和Renderer
進程的基礎知識和開發相關。不少都是我在開發PicGo
的時候碰到的問題、踩的坑。也許文中簡單的幾句話背後就是我無數次的查閱和調試。內容相比第一篇多了很多,但願這篇文章可以給你的electron-vue
開發帶來一些啓發。文中相關的代碼,你均可以在PicGo的項目倉庫裏找到,歡迎點個star~但願本文可以給你帶來幫助,這是我最開心的地方。若是喜歡,歡迎關注個人博客以及本系列文章的後續進展。
注:文中的圖片除未特意說明以外均屬於我我的做品,須要轉載請私信