ELECTRON API DEMOS笔记

原创
2017/03/12 22:12
阅读数 2K

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

 

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
3 收藏
0
分享
返回顶部
顶部