Appearance
electron简结
主进程和渲染进程
参考链接
详解
主进程
- 运行package.json中的main脚本的进程是主进程。
- 一个electron应用有且只有一个主进程。
- 主进程可以进行GUI相关的原生API操作。
渲染进程
- Electron 使用了 Chromium 来展示 web 页面,所以 Chromium 的多进程架构也被使用到。
- 每个web页面运行在它自己的渲染进程中。
- 使用BrowserWindow类开启一个渲染进程并将这个实例运行在该进程中,当一个BrowserWindow实例被销毁后,相应的渲染进程也会被终止。
- 渲染进程中不能调用原生资源,但是渲染进程中同样包含Node.js环境,所以可以引入Node.js模块,在Node.js支持下,可以在页面中和操作系统进行一些底层交互。
渲染进程调用nodejs
参考链接
详解
主进程(main.js):设置后可使用module和require全局变量
javascriptlet win = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: true, //设置为true就可以在这个渲染进程中调用Node.js } })
主进程和渲染进程通信
参考链接
详解
主进程和渲染进程之间可以通过ipcRenderer 和 ipcMain模块通信。
主进程主动向渲染进程发送消息
主进程(main.js)
javascript//主进程向渲染进程发送消息,'did-finish-load':当导航完成时发出事件,onload 事件也完成 win.webContents.on('did-finish-load', () => { win.webContents.send('msg', '消息来自主进程') })
渲染进程(index.html)
html<script> const {ipcRenderer} = require('electron') ipcRenderer.on('msg', (event, message) => { console.log(message) // 消息来自主进程 }) </script>
渲染进程主动向主进程发送消息
渲染进程(index.html)
javascriptconst {ipcRenderer} = require('electron') ipcRenderer.send('indexMsg','消息来自渲染进程')
复制代码主进程(main.js)
javascriptconst {ipcMain} = require('electron') ipcMain.on('indexMsg',(event,msg) => { console.log(msg) //消息来自渲染进程 })
渲染进程之间通信
参考链接
详解
全局共享属性
javascript//主进程 global.sharedObject = { user: '' } //渲染进程一 const {remote} = require('electron') remote.getGlobal('sharedObject').user = 'xmanlin' //渲染进程二 const {remote} = require('electron') console.log(remote.getGlobal('sharedObject').user) //xmanlin
ipcRenderer.sendTo()
javascriptipcRenderer.sendTo(webContentsId, channel, [, arg1][, arg2][, ...]) ipcRenderer.sendTo(windowId, 'ping', 'someThing') //webContentsId : Number //channel : String //...args : any[]
主进程(main.js)
javascript//创建一个新的渲染进程 let win2 = new BrowserWindow({ width: 800, height: 600, }) //为渲染进程设置唯一id win2.id = 2
页面1
html<script> const {ipcRenderer} = require('electron') //向id为2的渲染进程发送消息 ipcRenderer.sendTo(2,'msg1','来自渲染进程1的消息') </script>
页面2
html<script> const {ipcRenderer} = require('electron') ipcRenderer.on('msg1', (event, message) => { console.log(message) // 来自渲染进程1的消息 }) </script>
主进程做消息中转站
javascript//主进程 ipcMain.on('msg1', (event, message) => { yourWindow.webContents.send('msg2', message); } //渲染进程1 ipcRenderer.send('msg1', '来自渲染进程1的消息') //渲染进程2 ipcRenderer.on('msg2', (event, message) => { console.log(message) //来自渲染进程1的消息 })
项目打包
参考链接
详解
electron-builder可以打包为exe可执行文件,还可以打包为可安装程序
npm install electron-builder --save-dev
package.json 的build命令:"build": "node .electron-vue/build.js && electron-builder"
可以指定参数
--mac, -m, -o, --macos macOS打包 --linux, -l Linux打包 --win, -w, --windows Windows打包 --mwl 同时为macOS,Windows和Linux打包 --x64 x64 (64位安装包) --ia32 ia32(32位安装包)
注意:项目路径需要全英文才能打包
设置窗口默认最大化和全屏
参考链接
详解
默认最大化
javascript//主进程(main.js) let win = new BrowserWindow({show: false}) win.maximize() win.show()
默认全屏
javascript//主进程(main.js) let win = new BrowserWindow({fullscreen: true})
自定义菜单栏
参考链接
详解
使用 Menu 和 MenuItem 模块可以自定义应用程序菜单. 如果没有设置任何菜单, Electron 将默认生成一个最小的菜单
MenuItem的几个重要参数:
- label:菜单显示的文字
- click:点击菜单后的事件处理函数
- role:系统预定义的菜单,例如copy(复制)、paste(粘贴)、minimize(最小化)...
- enabled:指示是否启用该项目,此属性可以动态更改
- submenu:子菜单,也是一个MenuItem的数组
主进程
javascriptconst electron = require('electron') const BrowserWindow = electron.BrowserWindow const Menu = electron.Menu const app = electron.app let template = [{ label: '编辑', submenu: [{ label: '撤销', accelerator: 'CmdOrCtrl+Z', role: 'undo' }, { label: '重做', accelerator: 'Shift+CmdOrCtrl+Z', role: 'redo' }, { type: 'separator' }, { label: '剪切', accelerator: 'CmdOrCtrl+X', role: 'cut' }, { label: '复制', accelerator: 'CmdOrCtrl+C', role: 'copy' }, { label: '粘贴', accelerator: 'CmdOrCtrl+V', role: 'paste' }, { label: '全选', accelerator: 'CmdOrCtrl+A', role: 'selectall' }] }, { label: '查看', submenu: [{ label: '重载', accelerator: 'CmdOrCtrl+R', click: function (item, focusedWindow) { if (focusedWindow) { // 重载之后, 刷新并关闭所有的次要窗体 if (focusedWindow.id === 1) { BrowserWindow.getAllWindows().forEach(function (win) { if (win.id > 1) { win.close() } }) } focusedWindow.reload() } } }, { label: '切换全屏', accelerator: (function () { if (process.platform === 'darwin') { return 'Ctrl+Command+F' } else { return 'F11' } })(), click: function (item, focusedWindow) { if (focusedWindow) { focusedWindow.setFullScreen(!focusedWindow.isFullScreen()) } } }, { label: '切换开发者工具', 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: '应用程序菜单演示', click: function (item, focusedWindow) { if (focusedWindow) { const options = { type: 'info', title: '应用程序菜单演示', buttons: [[好的'], message: '此演示用于 "菜单" 部分, 展示如何在应用程序菜单中创建可点击的菜单项.' } electron.dialog.showMessageBox(focusedWindow, options, function () {}) } } }] }, { label: '窗口', role: 'window', submenu: [{ label: '最小化', accelerator: 'CmdOrCtrl+M', role: 'minimize' }, { label: '关闭', accelerator: 'CmdOrCtrl+W', role: 'close' }, { type: 'separator' }, { label: '重新打开窗口', accelerator: 'CmdOrCtrl+Shift+T', enabled: false, key: 'reopenMenuItem', click: function () { app.emit('activate') } }] }, { label: '帮助', role: 'help', submenu: [{ label: '学习更多', 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: '正在检查更新', enabled: false, key: 'checkingForUpdate' }, { label: '检查更新', visible: false, key: 'checkForUpdate', click: function () { require('electron').autoUpdater.checkForUpdates() } }, { label: '重启并安装更新', 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: `关于 ${name}`, role: 'about' }, { type: 'separator' }, { label: '服务', role: 'services', submenu: [] }, { type: 'separator' }, { label: `隐藏 ${name}`, accelerator: 'Command+H', role: 'hide' }, { label: '隐藏其它', accelerator: 'Command+Alt+H', role: 'hideothers' }, { label: '显示全部', role: 'unhide' }, { type: 'separator' }, { label: '退出', accelerator: 'Command+Q', click: function () { app.quit() } }] }) // 窗口菜单. template[3].submenu.push({ type: 'separator' }, { label: '前置所有', 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 })
可以使用 Menu 和 MenuItem 模块创建上下文或右键单击菜单
主进程
javascriptconst 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) })
渲染器进程
javascriptconst ipc = require('electron').ipcRenderer // 告诉主进程在单击示例按钮时显示菜单 const contextMenuBtn = document.getElementById('context-menu') contextMenuBtn.addEventListener('click', function () { ipc.send('show-context-menu') })
指定一个accelerator属性来指定操作的快捷键
javascript{ label: '最小化', accelerator: 'CmdOrCtrl+M', role: 'minimize' }
globalShortcut来注册全局快捷键
javascriptglobalShortcut.register('CommandOrControl+N', () => { dialog.showMessageBox({ type: 'info', message: '嘿!', detail: '你触发了手动注册的快捷键.', }) })
获取操作系统mac地址
参考链接
详解
javascriptvar os=require("os"); //获取mac地址 var mac = '' var networkInterfaces=os.networkInterfaces(); for(var i in networkInterfaces){ for(var j in networkInterfaces[i]){ if(networkInterfaces[i][j]["family"]==="IPv4" && networkInterfaces[i][j]["mac"]!=="00:00:00:00:00:00" && networkInterfaces[i][j]["address"]!=="127.0.0.1"){ mac = networkInterfaces[i][j]["mac"] } } }
窗体样式
参考链接
详解
主进程模块BrowserWindow用于创建和控制浏览器窗口
javascriptmainWindow = new BrowserWindow({ width: 1000, height: 800, // ... }); mainWindow.loadURL('http://www.conardli.top/');
无框窗口
javascriptnew BrowserWindow({ width: 200, height: 200, titleBarStyle: 'hidden', frame: false });
窗口拖拽
css.header { -webkit-user-select: none; -webkit-app-region: drag; }
在可拖拽区域内部设置 -webkit-app-region: no-drag则可以指定特定不可拖拽区域。
透明窗口
javascriptnew BrowserWindow({ transparent: true, frame: false });
remote
参考链接
详解
remote 模块为渲染进程(web页面)和主进程通信(IPC)提供了一种简单方法。可以调用 main 进程对象的方法, 而不必显式发送进程间消息。
javascriptimport { remote } from 'electron'; remote.dialog.showErrorBox('主进程才有的dialog模块', '我是使用remote调用的')
在调用远程对象的方法、函数或者通过远程构造函数创建一个新的对象,实际上都是在发送一个同步的进程间消息。
在渲染进程中创建的 dialog 对象其实并不在我们的渲染进程中,它只是让主进程创建了一个 dialog 对象,并返回了这个相对应的远程对象给了渲染进程。
对话框
参考链接
详解
错误提示
javascriptremote.dialog.showErrorBox('错误', '这是一个错误弹框!')
对话框
可以为指定几种不同的类型: "none", "info", "error", "question" 或者 "warning"
javascriptremote.dialog.showMessageBox({ type: 'info', title: '提示信息', message: '这是一个对话弹框!', buttons: [[确定', '取消'] }, (index) => { this.setState({ dialogMessage: `【你点击了${index ? '取消' : '确定'}!!】` }) })
文件框
用于打开或选择系统目录
javascriptremote.dialog.showOpenDialog({ properties: [[openDirectory', 'openFile'] }, (data) => { this.setState({ filePath: `【选择路径:${data[0]}】 ` }) })
信息框
javascriptlet options = { title: '信息框标题', body: '我是一条信息~~~', } let myNotification = new window.Notification(options.title, options) myNotification.onclick = () => { this.setState({ message: '【你点击了信息框!!】' }) }
系统功能
参考链接
详解
版本信息
主进程的process
javascriptprocess.versions.electron:electron版本信息 process.versions.chrome:chrome版本信息 process.versions.node:node版本信息 process.versions.v8:v8版本信息
当前应用根目录
javascriptremote.app.getAppPath()
获取当前系统根目录
nodejs
javascriptos.homedir();
复制粘贴
clipboard在渲染进程和主进程都可使用,用于在系统剪贴板上执行复制和粘贴操作。
javascript//以纯文本的形式写入剪贴板 clipboard.writeText(text[, type]) //以纯文本的形式获取剪贴板的内容 clipboard.readText([type])
截图
desktopCapturer用于从桌面捕获音频和视频的媒体源的信息。它只能在渲染进程中被调用。
javascriptgetImg = () => { this.setState({ imgMsg: '正在截取屏幕...' }) const thumbSize = this.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}`) this.setState({ imgMsg: `截图保存到: ${screenshotPath}` }) }) } }) }) } determineScreenShotSize = () => { const screenSize = screen.getPrimaryDisplay().workAreaSize const maxDimension = Math.max(screenSize.width, screenSize.height) return { width: maxDimension * window.devicePixelRatio, height: maxDimension * window.devicePixelRatio } }
程序保护
参考链接
详解
electron提供了crashReporter来记录崩溃日志,可以通过crashReporter.start创建一个崩溃报告器
javascriptimport { BrowserWindow, crashReporter, dialog } from 'electron'; // 开启进程崩溃记录 crashReporter.start({ productName: 'electron-react', companyName: 'ConardLi', submitURL: 'http://xxx.com', // 上传崩溃日志的接口 uploadToServer: false }); function reloadWindow(mainWin) { if (mainWin.isDestroyed()) { app.relaunch(); app.exit(0); } else { // 销毁其他窗口 BrowserWindow.getAllWindows().forEach((w) => { if (w.id !== mainWin.id) w.destroy(); }); const options = { type: 'info', title: '渲染器进程崩溃', message: '这个进程已经崩溃.', buttons: [[重载', '关闭'] } dialog.showMessageBox(options, (index) => { if (index === 0) mainWin.reload(); else mainWin.close(); }) } } export default function () { const mainWindow = BrowserWindow.fromId(global.mainId); mainWindow.webContents.on('crashed', () => { const errorMessage = crashReporter.getLastCrashReport(); console.error('程序崩溃了!', errorMessage); // 可单独上传日志 reloadWindow(mainWindow); }); }
当程序发生崩溃时,崩溃报日志将被储存在临时文件夹中名为YourName Crashes的文件文件夹中。submitURL用于指定崩溃日志上传服务器。 在启动崩溃报告器之前,可以通过调用app.setPath('temp', 'my/custom/temp')API来自定义这些临时文件的保存路径。还可以通过crashReporter.getLastCrashReport()来获取上次崩溃报告的日期和ID。
最小化到托盘
参考链接
详解
监听窗口的关闭事件,阻止用户关闭操作的默认行为,将窗口隐藏
javascriptfunction checkQuit(mainWindow, event) { const options = { type: 'info', title: '关闭确认', message: '确认要最小化程序到托盘吗?', buttons: [[确认', '关闭程序'] }; dialog.showMessageBox(options, index => { if (index === 0) { event.preventDefault(); mainWindow.hide(); } else { mainWindow = null; app.exit(0); } }); } function handleQuit() { const mainWindow = BrowserWindow.fromId(global.mainId); mainWindow.on('close', event => { event.preventDefault(); checkQuit(mainWindow, event); }); }
创建任务托盘
javascriptexport default function createTray() { const mainWindow = BrowserWindow.fromId(global.mainId); const iconName = process.platform === 'win32' ? 'icon.ico' : 'icon.png' tray = new Tray(path.join(global.__dirname, iconName)); const contextMenu = Menu.buildFromTemplate([ { label: '显示主界面', click: () => { mainWindow.show(); mainWindow.setSkipTaskbar(false); } }, { label: '退出', click: () => { mainWindow.destroy(); app.quit(); } }, ]) tray.setToolTip('electron-react'); tray.setContextMenu(contextMenu); }