Electron限制只启动一个实例、系统托盘实现

本文介绍了如何在Electron应用中限制只能启动一个实例,并在关闭窗口时将其隐藏到系统托盘,以及处理托盘图标路径问题和点击图标唤起/隐藏窗口的解决方案。
摘要由CSDN通过智能技术生成

需求说明

在使用electron打包的应用程序的时候,点击应用图标会打开多个实例,想要限制只启动一个应实例,并且点击应用的叉号,不关闭应用而是隐藏到系统托盘,右键托盘图标点击退出,才真正退出应用。

一、只启动一个实例实现

在主进程background.js中添加代码,在Electron的主进程中,可以使用app.requestSingleInstanceLock()方法来请求单一实例锁。如果该方法返回false,则表示已经有一个实例在运行,可以通过app.quit()方法退出当前实例。

1.1. 核心代码

/**
 *  请求单一实例锁,
 *  如果该方法返回false,
 *  则表示已经有一个实例在运行,
 *  可以通过app.quit()方法退出当前实例。
 */
const gotTheLock = app.requestSingleInstanceLock();

if (!gotTheLock) {
  // 已经有一个实例在运行,退出当前实例
  app.quit();
} else {
  // 监听第二个实例被运行时
  app.on("second-instance", (event, commandLine, workingDirectory) => {
    // 当有第二个实例被运行时,激活之前的实例并将焦点置于其窗口
    if (win) {
      if (win.isMinimized()) win.restore();
      win.focus();
    }
  });
}

1.2. 完整代码

"use strict";

import { app, protocol, BrowserWindow } from "electron";
import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
const isDevelopment = process.env.NODE_ENV !== "production";
let win = null;
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
  { scheme: "app", privileges: { secure: true, standard: true } },
]);

async function createWindow() {
  // Create the browser window.
  win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      // Use pluginOptions.nodeIntegration, leave this alone
      // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
      nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
      contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
    },
  });

  if (process.env.WEBPACK_DEV_SERVER_URL) {
    // Load the url of the dev server if in development mode
    await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL);
    if (!process.env.IS_TEST) win.webContents.openDevTools();
  } else {
    createProtocol("app");
    // Load the index.html when not in development
    win.loadURL("app://./index.html");
  }
}

/**
 *  请求单一实例锁,
 *  如果该方法返回false,
 *  则表示已经有一个实例在运行,
 *  可以通过app.quit()方法退出当前实例。
 */
const gotTheLock = app.requestSingleInstanceLock();

if (!gotTheLock) {
  // 已经有一个实例在运行,退出当前实例
  app.quit();
} else {
  // 监听第二个实例被运行时
  app.on("second-instance", (event, commandLine, workingDirectory) => {
    // 当有第二个实例被运行时,激活之前的实例并将焦点置于其窗口
    if (win) {
      if (win.isMinimized()) win.restore();
      win.focus();
    }
  });
  app.on("ready", async () => {
    createWindow();
  });
  // Quit when all windows are closed.
  app.on("window-all-closed", () => {
    // On macOS it is common for applications and their menu bar
    // to stay active until the user quits explicitly with Cmd + Q
    if (process.platform !== "darwin") {
      app.quit();
    }
  });

  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();
  });

}

二、系统托盘实现

2.1. 打包后托盘图标路径找不到的问题

// 打包后托盘图标路径找不到
tray = new Tray("./assets/icon/tray.png");

2.2. 解决办法

tray.png图片配置为额外资源

相关文章: electron的extraFiles和extraResources的配置和使用

  • 根目录新建lib文件夹,放入tray.png图片
    在这里插入图片描述

  • vue.config.js中配置extraFiles

const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
  transpileDependencies: true,
  pluginOptions: {
    electronBuilder: {
      builderOptions: {
        // 额外文件配置
        extraFiles: ["./lib"],
      },
    },
  },
});

打包后lib文件夹会被复制到打包后应用程序的根目录下(Windows/Linux),或者Content目录下(MacOS)

  • 如图(我是Windows系统)
    在这里插入图片描述

  • 获取图标文件路径方法如下

path.join(process.cwd(), "/lib/tray.png")

2.3. 系统托盘实现核心代码

app.on("ready", async () => {
  createWindow();
  tray = new Tray(path.join(process.cwd(), "/lib/tray.png"));
  const contextMenu = Menu.buildFromTemplate([
    {
      label: "退出",
      click: () => {
        win.destroy();
      },
    }, //我们需要在这里有一个真正的退出(这里直接强制退出)
  ]);
  tray.setToolTip("This is my application.");
  tray.setContextMenu(contextMenu);
  tray.on("click", () => {
    //我们这里模拟桌面程序点击通知区图标实现打开关闭应用的功能
    win.isVisible() ? win.hide() : win.show();
    win.isVisible() ? win.setSkipTaskbar(false) : win.setSkipTaskbar(true);
  });
  // 点击关闭按钮
  win.on("close", (event) => {
    // 隐藏页面
    win.hide();
    // 将应用从任务栏移出
    win.setSkipTaskbar(true);
    event.preventDefault();
  });
});

2.4. 解决添加系统托盘后,点击应用图标无法唤起窗口的问题

if (!gotTheLock) {
  // 已经有一个实例在运行,退出当前实例
  app.quit();
} else {
  // 创建窗口
  app.on("second-instance", (event, commandLine, workingDirectory) => {
    // 当有第二个实例被运行时,激活之前的实例并将焦点置于其窗口
    if (win) {
      if (win.isMinimized()) win.restore();
      win.focus();
      // ************解决添加系统托盘后,点击应用图标无法唤起窗口的问题
      if (!win.isVisible()) {
        win.show();
        win.setSkipTaskbar(true);
      }
    }
  });
}

三、全部完整代码

"use strict";

import { app, protocol, BrowserWindow } from "electron";
import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
const { Menu, Tray } = require("electron");
const path = require("path");
// 定义托盘实例
let tray = null;
// 定义窗口实例
let win = null;
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
  { scheme: "app", privileges: { secure: true, standard: true } },
]);

// 创建窗口方法
async function createWindow() {
  // Create the browser window.
  win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      // Use pluginOptions.nodeIntegration, leave this alone
      // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
      nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
      contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
    },
  });

  if (process.env.WEBPACK_DEV_SERVER_URL) {
    // Load the url of the dev server if in development mode
    await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL);
    if (!process.env.IS_TEST) win.webContents.openDevTools();
  } else {
    createProtocol("app");
    // Load the index.html when not in development
    win.loadURL("app://./index.html");
  }
}

/**
 *  单应用启动实现
 *  请求单一实例锁,
 *  如果该方法返回`false`,
 *  则表示已经有一个实例在运行,
 *  可以通过`app.quit()`方法退出当前实例。
 */
const gotTheLock = app.requestSingleInstanceLock();

if (!gotTheLock) {
  // 已经有一个实例在运行,退出当前实例
  app.quit();
} else {
  // 监听第二个实例被运行时
  app.on("second-instance", (event, commandLine, workingDirectory) => {
    // 当有第二个实例被运行时,激活之前的实例并将焦点置于其窗口
    if (win) {
      if (win.isMinimized()) win.restore();
      win.focus();
      // 解决添加系统托盘后,点击应用图标无法唤起窗口的问题
      if (!win.isVisible()) {
        win.show();
        win.setSkipTaskbar(true);
      }
    }
  });
  app.on("ready", async () => {
    createWindow();
    tray = new Tray(path.join(process.cwd(), "/lib/tray.png"));
    const contextMenu = Menu.buildFromTemplate([
      {
        label: "退出",
        click: () => {
          win.destroy();
        },
      }, //我们需要在这里有一个真正的退出(这里直接强制退出)
    ]);
    tray.setToolTip("This is my application.");
    tray.setContextMenu(contextMenu);
    tray.on("click", () => {
      //我们这里模拟桌面程序点击通知区图标实现打开关闭应用的功能
      win.isVisible() ? win.hide() : win.show();
      win.isVisible() ? win.setSkipTaskbar(false) : win.setSkipTaskbar(true);
    });
    // 点击关闭按钮
    win.on("close", (event) => {
      // 隐藏页面
      win.hide();
      // 将应用从任务栏移出
      win.setSkipTaskbar(true);
      event.preventDefault();
    });
  });
  // Quit when all windows are closed.
  app.on("window-all-closed", () => {
    // On macOS it is common for applications and their menu bar
    // to stay active until the user quits explicitly with Cmd + Q
    if (process.platform !== "darwin") {
      app.quit();
    }
  });

  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();
  });
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值