文章目录
需求说明
在使用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();
});
}
本文介绍了如何在Electron应用中限制只能启动一个实例,并在关闭窗口时将其隐藏到系统托盘,以及处理托盘图标路径问题和点击图标唤起/隐藏窗口的解决方案。
1791

被折叠的 条评论
为什么被折叠?



