create and Manage Windows
默认情况下, 使用BrowserWindow来新建一个窗口, 通过loadURL来加载html页面.
const {app, BrowserWindow} = require('electron');
const path = require('path');
app.on('ready', () => {
const modalPath = path.join('file://', __dirname + '/index.html');
let win = new BrowserWindow({width: 400, height: 320});
win.on('close', () => {
win = null;
});
win.loadURL(modalPath);
win.show();
});
我们可以监听resize和move事件, 来获取窗口的大小和位置:
const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
const path = require('path');
app.on('ready', () => {
const modalPath = path.join('file://', __dirname + '/index.html');
let win = new BrowserWindow({width: 400, height: 320});
win.on('resize', () => {
console.log(`${win.getSize()}`);
});
win.on('move', () => {
console.log(`${win.getPosition()}`);
});
win.on('close', () => {
win = null;
});
win.loadURL(modalPath);
win.show();
});
而focus在光标在窗口上时候触发, blur在光标在窗口外时候触发.
const electron = require('electron')
const app = electron.app
const BrowserWindow = electron.BrowserWindow
let win
app.on('ready', () => {
win = new BrowserWindow({width: 600, height: 400})
win.on('focus', () => {
console.log('focus...')
})
win.on('blur', () => {
console.log('blur...')
})
win.on('close', () => {
console.log('close...')
})
})
Handling window crashes and hangs
当程序崩溃时候,调用process.crash()来模拟,这时候可以监听crashed事件来决定如何处理.
const electron = require('electron')
const path = require('path')
const dialog = electron.dialog
const app = electron.app
const BrowserWindow = electron.BrowserWindow
let win
app.on('ready', () => {
const winPath = path.join('file://', __dirname + '/index.html')
win = new BrowserWindow({width: 400, height: 320})
win.webContents.on('crashed', () => {
const options = {
type: 'info',
title: 'Renderer Process Crashed',
message: 'This process has crashed',
buttons: ['Reload', 'Close']
}
dialog.showMessageBox(options, (index) => {
if (index === 0) win.reload()
else win.close()
})
})
win.on('close', () => win = null)
win.loadURL(winPath)
win.show()
})
而html页面如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>Hello World!</h1>
<a href="javascript:process.crash()">crash the process</a>
</body>
</html>
我们也可以通过process.hang()来模拟进程悬挂,通过监听unresponsive来决定如何处理.
const electron = require('electron')
const path = require('path')
const dialog = electron.dialog
const app = electron.app
const BrowserWindow = electron.BrowserWindow
let win
app.on('ready', () => {
const winPath = path.join('file://', __dirname + '/index.html')
win = new BrowserWindow({width: 400, height: 320})
win.webContents.on('unresponsive', () => {
const options = {
type: 'info',
title: 'Renderer Process Hanging',
message: 'This process has hanging',
buttons: ['Reload', 'Close']
}
dialog.showMessageBox(options, (index) => {
if (index === 0) win.reload()
else win.close()
})
})
win.webContents.on('responsive', () => {
console.log('responsive...')
})
win.on('close', () => win = null)
win.loadURL(winPath)
win.show()
})
而html页面如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>Hello World!</h1>
<a href="javascript:process.hang()">hang the process</a>
</body>
</html>
Customize Menus
使用Menu和MenuItem来创建菜单栏:
const electron = require('electron')
const BrowserWindow = electron.BrowserWindow
const Menu = electron.Menu
const app = electron.app
let template = [{
label: 'Edit',
submenu: [{
label: 'Undo',
accelerator: 'CmdOrCtrl+Z',
role: 'undo'
}, {
label: 'Redo',
accelerator: 'Shift+CmdOrCtrl+Z',
role: 'redo'
}, {
type: 'separator'
}, {
label: 'Cut',
accelerator: 'CmdOrCtrl+X',
role: 'cut'
}, {
label: 'Copy',
accelerator: 'CmdOrCtrl+C',
role: 'copy'
}, {
label: 'Paste',
accelerator: 'CmdOrCtrl+V',
role: 'paste'
}, {
label: 'Select All',
accelerator: 'CmdOrCtrl+A',
role: 'selectall'
}]
}, {
label: 'View',
submenu: [{
label: 'Reload',
accelerator: 'CmdOrCtrl+R',
click: function (item, focusedWindow) {
if (focusedWindow) {
// on reload, start fresh and close any old
// open secondary windows
if (focusedWindow.id === 1) {
BrowserWindow.getAllWindows().forEach(function (win) {
if (win.id > 1) {
win.close()
}
})
}
focusedWindow.reload()
}
}
}, {
label: 'Toggle Full Screen',
accelerator: (function () {
if (process.platform === 'darwin') {
return 'Ctrl+Command+F'
} else {
return 'F11'
}
})(),
click: function (item, focusedWindow) {
if (focusedWindow) {
focusedWindow.setFullScreen(!focusedWindow.isFullScreen())
}
}
}, {
label: 'Toggle Developer Tools',
accelerator: (function () {
if (process.platform === 'darwin') {
return 'Alt+Command+I'
} else {
return 'Ctrl+Shift+I'
}
})(),
click: function (item, focusedWindow) {
if (focusedWindow) {
focusedWindow.toggleDevTools()
}
}
}, {
type: 'separator'
}, {
label: 'App Menu Demo',
click: function (item, focusedWindow) {
if (focusedWindow) {
const options = {
type: 'info',
title: 'Application Menu Demo',
buttons: ['Ok'],
message: 'This demo is for the Menu section, showing how to create a clickable menu item in the application menu.'
}
electron.dialog.showMessageBox(focusedWindow, options, function () {})
}
}
}]
}, {
label: 'Window',
role: 'window',
submenu: [{
label: 'Minimize',
accelerator: 'CmdOrCtrl+M',
role: 'minimize'
}, {
label: 'Close',
accelerator: 'CmdOrCtrl+W',
role: 'close'
}, {
type: 'separator'
}, {
label: 'Reopen Window',
accelerator: 'CmdOrCtrl+Shift+T',
enabled: false,
key: 'reopenMenuItem',
click: function () {
app.emit('activate')
}
}]
}, {
label: 'Help',
role: 'help',
submenu: [{
label: 'Learn More',
click: function () {
electron.shell.openExternal('http://electron.atom.io')
}
}]
}]
function addUpdateMenuItems (items, position) {
if (process.mas) return
const version = electron.app.getVersion()
let updateItems = [{
label: `Version ${version}`,
enabled: false
}, {
label: 'Checking for Update',
enabled: false,
key: 'checkingForUpdate'
}, {
label: 'Check for Update',
visible: false,
key: 'checkForUpdate',
click: function () {
require('electron').autoUpdater.checkForUpdates()
}
}, {
label: 'Restart and Install Update',
enabled: true,
visible: false,
key: 'restartToUpdate',
click: function () {
require('electron').autoUpdater.quitAndInstall()
}
}]
items.splice.apply(items, [position, 0].concat(updateItems))
}
function findReopenMenuItem () {
const menu = Menu.getApplicationMenu()
if (!menu) return
let reopenMenuItem
menu.items.forEach(function (item) {
if (item.submenu) {
item.submenu.items.forEach(function (item) {
if (item.key === 'reopenMenuItem') {
reopenMenuItem = item
}
})
}
})
return reopenMenuItem
}
if (process.platform === 'darwin') {
const name = electron.app.getName()
template.unshift({
label: name,
submenu: [{
label: `About ${name}`,
role: 'about'
}, {
type: 'separator'
}, {
label: 'Services',
role: 'services',
submenu: []
}, {
type: 'separator'
}, {
label: `Hide ${name}`,
accelerator: 'Command+H',
role: 'hide'
}, {
label: 'Hide Others',
accelerator: 'Command+Alt+H',
role: 'hideothers'
}, {
label: 'Show All',
role: 'unhide'
}, {
type: 'separator'
}, {
label: 'Quit',
accelerator: 'Command+Q',
click: function () {
app.quit()
}
}]
})
// Window menu.
template[3].submenu.push({
type: 'separator'
}, {
label: 'Bring All to Front',
role: 'front'
})
addUpdateMenuItems(template[0].submenu, 1)
}
if (process.platform === 'win32') {
const helpMenu = template[template.length - 1].submenu
addUpdateMenuItems(helpMenu, 0)
}
app.on('ready', function () {
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
})
app.on('browser-window-created', function () {
let reopenMenuItem = findReopenMenuItem()
if (reopenMenuItem) reopenMenuItem.enabled = false
})
app.on('window-all-closed', function () {
let reopenMenuItem = findReopenMenuItem()
if (reopenMenuItem) reopenMenuItem.enabled = true
})
而通过ipc完成进程间通信,我们可以创建一个临时菜单栏(类似右键点击的效果):
Main Process:
const electron = require('electron')
const BrowserWindow = electron.BrowserWindow
const Menu = electron.Menu
const MenuItem = electron.MenuItem
const ipc = electron.ipcMain
const app = electron.app
const menu = new Menu()
menu.append(new MenuItem({ label: 'Hello' }))
menu.append(new MenuItem({ type: 'separator' }))
menu.append(new MenuItem({ label: 'Electron', type: 'checkbox', checked: true }))
app.on('browser-window-created', function (event, win) {
win.webContents.on('context-menu', function (e, params) {
menu.popup(win, params.x, params.y)
})
})
ipc.on('show-context-menu', function (event) {
const win = BrowserWindow.fromWebContents(event.sender)
menu.popup(win)
})
Renderer Process:
const ipc = require('electron').ipcRenderer
// Tell main process to show the menu when demo button is clicked
const contextMenuBtn = document.getElementById('context-menu')
contextMenuBtn.addEventListener('click', function () {
ipc.send('show-context-menu')
})
Register keyboard shortcuts
快捷键的操作主要涉及以下三个功能模块:Menu, Accelerator和globalShortcut:
const electron = require('electron')
const app = electron.app
const dialog = electron.dialog
const globalShortcut = electron.globalShortcut
app.on('ready', function () {
globalShortcut.register('CommandOrControl+Alt+K', function () {
dialog.showMessageBox({
type: 'info',
message: 'Success!',
detail: 'You pressed the registered global shortcut keybinding.',
buttons: ['OK']
})
})
})
app.on('will-quit', function () {
globalShortcut.unregisterAll()
})
按下ctrl + alt + k可看到效果.
Open external links and the file manager
使用shell模块实现.
用文件管理器打开文件:
const os = require('os')
const electron = require('electron')
const shell = electron.shell
const app = electron.app
app.on('ready', () => {
shell.showItemInFolder(os.homedir())
})
在app之外打开url:
const electron = require('electron')
const shell = electron.shell
const app = electron.app
app.on('ready', () => {
shell.openExternal('http://electron.atom.io')
})
Use system dialogs
这里简单介绍一下main process和renderer process.两者并没有什么本质的区别,main process通常用于处理与页面dom无关的逻辑,而renderer process用于处理于页面dom有关的逻辑.所以通常是html页面require(renderer-process.js)文件.
Open a File or Directory
这里,我们使用ipc进行消息传递,打开文件夹/文件.
main.js:
const {app, BrowserWindow} = require('electron');
const path = require('path');
const url = require('url');
const glob = require('glob')
let win;
loadDemos();
function createWindow() {
win = new BrowserWindow({width: 800, height: 600});
win.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
}));
win.webContents.openDevTools();
win.on('closed', () => {
win = null;
});
}
app.on('ready', createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (win === null) {
createWindow();
}
});
function loadDemos() {
let files = glob.sync(path.join(__dirname, 'main-process/*.js'))
files.forEach((file) => {
require(file)
})
}
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>Hello World!</h1>
<button id="select-directory">click me</button>
<p id="selected-file"></p>
<script type="text/javascript">
require('./renderer-process/open-file.js')
</script>
</body>
</html>
main-process/open-file.js:
const ipc = require('electron').ipcMain
const dialog = require('electron').dialog
ipc.on('open-file-dialog', (event) => {
dialog.showOpenDialog({
properties: ['openFile', 'openDirectory']
}, (files) => {
if (files) event.sender.send('selected-directory', files)
})
})
renderer-process/open-file.js:
const ipc = require('electron').ipcRenderer
const selectDirBtn = document.getElementById('select-directory')
selectDirBtn.addEventListener('click', (event) => {
console.log('click...')
ipc.send('open-file-dialog')
})
ipc.on('selected-directory', (event, path) => {
document.getElementById('selected-file').innerHTML = `You selected: ${path}`
})
Error Dialog
dialog.showErrorBox('An Error Message', 'Demonstrating an error message.')
Information Dialog
const options = {
type: 'info',
title: 'Information',
message: 'This is an information dialog.',
buttons: ['Yes', 'No']
}
dialog.showMessageBox(options, (index) => {
console.log('click...')
})
Save Dialog
const options = {
type: 'info',
title: 'Save an Image',
filters: [
{name: 'Images', extensions: ['jpg', 'png', 'gif']}
]
}
dialog.showSaveDialog(options, (filename) => {
console.log('click...')
})
Put your app in the tray
使用tray模块允许你在操作系统的提示栏中增加图标.
const ipc = require('electron').ipcMain
const dialog = require('electron').dialog
const Tray = require('electron').Tray
const Menu = require('electron').Menu
const app = require('electron').app
const path = require('path')
ipc.on('open-file-dialog', (event) => {
const iconName = process.platform === 'win32' ? 'windows-icon.png' : 'iconTemplate.png'
const iconPath = path.join(__dirname, iconName)
appIcon = new Tray(iconPath)
const contextMenu = Menu.buildFromTemplate([{
label: 'Remove',
click: () => {
event.sender.send('tray-removed')
}
}])
appIcon.setToolTip('Electron Demo in the tray.')
appIcon.setContextMenu(contextMenu)
})
ipc.on('remove-tray', () => {
appIcon.destroy()
})
app.on('window-all-closed', () => {
if (appIcon) appIcon.destroy()
})
Communication between processes
Asynchronous messages
我们从renderer process中发送ping消息给main process,main process收到消息后回复pong:
renderer process:
const ipc = require('electron').ipcRenderer
const selectDirBtn = document.getElementById('async-msg')
selectDirBtn.addEventListener('click', (event) => {
ipc.send('async-msg', 'ping')
})
ipc.on('async-reply', (event, arg) => {
const msg = `Async msg reply:${arg}`
document.getElementById('async-reply').innerHTML = msg
})
main process:
const ipc = require('electron').ipcMain
ipc.on('async-msg', (event) => {
event.sender.send('async-reply', 'pong')
})
Synchronous messages
使用sendSync来同步发送消息:
renderer process:
const ipc = require('electron').ipcRenderer
const selectDirBtn = document.getElementById('sync-msg')
selectDirBtn.addEventListener('click', (event) => {
const reply = ipc.sendSync('sync-msg', 'ping')
const msg = `sync msg reply:${reply}`
document.getElementById('sync-reply').innerHTML = msg
})
main process:
const ipc = require('electron').ipcMain
ipc.on('sync-msg', (event) => {
event.returnValue = 'pong'
})
Communicate with an invisible window
我们甚至可以创建一个window来执行计算功能.
renderer process:
const BrowserWindow = require('electron').remote.BrowserWindow
const ipcRenderer = require('electron').ipcRenderer
const path = require('path')
const invisMsgBtn = document.getElementById('invis-msg')
const invisReply = document.getElementById('invis-reply')
invisMsgBtn.addEventListener('click', function (clickEvent) {
const windowID = BrowserWindow.getFocusedWindow().id
const invisPath = 'file://' + path.join(__dirname, '/index.html')
let win = new BrowserWindow({ width: 400, height: 400, show: false })
win.loadURL(invisPath)
win.webContents.on('did-finish-load', function () {
const input = 100
win.webContents.send('compute-factorial', input, windowID)
})
})
ipcRenderer.on('factorial-computed', function (event, input, output) {
const message = `The factorial of ${input} is ${output}`
invisReply.textContent = message
})
index.html
<!DOCTYPE html>
<html lang="en">
<script>
const ipc = require('electron').ipcRenderer
const BrowserWindow = require('electron').remote.BrowserWindow
ipc.on('compute-factorial', (event, number, fromWindowId) => {
const result = factorial(number)
const fromWindow = BrowserWindow.fromId(fromWindowId)
fromWindow.webContents.send('factorial-computed', number, result)
window.close()
})
function factorial(num) {
if (num === 0) return 1
return num * factorial(num - 1)
}
</script>
</html>
这里需要确定的知识点在于: remote.BrowserWindow和BrowserWindow有什么区别?
Get app and system information
Get app information
console.log('got-app-path', app.getAppPath())
Get version information
console.log(process.versions)
the output is:
leicj@leicj:~/test$ electron .
{ http_parser: '2.7.0',
node: '7.4.0',
v8: '5.6.326.50',
uv: '1.10.1',
zlib: '1.2.8',
ares: '1.10.1-DEV',
modules: '53',
openssl: '1.0.2j',
electron: '1.6.2',
chrome: '56.0.2924.87',
'atom-shell': '1.6.2' }
Get system information
console.log(os.homedir())
Get screen information
const electronScreen = require('electron').screen
const size = electronScreen.getPrimaryDisplay().size
console.log(size)
Clipboard
Copy
下例中,当我们ctrl + v时候,Electron Demo!会被复制进去.
const clipboard = require('electron').clipboard
const copyBtn = document.getElementById('copy-to')
const copyInput = document.getElementById('copy-to-input')
copyBtn.addEventListener('click', () => {
if (copyInput.value !== '') copyInput.value = ''
copyInput.placeholder = 'Copied! Paste here to see.'
clipboard.writeText('Electron Demo!')
})
Paste
const clipboard = require('electron').clipboard
const pasteBtn = document.getElementById('paste-to')
pasteBtn.addEventListener('click', () => {
clipboard.writeText('What a demo!')
const msg = `Clipboard contents: ${clipboard.readText()}`
document.getElementById('paste-from').innerHTML = msg
})
MEDIA
Print to PDF
我们可以使用webContents.printToPDF来打印pdf文档.
renderer process:
const ipc = require('electron').ipcRenderer
const printPDFBtn = document.getElementById('print-pdf')
printPDFBtn.addEventListener('click', (event) => {
ipc.send('print-to-pdf')
})
ipc.on('wrote-pdf', (event, path) => {
const msg = `Wrote PDF to: ${path}`
document.getElementById('pdf-path').innerHTML = msg
})
main process:
const fs = require('fs')
const os = require('os')
const path = require('path')
const electron = require('electron')
const BrowserWindow = electron.BrowserWindow
const ipc = electron.ipcMain
const shell = electron.shell
ipc.on('print-to-pdf', (event) => {
const pdfPath = path.join(os.tmpdir(), 'print.pdf')
const win = BrowserWindow.fromWebContents(event.sender)
win.webContents.printToPDF({}, (error, data) => {
if (error) throw error
fs.writeFile(pdfPath, data, (error) => {
if (error) throw error
shell.openExternal('file://' + pdfPath)
event.sender.send('wrote-pdf', pdfPath)
})
})
})
Take a screenshot
我们可以使用desktopCapturer来获取屏幕截屏信息:
const electron = require('electron')
const desktopCapturer = electron.desktopCapturer
const electronScreen = electron.screen
const shell = electron.shell
const fs = require('fs')
const os = require('os')
const path = require('path')
const screenshot = document.getElementById('screen-shot')
const screenshotMsg = document.getElementById('screenshot-path')
screenshot.addEventListener('click', (event) => {
screenshotMsg.textContent = 'Gathering screens...'
const thumbSize = determineScreenShotSize()
let options = {types: ['screen'], thumbnailSize: thumbSize}
desktopCapturer.getSources(options, (error, sources) => {
if (error) return console.log(error)
sources.forEach((source) => {
if (source.name === 'Entire screen' || source.name === 'Screen 1') {
const screenshotPath = path.join(os.tmpdir(), 'screenshot.png')
fs.writeFile(screenshotPath, source.thumbnail.toPNG(), (error) => {
if (error) return console.log(error)
shell.openExternal('file://' + screenshotPath)
const msg = `Saved screenshot to: ${screenshotPath}`
screenshotMsg.textContent = msg
})
}
})
})
})
function determineScreenShotSize() {
const screenSize = electronScreen.getPrimaryDisplay().workAreaSize
const maxDimension = Math.max(screenSize.width, screenSize.height)
return {
width: maxDimension * window.devicePixelRatio,
height: maxDimension * window.devicePixelRatio
}
}