Electron-vue開發實戰1——Main程式和Renderer程式的簡單開發

Molunerfinn發表於2018-01-17

前言

前段時間,我用electron-vue開發了一款跨平臺(目前支援Mac和Windows)的免費開源的圖床上傳應用——PicGo,在開發過程中踩了不少的坑,不僅來自應用的業務邏輯本身,也來自electron本身。在開發這個應用過程中,我學了不少的東西。因為我也是從0開始學習electron,所以很多經歷應該也能給初學、想學electron開發的同學們一些啟發和指示。故而寫一份Electron的開發實戰經歷,用最貼近實際工程專案開發的角度來闡述。希望能幫助到大家。

預計將會從幾篇系列文章或方面來展開:

  1. electron-vue入門
  2. Main程式和Renderer程式的簡單開發
  3. 引入基於Lodash的json database——lowdb
  4. 跨平臺的一些相容措施
  5. 通過CI釋出以及更新的方式
  6. …(想到再寫)

說明

PicGo是採用electron-vue開發的,所以如果你會vue,那麼跟著一起來學習將會比較快。如果你的技術棧是其他的諸如reactangular,那麼純按照本教程雖然在render端(可以理解為頁面)的構建可能學習到的東西不多,不過在main端(electron的主程式)應該還是能學習到相應的知識的。

如果之前的文章沒閱讀的朋友可以先從之前的文章跟著看。

Main程式和Renderer程式的基本認識

從上一篇文章結尾部分我們執行成功的一個electron-vue的DEMO來直觀看看這兩個程式的粗淺認識:

Electron-vue開發實戰1——Main程式和Renderer程式的簡單開發

可以看到Main程式管理的是這個app視窗(BrowserWindow),而Renderer程式負責的就是我們熟悉的頁面UI渲染。不過實際上,它們遠遠不僅如此。下面一張圖能夠把它們所支援、管理的electron或者原生的模組大致列出來:

main &<br /> renderer process tree” class=”lazyload” data-height=”959″ src=”https://user-gold-cdn.xitu.io/2018/1/17/161020c5784d99f1?imageView2/0/w/1280/h/960/ignore-error/1″ data-width=”1280″><figcaption></figcaption></figure>
</p>
<blockquote>
<p>圖中列出來的大部分模組都是我們會在開發過程中用到的。</p>
</blockquote>
<p>它們有各自的模組,也有共有的模組比如<code>clipboard</code>等。還有一部分是Main程式裡的模組,不過可以通過<code>remote</code>模組,讓renderer程式也能使用。比如<code>Menu</code>比如<code>shell</code>等。</p>
<p>瞭解一下哪些模組在哪些程式裡,哪些模組可以通過<code>remote</code>模組讓renderer程式也能使用是有必要的,這樣我們後續開發的時候才能正確的使用。</p>
<p>上面的模組可能有些從名字裡並不能看出作用是啥,沒關係,後續的內容會慢慢涉及。</p>
<h2 class=Main程式開發

上面說到了Main程式一個顯著的作用就是建立app的視窗。我們來看看這個是怎麼實現的。

import { 
app, BrowserWindow
} from 'electron' // 從electron引入app和BrowserWindowlet mainWindowconst 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

首先需要注意的是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

BrowserWindow模組用於建立最常見的應用視窗。對於不同系統,建立的視窗的預設樣式也不太一樣。下面來看看macOS和windows的視窗在外觀上的區別:

mac版的

Electron-vue開發實戰1——Main程式和Renderer程式的簡單開發

windows版的

Electron-vue開發實戰1——Main程式和Renderer程式的簡單開發

可以看到二者在視窗頂部的操作區(最小化、最大化、關閉)和標題的位置以及選單的位置還是有明顯的不同的。它們跟系統原生的視窗是一致的。不過如果你想要美化一下也是沒問題的。比如:

mac版的PicGo

picgo-mac

和windows的PicGo

picgo-windows

其中mac版用了系統的操作區,而windows則沒有用系統的操作區,而是用圖示模擬的。不過同樣的地方是都未使用系統預設的titlebar。這個之後會結合renderer程式來說。

讓我們來看看建立一個BrowserWindow的常用配置:

let windowfunction 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模擬+例項方法來實現。

Tray

一開始看這個名字你可能並不知道這個是個什麼東西。可以把它理解為不同系統的工作列裡的圖示元件吧。

比如在macOS裡,Tray配合上圖示之後就是頂部欄裡的應用圖示了:

Electron-vue開發實戰1——Main程式和Renderer程式的簡單開發

比如在windows裡,Tray配合上圖示之後就是windows右下角的應用圖示了:

Electron-vue開發實戰1——Main程式和Renderer程式的簡單開發

需要注意的是,windows和macOS裡,圖示的大小都是16*16px。macOS下頂部欄的圖示通常都是走黑白路線,所以可以為兩種系統分別準備不同的圖示。PicGoTray的生成程式碼大致如下:

function createTray () { 
const menubarPic = process.platform === 'darwin' ? `${__static
}
/menubar.png`
: `${__static
}
/menubar-nodarwin.png`
tray = new Tray(menubarPic) // 指定圖片的路徑 // ... 其他程式碼
}複製程式碼

注意上述程式碼裡有一個${__static
}
的變數。該變數是electron-vue為我們暴露出來的專案根目錄下的static資料夾的路徑。通過這個路徑,在開發和生產階段都能很好的定位你的靜態資源所在的目錄。是個很方便的變數。

當然Tray並不只是一個圖示而無其他作用了。Tray支援很多有用的事件。其中最關鍵的兩個是clickright-click。分別對應滑鼠左鍵點選和滑鼠右鍵點選事件。

滑鼠左鍵點選事件

  • 在macOS系統下,滑鼠左鍵點選Tray的icon可能會出現配置選單,也有可能會出現應用視窗。
  • 在windows下,滑鼠左鍵點選Tray的icon通常會出現應用的視窗。

滑鼠右鍵點選事件

  • 在macOS系統下,滑鼠右鍵點選Tray的icon通常會出現配置選單。
  • 在windows系統下,同上。

所以需要我們去適配不同作業系統下使用者的操作習慣。

對應於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上時實現圖片上傳的功能,就是用到了上述的一些事件:

Electron-vue開發實戰1——Main程式和Renderer程式的簡單開發

尤其注意到在拖拽上的時候和拖拽結束後的時候icon是不一樣的。在PicGo裡是這樣實現的,很簡單:

  tray.on('drag-enter', () =>
{
tray.setImage(`${__static
}
/upload.png`
)
}) tray.on('drag-end', () =>
{
tray.setImage(`${__static
}
/menubar.png`
)
})複製程式碼

Tray另一個重要的作用就是開啟選單項。這個將結合下一節Menu一起說明。

Menu

electron威力強大的Menu元件,既能夠生成系統選單項,也能實現繫結應用常用快捷鍵的功能。

先來看看什麼是系統選單項:

macOS

Electron-vue開發實戰1——Main程式和Renderer程式的簡單開發

windows

Electron-vue開發實戰1——Main程式和Renderer程式的簡單開發
Electron-vue開發實戰1——Main程式和Renderer程式的簡單開發

主要分兩種。

  • 第一種是app的選單。對於macOS來說就是頂部欄左側區域的選單項。對於windows而言就是一個視窗的標題欄下方的選單區。
  • 第二種是類似於右鍵選單的選單。

第一種選單可以通過Menu.setApplicationMenu()來實現。

第二種選單可以通過兩個步驟來展示:

1. 建立選單:

 const contextMenu = Menu.buildFromTemplate([...])複製程式碼

2. 展示選單:

tray.on('right-click', () =>
{
// 右鍵點選tray的時候 tray.popUpContextMenu(contextMenu) // 彈出選單
})複製程式碼

這裡我們只介紹了Menu本身。其實組成Menu的是一個一個的MenuItem。它們有很多型別:

  1. normal
  2. separator
  3. submenu
  4. checkbox
  5. radio

以及很多角色:

  1. quit
  2. copy
  3. redo
  4. undo
  5. minimize
  6. close
  7. reload

通常來說,配置的選單項基本從型別裡來組合。比如PicGo的選單項:

Electron-vue開發實戰1——Main程式和Renderer程式的簡單開發

這裡面就有normal、submenu、checkbox和radio四種型別。其中預設是normal。

角色的話通常對應的是一些常見的行為。比如quit是退出app,比如minimize是最小化,比如copy是複製。不過需要注意的是,如果你沒有在建立app選單裡指定這些操作的快捷鍵的話,那麼一些常見的快捷操作就無法在你的app裡使用了。比如ctrl+c或者command+c複製這個操作,如果你沒有通過Menu.setApplicationMenu()來設定這個快捷鍵的話,那麼在你的electron應用裡就無法執行復制的操作了。PicGo在早期版本里也犯了這個錯誤。當時的問題是我在開發模式下是沒有問題的,但是在生產模式下就無法進行復制貼上操作。後來查了一下原因,發現原來在開發模式下,electron會置入預設的一些快捷操作選單,如圖:

Electron-vue開發實戰1——Main程式和Renderer程式的簡單開發

所以在生產模式如果我沒有置入這些快捷鍵的話,使用者就無法使用了。這個是大坑

說了這麼多,來看看生成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指定你想要的快捷鍵。諸如ShiftCtrlCmd等鍵位縮寫。如果是組合鍵,就加上+。尤其注意到,因為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程式的開發。

Renderer程式開發

對於electron-vue而言,renderer程式其實大部分就是在寫我們平時常寫的前端頁面罷了。不過相對於平時在瀏覽器裡寫的頁面,在electron裡寫頁面的時候你還能用到不少非瀏覽器端的模組,比如fs,比如electron通過remote模組暴露給renderer程式的模組。接下去我們來看看renderer程式有哪些需要注意的地方。

請使用Hash模式

往常我們在寫Vue的時候都比較喜歡開啟路由的history模式,因為這樣在瀏覽器的位址列上看起來比較好看——沒有hash的#號,就如同請求後端的url一般。然而需要注意的是,history模式需要後端伺服器的支援。

可能很多朋友平時開發的時候沒有感覺,那是因為vue-cli裡在開發模式下啟動的webpack-dev-server幫你實現了服務端的history-fallback的特性。所以在實際部署的時候,至少都需要在你的web伺服器程式諸如nginxapache等配置相關的規則,讓前端路由返回給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`
複製程式碼

實現自己的titlebar

在上面講BrowserWindow的時候,我說到有時為了應用的美觀,並不想讓我們的應用視窗採用系統預設的titlebar,而想用自己寫的來實現。這樣的話就在建立你的BrowserWindow的配置里加上一句

titleBarStyle: 'hidden'複製程式碼

這樣就行了。然後你就可以自行在renderer程式的頁面裡模擬一個頂部的titlebar了,比如上面提到的PicGotitlebar的樣子。實際上程式碼也很簡單:

<
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了。

drag&
drop的避免

通常我們用Chrome的時候,有個特性是比如你往Chrome裡拖入一個pdf,它就會自動用內建的pdf閱讀器開啟。你往Chrome裡拖入一張圖片,它就會開啟這張圖片。由於我們的electron應用的BrowserWindow其實內部也是一個瀏覽器,所以這樣的特性依然存在。而這也是很多人沒有注意的地方。也就是當你開發完一個electron應用之後,往裡拖入一張圖片,一個pdf等等,如果不是一個可拖拽區域(比如PicGo的上傳區),那麼它就不應該開啟這張圖、這個pdf,而是將其排除在外。

所以我們將在全域性監聽dragdrop事件,當使用者拖入一個檔案但是又不是拖入可拖拽區域的時候,應該將其遮蔽掉。因為所有的頁面都應該要有這樣的特性,所以我寫了一個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模組的使用

remote模組是electron為了讓一些原本在Main程式裡執行的模組也能在renderer程式裡執行而建立的。以下說幾個我們會用到的。

electron-vue裡內建了vue-electron這個模組,可以在vue裡很方便的使用諸如this.$electron.remote.xxx來使用remote的模組。

shell

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的用法可以參考文件

dialog

有的時候我們會有開啟原生的對話方塊的需求。比如PicGo的版本資訊:

macOS

Electron-vue開發實戰1——Main程式和Renderer程式的簡單開發

windows

Electron-vue開發實戰1——Main程式和Renderer程式的簡單開發

這個時候就可以通過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可能很多人能夠理解。但是為什麼要使用BrowserWindow呢?因為需要定位你開啟Menu的視窗。

在PicGo裡,有一個點選按鈕開啟Menu的操作,大致如下:

buildMenu () { 
const template = [...] this.menu = Menu.buildFromTemplate(template)
}, openDialog () {
this.menu.popup(remote.getCurrentWindow) // 獲取當前開啟Menu的視窗
}複製程式碼

這裡的menu.popup就需要你指定一下開啟這個menu的視窗。它將自動定位你點選的位置而彈出。

main程式和renderer程式的通訊

在Vue裡,如果是非父子元件通訊,很常用的是通過Bus Event來實現的。而electron裡的不同程式間的通訊其實也很類似,是通過ipcMainipcRenderer來實現的。其中ipcMain是在main程式裡使用的,而ipcRenderer是在renderer程式裡使用的。

ipcMain和ipcRenderer

官網的例子其實很簡潔明瞭了,我放出來:

// 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

webContents其實是BrowserWindow例項的一個屬性。也就是如果我們需要在main程式裡給某個視窗某個頁面傳送訊息,則必須通過win.webContents.send()方法來傳送。

程式碼大致如下:

// In main processlet win = new BrowserWindow({...
})win.webContents.send('img-files', imgs)複製程式碼
// In renderer processipcRenderer.on('img-files', (event, files) =>
{
console.log(files)
})複製程式碼

所以必須指定要傳送的視窗,才能將資訊準確送達。

總結

本文詳細地講述了electron裡Main程式和Renderer程式的基礎知識和開發相關。很多都是我在開發PicGo的時候碰到的問題、踩的坑。也許文中簡單的幾句話背後就是我無數次的查閱和除錯。內容相比第一篇多了不少,希望這篇文章能夠給你的electron-vue開發帶來一些啟發。文中相關的程式碼,你都可以在PicGo的專案倉庫裡找到,歡迎點個star~希望本文能夠給你帶來幫助,這是我最開心的地方。如果喜歡,歡迎關注我的部落格以及本系列文章的後續進展。

注:文中的圖片除未特地說明之外均屬於我個人作品,需要轉載請私信

來源:https://juejin.im/post/5a5ebb94f265da3e32458205

相關文章