[轉(zhuǎn)載自http://get.ftqq.com/7870.get]
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8">
<meta property="wb:webmaster" content="6fb2b4de5a6b35cc" />
<link rel="icon"
type="image/png"
href="/static/image/get32px.png">
<title>用Electron開發(fā)桌面應(yīng)用 | @Get社區(qū)</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="/static/css/app.css?v=2014" type="text/css" >
<link rel="stylesheet" href="/static/css/jdc.icon.css?v=2014" type="text/css" >
<link rel="stylesheet" href="/static/css/jquery.jscrollpane.css" type="text/css" >
<link rel="stylesheet" href="/static/css/../components/jquery.atwho/dist/css/jquery.atwho.min.css" type="text/css" >
<link rel="stylesheet" type="text/css" >
<link rel="stylesheet" type="text/css" href="/static/css/yue.css">
<link rel="stylesheet" type="text/css" href="/static/css/article-btn.css">
<link rel="stylesheet" type="text/css" href="/static/css/imgshare.css">
<script src="http://cdn.staticfile.org/jquery/2.2.1/jquery.min.js"></script>
<script src="/static/script/bootstrap.min.js"></script>
<script src="/static/script/jquery.jscrollpane.min.js"></script>
<script src="/static/script/jquery.mousewheel.js"></script>
<script src="/static/script/../components/notifyjs/dist/notify-combined.min.js"></script>
<script src="/static/script/../components/jquery.onoff/dist/jquery.onoff.min.js"></script>
<script src="/static/script/../components/jquery.atwho/dist/js/jquery.atwho.min.js"></script>
<script src="/static/script/../components/Caret.js/dist/jquery.caret.min.js"></script>
<script src="/static/script/../components/jquery-waypoints/waypoints.min.js"></script>
<script src="http://cdn.staticfile.org/highlight.js/9.0.0/highlight.min.js"></script>
<script src="/static/script/app.js"></script>
<script type="text/javascript" src="/static/script/jquery.imgshare.js" ></script>
<script type="text/javascript" src="/static/script/timeago.js" ></script>
<script type="text/javascript" src="/static/script/locales/timeago.zh-cn.js" ></script>
<script>
window.zhuge = window.zhuge || [];
window.zhuge.methods = "_init debug identify track trackLink trackForm page".split(" ");
window.zhuge.factory = function(b) {
return function() {
var a = Array.prototype.slice.call(arguments);
a.unshift(b);
window.zhuge.push(a);
return window.zhuge
}
};
for (var i = 0; i < window.zhuge.methods.length; i++) {
var key = window.zhuge.methods[i];
window.zhuge[key] = window.zhuge.factory(key)
}
window.zhuge.load = function(b, x) {
if (!document.getElementById("zhuge-js")) {
var a = document.createElement("script");
var verDate = new Date();
var verStr = verDate.getFullYear().toString()
- verDate.getMonth().toString() + verDate.getDate().toString();
a.type = "text/javascript";
a.id = "zhuge-js";
a.async = !0;
a.src = (location.protocol == 'http:' ? "http://sdk.zhugeio.com/zhuge-lastest.min.js?v=" : 'https://zgsdk.zhugeio.com/zhuge-lastest.min.js?v=') + verStr;
var c = document.getElementsByTagName("script")[0];
c.parentNode.insertBefore(a, c);
window.zhuge._init(b, x)
}
};
window.zhuge.load('e1189bf28dd14a39a2f72b3a47abc3b7');
</script>
</head>
<body id="body">
<div class="navbar navbar-fixed-top navbar-get" role="navigation" id="theheader">
<div class="container clearfix">
<a class="logo" href="/?c=default">
<span class="get">Get</span>社區(qū)</a> <div class="new_message_notice"></div>
<div class="navbar-collapse collapse pull-right">
<ul class="get-cate-nav">
<li class="dropdown active ">
<button class="btn btn-getnar dropdown-toggle" type="button" onclick="location='/?c=default'">
新知
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1">
<li class=""><a href="/">全部</a></li>
<li class="" role="presentation"><a href="/?cate=1" role="menuitem" tabindex="-1">技術(shù)</a></li>
<li class="" role="presentation"><a href="/?cate=2" role="menuitem" tabindex="-1">設(shè)計</a></li>
<li class="" role="presentation"><a href="/?cate=3" role="menuitem" tabindex="-1">產(chǎn)品</a></li>
<li class="" role="presentation"><a href="/?cate=4" role="menuitem" tabindex="-1">創(chuàng)業(yè)</a></li>
<li class=""><a href="/?a=kb">我的</a></li>
<li class=""><a href="/?a=feed">我關(guān)注的</a></li>
<li class="" ><a href="/?a=submit" target="_blank">+添加新知</a></li>
</ul>
</li>
<li class="dropdown ">
<button class="btn btn-getnar dropdown-toggle" type="button" onclick="location='/?c=card'">
卡片流
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1">
<li class=""><a href="/?c=card">我的首頁</a></li>
<li class=""><a href="/?c=card&a=explore">隨便看看</a></li>
</ul>
</li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="row row-offcanvas row-offcanvas-right">
<div class="col-xs-12 col-sm-9">
<div class="get-article-area " id="gacontent">
<h2 class="green">用Electron開發(fā)桌面應(yīng)用</h2>
<p class="exp">用JavaScript浮创,Node.js和Eletron創(chuàng)建發(fā)聲器應(yīng)用的詳細指南</p>
<div class="yue top20">
<blockquote><p>本文主要介紹了如何用Electron配合JavaScript等web技術(shù)創(chuàng)建桌面應(yīng)用秘蛔。
</p><p>由 <a target="_blank">Bazzzinga威</a> 同學(xué)翻譯自 Medium。<a >https://medium.com/developers-writing/building-a-desktop-application-with-electron-204203eeb658</a>
</p></blockquote><h1>什么是JavaScript桌面應(yīng)用</h1><p>在我心中磅氨,桌面應(yīng)用一直占據(jù)著一個特殊的地位炼蛤。隨著瀏覽器悯周,移動設(shè)備變得越來越強大闻葵,被移動和web應(yīng)用取代的桌面應(yīng)用呈穩(wěn)定下滑趨勢晃虫。但編寫桌面應(yīng)用還是有很多優(yōu)勢的--它們會一直存在于你的開始目錄或者Dock欄中荠商,可以被alt(cmd)+tab來回切換寂恬,并且大部分比web應(yīng)用與底層系統(tǒng)聯(lián)系的更緊密(快捷鍵,通知推送等)莱没。</p><p>本文中初肉,我會引導(dǎo)你用JavaScript創(chuàng)建一個簡單桌面應(yīng)用,接觸相關(guān)的概念饰躲。</p><p>


<h1>Hello, world!</h1><h2><b>練習(xí)用資料庫</b>
</h2><p>現(xiàn)在,讓我們做好準備浮梢,用傳統(tǒng)的「Hello跛十,World」來開始。</p><p>本指南的同步練習(xí)資料庫是<a target="_blank">sound-machine-tutorial</a>秕硝。首先把資料庫clone下來:</p><p></p><pre class="">git clone <a rel="nofollow">https://github.com/bojzi/sound-machine-electron-guide.git</a></pre><p>進入sound-machine-tutorial文件夾芥映,用下面的代碼在git的tag之間切換:</p><pre class="">git checkout <tag-name></pre><p>我會提示你該用哪個tag:</p><pre class="">請切換至:
git checkout 00-blank-repository</pre><p>當你clone完代碼,切換到你想要的tag远豺,運行:</p><pre class="">npm install</pre><p></p><p>這樣你安裝好全部Node模塊了奈偏。</p><p>如果你不能轉(zhuǎn)換到另一個tag,最簡單的辦法是重置你的資料庫狀態(tài)再切換:</p><p></p><pre class="">git add -A
git reset --hard</pre><p><b>開始</b></p><pre class="">請切換到00-blank-repository這個tag:
git checkout 00-blank-repository</pre><p>在項目文件夾中新建package.json文件躯护,寫入下面的內(nèi)容:</p><p></p><p>這個簡單的package.json文件:</p><ul><li>設(shè)置應(yīng)用的名字和版本號惊来,</li><li>設(shè)置Electron主進程運行的腳本(main.js),</li><li>設(shè)置一個很實用的快捷鍵--在你的CLI(命令行)中可以用「npm start」方便地啟動應(yīng)用棺滞。</li></ul><p>現(xiàn)在該安裝Electron了裁蚁,最簡單的方式是用npm為你的操作系統(tǒng)安裝預(yù)構(gòu)建的二進制文件。并在package.json文件中將它設(shè)置為開發(fā)依賴(用--save-dev命令后綴自動設(shè)置)继准。在CLI中運行命令:</p><pre class="">npm install --save-dev electron-prebuilt</pre><p></p><p>預(yù)構(gòu)建的二進制文件是為所在的操作系統(tǒng)量身訂造的枉证,可以運行「npm start」。我們將它作為開發(fā)依賴安裝是因為只在開發(fā)過程中用到它移必。</p><p></p><p>就這樣刽严,Electron開發(fā)所需的一切都準備好了。</p><h2><b>跟世界打個招呼</b></h2><p>新建app文件夾避凝,在其中新建有下面代碼的index.html文件:</p><pre class=""><h1>Hello, world!</h1></pre><p></p><p>在項目的根目錄下新建一個main.js文件。Electron的主進程將用它來啟動并創(chuàng)建「Hello, world」桌面應(yīng)用眨补。main.js中的代碼:</p><pre class="">'use strict';
var app = require('app');
var BrowserWindow = require('browser-window');
var mainWindow = null;
app.on('ready', function() {
mainWindow = new BrowserWindow({
height: 600,
width: 800
});
mainWindow.loadUrl('file://' + __dirname + '/app/index.html');
});
</pre><p></p><p>沒什么嚇人的管削,不是嗎?</p><p>「app」模塊會控制應(yīng)用的生命周期(例如撑螺, 對應(yīng)用的ready狀態(tài)做出反應(yīng))含思。</p><p>「BrowserWindow」模塊為你創(chuàng)建窗口。</p><p>「mainWindow」對象是你應(yīng)用的主窗口甘晤,被聲明成null含潘,否則當JavaScript垃圾回收掉這個對象時,窗口會被關(guān)閉线婚。</p><p>當「app」捕獲ready事件遏弱,「BrowserWindow」創(chuàng)建一個800*600大小的窗口。</p><p>瀏覽器窗口的渲染進程會渲染index.html文件塞弊。</p><p>在CLI中鍵入下面命令啟動「Hello, World!」:</p><p></p><pre class="">npm start</pre><p>現(xiàn)在為你的第一個Electron程序歡呼吧漱逸。</p><p>

</p>
<p></p><h1>開發(fā)真正的應(yīng)用</h1><h2><b>超棒的發(fā)聲器</b></h2><p>首先泪姨,什么是發(fā)聲器?</p><p>發(fā)聲器當你點擊不同按鈕時會播放不同聲音的小設(shè)備饰抒,大部分是卡通或特效聲肮砾。是在辦公室用來放松心情的,很有趣的工具袋坑,隨著開發(fā)的進行仗处,會碰到的很多新的概念,所以這也是一個很好的開發(fā)桌面應(yīng)用的實例(也是一個非常棒的發(fā)聲器)枣宫。</p><p></p><p>

<h2>實現(xiàn)發(fā)聲器的基礎(chǔ)功能</h2><p><b>應(yīng)用的結(jié)構(gòu)</b></p><p></p><p>你已經(jīng)實現(xiàn)了一個運行正常的「Hello World!」應(yīng)用输吏,現(xiàn)在是時候?qū)崿F(xiàn)一個發(fā)聲器應(yīng)用了权旷。</p><p>典型的發(fā)聲器功能包括幾排按鈕,點擊播放聲音贯溅,這些聲音大部分是卡通式的或者特效式的(如大笑拄氯,鼓掌,玻璃碎裂聲等)它浅。</p><p>那就是我們要完成的第一個功能--能對點擊能做出響應(yīng)的發(fā)聲器译柏。</p><p>

git add -A
git reset --hard</pre><pre class="">切換到01-start-project這個tag:
git checkout 01-start-project</pre><p></p><p>為了簡便,發(fā)聲器將只有兩種聲音影所,但擴展到全部16種聲音也非常簡單蹦肴,只需要其他聲音和圖標文件,修改index.html就可以猴娩。</p><p><b>完成主進程</b></p><p>用main.js定義發(fā)聲器的外觀阴幌。用下面的代碼代替原內(nèi)容:</p><pre class="">'use strict';
var app = require('app');
var BrowserWindow = require('browser-window');
var mainWindow = null;
app.on('ready', function() {
mainWindow = new BrowserWindow({
frame: false,
height: 700,
resizable: false,
width: 368
});
mainWindow.loadUrl('file://' + __dirname + '/app/index.html');
});
</pre><p></p><p></p><p>我們用傳給「app」模塊的尺寸參數(shù),自定義了新建窗口的大小卷中,設(shè)定它是固定尺寸并且無邊欄矛双。它會浮在你的桌面上,就像真的發(fā)聲機一樣蟆豫。</p><p>現(xiàn)在的問題是 -- 如何移動一個沒有邊欄的窗口(沒有標題欄)议忽,如何關(guān)閉它?</p><p>我很快就會講解自定義窗口(應(yīng)用)關(guān)閉(并介紹一種主進程和渲染進程通信的方法)十减,但拖動部分很簡單栈幸,在index.css(app/css文件夾下)文件中:</p><p></p><pre class="">html,
body {
...
-webkit-app-region: drag;
...
}
</pre><p>-webkit-app-region:drag;使整個html變成一個可拖動的對象。現(xiàn)在有一個問題帮辟,你不能點擊可拖動對象里的按鈕速址。答案就是-webkit-app-region: no-drag;能定義不可拖動(但是可以點擊)的對象,參考index.css的中的代碼:</p><pre class="">.button-sound {
...
-webkit-app-region: no-drag;
}
</pre><p><b>在窗口中顯示發(fā)聲器</b></p><p></p><p>main.js文件現(xiàn)在可以新建一個窗口來顯示發(fā)聲器由驹。如果用npm start啟動應(yīng)用逢防,你可以看到發(fā)聲器非常逼真〖酪現(xiàn)在點擊沒有反應(yīng)炉菲,這并不奇怪他嚷,我們只有一個靜態(tài)的web頁面。</p><p>添加下面的代碼到index.js(app/js文件夾)文件中會添加交互效果:</p><p></p><pre class="">'use strict';
var soundButtons = document.querySelectorAll('.button-sound');
for (var i = 0; i < soundButtons.length; i++) {
var soundButton = soundButtons[i];
var soundName = soundButton.attributes['data-sound'].value;
prepareButton(soundButton, soundName);
}
function prepareButton(buttonEl, soundName) {
buttonEl.querySelector('span').style.backgroundImage = 'url("img/icons/' + soundName + '.png")';
var audio = new Audio(__dirname + '/wav/' + soundName + '.wav');
buttonEl.addEventListener('click', function () {
audio.currentTime = 0;
audio.play();
});
}
</pre><p></p><p>代碼很簡單甥郑,我們:</p><ul><li>查詢所有聲音按鈕渣触,</li><li>遍歷所有的按鈕讀取data-sound屬性,</li><li>給每個按鈕加背景圖壹若,</li><li>給每個按鈕加一個點擊事件來播放音頻(調(diào)用<a target="_blank">HTML AudioElement接口</a>)</li></ul><p>CLI中輸入下面命令來測試應(yīng)用:</p><p></p><pre class="">npm start</pre><p>

</p>
<h2>用遠程事件從瀏覽器窗口關(guān)閉應(yīng)用</h2><pre class="">請切換到02-basic-sound-machine這個tag:
git checkout 02-basic-sound-machine</pre><p>簡要重述--應(yīng)用窗口(更準確的說是渲染進程)應(yīng)該不能與GUI(用來關(guān)閉窗口)通信的,官方的<a target="_blank">Electron快速入門指南</a>寫到:</p><blockquote>在web頁面皂冰,不允許調(diào)用原生GUI相關(guān)的API店展,因為在web頁面管理原生GUI資源是很危險的,會很容易泄露資源秃流。如果你想在web頁面施行GUI操作赂蕴,web頁面的渲染進程必須要與主進程通信,請求主進程來完成這些操作舶胀。</blockquote><p>Electron提供<a target="_blank">ipc(進程間通信)模塊</a>來實現(xiàn)這類通信概说。ipc模塊可實現(xiàn)從通道訂閱消息碧注,發(fā)送消息給通道的訂閱者,通道區(qū)分消息的接收者糖赔,用字符來標識(例如萍丐,通道1,通道2)放典。消息也可以包含數(shù)據(jù)逝变。當接收到消息,訂閱者可以做出反應(yīng)奋构,甚至回復(fù)消息壳影。消息最大的好處就是隔離 -- 主進程不必知道哪個渲染進程發(fā)出消息。</p><p>

ipc.on('close-main-window', function () {
app.quit();
});
</pre><p></p><p>引入ipc模塊后掺栅,通過通道訂閱消息就變得很簡單,on()方法設(shè)置訂閱的通道名芥驳,定義回調(diào)函數(shù)柿冲。</p><p>渲染進程要通過通道發(fā)送消息,將下面代碼加入index.js:</p><pre class="">var ipc = require('ipc');
var closeEl = document.querySelector('.close');
closeEl.addEventListener('click', function () {
ipc.send('close-main-window');
});
</pre><p></p><p></p><p>同樣兆旬,我們引入ipc模塊假抄,給關(guān)閉按鈕的元素綁定一個click事件。當點擊關(guān)閉按鈕時丽猬,通過「close-main-window」通道的send()方法發(fā)送消息宿饱。</p><p>這里還有個小問題,如果不注意會卡住你脚祟,我們已經(jīng)討論過--可拖動區(qū)域的可點擊性谬以。index.css需要把關(guān)閉按鈕定義成不可拖動:</p><pre class="">.settings {
...
-webkit-app-region: no-drag;
}
</pre><p></p><p>就這樣,現(xiàn)在可以點擊關(guān)閉按鈕關(guān)閉我們的應(yīng)用了由桌。因為要監(jiān)聽事件或傳遞參數(shù)为黎,通過ipc模塊通信比較復(fù)雜。我們后面會看到一個傳遞參數(shù)的例子行您。</p>
<h2>用全局快捷鍵播放聲音</h2><pre class="">請切換到名為03-closable-sound-machine的tag:
git checkout 03-closable-sound-machine</pre><p></p><p>基礎(chǔ)的發(fā)聲器工作順利铭乾,但是我們有一個易用性問題--如果發(fā)聲器一定需要切到應(yīng)用窗口,再點擊才能播放娃循,這個發(fā)聲器有什么用炕檩?</p><p>這時我們需要的就是全局快捷鍵。Electron提供一個<a target="_blank">全局快捷鍵模塊</a>捌斧,允許你監(jiān)聽自定義的鍵盤組合并做出反應(yīng)笛质。鍵盤組合也被叫做<a target="_blank">加速器</a>泉沾,是一系列鍵盤點擊組成的字符串(例如 “Ctrl+Shift+1”)。</p><p></p><p>

app.on('ready', function() {
... // existing code from earlier
globalShortcut.register('ctrl+shift+1', function () {
mainWindow.webContents.send('global-shortcut', 0);
});
globalShortcut.register('ctrl+shift+2', function () {
mainWindow.webContents.send('global-shortcut', 1);
});
});
</pre><p></p><p>首先潭袱,我們需要引入global-shortcut模塊。然后當我們的程序加載完成锋恬,我們注冊兩個快捷鍵--一個響應(yīng)Ctrl屯换,Shift,1組合鍵与学,另一個響應(yīng)Ctrl彤悔,Shift,2組合鍵索守。兩者都會通過「global-shortcut」通道發(fā)送一條帶一個參數(shù)的消息晕窑。我們用這些參數(shù)來播放相應(yīng)的聲音。在index.js中加入以下代碼:</p><pre class="">ipc.on('global-shortcut', function (arg) {
var event = new MouseEvent('click');
soundButtons[arg].dispatchEvent(event);
});
</pre><p>為了方便卵佛,我們會模擬一次按鈕點擊杨赤,用我們創(chuàng)建的soundButton選擇器給按鈕綁定一個播放聲音。當收到帶有參數(shù)1的消息截汪,我們在soundButton[1]元素上模擬一次鼠標點擊(在正式環(huán)境的應(yīng)用疾牲,你應(yīng)該封裝播放聲音的代碼,并執(zhí)行它)衙解。</p>
<h2>在新的窗口修改鍵位配置</h2><pre class="">切換到名為04-global-shortcuts-bound的tag:
git checkout 04-global-shortcuts-bound</pre><p></p><p>系統(tǒng)同時運行很多應(yīng)用程序阳柔,我們預(yù)想的快捷鍵可能已經(jīng)被占用了。這正是我們將要新建一個設(shè)置窗口蚓峦,保存我們想要的鍵位修改的原因舌剂。</p><p>要實現(xiàn)這個目標,我們需要:</p><ul><li>主窗口要有一個設(shè)置按鈕暑椰,</li><li>一個設(shè)置窗口(需要相應(yīng)的HTML霍转,CSS和JavaScript文件),</li><li>ipc消息用來打開干茉,關(guān)閉設(shè)置窗口及更新全局快捷鍵,</li><li>保存或讀取用戶系統(tǒng)里JSON格式的設(shè)置文件很泊。</li></ul><p><b>設(shè)置按鈕和設(shè)置窗口</b></p><p></p><p>類似關(guān)閉主窗口角虫,當點擊設(shè)置按鈕時我們通過通道從index.js發(fā)送消息沾谓。將下面代碼加入index.js:</p><p></p><pre class="">var settingsEl = document.querySelector('.settings');
settingsEl.addEventListener('click', function () {
ipc.send('open-settings-window');
});
</pre><p>點擊設(shè)置按鈕后,通道「open-settings-window」會發(fā)送一條消息到主進程戳鹅。main.js現(xiàn)在需要做出響應(yīng)均驶,新建一個窗口,將下面代碼插入main.js:</p><pre class="">var settingsWindow = null;
ipc.on('open-settings-window', function () {
if (settingsWindow) {
return;
}
settingsWindow = new BrowserWindow({
frame: false,
height: 200,
resizable: false,
width: 200
});
settingsWindow.loadUrl('file://' + __dirname + '/app/settings.html');
settingsWindow.on('closed', function () {
settingsWindow = null;
});
});
</pre><p></p><p>沒有什么新概念枫虏,我們會像打開主窗口一樣打開新的設(shè)置窗口妇穴。不同之處是要先檢查設(shè)置窗口是不是已經(jīng)被打開,以防重復(fù)打開隶债。</p><p>打開后腾它,需要一種方法關(guān)閉設(shè)置窗口。同樣的死讹,我們會通過通道發(fā)送一條消息瞒滴,但這次消息是從settings.js發(fā)出,將下面代碼寫入setting.js:</p><pre class="">'use strict';
var ipc = require('ipc');
var closeEl = document.querySelector('.close');
closeEl.addEventListener('click', function (e) {
ipc.send('close-settings-window');
});
</pre><p></p><p>在main.js里面監(jiān)聽那個通道赞警,代碼如下:</p><pre class="">ipc.on('close-settings-window', function () {
if (settingsWindow) {
settingsWindow.close();
}
});
</pre><p></p><p>我們的設(shè)置窗口就完成了妓忍。</p><p><b>保存和讀取用戶的設(shè)置</b></p><p></p><pre class="">切換到名為05-settings-window-working的tag:
git checkout 05-settings-window-working</pre><p></p><p>與設(shè)置窗口交互,保存設(shè)置愧旦,再讀取到我們的應(yīng)用的過程大致是這樣的:</p><ul><li>編寫一個可以保存和讀取我們在JSON文件中保存設(shè)置信息的辦法世剖,</li><li>初始化設(shè)置窗口時,顯示這些設(shè)置笤虫,</li><li>通過客戶的交互更新設(shè)置旁瘫,</li><li>通知主程序新的設(shè)置。</li></ul><p>我們可以簡單的保存和讀取main.js中的設(shè)置耕皮,但模塊把邏輯抽象出來境蜕,以便我們可以在不同的地方引用,這看看起來更好凌停。</p><p></p><p><b>Working with a JSON configuration</b></p><p>那就是我們新建configuration.js的原因粱年。Node.js用<a target="_blank">CommonJS模塊規(guī)范</a>,這意味著你只可以暴露你的API罚拟,而其他文件或方法會引用API提供的方法台诗。</p><p>

</p><pre class="">npm install --save nconf</pre><p></p><p>npm將nconf模塊作為應(yīng)用的依賴安裝粱快。在我們打包應(yīng)用給終端用戶時(相對用save-dev參數(shù)會只在開發(fā)環(huán)境中引入模塊)將被引入和使用。</p><p>configuration.js文件非常的簡單,在項目根目錄下新建configuration.js文件事哭,寫入代碼:</p><pre class="">'use strict';
var nconf = require('nconf').file({file: getUserHome() + '/sound-machine-config.json'});
function saveSettings(settingKey, settingValue) {
nconf.set(settingKey, settingValue);
nconf.save();
}
function readSettings(settingKey) {
nconf.load();
return nconf.get(settingKey);
}
function getUserHome() {
return process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'];
}
module.exports = {
saveSettings: saveSettings,
readSettings: readSettings
};
</pre><p></p><p></p><p>nconf只需要知道你的設(shè)置要保存到哪里漫雷,這里我們設(shè)置為客戶的主文件夾和一個文件名。獲取用戶的主文件夾非常簡單鳍咱,只需要區(qū)別不同系統(tǒng)調(diào)用Node.js(process.env)(如用getUserHome()方法)降盹。</p><p>通過nconf的內(nèi)建方法來保存或讀取設(shè)置(set()方法保存,get()方法讀取谤辜,用save()和load()方法進行文件操作)蓄坏,用符合CommonJS規(guī)范的module.exports語法來導(dǎo)出API。</p><p><b>初始化修改的快捷鍵</b></p><p></p><p>在我們進行設(shè)置的交互之前丑念,應(yīng)初始化設(shè)置涡戳,以防我們先啟動應(yīng)用丟失設(shè)置信息。我們把變更鍵保存在一個數(shù)組中渠欺,數(shù)組以「shortcutKeys」為鍵妹蔽,在main.js里初始化,我們首先要引用configuration模塊:</p><pre class="">'use strict';
var configuration = require('./configuration');
app.on('ready', function () {
if (!configuration.readSettings('shortcutKeys')) {
configuration.saveSettings('shortcutKeys', ['ctrl', 'shift']);
}
...
}
</pre><p></p><p>嘗試讀取「shortcutKeys」鍵對應(yīng)的值挠将,如果讀取不到胳岂,就設(shè)置一個初始值。</p><p>現(xiàn)在要重寫main.js中的全局快捷鍵舔稀,這個方法可以在后面更新設(shè)置的時候直接調(diào)用乳丰。 去掉原來在main.js中注冊快捷鍵的方法,改成:</p><pre class="">app.on('ready', function () {
...
setGlobalShortcuts();
}
function setGlobalShortcuts() {
globalShortcut.unregisterAll();
var shortcutKeysSetting = configuration.readSettings('shortcutKeys');
var shortcutPrefix = shortcutKeysSetting.length === 0 ? '' : shortcutKeysSetting.join('+') + '+';
globalShortcut.register(shortcutPrefix + '1', function () {
mainWindow.webContents.send('global-shortcut', 0);
});
globalShortcut.register(shortcutPrefix + '2', function () {
mainWindow.webContents.send('global-shortcut', 1);
});
}
</pre><p></p><p>方法會重置全局快捷鍵内贮,現(xiàn)在我們可以設(shè)置新的快捷鍵产园,從設(shè)置文件讀取變更鍵數(shù)組,轉(zhuǎn)換<a target="_blank">類加速器規(guī)則字符串</a>夜郁,再注冊全局快捷鍵什燕。</p><p><b>與設(shè)置窗口交互</b></p><p>回到settings.js,我們要綁定click事件來修改我們的全局快捷鍵竞端。首先屎即,我們遍歷所有勾選的復(fù)選框(從configuration模塊中讀取):</p><pre class="">var configuration = require('../configuration.js');
var modifierCheckboxes = document.querySelectorAll('.global-shortcut');
for (var i = 0; i < modifierCheckboxes.length; i++) {
var shortcutKeys = configuration.readSettings('shortcutKeys');
var modifierKey = modifierCheckboxes[i].attributes['data-modifier-key'].value;
modifierCheckboxes[i].checked = shortcutKeys.indexOf(modifierKey) !== -1;
... // Binding of clicks comes here
}
</pre><p></p><p>現(xiàn)在我們要給復(fù)選框綁定行為事富。記得設(shè)置窗口(渲染進程)不能改動GUI綁定技俐。這意味著我們需要從setting.js通過ipc發(fā)送消息(后面會處理消息):</p><pre class="">for (var i = 0; i < modifierCheckboxes.length; i++) {
...
modifierCheckboxes[i].addEventListener('click', function (e) {
bindModifierCheckboxes(e);
});
}
function bindModifierCheckboxes(e) {
var shortcutKeys = configuration.readSettings('shortcutKeys');
var modifierKey = e.target.attributes['data-modifier-key'].value;
if (shortcutKeys.indexOf(modifierKey) !== -1) {
var shortcutKeyIndex = shortcutKeys.indexOf(modifierKey);
shortcutKeys.splice(shortcutKeyIndex, 1);
}
else {
shortcutKeys.push(modifierKey);
}
configuration.saveSettings('shortcutKeys', shortcutKeys);
ipc.send('set-global-shortcuts');
}
</pre><p></p><p>我們遍歷了所有的復(fù)選框,綁定click事件统台,在每次點擊時判斷是否含有變更鍵雕擂。然后根據(jù)結(jié)果,修改數(shù)組贱勃,保存結(jié)果到設(shè)置井赌,再給主進程發(fā)送消息谤逼,它會更新我們的全局快捷鍵。</p><p>下面要在main.js里的設(shè)置「set-global-shortcuts」這個ipc通道來更新我們的全局快捷鍵:</p><pre class="">ipc.on('set-global-shortcuts', function () {
setGlobalShortcuts();
});
</pre><p></p><p></p><p>很簡單仇穗,像這樣森缠,我們的全局快捷鍵就配置好了!</p>
<h2>菜單上有什么?</h2><pre class="">切換到名為06-shortcuts-configurable的tag:
git checkout 06-shortcuts-configurable</pre><p>對桌面應(yīng)用來說,另一個重要的概念就是菜單欄仪缸。分為上下文菜單(右擊菜單),托盤菜單(綁定到托盤圖標)列肢,應(yīng)用菜單(在OS X上)等多種恰画。</p><p>

</p><p>remote模塊實現(xiàn)從渲染進程向主進程發(fā)送RPC式調(diào)用。你引入模塊欧聘,在渲染進程操作片林,方法在主進程被初始化,你調(diào)用的方法都在主進程被執(zhí)行怀骤。實際中费封,這意味著你在index.js遠程請求原生的GUI模塊,調(diào)用它們的方法蒋伦,都會在main.js中執(zhí)行弓摘。你可以在index.js里引入BrowserWindow模塊,初始化一個瀏覽器窗口痕届。背后的原理是韧献,異步調(diào)用新的瀏覽器窗口的主進程。</p><p>

</p><p>現(xiàn)在我們創(chuàng)建一個菜單研叫,并把它綁定到托盤圖標锤窑,在index.js中加入下面代碼:</p><pre class="">var remote = require('remote');
var Tray = remote.require('tray');
var Menu = remote.require('menu');
var path = require('path');
var trayIcon = null;
if (process.platform === 'darwin') {
trayIcon = new Tray(path.join(__dirname, 'img/tray-iconTemplate.png'));
}
else {
trayIcon = new Tray(path.join(__dirname, 'img/tray-icon-alt.png'));
}
var trayMenuTemplate = [
{
label: 'Sound machine',
enabled: false
},
{
label: 'Settings',
click: function () {
ipc.send('open-settings-window');
}
},
{
label: 'Quit',
click: function () {
ipc.send('close-main-window');
}
}
];
var trayMenu = Menu.buildFromTemplate(trayMenuTemplate);
trayIcon.setContextMenu(trayMenu);
</pre><p></p><p>原生的GUI模塊(菜單和托盤)的方法會被遠程調(diào)用,是很安全的嚷炉。</p><p>把圖標定義成托盤圖標渊啰。OS X支持圖像模板(依照慣例,圖像的文件名以「Template」結(jié)尾渤昌,可以被當做一個模板圖像)虽抄,這讓使用深淺色主題變得很容易。其他系統(tǒng)用常規(guī)的圖標独柑。</p><p>在Electron中有很多種方法創(chuàng)建菜單迈窟。我們的方法是創(chuàng)建一個菜單模板(一個包含菜單項的簡單數(shù)組),用那個模板創(chuàng)建菜單忌栅。最后车酣,綁定新的菜單到托盤圖標曲稼。</p><p></p>
<h2>打包你的應(yīng)用</h2><pre class="">切換到名為07-ready-for-packaging的tag:
git checkout 07-ready-for-packaging</pre><p></p><p>如果不能讓人們下載使用,這樣的應(yīng)用有什么意義湖员?</p><p>

</p><p></p><p>用「<a target="_blank">electron-packager</a>」為所有系統(tǒng)打包你的應(yīng)用很簡單贫悄。簡單來說,「electron-packager」幫你完成所有用Electron打包你應(yīng)用的工作娘摔,最終生成你要發(fā)布的平臺的安裝包窄坦。</p><p>它可以作為CLI應(yīng)用或構(gòu)建過程的一部分,更復(fù)雜的構(gòu)建情況不在本文所涉及范圍內(nèi)凳寺,但我們?nèi)绻苡么虬_本鸭津,會使打包更簡單。用「electron-packager」比較麻煩肠缨,打包應(yīng)用的基本命令是:</p><p></p><pre class="">electron-packager <location of project> <name of project> <platform> <architecture> <electron version> <optional options></pre><p></p><p>其中逆趋,</p><ul><li>location of project是你項目文件夾的位置,</li><li>name of project定義你的項目名晒奕,</li><li>platform決定要構(gòu)建的平臺(all 包括Windows闻书,Mac和Linux ),</li><li>architecture決定構(gòu)建哪個構(gòu)架下(x86或x64脑慧,all表示兩者)魄眉,</li><li>electron version讓你選擇要用的Electron版本</li></ul><p></p><p>第一次打包用時比較久,因為要下載平臺的二進制文件闷袒,隨后的打包將會快的多杆融。</p><p>我(在Mac系統(tǒng))打包發(fā)聲器應(yīng)用的命令是:</p><p></p><pre class="">electron-packager ~/Projects/sound-machine SoundMachine --all --version=0.30.2 --out=~/Desktop --overwrite --icon=~/Projects/sound-machine/app/img/app-icon.icns</pre><p></p><p>命令的選項理解起來都比較簡單。為了獲得精美的圖標霜运,你首先要找一款類似<a target="_blank">這個軟件</a>可以把PNG文件轉(zhuǎn)換到這些格式的工具脾歇,把它轉(zhuǎn)換成.icns格式(Mac用)或者.ico格式(Window用)。如果在非Windows系統(tǒng)給Windows平臺的應(yīng)用打包淘捡,你需要安裝wine(Mac用戶用brew藕各,Linux用戶用apt-get)。</p><p>每次都打這么長的命令很不方便焦除,可以在package.json中加另一個腳本激况。首先,把electron-packager作為開發(fā)依賴安裝:</p><p></p><pre class="">npm install --save-dev electron-packager</pre><p>現(xiàn)在我們可以在package.json中添加腳本:</p><pre class="">"scripts": {
"start": "electron .",
"package": "electron-packager ./ SoundMachine --all --out ~/Desktop/SoundMachine --version 0.30.2 --overwrite --icon=./app/img/app-icon.icns"
}</pre><p>在命令行里執(zhí)行下面的命令:</p><pre class="">npm run-script package</pre><p></p><p>這個打包命令會啟動electron-packager膘魄,在當前目錄下找到目標應(yīng)用文件乌逐,打包,保存到桌面创葡。如果你用的是Windows系統(tǒng)浙踢,需要修改腳本,不過改動很小灿渴。</p><p>當前狀態(tài)的發(fā)聲器洛波,最后打包后大小高達100MB胰舆。別擔心,可以把它壓縮到不到一半的容量蹬挤。</p><p>如果你想要更進一步缚窿,可以嘗試<a target="_blank">electron-builder</a>,它用electron-packager生成的打包好的文件焰扳,可以生成自動安裝包倦零。</p><p></p>
<h2>可以添加的其他功能</h2><p></p><p>應(yīng)用已經(jīng)打包好,準備就緒吨悍。你也可以添加自己想要的功能光绕。</p><p>這是一些想法:</p><ul><li>可以顯示應(yīng)用的快捷鍵,作者等信息的幫助界面畜份,</li><li>加一個綁定菜單的圖標入口可以打開信息界面,</li><li>為了更快的編譯和分發(fā)欣尼,編寫打包的腳本爆雹,</li><li>用<a target="_blank">node-notifier</a>加入通知功能,推送用戶正播放的是什么聲音愕鼓,</li><li>用lodash得到更整潔的代碼钙态,</li><li>打包應(yīng)用前,將你所有的CSS和JavaScript文件用構(gòu)建工具壓縮菇晃,</li><li>檢查應(yīng)用是否有新版本册倒,用服務(wù)器調(diào)用之前介紹的node-notifier并通知客戶</li></ul><p>挑戰(zhàn)來了--嘗試抽取出發(fā)聲器瀏覽器窗口的邏輯,用這些邏輯在瀏覽器中創(chuàng)建web頁面磺送,實現(xiàn)相同的發(fā)聲器驻子。一個代碼庫--兩個產(chǎn)品(桌面應(yīng)用和web應(yīng)用),超棒估灿!</p><p></p>
<p></p><h1>深入Electron</h1><p>我們只接觸到了Electron比較淺顯知識崇呵。實際上,實現(xiàn)如查看主機電源選項或在界面上顯示多種信息都很簡單馅袁。這些功能已經(jīng)內(nèi)建好域慷,請<a target="_blank">查閱Electronde API文檔</a>。</p><p>Electron的API文檔只是Electron在Github上資料庫的一小部分汗销,其他文件夾也值得一看犹褒。</p><p>Sindre Sorhus正在維護<a target="_blank">超酷的Electron資源列表</a>,你可以在列表中找到很多很酷的項目弛针,也有Electron應(yīng)用構(gòu)架方面很好的總結(jié)叠骑,可以學(xué)習(xí)之后重構(gòu)我們的代碼。</p><p>最后削茁,Electron是基于io.js(將合并回Node.js)的座云,兼容絕大部分的Node.js模塊疙赠,可以用來擴展你的應(yīng)用,查看<a target="_blank">npmjs.com</a>來獲取你需要的信息朦拖。</p>
<p></p><h1>這就完了圃阳?</h1><p>當然不是。</p><p>現(xiàn)在是時候來創(chuàng)建更復(fù)雜的應(yīng)用了璧帝。在本指南中捍岳,我沒有選擇用更多函數(shù)庫和構(gòu)建工具,只強調(diào)了重要的概念睬隶。你也可以用ES6或Typescript來寫你的應(yīng)用锣夹,使用Angular或React框架,用gulp或Grunt來簡化你的構(gòu)建過程苏潜。</p><p>為什么不用你最喜歡的語言银萍,框架和構(gòu)建工具,配合Flickr API恤左,node-flickrapi創(chuàng)建一個Flickr桌面同步應(yīng)用呢贴唇?或者用Google官方的Node.js函數(shù)庫創(chuàng)建一個Gmail客戶端?</p><p>選一個吸引你的想法飞袋,創(chuàng)建一個資料庫戳气,開始做吧。</p></div>
<div class="bbox">
本文來自<a target="_blank">GET社區(qū)翻譯計劃</a>巧鸭,每翻譯一篇高質(zhì)量文章瓶您,可獲100元獎金。翻譯計劃贊助商:
<ul class="binfo">
<li><a target="_blank"></a></li>
</ul>
轉(zhuǎn)載必須保留來源和以上贊助商信息纲仍。
</div>
<div class="sinfo exp">
本文由 <a href="/1.user" target="_blank">Easy<span aria-hidden="true" class="jdc-jobdeer jdcicon"></span></a> 第一時間收藏到
GET呀袱,原文來自 → <a target="_blank"> medium.com </a>
</div>
<ul class="get-article-actions">
<li><a target="_blank">分享到微博</a></li>
<li class="kblink-7870"><a href="javascript:add2kb('7870','top');" >收藏本文</a></li>
<li class="followlink-1"><a href="javascript:follow('1');void(0);">關(guān)注@Easy</a></li>
</ul>
<div class="visible-xs-block wxlast">
<center>

<p>「GetParty」</p>
<p>關(guān)注微信號,推送好文章</p>
<p>微信中長按圖片即可關(guān)注</p>
<p><a href="/?fr=qrbt" class="btn btn-success">更多精選文章</a></p>
</center>
</div>
<div id="article-7870">
<a href="javascript:toggle_inline_comment('7870','article');void(0);" class="get" name="comment"><span class="jdc-coffee"></span>評論(4)</a>
<div class="top20"></div>
</div>
<script>toggle_inline_comment('7870','article');
$(".timeago").timeago();</script>
<div class="hbox"></div>
</div>
<script>
$( document ).ready(function() {
highlight();
// 支持圖片分享到微博
setTimeout( function()
{
$('.get-article-area').imgShare();
} , 2000 );
});
</script>
</div>
<div class="hidden-xs col-sm-3 sidebar-offcanvas" id="sidebar" role="navigation">
<input class="nh" type="checkbox" onchange="change2('https://medium.com/@bojzi/building-a-desktop-application-with-electron-204203eeb658');$(this).prop('checked', false);" />
<script type="text/javascript">
$('input.nh').onoff();
</script>
<script>
$('.sinfo.exp').waypoint(function(direction) {
if( direction == 'down' )
{
$("#wechat_code").show();
}
if( direction == 'up' )
{
$("#wechat_code").hide();
}
},{ offset:'bottom-in-view'});
</script>
<div id="wechat_code">

<p>關(guān)注微信號郑叠,推送好文章</p>
</div> </div>
</div>
<div class="get-last"></div>
<div class="get-user-menu">
<a href="/?c=weibo&a=login" class="link">微博一鍵登入</a><span class="top" ><a href="#"><span class="jdc-arrow-up-thick"></span></a></span>
</div>
<div class="modal fade" id="get_float" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" >
<div class="modal-dialog">
</div>
</div>
<div id="get_aside" class="clearfix">
<div class="pull-right"><a href="javascript:hide_aside();void(0);" class="get btn">×</a></div>
<div class="content"><center class="top50">
</center></div>
</div>
<div id="shadow_dom"></div>
<script type="text/javascript">
//emojify.run();
//$('#get_float').modal();
$(window).scroll( function()
{
var value = $(document).scrollTop();
//console.log('in value='+value);
if ( value > 0 )
$("#theheader").addClass('smaller');
else
$("#theheader").removeClass('smaller');
});
if( $(".snapbox").length > 0 )
$(".snapbox").append($("<div class='floatingfooter'></div>"));
$( document ).ready(function() {
$( '.yue a[href^="http://"]' ).attr( 'target','_blank' );
$( '.yue a[href^="https://"]' ).attr( 'target','_blank' );
});
</script>
</div>
</body>
</html>