本章主要內容:javascript
- 使用Electron的
dialog
模塊實現一個本機打開文件對話框- 促進主進程和渲染器進程之間的通訊
- 將功能從主進程暴露給渲染器進程
- 使用Electron的
remote
模塊從主進程導入功能到渲染器進程- 使用
webContents
模塊將信息從主進程發送到呈現器進程,並使用ipcRenderer
模塊爲來自主進程的消息設置監聽器
在前一章中,咱們爲第一個Electron項目打下了基礎,這是一個筆記應用程序,它從左窗格中取出Markdown,並在右窗格中將其呈現爲HTML。咱們設置了主進程並將其配置爲生成一個呈現器。咱們創建了package.json,安裝了必要的依賴項,建立了主進程和呈現器進程,並佈置了UI。咱們還探索了使咱們的應用程序看起來像桌面應用程序的方法,可是咱們尚未添加一個傳統web應用程序所不能作的功能。html
如今,應用程序容許用戶在Markdown視圖中編寫。當用戶在Markdown視圖中按下一個鍵,應用程序將自動呈現Markdown爲HTML並在HTML視圖中顯示它。java
在本章中,咱們將添加觸發本機文件對話框的功能,並從文件系統上的任何位置選擇文本文件並將其加載到應用程序中。在這章的最後,渲染進程的瀏覽器窗口中的「打開文件」按鈕將從主進程觸發「打開文件」對話框。在此以前,有必要更深刻地討論一下如何在進程之間進行通訊。咱們從第3章的分支開始,能夠在第三章代碼找到它。本章末尾的代碼能夠在第四章代碼-使用本機文件對話框和幫助進程間溝通中找到。或者,您能夠下拉主分支並檢出這兩個分支中的任何一個。node
git clone https://github.com/sanshengshui/AUG
git checkout -f 第4章-使用本機文件對話框和幫助進 程間通信
複製代碼
開始的一個簡單方法是,當應用程序第一次啓動併發出ready事件時,提示用戶打開一個文件,如圖4.1所示。在建立BrowserWindow實例以前,應用程序已經在偵聽ready事件。本章稍後,咱們將學習如何從UI觸發此功能。在下一章中,咱們還將學習如何從應用程序菜單中觸發它。git
圖4.1 咱們的應用程序將在啓動時觸發「打開文件」對話框。到本章結束時,此功能將被從UI觸發對話框的功能所取代。github
您可使用Electron dialog
模塊建立本機對話框。將清單4.1中的代碼添加到app/main.js
中,就在須要其餘Electron模塊的地方。web
列表4.1 導入dialog模塊數據庫
const { app, BrowserWindow, dialog } = require('electron');
複製代碼
最終,應用程序能從多個位置觸發文件打開功能。第一步是建立一個稍後要引用的函數,首先,將選擇的文件名稱打印到控制檯。json
列表4.2 建立一個getFileFromUser()函數: ./app/main.js數組
const getFileFromUser = () => {
const files = dialog.showOpenDialog({ //觸發操做系統的OpenFile對話框。咱們還將不一樣配置參數的JavaScript對象傳遞給函數。
properties: ['openFile'] //配置對象在「打開文件」對話框中設置不一樣的屬性。
});
if (!files) { return; } //若是沒有任何文件,請儘早從函數中返回。
console.log(files); //將文件打印到控制檯
};
複製代碼
咱們的getFileFromUser()
函數是dialog.showOpenDialog()
的一個包裝器,咱們能夠在應用程序的多個地方使用而無需重複。它將觸發dialog上的showOpenDialog()
方法,並傳遞一個JavaScript對象,該對象具備不一樣的設置,咱們能夠根據須要進行調整。在JavaScript中,對象的鍵稱爲其屬性。咱們正在建立的對話框的某些特性須要傳遞給dialog.showOpenDialog()
配置的對象屬性。其中一個設置是對話框自己的屬性,配置對象上的properties屬性接受咱們能夠在對話框上設置的不一樣標誌的數組。在本例中,咱們只激活openFile
標誌,它表示此對話框用於選擇要打開的文件,而不是選擇多個目錄或多個文件。其餘可用的標誌是openDirectory
和multiselection
。
dialog.showOpenDialog()
返回所選文件的名稱,用戶選擇的路徑數組存儲在名爲files的變量中。若是用戶按下取消,若是咱們試圖在未定義的狀況下調用文件的任何方法,dialog.showOpenDialog()
將返回未定義的並中斷。
必須在應用程序的某個地方調用getFileFromUser()
來觸發對話框。最終,它將從UI和應用程序菜單中調用。如今,一個方便的地方是應用程序中啓動時,當應用程序模塊觸發它的ready事件時調用getFileFromUser()
。以下面的清單所示,當咱們的UI被配置爲從渲染器進程中觸發getFileFromUser()
時,這個步驟將被刪除。
列表4.3 在應用程序第一次準備好時調用getFileFromUser()
app.on('ready', () => {
mainWindow = new BrowserWindow({ show: false });
mainWindow.loadFile('app/index.html');
mainWindow.once('ready-to-show', () => {
mainWindow.show();
getFileFromUser(); //當窗口準備顯示時,咱們將調用getFileFromUser(),getFileFromUser()在清單4.2中定義
});
mainWindow.on('closed', () => {
mainWindow = null;
});
});
複製代碼
當咱們的應用程序啓動並徹底加載窗口時,用戶將當即看到一個文件對話框,這將容許他們選擇一個文件(參見圖4.2)。咱們最終從啓動過程當中刪除這個函數調用,並將其分配給UI中的"Open File"按鈕。
圖4.2 Electron可以在其支持的每一個操做系統中觸發本機文件對話框。
在圖4.3中,咱們能夠在終端中顯示的"Open File"對話框中看到選擇的結果。注意dialog.showOpenDialog()
返回一個數組。若是在對話框的屬性數組中激活多重選擇,用戶能夠選擇 多個文件。爲了一致性,Electron老是返回一個數組。
圖4.3 選擇文件後,文件的完整路徑將被記錄到終端窗口中的控制檯。
dialog.showOpenDialog()
返回一個數組,其中包含用戶選擇的文件的路徑,但它並不表明咱們閱讀這些文件。根據構建的文件類型,咱們可能但願以不一樣的方式處理打開文件。在這個應用程序中,文件的內容被讀取並當即顯示在UI中。當用戶選擇文件時,處理複製圖像或將圖像上載到外部服務的不一樣應用程序可能採用相反的方法。另一個應用程序可能會在播放列表中添加一個大的電影供之後觀看。在這種狀況下,當即打開大文件是浪費時間。
Node提供了一組用於處理其標準庫中的文件的工具。內置的fs
庫處理常見的文件系統操做,好比讀取和寫入文件,因此應該要求它位於app/main.js的頂部。
列表 導入Node的fs模塊: ./app/main.js
const { app, BrowserWindow, dialog } = require('electron');
const fs = require('fs'); //引入Node fs庫
app.on( 'ready', ()=> {...}); // 爲清楚起見省略了代碼。
const getFileFromUser = () => {
const files = dialog.showOpenDialog(mainWindow, {
properties: ['openFile']
});
if (!files) {return;}
const file = files[0]; //從數組中取出第一個文件
const content = fs.readFileSync(file).toString(); //從文件中讀取,並將生成的緩衝區轉換爲字符串。
console.log(content);
}
複製代碼
在清單4.4中,應用程序一次只打開一個文件。files[0]
從dialog.showOpenDialog()
中選擇數組中的第一個和惟一文件路徑。在fs.readFileSync(file)
中,文件路徑做爲參數傳遞給fs.readFileSync()
。Node不知道打開了什麼類型的文件,因此fs.readFileSync()
返回一個緩衝區對象。可是,咱們知道,在這個特定的應用程序中,咱們一般使用純文本。咱們將它轉換爲一個字符串,並將文件的內容記錄到終端,如圖4.4所示。
圖4.4 文件的內容被記錄到用戶的終端。
如圖4.4所示,getFileFromUser()成功地將文本文件的內容記錄到終端。但有一個問題,默認狀況下,dialog.showOpenDialog()容許咱們打開計算機上的任何文件,而不考慮準備處理什麼類型的文件。圖4.5顯示了經過對話框打開圖像文件而不是文本文件時的問題結果。
圖4.5 若是用戶選擇非文本文件,函數將記錄二進制數據。
許多桌面應用程序能夠限制用戶能夠打開的文件類型,這也適用於用Electron構建的應用程序。咱們的應用程序不適合打開音樂文件,因此咱們可能不該該讓用戶選擇mp3。能夠將其餘選項添加到傳遞給dialog.showOpenDialog()
的配置對象中,以將對話框限制爲咱們白名單中的文件擴展名。
列表4.5 白名單特定的文件類型: ./app/main.js
const getFileFromUser = exports.getFileFromUser = () => {
const files = dialog.showOpenDialog({
properties: ['openFile'],
filters: [ //filters屬性容許咱們指定應用程序應該可以打開那些類型的文件,並禁止不符合咱們標準的任何文件。
{ name: 'Text Files', extensions: ['txt'] },
{ name: 'Markdown Files', extensions: ['md', 'markdown'] }
]
});
if (!files) { return; }
const file = files[0];
const content = fs.readFileSync(file).toString();
console.log(content);
};
複製代碼
在清單中,咱們向傳遞給dialog.showOpenDialog()
的對象添加了第二個屬性。在Windows中,對話框在下拉框中Markdown文件的名稱,如圖4.6所示。在macOS中,沒有下拉菜單,可是咱們不能選擇沒有任何擴展的圖像,如圖4.7所示。
Electron應用被設計成跨平臺的,者意味着它們能夠再macOS、Windows和Linux上運行。Electron提供了與本地特性和APIs,這些特性和APIs存在於每一個支持的操做系統中,但不存在於其餘操做系統中。咱們在前面爲文件擴展名過濾器提供名稱時就看到了這一點,這個名稱出如今Windows中,可是macOS沒有這個功能。Electron利用了這個特性,若是它是可用的,但它仍然在沒有的狀況下工做。
在macOS中,咱們可以從窗口頂部從表格的形式顯示對話框,而不是顯示在窗口前面(清單4.6)。經過在配置對象以前傳遞對BrowserWindow
實例的引用(咱們已經將其存儲在mainWindow
中)做爲dialog.showOpenDialog()
的第一個參數,咱們能夠輕鬆地在Electron中建立這個UI。
圖4.6 在Windows中,咱們能夠在不一樣類型的文件之間切換。
圖4.7 macOS不支持在不一樣類型的文件之間切換,但容許咱們選擇filter選項定義的任何符合條件的文件。
列表4.6 在macOS中建立工做表對話框: ./app/main.js
const getFileFromUser = exports.getFileFromUser = () => {
const files = dialog.showOpenDialog(mainWindow, { // 傳遞對BrowserWindow實例的引用對話框。showOpenDialog將致使macOS將對話框顯示爲從窗口標題欄向下的工做表。它對Windows和Linux沒有影響。
properties: ['openFile'],
filters: [
{ name: 'Text Files', extensions: ['txt'] },
{ name: 'Markdown Files', extensions: ['md', 'markdown'] }
]
});
if (files) { return; }
const file = files[0];
const content = fs.readFileSync(file).toString();
console.log(content);
};
複製代碼
經過這個簡單的更改,Electron如今將Open File對話框顯示爲一個工做表,該工做表從傳遞給方法的窗口下拉,如圖4.8所示。
圖4.8 在macOS中,打開文件對話框如今從菜單的標題欄下拉,而不是做爲應用程序窗口前面的附加窗口出現。
咱們已經編寫了用於在主進程中選擇和讀取文件的全部代碼。可是咱們如何將文件的內容發送到渲染器進程呢?如何從UI中觸發主進程中的getFileFromUser()
函數?
在構建傳統web應用程序時,咱們必須處理相似的問題。這並不徹底相同,由於全部的代碼都在客戶機的計算機上運行,可是考慮一下咱們一般如何構建web應用程序,能夠做爲理解如何構造Electron應用程序的一個有用的比喻。 參見圖4.9。
圖4.9 Electron與傳統web應用程序的職責劃分
在web上,咱們一般在如下兩個地方編寫代碼: 在服務器上或在用戶瀏覽器中運行的客戶端代碼。客戶端代碼呈現UI,它監聽並處理用戶操做,並更新UI以顯示應用程序的當前狀態。然而,咱們對客戶端代碼所能作的事件是有限制的。正如咱們在第一章中討論的,咱們不能讀取數據庫或文件系統。服務端代碼在咱們的計算機上運行,它能夠訪問數據庫,它能夠寫入咱們系統上的日誌文件。
在傳統的web應用程序中,咱們一般使用HTTP之類的協議來促進客戶機和服務端進程之間的通訊。使用HTTP,客戶機能夠發送帶有信息的請求,服務器接受此請求,適當地處理它,並向客戶機發送響應。
在Electron應用程序中,狀況有些不一樣。正如咱們在前幾章中討論過的,Electron應用由多個進程組成: 一個主進程和一個或多個渲染進程。全部東西都在咱們的計算機上運行,可是角色的分離與客戶機-服務器模型相似。咱們不使用HTTP在進程之間通訊。相反,Electron提供了幾個模塊來協調主進程和渲染進程之間的通訊。
咱們的主進程負責與本機操做系統APIs進行鏈接,它負責生成渲染器進程、定義應用程序菜單和顯示打開和保存對話框、註冊全局快捷方式、從操做系統請求電源信息、以及更多。執行這些任務所需的模塊在Electron僅在主進程中可用來實現這一點,如圖4.10所示。
圖4.10 Electron提供不一樣的模塊給主進程和渲染進程。這些模塊表明了Electron的代碼功能,到您閱讀本文時,這個列表可能還會增加,而且可能還不完整。我鼓勵您訪問文檔以查看最新的和最棒的特性。
Electron只向每一個進程提供其模塊的一個子集,而不保留咱們訪問與Electron模塊分離的Node的APIs。若是願意,咱們能夠從渲染器進程訪問數據庫和文件系統,可是有一些使人信服的理由將這種功能保留在主進程中。咱們可能有不少渲染器進程,可是咱們老是隻有一個主進程。從咱們的衆多的渲染器讀取和寫入文件系統可能會出現問題;一個或多個進程試圖同時寫入同一個文件,或者從一個文件中讀取,而另外一個渲染器進程正在重寫該文件。
JavaScript中的一個給定進程在一個線程上執行咱們的代碼,而且一次只能作一件事。經過將這些任務委託給主進程,咱們能夠確信一次只有一個進程執行對給定文件或數據庫的讀寫。其餘任務遵循正常的JavaScript協議,在事件隊列中耐心等待,直到主進程完成當前任務。
主進程處理調用本機操做系統APIs或提供文件系統訪問的任務是有意義的,可是觸發這些操做的UI在渲染器進程中調用。即便全部的代碼都在同一臺計算機上運行,咱們仍然須要協調進程之間的通訊,由於咱們必須協調客戶機和服務器之間的通訊。
最近,出現了WebSockets和WebRTC等協議,它們容許客戶機和服務器之間的雙向通訊,甚至客戶機之間的通訊,而不須要中央服務器來促進通訊。當咱們構建桌面應用程序時,咱們一般不會使用HTTP或WebSockets,可是Electron有幾種協調進程間通訊的方法,咱們將在本章開始探討,如圖4.11所示。
圖4.11 實現打開文件按鈕涉及協調渲染器進程和主進程。
咱們的UI包含一個帶有標籤Open File的按鈕。當用戶單擊此按鈕時,咱們的應用程序應該提供一個對話框,容許用戶選擇要打開的文件。在用戶選擇一個文件以後,咱們的應用程序應該讀取文件的內容,在應用程序的左窗格中顯示它們,並在右窗格中呈現相應的HTML。
正如您可能已經猜到的,這須要咱們在二者之間進行協調渲染器進程(單擊按鈕的地方)和主進程(負責顯示對話框並從文件系統中讀取所選文件)。讀取文件以後,主進程須要將文件的內容發送回渲染器進程(下一個清單),以便分別在左窗格和右窗格中顯示和呈現。
列表4.7 在渲染器進程中添加事件監聽器
const marked = require('marked');
const markdownView = document.querySelector('#markdown');
const htmlView = document.querySelector('#html');
const newFileButton = document.querySelector('#new-file');
const openFileButton = document.querySelector('#open-file');
const saveMarkdownButton = document.querySelector('#save-markdown');
const revertButton = document.querySelector('#revert');
const saveHtmlButton = document.querySelector('#save-html');
const showFileButton = document.querySelector('#show-file');
const openInDefaultButton = document.querySelector('#open-in-default');
const renderMarkdownToHtml = (markdown) => {
htmlView.innerHTML = marked(markdown, { sanitize: true });
};
markdownView.addEventListener('keyup', (event) => {
const currentContent = event.target.value;
renderMarkdownToHtml(currentContent);
});
openFileButton.addEventListener('click', () => { //選擇一個更新的CSS框模型,它將正確地設置元素的寬度和高度
alert('You clicked the "Open File" button.');
});
複製代碼
首先將事件監聽器添加到渲染器進程中的Open File按鈕。有了事件監聽器,就能夠與主進程協調,觸發前面建立的Open File對話框。
Electron提供了許多方便進程間通訊的方法。第一個是remote
模塊-一種從渲染器進程到主進程執行進程間通訊的簡單方法。 remote
模塊(僅在呈現器進程中可用)經過鏡像主進程中可訪問的模塊,充當主進程的代理。當咱們訪問任何這些屬性時,遠程模塊還負責與主進程之間的通訊。
如圖4.12所示,remote
模塊有幾個屬性,這些屬性與僅對主進程可用的模塊重疊。在咱們的渲染器進程中,咱們能夠引用remote
模塊,它提供了對主進程中的對象和屬性的訪問,如圖4.13所示。
圖4.12
remote
模塊提供對一般僅對主進程可用的模塊的訪問。
圖4.13
remote
模塊提供對一般僅對主進程可用的模塊的訪問。
當咱們調用remote
對象上的方法或屬性時,它向主進程發送同步消息,在主進程中執行,並將結果發送回渲染器進程。remote
模塊容許咱們在主進程中定義功能,而且很容易使其對渲染器進程可用。
Open File
函數應用程序如今能夠觸發「Open File」對話框並讀取用戶在主進程中選擇的文件。咱們還向進程中的Open File按鈕添加了一個事件監聽器。如今只須要使用咱們前面討論過的進程間通訊技術將它們鏈接起來。
經過remote
模塊使用主進程的功能,咱們須要利用Node的CommonJS模塊系統嚮應用程序中的其餘文件公開該功能。在本書中,咱們使用了require
從Electron,Node標準庫和第三方庫中提取功能,但這是咱們第一次將其與咱們的代碼一塊兒使用。讓咱們花幾分鐘回顧一下它是如何工做的。
Node的模塊系統由2個主要的方法所組成:從其餘來源獲取功能的能力,以及導出功能供其餘來源使用的能力。當咱們須要來自其餘資源的代碼時,其餘資源能夠是咱們編寫的文件、一個第三方模塊、一個Node模塊或Electron提供的模塊。咱們在主進程和渲染進程的頂部都使用了Node的內置requrie
函數
當咱們須要一個模塊時,咱們究竟要導入什麼?在Node中,咱們顯式地聲明應該從模塊導出什麼功能,如清單4.8所示。這個函數在清單4.9中導入,Node中的每一個模塊都有一個名爲exports
的內置對象,它從一個空對象開始。當咱們從另外一個文件中須要導出對象時,添加到導出對象的任何內容都是可用的。
清單4.8 在Node導出一個函數: basic-math.js
exports.addTwo = n => n + 2;
複製代碼
清單4.9 在Node導入一個函數
const basicMath = require('./basic-math');
basicMath.addTwo(4); //返回6
複製代碼
內置的require
函數不能跨進程工做。當咱們在渲染器進程中工做時,咱們使用內置的require
函數導入的任何功能都將是渲染器進程的一部分。當咱們在主進程中工做時,咱們須要的任何功能都將是主進程的一部分。可是當咱們在渲染器進程中想要從主進程中得到功能時,會發生什麼呢?
Electron的remote
模塊有它本身的require
方法,在咱們的渲染器進程中容許它從主進程獲取功能。使用remote.require
返回代理對象—相似於遠程對象上的其餘屬性。Electron表明咱們負責全部的進程間通訊。
要實現本章開頭所述的功能,主進程必須導出它的getFileFromUser()
函數,以便咱們能夠將它導入到渲染器進程代碼中。這個清單更新了app/main.js中的一行。
清單4.10 從渲染器進程中導出打開文件對話框的功能: ./app/main.js
const getFileFromUser = exports.getFileFromUser = () => { //除了在這個文件中建立一個常量外,咱們還將它指定爲exports對象的一個屬性,該屬性能夠從其餘文件(特別是渲染器進程)訪問。
const files = dialog.showOpenDialog(mainWindow, {
properties: ['openFile'],
filters: [
{ name: 'Text Files', extensions: ['txt'] },
{ name: 'Markdown Files', extensions: ['md', 'markdown'] }
]
});
if (files) { returun; }
const file = files[0];
const content = fs.readFileSync(file).toString();
console.log(content);
};
複製代碼
代碼接受咱們建立的getFileFromUser()
函數,並將其導出爲exports對象上具備相同名稱的屬性。渲染進程須要引入Electron的 remote
模塊,而後使用remote.require
。從渲染器進程的主進程獲取對getFileFromUser()
函數的引用。這與清單4.11中內置的require函數不一樣,由於導入的代碼是根據主進程計算的,而不是根據引入它的渲染器進程計算的。這須要四個步驟:
在渲染器進程中須要Electron。
存儲對remote
的引用。
使用remote.require
請求主進程。
存儲從主進程導出的getFileFromUser()
函數的應用。
列表4.11 渲染器進程中須要主進程的功能: ./app/renderer.js
const { remote } = require('electron');
const mainProcess = remote.require('./main.js');
複製代碼
如今,咱們能夠在渲染器進程中調用從主進程導出getFileFromUser()
函數。讓咱們替換事件監聽器中的功能,以觸發Open File對話框,而不是觸發警報。
列表4.12 從UI觸發主進程中的getFileFromUser(): ./app/ renderer.js
openFileButton.addEventListener('click', () => {
mainProcess.getFileFromUser();
});
複製代碼
若是咱們啓動Electron應用程序並單擊「Open File」按鈕,它將正確地觸發「打開文件」對話框。有了這些,咱們仍然只將文件記錄到主進程中的控制檯。爲了完成咱們的特性,主進程必須將文件的內容發送回呈現器進程,以便在咱們的應用程序中顯示。
remote
模塊促進了渲染器進程訪問主進程的能力,可是它不容許主進程訪問渲染器進程。要將用戶選擇的文件內容發送回要在UI中呈現的渲染器進程的話,咱們須要學習進程之間通訊的另外一種技術。
每一個BrowserWindow
實例都有一個名爲webContents
的屬性,它存儲一個對象,該對象負責在調用new BrowserWindow()
時建立的web瀏覽器窗口。webContents
與app
相似,由於它在渲染器進程中根據web頁面的生命週期發出事件。
如下是一些不完整的事件列表,你能夠在webContents
對象上監聽:
webContents
還有許多方法,能夠在渲染器進程中觸發與主進程不一樣的函數。在前一章中,咱們經過主進程使用mainWindow.webContents.openDevTools()
在渲染器進程中打開了Chrome開發工具。mainWindow.loadURL('file://${__dirname}/ index.html')
是mainWindow.webContents.loadURL()
的別名,它在應用程序首次啓動時將HTML文件加載到渲染器進程中。圖4.14顯示了更多的別名。
圖4.14 BrowserWindow實例的方法是Electron webContents API的別名。
webContents
有一個名爲send()
的方法,它將信息從主進程發送到渲染器進程。webContents.send()
接受可變數量的參數。第一個參數是用來發送消息的通道的名稱,它是一個任意字符串。渲染器進程中的事件監聽器在同一通道上監聽。當咱們看到它的行動時,這種流動將變得更加清晰。第一個參數以後的全部後續參數都傳遞給渲染器進程。
咱們當前實現是讀取用戶選擇的文件並打印到終端上,mainWindow.webContents.send()
將文件的內容發送到渲染器進程中。下一章將介紹打開文件的其餘方法,這些方法不須要一個對話框來提示用戶選擇特定的文件,由於咱們確實會遇到一些狀況,在不觸發對話框的狀況下打開文件。
列表4.13 從主進程發送內容到渲染器進程: ./app/main.js
const getFileFromUser = exports.getFileFromUser = () => {
const files = dialog.showOpenDialog(mainWindow, {
properties: ['openFile'],
filters: [
{ name: 'Text Files', extensions: ['txt'] },
{ name: 'Markdown Files', extensions: ['md', 'markdown'] }
]
});
if (files) { openFile(files[0]); } // 在前面,在文件未定義的狀況下,使用return語句中斷了函數。在本例中,當dialog.showOpenFile()成功返回一個文件路徑數組時,咱們將調整邏輯並將第一個文件傳遞給Open File。
};
const openFile = (file) => {
const content = fs.readFileSync(file).toString();
mainWindow.webContents.send('file-opened', file, content); // 咱們將經過"file-opened"通道將文件的名稱及其內容發送到渲染器進程
};
複製代碼
主進程如今經過打開的文件file-opened
通道廣播文件的名稱及其內容。下一步是使用ipcRenderer
模塊在渲染器進程中file-opened
通道上設置監聽器。Electron提供了兩個基本模塊,用於在進程之間來回發送消息: ipcRenderer
和ipcMain
。每一個模塊僅在與之共享名稱的進程類型中可用。
ipcRender
能夠向主進程發送消息,最重要的是,它還能夠監聽使用webContents.send()
從主進程發送的消息。它在渲染器進程中須要ipcRenderer
模塊。
列表4.14 導入
ipcRenderer
模塊: ./app/renderer.js
const { remote, ipcRenderer } = require('electron'); //將在咱們的渲染器進程中導入ipcRenderer模塊
const mainProcess = remote.require('./main.js')
複製代碼
有了這些,咱們如今能夠設置一個監聽器。ipcRenderer
監聽file-opened
通道,將內容添加到頁面,並將Markdown渲染爲HTML。
列表4.15 在
file-opened
通道上監聽消息
ipcRenderer.on('file-opened', (event, file, content) => {
markdownView.value = content;
renderMarkdownToHtml(content);
});
複製代碼
ipcRenderer.on
接受兩個參數:要監聽的參數和一個回調函數,回調函數定義當渲染器進程在設置監聽器的通道上接受到消息時要採起的操做。回調函數在調用時提供幾個參數,第一個是事件對象,它與瀏覽器中的普通事件監聽器同樣。它包含關於咱們爲其設置監聽器事件的消息,其餘參數是在主進程中使用webContents.send()
時提供的。在清單4.13中,咱們發送了文件的名稱及其內容,這些將是傳遞給監聽器的附加參數。
有了這些新增功能,用戶如今能夠單擊Open File按鈕,使用本機文件對話框選擇一個文件,並在UI中呈現內容。咱們已經成功地實現了咱們在本章開始時設定的特性,咱們的主進程和渲染進程的代碼應該相似於如下兩個清單。
列表4.16 在主進程實現打開文件的功能: ./app/main.js
const{ app, BrowserWindow,dialog } = require('electron');
const fs = require('fs');
let mainWindow = null;
app.on('ready', () => {
mainWindow = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true
}
})
mainWindow.loadFile('app/index.html');
mainWindow.once('ready-to-show', () => {
mainWindow.show();
});
mainWindow.on('closed', () => {
mainWindow = null;
});
});
const getFileFromUser = exports.getFileFromUser = () => {
const files = dialog.showOpenDialog(mainWindow, {
properties: ['openFile'],
filters: [
{ name: 'Text Files', extensions: ['txt'] },
{ name: 'Markdown Files', extensions: ['md', 'markdown'] }
]
});
if (files) { openFile(files[0]); }
};
const openFile = (file) => {
const content = fs.readFileSync(file).toString();
mainWindow.webContents.send('file-opened', file, content);
};
複製代碼
列表4.17 打開文件功能實現: ./app/renderer.js
const { remote, ipcRenderer } = require('electron');
const mainProcess = remote.require('./main.js')
const marked = require('marked');
const markdownView = document.querySelector('#markdown');
const htmlView = document.querySelector('#html');
const newFileButton = document.querySelector('#new-file');
const openFileButton = document.querySelector('#open-file');
const saveMarkdownButton = document.querySelector('#save-markdown');
const revertButton = document.querySelector('#revert');
const saveHtmlButton = document.querySelector('#save-html');
const showFileButton = document.querySelector('#show-file');
const openInDefaultButton = document.querySelector('#open-in-default');
const renderMarkdownToHtml = (markdown) => {
htmlView.innerHTML = marked(markdown, { sanitize: true });
};
markdownView.addEventListener('keyup', (event) => {
const currentContent = event.target.value;
renderMarkdownToHtml(currentContent);
});
openFileButton.addEventListener('click', () => {
mainProcess.getFileFromUser();
});
ipcRenderer.on('file-opened', (event, file, content) => {
markdownView.value = content;
renderMarkdownToHtml(content);
});
複製代碼
fs
模塊來讀寫文件系統。dialog. showopendialog()
中提供對該窗口的引用做爲第一個參數,使對話框從其中一個窗口做爲工做表下拉。remote
模塊爲主進程模塊和函數提供代理,並使該功能在渲染器進程中可用。webContents.send ()
命令將消息從主進程發送到渲染器進程。ipcRenderer
模塊監聽主進程發送渲染器進程的消息。file-opened
的通道發送和偵聽消息。