从Angular组件关闭Electron应用程序

4

我正在尝试弄清楚如何通过一个Angular组件关闭Electron应用程序。我在main.js中使用frame: false来移除菜单栏。 我有一个位于电子应用程序右上角的组件上的关闭按钮。 我希望能够在点击时从component.ts文件中关闭应用程序,但是我没有看到任何从Angular组件关闭Electron的示例。

我认为以下代码可以工作,但实际上并不行。 我从main.js导出一个函数,并从组件中调用该函数。像这样(请参见closeApp()):

const { app, BrowserWindow } = require('electron')
const url = require("url");
const path = require("path");

let mainWindow

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    frame: false,
    webPreferences: {
      nodeIntegration: true
    }
  })
  mainWindow.maximize();
  mainWindow.loadURL(
    url.format({
      pathname: path.join(__dirname, `/dist/index.html`),
      protocol: "file:",
      slashes: true
    })
  );

...

function closeApp() {
  mainWindow = null
}

export { closeApp };

然后我会尝试将它导入到组件中,如下所示:

import { Component, OnInit } from '@angular/core';
import { closeApp } from '../../../main.js';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {

  }
  testClose() {
    closeApp();
  }

}

我该如何从Angular中关闭Electron应用程序?感谢任何帮助!

我在使用Electron时遇到了构建错误。你能分享一下这些错误吗? - Roddy of the Frozen Peas
更正:我确实有构建错误,但它们与此无关。现在构建错误已经消失了,所以这不是问题。 - user6680
我认为这个问题的答案仍然适用:https://dev59.com/JlgQ5IYBdhLWcg3wGgFY - Roddy of the Frozen Peas
如果我尝试这个解决方案:https://dev59.com/JlgQ5IYBdhLWcg3wGgFY#43314199,那么我会得到编译器错误。这是我从该解决方案中更新的更改和编译器错误。https://pastebin.com/yXLqaKXz 你知道如何解决这些错误吗? - user6680
你不能使用IPC调用吗?我通常有一个Angular服务来处理与Node进程的所有通信。你可以查看这篇文章以获取更多解释。 - RJM
我按照那篇文章创建了服务,但是在main.ts中设置时总是出错。例如,如果我在main ts中导入import { ipcMain, IpcMessageEvent } from 'electron';,它会显示Cannot use import statement outside a module,但这就是文章的设置方式。我尝试使用require导入,但也不起作用。我创建了一个简化版本的代码库,希望您能看到问题所在... https://github.com/user6680/angular-ipc 。非常感谢您的帮助! - user6680
3个回答

1

您可以在浏览器窗口中使用以下代码。请注意,它需要enableRemoteModule,出于安全原因不建议使用。但是,您可以通过编写更复杂的代码来解决这个问题,但这应该向您展示基本思路。

更简单但不太安全的方法

在您的主进程中:

mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    frame: false,
    fullscreen: false,
    webPreferences: {
      nodeIntegration: true,
      enableRemoteModule: true
    }
  })

从您的mainWindow,在渲染器中
在单击应用程序的关闭按钮(您的自定义按钮)后,您应该运行以下逻辑:
const win = remote.getCurrentWindow();
win.close();

https://www.electronjs.org/docs/api/browser-window#winclose

win.destroy() 也可用,但应优先使用 win.close()


更加安全但更为困难的方法

这需要使用一个预加载脚本 这是另一个问题,但我会提供代码来演示如何使用这种方式,但由于多种原因,这种方式更难实现。

尽管如此,在长期运行中,这是您应该采用的方法。

  import { browserWindow, ipcMain } from 'electron';

  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    frame: false,
    fullscreen: false,
    preload: [Absolute file path to your preload file],
    webPreferences: {}
  });

  ipcMain.handle('close-main-window', async (evt) => {
    mainWindow.close();
    // You can also return values from here

  });


preload.js

  const { ipcRenderer } = require('electron');

  // We only pass closeWindow to be used in the renderer page, rather than passing the entire ipcRenderer, which is a security issue
  const closeWindow = async () => {
     await ipcRenderer.invoke('close-main-window'); // We don't actually need async/await here, but if you were returning a value from `ipcRenderer.invoke` then you would need async/await
  }

  window.api = {
    closeWindow
  }


渲染器页面
  
// I don't know Angular, but just call window.api.closeWindow() in response to the button you want to use


我在我的组件中尝试了您的代码,如下所示:https://pastebin.com/Xi7vrH64 但是我遇到了错误:https://postimg.cc/rzHNVJKr。这是我在component.ts文件中使用的代码:https://pastebin.com/Xi7vrH64 为了解决这个问题,我进行了谷歌搜索,似乎我可以在tsconfig.json或tsconfig.app.json或两者都添加"types": ["node"]并运行npm install @types/node,但是然后我会得到Module not found: Error: Can't resolve 'fs' in 'C:\Users\Phil\Documents\... - user6680
https://github.com/user6680/angular-ipc/blob/ecaddf3c446b9acacb411473c8f0a11ac57ed7b0/main.ts#L17 将 contextIsolation 删除并替换为 nodeIntegration: true一旦您完成了这个步骤(或者如果我漏掉了什么,请告诉我),您应该阅读一些关于这些设置的文章,以了解它们的作用。contextIsolation 是相当严格的。 - Slbox
在 main.ts 文件中,webPreferences 被设置为 webPreferences: { nodeIntegration: true },但是在你的解决方案所使用的组件中,我一直在尝试声明 remote,但没有成功。如果我像这样声明 remote:const remote = require('electron').remote;,那么我会得到 https://postimg.cc/xk7QzhwX 的错误。我该如何声明 remote,以便我可以在组件中使用它,从而执行你的答案中的代码?由于奖励即将到期,而你一直是最有帮助的人,所以我会释放奖励,但我希望你仍然能帮助我解决这个问题。 - user6680
你仍然需要设置 enableRemoteModule: true https://www.electronjs.org/docs/breaking-changes#default-changed-enableremotemodule-defaults-to-false启用后会有什么结果? - Slbox
1
我修改了我的代码以添加enableRemoteModule: true,但是当我在Angular组件中取消注释const remote = require('electron').remote;时,它仍然会抛出错误。参考:https://postimg.cc/rR0njm3S 和 https://postimg.cc/rKGCkx1D。如果我注释掉这一行```const remote = require('electron').remote;const win = remote.getCurrentWindow(); win.close();```,那么错误就不会发生。 - user6680
显示剩余9条评论

0
将以下代码添加到您的main.ts文件中。
mainWindow.on('closed', () => {
    // Dereference the window object, usually you would store window
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    
    mainWindow = null;
    
  });

return mainWindow;

mainWindow.on("Closed")会捕获关闭按钮的点击事件,并在此时将win设置为null,从而关闭窗口。

这是我的main.ts文件及其函数。

import { app, BrowserWindow, screen, Menu } from 'electron';
import * as path from 'path';
import * as url from 'url';
import * as electronLocalshortcut from 'electron-localshortcut'

let win: BrowserWindow = null;
const args = process.argv.slice(1),
  serve = args.some(val => val === '--serve');

function createWindow(): BrowserWindow {

  const electronScreen = screen;
  const size = electronScreen.getPrimaryDisplay().workAreaSize;

  // Create the browser window.
  win = new BrowserWindow({
    x: 0,
    y: 0,
    width: size.width,
    height: size.height,
    icon: path.join(__dirname, 'dist/assets/logo.png'),
    webPreferences: {
      nodeIntegration: true,
      allowRunningInsecureContent: (serve) ? true : false,
    },
  });


  // Disable refresh
  win.on('focus', (event) => {
    console.log("event of on fucous ");
    electronLocalshortcut.register(win, ['CommandOrControl+R', 'CommandOrControl+Shift+R', 'F5'], () => { })
  })

  win.on('blur', (event) => {
    console.log("event of on blue ");
    electronLocalshortcut.unregisterAll(win)
  })
  if (serve) {
    require('electron-reload')(__dirname, {
      electron: require(`${__dirname}/node_modules/electron`)
    });
    win.loadURL('http://localhost:4200');
  } else {
    win.loadURL(url.format({
      pathname: path.join(__dirname, 'dist/index.html'),
      protocol: 'file:',
      slashes: true
    }));
  }

  if (serve) {
    win.webContents.openDevTools();
  }

  win.on('close', (e) => {
    // Do your control here

    e.preventDefault();

  });
  // Emitted when the window is closed.
  win.on('closed', () => {
    // Dereference the window object, usually you would store window
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    // if (JSON.parse(localStorage.getItem('isRunning'))) {
    //   alert('Your timer is running')
    // } else {
    win = null;
    // }
  });


  return win;
}

try {

  // Custom menu.
  const isMac = process.platform === 'darwin'

  const template: any = [
    // { role: 'fileMenu' }
    {
      label: 'File',
      submenu: [
        isMac ? { role: 'close' } : { role: 'quit' }
      ]
    },
    // { role: 'editMenu' }
    {
      label: 'Window',
      submenu: [
        { role: 'minimize' },
      ]
    }
  ]

  const menu = Menu.buildFromTemplate(template)
  Menu.setApplicationMenu(menu)

  // This method will be called when Electron has finished
  // initialization and is ready to create browser windows.
  // Some APIs can only be used after this event occurs.
  app.on('ready', createWindow);

  // Quit when all windows are closed.
  app.on('window-all-closed', () => {
    // On OS X 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 OS X 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 (win === null) {
      createWindow();
    }
  });

} catch (e) {
  // Catch Error
  // throw e;
}

谢谢。


你是如何声明win的?如果你看一下我的main.ts https://github.com/user6680/angular-ipc/blob/master/main.ts ,你会发现它还没有被定义,所以我不确定你是如何定义它的。目前mainWindow应该是窗口对象,这就是为什么我将其设置为null的原因。此外,我正在使用ipc尝试从angular组件发送关闭命令,所以在那个BrowserWindow({...对象中,frame被设置为false,这样点击按钮就是来自angular而不是框架。 - user6680
将win替换为mainWindow。我的错误。@user6680 - Pushprajsinh Chudasama
1
这是我的 main.ts 文件的样子。我正在更新我的回答。@user6680 - Pushprajsinh Chudasama
1
感谢您的更新,但有两件事。我已经更新了我的main.ts文件以使用您编辑过的main.ts文件,但是我遇到了https://postimg.cc/wyX24Cwb的问题。此外,它没有回答我的问题,即如何从angular组件关闭electron应用程序。如果没有这个错误,我相信它只会通过电子框架关闭应用程序,而不是通过angular组件。最好是能够看到IPC angular服务如何根据我的原始问题执行。这是一个示例存储库,我创建它来演示我正在尝试通过IPC从Angular服务执行的操作https://github.com/user6680/angular-ipc。 - user6680
@user6680,你试过我的答案了吗?如果你试一下的话,它会向你展示如何解决你的问题。通过IPC实现它唯一的区别就是你需要编写更多的代码来完成同样的事情。如果你理解了我分享的简单代码,那么你也将完成95%的IPC解决方案。 - Slbox

0
这可能不是最好的方法,但它似乎是一个解决方法: 为了捕获此事件,我使用了 'window.onbeforeunload'。
例如:
export class AppComponent implements OnInit {

  
  ngOnInit(): void {   

    window.onbeforeunload = (e: BeforeUnloadEvent) => {
     // Do something...
    }
  }
}

您的回答可以通过添加其他支持信息来改进。请进行[编辑]以添加更多细节,例如引用或文献,以便他人可以确认您的答案是否正确。您可以在帮助中心中找到有关如何撰写良好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接