Electron 入门

Electron 入门

本文章的 electron 版本:19.0.3

学习体会:

  1. electron 更新非常快,一切以官方文档优先
  2. 一定多敲代码而不是看看,因为 electron 坑挺多的。提前遇坑,未来的开发体验会更顺畅

1. electron 安装

yarn init -y # 包管理初始化
yarn add electron # 下载 electron

下载完后通过 npx electron -v 查看是否下载成功。如果下载成功了会返回版本号。

此时在根目录中执行以下命令:./node_modules/.bin/electron

electron 会开启一个默认界面

2. hello world

在一个 HTML 文件里写一个最简单的 hello world。首先创建一个 main.js ,这是主进程,用于启动加载页面。当 app ready 状态的时候,触发事件,此时创建一个 BrowserWindow 实例,并在这个窗口内加载页面。

// 引入 app 和窗口引用
const { app, BrowserWindow } = require("electron");

// 创建窗口函数
const createWindow = () => {
  const win = new BrowserWindow({
    width: 300,
    height: 300,
  });

  win.loadFile("index.html");
};

// 监控主进程,准备好后启动窗口
app.whenReady().then(() => {
  createWindow();
});

写完后执行命令 ./node_modules/.bin/electron .,或者为了以后方便,写在 package.json 里的 scripts,然后 yarn start

  "scripts": {
    "start": "./node_modules/.bin/electron ."
  }

可以看到 app 被成功加载出来。

关闭进程是好习惯,关闭窗口后,根据操作系统类别来判断是否完全退出。

Linux 和 Windows 系统如果关闭了所有窗口,便是完全退出了 App,macOS 需要 docker 栏下方的白色点消失才代表退出 App,窗口关闭了并不代表 App 完全退出。

因此 macOS 和非 macOS 的退出判断逻辑得翻开。以下是 main.js 所有代码,包括了退出的逻辑:

// 引入 app 和窗口引用
const { app, BrowserWindow } = require("electron");

// 创建窗口函数
const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
  });

  // 加载 index.html,启动渲染进程
  win.loadFile("index.html");
  // 打开开发工具
  // mainWindow.webContents.openDevTools()
};

// 这段程序将会在 Electron 结束初始化
// 和创建浏览器窗口的时候调用
// 部分 API 在 ready 事件触发后才能使用。
app.whenReady().then(() => {
  createWindow();

  app.on("activate", () => {
    // On macOS it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

// 除了 macOS 外,当所有窗口都被关闭的时候退出程序。 There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on("window-all-closed", () => {
  if (process.platform !== "darwin") app.quit();
});

// In this file you can include the rest of your app's specific main process
// code. 也可以拆分成几个文件,然后用 require 导入。

3. electron 应用获取本地资源

electron 可以集成 node 的功能。现在要实现一个功能,点击 App 上的按钮,来获取到本地文件上的信息并输出。

修改 main.js 里的配置项

首先,开启窗口的 window 配置里面配置一个属性,webPreferences,配置自定义选项。

  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      // 集成 node
      nodeIntegration: true,
      // 关闭上下文隔离,兼容 require 引入
      contextIsolation: false,
    },
  });

nodeIntegration: true 表示页面具有 node 继承,并且可以使用 node API 去访问低层系统资源,这样就可以读取本地资源了。

后边 node 里边会用到 require 语句,配置 contextIsolation: false 即可。

页面 index 写法

常规的原生写法,后面测试效果用

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>hello world</title>
</head>

<body>
  <button id="btn">点击获取运动列表</button>
  <div id="sports"></div>
  <script src="./render/index.js"></script>
</body>

</html>
render/index.js 写法

里面写了点击按钮事件触发后,读取 txt 文件并填充 div 内容的逻辑。

const fs = require("fs");
const path = require("path");

window.onload = function () {
  const btn = document.querySelector("#btn");
  const sports = document.querySelector("#sports");
  btn.addEventListener("click", function () {
    const filePath = path.resolve(__dirname, "sport.txt");
    fs.readFile(filePath, (err, data) => {
      sports.innerHTML = data;
    });
  });
};

4. remote

remote 帮助在渲染进程中调用主进程里使用的方法和对象。

举个例子,想要有个功能,希望点击应用的其中一个按钮后,启动一个新应用窗口。实现效果如下图:

因为窗口页面是渲染进程,启动新应用窗口需要用到主进程。因此要实现这个功能的话,就需要用到 remote。

安装 remote 相关包:

yarn add @electron/remote

4.1 主进程支持 remote

主进程代码:

// 引入 app 和窗口引用
const { app, BrowserWindow } = require("electron");

// 初始化 remote
require("@electron/remote/main").initialize();

// 创建窗口函数
const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      // 集成 node
      nodeIntegration: true,
      // 关闭上下文隔离,兼容 require 引入
      contextIsolation: false,
    },
  });

  // 加载 index.html
  win.loadFile("demo.html");
  // 打开开发工具
  // mainWindow.webContents.openDevTools()
};

// 这段程序将会在 Electron 结束初始化
// 和创建浏览器窗口的时候调用
// 部分 API 在 ready 事件触发后才能使用。
app.whenReady().then(() => {
  createWindow();

  app.on("activate", () => {
    // On macOS it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

// 开启 remote
app.on("browser-window-created", (_, window) => {
  require("@electron/remote/main").enable(window.webContents);
});

// 除了 macOS 外,当所有窗口都被关闭的时候退出程序。 There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on("window-all-closed", () => {
  if (process.platform !== "darwin") app.quit();
});

// In this file you can include the rest of your app's specific main process
// code. 也可以拆分成几个文件,然后用 require 导入。

这里有两个重要的细节:

  1. remote 需要初始化

    // 初始化 remote
    require("@electron/remote/main").initialize();
    
  2. app 开启 remote 的支持

    // 开启 remote
    app.on("browser-window-created", (_, window) => {
      require("@electron/remote/main").enable(window.webContents);
    });
    

4.2 写加载的 HTML 页面

这个 demo 加载的是 demo.html 页面,代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <button id="btn">打开新的窗口</button>
  <script src="render/demo.js"></script>
</body>
</html>

有个按钮,期望点击它会产生一个新窗口。同时加载 demo.js 文件。

4.3 渲染进程调用主进程方法

在 demo.js 中,代码如下:

const { BrowserWindow } = require("@electron/remote");

const btn = document.querySelector("#btn");

const createWindow = () => {
  const win = new BrowserWindow({
    width: 500,
    height: 500,
  });
  win.loadFile("newPage.html");
};

window.onload = function () {
  btn.addEventListener("click", createWindow);
};

看起来和主进程的代码差别不是很大,在点击的时候,创建一个新的页面。其中,引用的 BrowserWindow 来自于 @electron/remote

5. electron 菜单栏的创建和绑定事件

新建一个文件,menu.js,用于写菜单栏,然后放到主进程里。

5.1 写菜单栏样式

用什么数据结构能准确地表示菜单栏?如果自己设计的话,肯定会想到数组,数组内存放着每个菜单选项,然后菜单选项是一个对象,里面存储菜单选项的相关信息。

官网上有写菜单栏的示范案例:

const { app, Menu } = require("electron");

const isMac = process.platform === "darwin";

const template = [
  // { role: 'appMenu' }
  ...(isMac
    ? [
        {
          label: app.name,
          submenu: [
            { role: "about", label: "关于" },
            { type: "separator" },
            { role: "services" },
            { type: "separator" },
            { role: "hide" },
            { role: "hideOthers" },
            { role: "unhide" },
            { type: "separator" },
            { role: "quit" },
          ],
        },
      ]
    : []),
  // { role: 'fileMenu' }
  {
    label: "File",
    submenu: [isMac ? { role: "close" } : { role: "quit" }],
  },
  // { role: 'editMenu' }
  {
    label: "Edit",
    submenu: [
      { role: "undo" },
      { role: "redo" },
      { type: "separator" },
      { role: "cut" },
      { role: "copy" },
      { role: "paste" },
      ...(isMac
        ? [
            { role: "pasteAndMatchStyle" },
            { role: "delete" },
            { role: "selectAll" },
            { type: "separator" },
            {
              label: "Speech",
              submenu: [{ role: "startSpeaking" }, { role: "stopSpeaking" }],
            },
          ]
        : [{ role: "delete" }, { type: "separator" }, { role: "selectAll" }]),
    ],
  },
  // { role: 'viewMenu' }
  {
    label: "View",
    submenu: [
      { role: "reload" },
      { role: "forceReload" },
      { role: "toggleDevTools" },
      { type: "separator" },
      { role: "resetZoom" },
      { role: "zoomIn" },
      { role: "zoomOut" },
      { type: "separator" },
      { role: "togglefullscreen" },
    ],
  },
  // { role: 'windowMenu' }
  {
    label: "Window",
    submenu: [
      { role: "minimize" },
      { role: "zoom" },
      ...(isMac
        ? [
            { type: "separator" },
            { role: "front" },
            { type: "separator" },
            { role: "window" },
          ]
        : [{ role: "close" }]),
    ],
  },
  {
    role: "help",
    submenu: [
      {
        label: "Learn More",
        click: async () => {
          const { shell } = require("electron");
          await shell.openExternal("https://electronjs.org");
        },
      },
    ],
  },
];

const menu = Menu.buildFromTemplate(template);

module.exports = {
  menu
};

上面的 template 变量是存储菜单栏格式的数据结构。里面的基本信息有:

  • role:职责,内置了常用的功能,不用自己写菜单点击后的效果

  • label:菜单栏显示文字。如果没有 label,则使用 role 配置的显示文字

  • submenu:存放子菜单

  • type: "separator":分隔符

  • click:点击菜单选项后发生的事件

    例如点击后想跳出一个新窗口,代码这样写:

    click: () => {
      let win = new BrowserWindow({
        width: 500,
        height: 500,
      });
      win.loadFile("newPage.html");
      win.on("closed", () => {
        win = null
      });
    };
    

    官方示例里:

    {
      label: "Learn More",
      click: async () => {
        const { shell } = require("electron");
        await shell.openExternal("https://electronjs.org");
      },
    },
    
  • acclerator:绑定快捷键

    菜单栏里的选项都可以绑定快捷键,且 role 自动都帮忙配置好了,如果要自己配置快捷键,只要如下配置即可。

    accelerator: isMac ? 'Alt+Cmd+I' : 'Alt+Shift+I',
    

建立起数据结构后,用 Menu 对象进行创建。

macOS 适配

里面还有个 isMac,用来判断当前系统是否是 macOS 系统。如果是 macOS 系统,就会多一个 label 为 app.name 的菜单。为啥要这样做,等下文的显示效果出来就知道了。

5.2 在主进程中引用菜单栏并生效

当 app 准备好后,生成菜单栏。

main.js 部分代码如下:

app.whenReady().then(() => {
  createWindow();
  // 创建菜单栏
  Menu.setApplicationMenu(menu);
  app.on("activate", () => {
    // On macOS it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

5.3 显示效果

启动后看一下菜单栏的显示效果:

可以看出是一一对应的,我因为把 about 的 label 设置为了“关于”,菜单栏上才显示自己定义的“关于”字样。

苹果系统因为软件一定会有一个 app.name 的菜单选项,并且系统会自定义菜单选项,因此自定义 app.name 菜单是挺有必要的。

6. electron 右键菜单

右键点击出现菜单事件在渲染进程中书写。因为菜单栏的创建需要调用主进程的方法,因此需要调用 remote。

// render/demo.js
const { BrowserWindow, Menu, getCurrentWindow } = require("@electron/remote");

const btn = document.querySelector("#btn");

const createWindow = () => {
  const win = new BrowserWindow({
    width: 500,
    height: 500,
  });
  win.loadFile("newPage.html");
};

window.onload = function () {
  btn.addEventListener("click", createWindow);
};

// 右键菜单内容,remote 的 Menu 不支持 role,有点麻烦
const menuList = [
  { role: "copy", label: "复制", accelerator: "cmd+c" },
  { role: "paste", label: "粘贴", accelerator: "cmd+d" },
];

const contextmenu = Menu.buildFromTemplate(menuList);

// 右键触发事件
window.addEventListener("contextmenu", (e) => {
  // 阻止默认行为
  e.preventDefault();
  contextmenu.popup({ window: getCurrentWindow() });
});

实现效果:

7. 通过链接打开浏览器

7.1 默认情况

现在渲染页面的 HTML 如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h1>
    <a id="baidu" href="https://www.baidu.com">百度一下</a>
  </h1>
  <script src="./render/demo2.js"></script>
</body>
</html>

那么在 electron 中点击 a 标签的默认效果是,在当前窗口中打开。

7.2 在浏览器中打开网页

如果想要点击 a 标签后,页面在默认的浏览器中打开而不是当前的 electron 窗口,就需要用到 shell.openExternal

渲染进程中这样写:

const { shell } = require("electron");

const baidu = document.getElementById("baidu");
baidu.addEventListener("click", (e) => {
  e.preventDefault();
  // 获取当前元素的 href
  const baiduHref = baidu.getAttribute("href");
  shell.openExternal(baiduHref);
});

便可以在本地的默认浏览器打开百度网页。

8. electron 嵌入网页和打开子窗口

8.1 嵌入网页

在主进程里这样写就行:

// 创建窗口函数
const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      // 集成 node
      nodeIntegration: true,
      // 关闭上下文隔离,兼容 require 引入
      contextIsolation: false,
    },
  });
  // 创建并在页面内设置内嵌网页
  const view = new BrowserView();
  win.setBrowserView(view);
  // 设置 view 样式和属性
  view.setBounds({
    x: 50,
    y: 100,
    width: 400,
    height: 300,
  });
  // 加载百度网页
  view.webContents.loadURL("https://www.baidu.com");
  // 加载 index.html
  win.loadFile("demo2.html");
  // 打开开发工具
  // mainWindow.webContents.openDevTools()
};

使用 BrowserView 类创建实例 view,然后窗口设置 view 并设置相关的属性,如在窗口里的坐标和显示的长宽等。

显示效果如下:

8.2 打开子窗口

现在在渲染进程里写点击按钮后,打开子窗口的事件:

这里使用 window.open 方法来打开。这个方法默认是在一个新窗口里打开的。

// render/demo2.js
const mybtn = document.getElementById("mybtn");
mybtn.addEventListener("click", (e) => {
  window.open("https://www.baidu.com");
});

显示效果如下:

9. 子窗口向父窗口通信

9.1 父窗口页面代码

渲染进程的父窗口改成这样:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="mytext"></div>
  <button id="mybtn">打开子窗口</button>
  <script src="./render/demo2.js"></script>
</body>
</html>

有一个按钮用来打开子窗口,然后 #mytest 用来接受父窗口传递过来的信息。

9.2 父窗口打开子窗口

常规代码,就不多说了

// render.js
const mybtn = document.getElementById("mybtn");
mybtn.addEventListener("click", (e) => {
  window.open("children.html");
});

9.3 子窗口页面代码

因为子窗口的代码比较少,所以 js 代码也写在了 html 文件中。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <h2>我是子窗口</h2>
  <button id="childrenBtn">向父窗口传递信息</button>
</body>
<script>
  const childrenBtn = document.getElementById("childrenBtn")
  childrenBtn.addEventListener('click', (e) => {
    // 子窗口传递信息给父窗口
    window.opener.postMessage('我是子窗口传递过来的信息')
  })
</script>

</html>

子窗口当点击按钮的时候,触发监听事件,监听事件将会通过 window.opener.postMessage 传递信息到父窗口。

9.3 父窗口接受子窗口传递的信息

通过监听 message 事件触发回调,回调里有一个参数 message,message.data 即为子窗口传递过来的数据。

接受到数据后,用 innerHTML 显示到窗口上即可。

// render/demo2.js
window.addEventListener("message", (message) => {
  const mytext = document.getElementById("mytext");
  mytext.innerHTML = message.data;
});
最终效果

10. 启用文件对话框

启用一个新窗口,名称为 dialog-test.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <button id="openBtn">点击按钮选择文件</button>
  <img id="img" src="" alt="">
</body>
<script src="../render/dialog-test.js"></script>

</html>

期望的目标是,点击按钮后弹出文件对话框,只能选择图片。选择之后可以将图片显示到窗口中。

接下来讲一下流程:

  1. 因为调用文件对话框需要用到主进程方法,因此得从 remote 中引用 dialog
  2. 接着,点击按钮后触发事件,dialog.showOpenDialog 就是打开文件对话框的方法,里边的参数有对话框标题、限制的文件类型、确认按钮自定义文本、选择多个文件等。这是个异步方法,用户选择完后返回结果 result。
  3. 本测试代码是单选图片,因此获取图片的路径只需要 result.filePaths[0] 即可。

对应的渲染进程 js 代码如下:

// render/dialog-test.js
const { dialog } = require("@electron/remote");

const openBtn = document.getElementById("openBtn");
openBtn.addEventListener("click", async () => {
  // 这个方法是异步的,返回的是用户选择的文件
  try {
    const result = await dialog.showOpenDialog({
      // 文件对话框标题
      title: "请选择文件",
      // 限制文件类型
      filters: [{ name: "Images", extensions: ["jpg", "png", "gif"] }],
      // 自定义按钮
      buttonLabel: "打开图片",
    });
    const img = document.getElementById("img");
    img.setAttribute("src", result.filePaths[0]);
  } catch (error) {
    console.log(error);
  }
});

显示效果:

11. 保存文件对话框和保存文件操作

保存文件对话框样子本来就和文件对话框很像,使用 dialog.showSaveDialog 便可以生成保存文件对话框,同时这个方法也是异步的,用户在点击保存后返回结果 result,其中 result.filePath 为保存路径。

下面要实现一个功能,存储文件后,将 “hello” 写到文件里。

实现的代码如下:

// render/dialog-test.js
const saveBtn = document.getElementById("saveBtn");
saveBtn.addEventListener("click", async () => {
  try {
    const result = await dialog.showSaveDialog({
      title: "保存文件",
    });
    // result.filePath 是保存的路径
    fs.writeFile(result.filePath, "hello", (error) => {
      if (error) {
        console.log(`创建失败:${error}`);
      } else {
        console.log("创建成功");
      }
    });
  } catch (error) {
    console.log(error);
  }
});

显示效果:

12. electron 消息对话框

上面的文件对话框和保存对话框是特殊的消息对话框,现在介绍一下常规的消息对话框。

消息对话框打开方式:dialog.showMessageBox。用法和上面的基本相同。

重点在于 buttons,如果点击 buttons 里的第一个元素,那么返回值 result.response 的值为 0,第二个元素为 1,以此类推。

// render/dialog-test.js
const messageBtn = document.getElementById("messageBtn");
messageBtn.addEventListener("click", async () => {
  try {
    const result = await dialog.showMessageBox({
      // 对话框类型
      type: "info",
      title: "这是个普通的对话框",
      message: "点击啥反应没有",
      // 按钮显示文本,value 值为 index,重点
      buttons: ["不点了", "点一下", "点我"],
    });
    const value = result.response;
    // ......
  } catch (error) {
    console.log(error);
  }
});

关于处理的代码就不写了,根据 value 值判断然后做相应的逻辑即可。

显示效果:

13. electron 断网测试

没啥难度,就是得需要知道两个事件,一个是 online 事件,一个是 offline 事件,在渲染进程里监听即可。

// render/online-check.js

// offline -> online 网络连接的瞬间触发
window.addEventListener("online", () => {
  alert("恢复网络~~~");
});

// on-line -> offline 断网的瞬间触发
window.addEventListener("offline", () => {
  alert("断网了...");
});

断网效果:

联网效果同理。

14. 消息通知

消息通知本质上是 HTML5 内容,网页端也可以实现。

js 代码如下:

const notifyBtn = document.getElementById("notifyBtn");

notifyBtn.addEventListener("click", () => {
  const option = {
    title: "消息标题",
    body: "消息内容",
    // icon: "通知图标 URL"
  };
  new window.Notification(option.title, option);
});

15. electron 注册全局快捷键

全局快捷键,例如 vscode 打开资源管理器栏和快捷搜索文件等操作,用快捷键就可以呼出。

全局快捷键是全局注册且得用到主线程方法,因此在主进程里注册即可。必须在 app ready 之后注册!!!

注册快捷键关键代码:

// 注册全局快捷键
  globalShortcut.register("CommandOrControl+E", () => {
    win.loadURL("https://www.baidu.com");
  });
};

接受两个参数,第一个参数是快捷键组合,第二个参数是触发快捷键后的回调。上面的意思是,按下 cmd/ctrl + e,窗口加载百度主页。

注意:**全局快捷键意思就是,在操作系统的全局都生效这个快捷键。**比如,我在网易云音乐界面选歌的时候 cmd+e 一下,启动的 app 也会加载百度网页,这很有可能占用了其他 app 的快捷键。因此,在 app 退出的时候需要注销快捷键,释放全局对快捷键的绑定。

解除绑定的关键代码:

app.on('will-quit', () => {
  // 注销快捷键
  // globalShortcut.unregister('CommandOrControl+X')

  // 注销所有快捷键
  globalShortcut.unregisterAll()
})

主进程所有代码如下:

// 引入 app 和窗口引用
const {
  app,
  BrowserWindow,
  Menu,
  BrowserView,
  globalShortcut,
} = require("electron");
const { menu } = require("./main/menu");

// 初始化 remote
require("@electron/remote/main").initialize();

// 创建窗口函数
const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      // 集成 node
      nodeIntegration: true,
      // 关闭上下文隔离,兼容 require 引入
      contextIsolation: false,
    },
  });

  // 加载 HTML 文件
  win.loadFile("./pages/notification.html");

  // 注册全局快捷键
  globalShortcut.register("CommandOrControl+E", () => {
    win.loadURL("https://www.baidu.com");
  });
};

// 这段程序将会在 Electron 结束初始化
// 和创建浏览器窗口的时候调用
// 部分 API 在 ready 事件触发后才能使用。
app.whenReady().then(() => {
  createWindow();
  // 创建菜单栏
  Menu.setApplicationMenu(menu);
  app.on("activate", () => {
    // On macOS it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

// 开启 remote
app.on("browser-window-created", (_, window) => {
  require("@electron/remote/main").enable(window.webContents);
});

// 除了 macOS 外,当所有窗口都被关闭的时候退出程序。 There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on("window-all-closed", () => {
  if (process.platform !== "darwin") app.quit();
});

app.on('will-quit', () => {
  // 注销快捷键
  // globalShortcut.unregister('CommandOrControl+X')

  // 注销所有快捷键
  globalShortcut.unregisterAll()
})

// In this file you can include the rest of your app's specific main process
// code. 也可以拆分成几个文件,然后用 require 导入。
判断全局快捷键是否已经注册
const isReigistered = globalShortcut.isRegistered('CommandOrControl+X')

根据返回的布尔值来判断对应的快捷键是否注册成功,这个可以避免快捷键冲突问题。

16. 剪贴板功能使用

剪贴板功能常用于点击某个按钮来让 app 帮你复制一段内容,例如激活码、邀请码等。剪贴板的调用要用到 electron 里的 clipboard 对象。

剪贴板里写入内容只需要调用 clipboard.writeText 方法即可。

<body>
  <div>
    激活码: <span id="code">f43ojt8h39ghjoh843g8h</span>
    <button id="btn">点击复制激活码</button>
  </div>
</body>
const { clipboard } = require("@electron/remote");

const code = document.getElementById("code");
const btn = document.getElementById("btn");

btn.addEventListener("click", () => {
  // 剪贴板写入内容
  clipboard.writeText(code.innerHTML);
  new window.Notification("复制成功", { body: "提示内容" });
});

点击显示效果:

文本也成功地贴到了剪贴板里。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值