Remote 模块的使用

当我们知道了 Electron 有主进程和渲染进程后,我们还要知道一件事,就是 Electron 的 API 方法和模块也是分为可以在主进程和渲染进程中使用。那如果我们想在渲染进程中使用主进程中的模块方法时,可以使用Electron Remote解决在渲染和主进程间的通讯。

渲染进程中打开新窗口

在之前的 index.html 文件上修改如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <meta
      http-equiv="X-Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <title></title>
  </head>

  <body>
    <h1>this is the main index.html</h1>
    <button id="btn">打开新的窗口</button>
  </body>
  <script>
    const { remote } = require("electron");
    const btn = document.querySelector("#btn");
    const BrowserWindow = require("electron").remote.BrowserWindow;
    btn.onclick = () => {
      let newWin = new BrowserWindow({
        width: 300,
        height: 400
      });
      newWin.loadFile("./new.html");
      newWin.on("close", () => {
        newWin = null;
      });
    };
  </script>
</html>

::: tip 当我们在渲染进程中使用 remote,必须在主线程中将webPreferences.enableRemoteModule设置为 true。否则将无法使用远程调用模块。 :::

菜单的基本使用

编写菜单模板

创建菜单模板的方式有两种:

  1. 通过 MenuItem 构造
const { Menu, MenuItem } = require("electron");
const menu = new Menu();
menu.append(
  new MenuItem({
    label: "Electron",
    submenu: [
      {
        role: "Help",
        accelerator:
          process.platform === "darwin" ? "Alt+Cmd+H" : "Ctrl+Shift+H",
        click: () => {
          console.log(`111`);
        }
      }
    ]
  })
);

new MenuItem([options])

  • options Object (可选)
    • click Function (可选) - 回调函数,当被点击的时候触发。回调参数如下:
      • menuItem MenuItem
      • browserWindow BrowserWindow undefined - 窗口未打开时,其值为 undefined
      • event KeyboardEvent
    • role String (可选) - 可以是 undo, redo, cut, copy, paste, pasteAndMatchStyle等等。 定义菜单项的行为,当传入的参数值是以上列举之一时,click 属性将被忽略。 参见 roles
    • type String (可选)-可以是 normal、separator、submenu、checkbox 或 radio。
    • label String (可选)
    • accelerator String (可选) - 快捷键。 详参见 Accelerator
    • submenu (MenuItemConstructorOptions[] Menu) (可选)
  1. 通过一个和 MenuItem 配置参数一致数组对象和 Menu.buildFromTemplate 构造
const BrowserWindow = require("electron").remote.BrowserWindow;

const template = [
  {
    label: "编辑",
    sublabel: "desc",
    submenu: [
      {
        label: "撤销",
        accelerator: "Ctrl+Shift+Z",
        click: () => {
          console.log(`Undo`);
        }
      }
    ]
  },
  {
    label: "打开新窗口",
    click: () => {
      let win = new BrowserWindow({
        width: 200,
        height: 300
      });
      win.loadFile("./new.html");
      win.on("close", () => {
        win = null;
      });
    }
  }
];

const menu = Menu.buildFromTemplate(template);

创建顶部菜单

创建好了模板之后,在主线程中,我们就可以通过 Menu.setApplicationMenu( )方法来将创建顶部菜单

// main.js
...
Menu.setApplicationMenu(menu);

创建右键菜单

右键菜单的响应事件是写在渲染进程中的,也就是写在index.html中的,所以要是使用,就用到到remote模块进行操作了。

// index.html
...
const { remote } = require("electron")
const template = [
    { label: "刷新" },
    { label: "复制" },
    { label: "粘贴" },
    {
        label: "新建", submenu: [{
            label: "文件夹",
            accelerator: "Ctrl+Shift+Z",
            click: (() => {
                console.log(`Undo`)
            })
        }],
    }
]

const contextmenu = remote.Menu.buildFromTemplate(template);

window.addEventListener("contextmenu", (e) => {
    e.preventDefault();
    contextmenu.popup({ window: remote.getCurrentWindow() })

})

浏览器打开链接

默认案例演示

我们先来看一下,在 electron 中默认打开一个链接是什么样的,代码如下:

// index.html
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title></title>
  </head>

  <body>
    <a href="http://175.24.90.8:3200/">访问我的博客</a>
  </body>
</html>

这时候我们运行程序,点击链接以后,可以看到是在窗口中直接打开的,而不是在浏览器中打开。我们现在要作的就是在浏览器中打开。

Shell 模块在浏览中打开链接

// index.html
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title></title>
  </head>

  <body>
    <a id="oLink" href="http://175.24.90.8:3200/">访问我的博客</a>
  </body>

  <script>
    const { shell } = require("electron");
    const oLink = document.querySelector("#link");
    oLink.onclick = function(e) {
      e.preventDefault();
      const href = this.getAttribute("href");
      if (href) {
        shell.openExternal(href);
      }
    };
  </script>
</html>

子窗口和父窗口之间的通讯

window.open函数

打开一个新窗口并加载 URL。

当调用 window.open 以在网页中创建新窗口时,将为url 创建一个新的BrowserWindow 实例,并返回一个代理至 window.open以让页面对其进行有限的控制。

为了与传统的web page相兼容,这个代理只保留的标准的函数方法,若想完全控制新窗口,请直接使用BrowserWindow方法。

新创建的BroswerWindow默认会继承父窗口的options。若你想覆盖,请提供features参数。

window.open(url[, frameName][, features])

  • url String - 页面地址
  • frameName String(可选)
  • features String(可选)

创建一个新窗口,并返回一个 BrowserWindowProxy 类的实例。

features 字符串遵循标准浏览器的格式,但每个 feature 必须是BrowserWindow 选项中的字段。 例如: zoomFactor, nodeIntegration, preload, javascript, contextIsolation, webviewTag.

示例如下:

window.open("http://175.24.90.8:3200/", "_blank", "nodeIntegration=no");
注意:
  • 如果在父窗口中禁用了 Node integration, 则在打开的 window 中将始终被禁用。
  • 如果在父窗口中启用了上下文隔离, 则在打开的 window中将始终被启用。
  • 父窗口禁用 Javascript,打开的 window 中将被始终禁用
  • features 中给定的非标准特性 (不由 Chromium 或 Electron 处理) 将被传递到 additionalFeatures 参数中的任何已注册 webContent 的 new-window 事件处理程序。

子父窗口之间的通讯

window.opener.postMessage()子窗口向父窗口传递消息

window.opener.postMessage(message,targetOrigin),是将消息发送给指定来源的父窗口,如果未指定来源则发送给*,即所有窗口。

  • message : 传递的消息,是 String 类型的值
  • targetOrigin : 指定发送的窗口

新建一个popup.html。当点击按钮的时候向父窗口传递消息

// popup.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>popup_page</title>
  </head>

  <body>
    <h1>我是popup的子窗口</h1>
    <button id="popBtn">向父窗口传递信息</button>
  </body>
  <script type="text/javascript">
    const oBtn = document.querySelector("#popBtn");
    oBtn.onclick = function(e) {
      window.opener.postMessage("this is message from popup_page");
    };
  </script>
</html>

index.html中打开popup.html并监听message事件

// index.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>Remote</title>
  </head>

  <body>
    <button id="btn">window.open打开新的窗口</button>
    接收到的消息是:
    <p id="text"></p>
  </body>
  <script>
    iframeBtn.onclick = function() {
      window.open("./popup.html");
    };

    window.addEventListener("message", msg => {
      const oText = document.querySelector("#text");
      oText.innerHTML = msg.data;
    });
  </script>
</html>

BrowserWindowProxy

操纵子浏览器窗口 使用 window.open 创建一个新窗口时会返回一个 BrowserWindowProxy对象,并提供一个有限功能的子窗口.

实例方法

BrowserWindowProxy 对象具有以下实例方法:

win.blur()

将焦点从子窗口中移除.

win.close()

不调用卸载事件,便关闭了子窗口。

win.focus()

聚焦子窗口(即窗口置顶)

实例属性

BrowserWindowProxy 对象具有以下实例属性:

win.closed

在子窗口关闭后设置为 true 的 Boolean

嵌入网页 BroswerView

创建和控制视图

BrowserView 被用来让 BrowserWindow 嵌入更多的 web 内容。 它就像一个子窗口,除了它的位置是相对于父窗口。 这意味着可以替代webview标签.

这很类似 Web 中的<iframe>标签。需要注意的是 BrowserView 是主进程中的类,所以只能在主进程中使用。

打开根目录下打开main.js,直接引入并使用 BrowserView 就可以实现键入网页到应用中。

const { BrowserView } = require("electron");
const view = new BrowserView();
mainWindow.setBrowserView(view);
view.setBounds({ x: 20, y: 200, height: 500, width: 400 });
view.webContents.loadURL("http://175.24.90.8:3200/");

new BrowserView([options])

  • options Object (可选)

实例方法

使用 new BrowserView 创建的对象具有以下实例方法:

  • view.setBounds(bounds) 实验功能

    • bounds Retangle
      • x Number (必填) - 相对于主窗口左上角的 x 轴坐标
      • y Number (必填) - 相对于主窗口左上角的 y 轴坐标
      • width Number (必填) - 矩形窗口的宽度
      • height Number (必填) - 矩形窗口的高度

调整视图的大小,并将它移动到窗口边界

  • view.getBounds(bounds) 实验功能

返回 Rectangle

  • view.setAutoResize(options) 实验功能

    • options Object (可选)
      • width Boolean (可选) - 网页的宽度是否跟随窗口的变化而变化
      • height Boolean (可选) - 网页的高度是否跟随窗口的变化而变化
      • horizontal Boolean (可选) - 网页的宽度和 x 轴坐标是否跟随窗口的变化而变化
      • vertical Boolean (可选) - 网页的高度和 y 轴坐标是否跟随窗口的变化而变化

配置视图改变时,网页的缩放规则

  • view.setBackgroundColor(color) 实验功能

设置网页窗口的背景颜色

Dialog

::: tip 显示用于打开和保存文件、警报等的本机系统对话框。详情 :::

dialog.showOpenDialog([browserWindow,]options)

  • browserWindow BrowserWindow (可选)
  • options Object (必填)
    • title String (可选) - 对话框窗口的标题
    • defaultPath String (可选) - 对话框的默认展示路径
    • buttonLabel String (可选) - 「确认」按钮的自定义标签, 当为空时, 将使用默认标签。
    • filters FileFilter - 文件过滤配置

返回Promise<Object>Object 包含以下成员:

  • canceled Boolean 用户是否点击了弹框内的取消按钮
  • filePaths String[] - 用户选择的文件路径的数组. 若用户点击取消,则返回空数组。

browserWindow 参数允许该对话框将自身附加到父窗口, 作为父窗口的模态框。

filters表明了哪些文件是可以被选取的,例如:

...
{
  filters: [
    { name: 'Images', extensions: ['jpg', 'png', 'gif'] },
    { name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] },
    { name: 'Custom File Type', extensions: ['as'] },
    { name: 'All Files', extensions: ['*'] }
  ]
}

下面的例子标明了如何在渲染进程中打开一个文件选取对话框

const { dialog } = require("electron").remote;
dialog
  .showOpenDialog({
    title: "请选择图片",
    buttonLabel: "打开这个",
    filters: [{ name: "jpg", extensions: ["jpg"] }]
  })
  .then(data => {
    console.log(data.canceled, data.filePaths);
  });

dialog.showSaveDialog([browserWindow,]options)

  • browserWindow BrowserWindow (可选)
  • options Object (必填)
    • title String (可选) - 对话框窗口的标题
    • defaultPath String (可选) - 对话框的默认展示路径
    • buttonLabel String (可选) - 「确认」按钮的自定义标签, 当为空时, 将使用默认标签。
    • filters FileFilter - 文件过滤配置

返回Promise<Object>Object 包含以下成员:

  • canceled Boolean 用户是否点击了弹框内的取消按钮
  • filePath String Undefined - 文件保存的路径。 若用户点击取消,则返回 undefined。

下面的例子展示了如何保存文件并写入内容

const fs = require("fs");
const { dialog } = require("electron").remote;

dialog
  .showSaveDialog({
    title: "保存文件"
  })
  .then(data => {
    if (!data.canceled) {
      fs.writeFileSync(data.filePath, "这是我用fs模块保存的文件啊");
    }
  })
  .catch(err => {
    console.error(err);
  });

dialog.showErrorBox(title,content)

  • title String - 显示在错误框中的标题.
  • content String - 显示在错误框中的文本内容.

显示一个显示错误消息的模态对话框。

这个 API 可以在 app 模块触发 ready 事件之前被安全地调用,它通常用在启动时报告错误。

dialog.showMessageBox([browserWindow,]options)

  • browserWindow BrowserWindow (可选)
  • options Object (必填)
    • type String (可选) - 可以为 none, info, error, question 或者 warning. 在 Windows 上, questioninfo显示相同的图标, 除非你使用了 “icon” 选项设置图标。
    • title String (可选) - message box 的标题,一些平台不显示。
    • message String - message box 的内容。
    • detail String (可选) - 额外信息。
    • buttons String - 字符串组成的按钮数组,在 windows 中,若此参数为空,则会默认显示一个”OK”的按钮。
    • checkboxLabel String (可选) - 若值不为空,则 message box 会展示一个一个 checkbox,并将 label 设置为此值。
    • checkboxChecked Boolean (可选) - 是否默认选中 checkbox,默认 false。
    • icon NativeImage (可选) - 弹框的 icon 。
    • noLink Boolean (可选) - 是否不开启联想(尝试用本地所使用的语言去替换 Buttons 中的配置,例如 Yes 联想成(是 Y),Cancel 联想成取消)。默认 false 有联想。

返回Promise<Object>Object 包含以下成员:

  • response Number 所点击的按钮的额索引
  • checkboxChecked Boolean - chechbox 的选中状态。

以下列子展示了如何打开一个消息对话框:

const { dialog } = require("electron");
dialog
  .showMessageBox({
    type: "error",
    title: "confirm title",
    message: "confirm message",
    detail: "confirm details",
    checkboxLabel: "我同意",
    buttons: ["Yes", "Cancel", "OK"],
    defaultId: 2,
    noLink: false // 是否不开启联想(尝试用本地所使用的语言去替换Buttons中的配置,例如Yes联想成(是Y),Cancel联想成取消)。默认false有联想
  })
  .then(res => {
    console.log(res, res.response, res.checkboxChecked);
  });

检测网络状态

使用 js 监听网络状态的变化

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h2> 断网提醒测试 </h2>
</body>
<script>
    window.addEventListener('online',function(){
        alert('我来了,我们继续哦!')
    })

    window.addEventListener('offline',function(){
        alert('先行离开一会,请稍等!')
    })
</script>
</html>

Notification

展示如何创建一个消息通知

在主进程中

const { Notification } = require("electron");
const notification = {
  title: "Basic Notification",
  body: "Notification from the Main process"
};
new Notification(notification).show();

在渲染进程中进程中

new window.Notification("tips", {
  body: "notification from the render process"
});

键盘快捷键

全局快捷键

示例如下:

const { app, BrowserWindow, globalShortcut } = require("electron");
let mainWindow = null;
app.on("ready", () => {
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800
  });
  globalShortcut.register("Ctrl+N", () => {
    mainWindow.loadURL("http://175.24.90.8:3200/");
  });

  const isRegister = globalShortcut.isRegistered("Ctrl+N"); // 查看快捷键是否注册成功
});

app.on("will-quit", () => {
  // 退出时注销全局的快捷键
  globalShortcut.unregister("Ctrl+N");
  globalShortcut.unregisterAll();
});

剪切板

示例如下:

<!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>Clipboard</title>
  </head>

  <body>
    激活码 <span id="code">sdmaspdmasopdopas</span>
    <br />
    <button id="btn">点击复制</button>
  </body>
  <script type="text/javascript">
    const { clipboard } = require("electron");
    const oCode = document.querySelector("#code");
    const oBtn = document.querySelector("#btn");

    oBtn.onclick = () => {
      clipboard.writeText(oCode.innerText);
      alert("复制成功");
    };
  </script>
</html>

自定义 Windows 任务栏

弹出列表

::: tip Windows 允许应用程序自定义一个菜单栏,当用户右键单击任务栏中的应用图标可以看到该菜单栏。 该上下文菜单被成为弹出列表。 :::

示例如下:

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

app.setUserTasks([
  {
    program: process.execPath,
    arguments: "--new-window",
    iconPath: process.execPath,
    iconIndex: 0,
    title: "New Window For Custome",
    description: "Create a new window By Electron Api "
  }
]);

app.setUserTasks([]); // 清空任务栏列表

闪烁框

在 Windows 上,你可以突出显示任务栏按钮以获得用户的关注。 这与在 macOS 上 dock 弹跳图标相似。

引自 MSDN:

通常, 会闪现一个窗口, 通知用户该窗口需要注意, 但是该窗口当前没有键盘焦点。

要在 BrowserWindow 的任务栏按钮突出显示,可以使用 BrowserWindow.flashFrame API

示例如下:

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

const win = new BrowserWindow();

win.once("focus", () => win.flashFrame(false));
win.flashFrame(true);

注意:别忘了调用 win.flashFramework(false) 来关闭闪烁。 在上面的示例中, 当窗口进入焦点时会调用它, 但您可能会使用超时或其他一些事件来禁用它。