首页 > 其他分享 >Electron 案例

Electron 案例

时间:2023-06-10 14:11:20浏览次数:57  
标签:窗口 js 案例 Electron remindWindow 进程 mainWindow

 

入门Electron,手把手教你编写完整实用案例

Electron简介

Electron是干什么的? 简单来讲,Electron 使用 JavaScript,HTML 和 CSS,来构建跨平台的桌面应用程序。

按照官方的说法:如果你可以建一个网站,你就可以建一个桌面应用程序。

和传统的桌面应用相比,使用Electron开发更容易上手,开发效率更高。并且,web技术应用广泛、生态繁荣,Electron可以使用几乎所有的Web生态领域及Node.js生态领域的组件和技术方案。

与网页应用相比,Electron基于Chromium 和 Node.js,可以避免令人头痛的浏览器兼容问题。而Web前端受限访问的文件系统、系统托盘、系统通知等,开发Electron应用时可以自由地使用。

简单理解Electron工作机制

使用Electron开发的桌面应用,类似于简易版的、定制版的Chrome浏览器,当然这个浏览器中的页面不能通过输入网址打开,而是由开发者写好的。

image.png 和浏览器架构类似,Electron应用程序区分主进程和渲染进程。

主进程负责控制应用程序的生命周期、创建和管理应用程序窗口,有着多种控制原生桌面功能的模块,例如菜单、对话框以及托盘图标。

渲染进程负责完成渲染界面、接收用户输入、响应用户的交互等工作。

一个Electron应用只有一个主进程,但可以有多个渲染进程。

在之前的文章中,我们有讲过浏览器中的进程,可略作参考。

案例入门

下面我们将从一个任务管理的案例入门,了解electron的整体开发流程和一些基本的细节知识。

案例效果

mainWindow.gif

功能分析:

1、记录待完成任务和已完成任务

2、通过新建,添加待完成任务,并设置时间

3、点击完成任务,跳转到已完成界面;点击删除,可以删除任务

4、点击右上角的 × 按钮,可以关闭主界面,要再次打开主界面,可以通过系统托盘

5、设定的时间到了,会在右下角弹出提醒框,如下图所示。

image.png

初始化项目

项目是由原生js开发,在后面的文章中,我们会再探讨electron和vue、react这些前端框架的结合。

mkdir tasky
cd tasky
npm init

安装electron

npm install electron --S

创建一个hello world应用程序

(1)第一步,在项目根目录下,创建index.js,作为应用程序的入口文件。因为Electron是基于Node.js,所以入口文件使用Node.js语法。内容如下:

//引入两个模块:app 和 BrowserWindow

//app 模块,控制整个应用程序的事件生命周期。
//BrowserWindow 模块,它创建和管理程序的窗口。

const { app, BrowserWindow } = require('electron')

//在 Electron 中,只有在 app 模块的 ready 事件被激发后才能创建浏览器窗口
app.on('ready', () => {

  //创建一个窗口
  const mainWindow = new BrowserWindow()
  
  //窗口加载html文件
  mainWindow.loadFile('./src/main.html')
})

(2)第二步,创建窗口需要加载的html文件。

为了方便后面的文件管理,我们新建一个 src 文件夹,用于存放web页面资源,比如html、css、js、图片等。

// ./src/main.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>
  hello world
</body>
</html>

(3)启动程序。

修改package.jsonscripts,如下:

image.png

然后在项目根目录运行:npm run start

这样我们的hello world应用程序就跑起来了。入门Electron,就是这么简单!

image.png

简单的基础调试

1、主进程运行时的一些提示信息会在命令行显示,比如,在index.js加入console.log

app.on('ready', () => {
  console.log('just test console.log')
  const mainWindow = new BrowserWindow()
  mainWindow.loadFile('./src/main.html')
})

就可以在命令行看到打印的值:

image.png

index.js中的内容发生改变,默认要手动重启,比较麻烦。这里我们加入nodemon,它可以监控node.js 源代码的变化,并自动重启应用。

安装: npm i nodemon --D

修改scripts"start": "nodemon --watch index.js --exec electron ."

再次运行npm run start,当index.js内容变化时,就会自动重新执行electron .来重启应用。

2、窗口页面的调试方法和chrome浏览器类似。点击菜单栏的View --- Toggle Developer Tools,或者按它对应的快捷键,就会出现我们熟悉的开发者工具界面。

image.png

当页面内容发生变化,可以点击View --- Reload,或者快捷键ctrl+r,刷新页面内容。页面热更新会后续讲到。

开始coding

项目目录结构如下,其中src文件夹存放的就是web页面相关的内容。

image.png 项目有两个窗口:主窗口和提醒窗口。在主窗口中管理任务,当任务设定时间到,会在屏幕右下角出现提醒窗口。

应用不涉及服务端数据,任务数据主要使用localStorage来存储。

创建主窗口:

const iconPath = path.join(__dirname, './src/img/icon.png')   //应用运行时的标题栏图标
let mainWindow
app.on('ready', () => {
  mainWindow = new BrowserWindow({
    resizable: false,   //不允许用户改变窗口大小
    width: 800,        //设置窗口宽高
    height: 600,
    icon: iconPath,     //应用运行时的标题栏图标
    webPreferences:{    
      backgroundThrottling: false,   //设置应用在后台正常运行
      nodeIntegration:true,     //设置能在页面使用nodejs的API
      contextIsolation: false,
      preload: path.join(__dirname, './preload.js')
    }
  })
  mainWindow.loadURL(`file://${__dirname}/src/main.html`)
}

main.html的内容很简单,有兴趣的童鞋可以去看源码,这里就不贴了。

image.png

默认Electron应用顶部有标题栏和菜单栏。纵观各个桌面应用程序,基本都是定制的顶部控制区域。对于我们这个应用,暂时只要一个关闭按钮,所以我们将去掉这一部分,将窗口关闭按钮写在main.html页面中。

无边框窗口

要创建无边框窗口,只需在 BrowserWindow 的 options 中将 frame 设置为 false:

mainWindow = new BrowserWindow({
   frame: false,
   ...
})

标题栏和菜单栏消失了,但也会有几个问题:

1、虽然菜单栏消失了,但是依然可以通过快捷键进行菜单操作,比如ctrl+shift+i打开开发者工具,为避免这种情况,我们需要去掉菜单栏:

mainWindow.removeMenu()

2、默认情况下,无边框窗口是不可拖拽的。应用程序需要在 CSS 中指定 -webkit-app-region: drag 来告诉 Electron 哪些区域是可拖拽的。

html,body {
  height: 100%;
  width: 100%;
}

body{
  -webkit-app-region: drag;
}

如果用上面的属性使整个窗口都可拖拽,则必须将其中的按钮标记为不可拖拽,否则按钮将无法点击。

.enable-click {
  -webkit-app-region: no-drag;
}

3、当点击自定义的窗口关闭按钮,我们并不希望退出程序,只是将窗口隐藏,可以通过系统托盘再次打开窗口。 image.png

系统托盘

程序启动时,将应用程序加入系统托盘。在Electron中,借助Tray模块实现。

const { app, BrowserWindow, Tray, Menu } = electron
const iconPath = path.join(__dirname, './src/img/icon.png')
let mainWindow, tray
app.on('ready', () => {
  mainWindow = new BrowserWindow({
    //... options
  })
  mainWindow.loadURL(`file://${__dirname}/src/main.html`)
  
  tray = new Tray(iconPath)      //实例化一个tray对象,构造函数的唯一参数是需要在托盘中显示的图标url  
  
  tray.setToolTip('Tasky')       //鼠标移到托盘中应用程序的图标上时,显示的文本
  
  tray.on('click', () => {       //点击图标的响应事件,这里是切换主窗口的显示和隐藏
    if(mainWindow.isVisible()){
      mainWindow.hide()
    }else{
      mainWindow.show()
    }
  })
  
  tray.on('right-click', () => {    //右键点击图标时,出现的菜单,通过Menu.buildFromTemplate定制,这里只包含退出程序的选项。
    const menuConfig = Menu.buildFromTemplate([
      {
        label: 'Quit',
        click: () => app.quit()
      }
    ])
    tray.popUpContextMenu(menuConfig)
  })

})

IPC通信

回到上一个问题。点击页面内的按钮怎样隐藏窗口?这就需要用到IPC通信了。

image.png IPC(Inter-Process Communication),就是进程间通信。Electron应用程序区分主进程和渲染进程,有时候,两者之间需要通信,传输一些数据、发送一些消息。

渲染进程 TO 主进程

比如,点击关闭按钮,就需要渲染进程向主进程发送隐藏主窗口的请求。

渲染进程使用Electron内置的ipcRenderer模块向主进程发送消息,ipcRenderer.send方法的第一个参数是消息管道名称。

//页面的js代码:
const electron = require('electron')
const { ipcRenderer } = electron

closeDom.addEventListener('click', () => {
  ipcRenderer.send('mainWindow:close')
})

主进程通过ipcMain接收消息,ipcMain.on方法的第一个参数也为消息管道的名称,与ipcRenderer.send的名称对应,第二个参数是接收到消息的回调函数。

//入口文件index.js
ipcMain.on('mainWindow:close', () => {
  mainWindow.hide()
})

主进程 TO 渲染进程

主进程向渲染进程发送消息是通过渲染进程的webContents。在mainWindow渲染进程设定了任务后,会传输给主进程任务信息,当任务时间到了,主进程会创建提醒窗口remindWindow,并通过remindWindow.webContents将任务名称发给remindWindow

function createRemindWindow (task) {
 
  remindWindow = new BrowserWindow({
     //options
  })
  remindWindow.loadURL(`file://${__dirname}/src/remind.html`)
  
  //主进程发送消息给渲染进程
  remindWindow.webContents.send('setTask', task)

}

remindWindow渲染进程中,通过ipcRenderer.on接受消息。

ipcRenderer.on('setTask', (event,task) => {
   document.querySelector('.reminder').innerHTML = 
      `<span>${decodeURIComponent(task)}</span>的时间到啦!`
})

image.png

渲染进程 TO 渲染进程

渲染进程之间传递消息,可以通过主进程中转,即窗口A先把消息发送给主进程,主进程再把这个消息发送给窗口B,这种非常常见。

也可以从窗口A直接发消息给窗口B,前提是窗口A知道窗口B的webContents的id。

ipcRenderer.sendTo(webContentsId, channel, ...args)

值得注意的是,我们在页面的js代码中使用了require,这也是Electron的一大特点,在渲染进程中可以访问Node.js API。这样做的前提是在创建窗口时配置webPreferencesnodeIntegration: truecontextIsolation: false

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

窗口位置

当任务时间到,提醒窗口会在屏幕右下角出现。怎样设定窗口位置呢?

function createRemindWindow (task) {
  //创建提醒窗口
  remindWindow = new BrowserWindow({
    //...options
  })
  
  //获取屏幕尺寸
  const size = screen.getPrimaryDisplay().workAreaSize
  
  //获取托盘位置的y坐标(windows在右下角,Mac在右上角)
  const { y } = tray.getBounds()
  
  //获取窗口的宽高
  const { height, width } = remindWindow.getBounds()
  
  //计算窗口的y坐标
  const yPosition = process.platform === 'darwin' ? y : y - height
  
  //setBounds设置窗口的位置
  remindWindow.setBounds({
    x: size.width - width,     //x坐标为屏幕宽度 - 窗口宽度
    y: yPosition,
    height,
    width 
  })
  
  //当有多个应用时,提醒窗口始终处于最上层
  remindWindow.setAlwaysOnTop(true)
  
  remindWindow.loadURL(`file://${__dirname}/src/remind.html`)
}

关闭窗口

提醒窗口会在一段时间后关闭,可以通过remindWindow.close()来关闭窗口。

当窗口关闭后,我们可以设置remindWindow = null来回收分配给该渲染进程的资源。

remindWindow.on('closed', () => { remindWindow = null })

结语

这篇文章主要是介绍electron的一些基础知识,下一篇文章,我们将探讨electron的打包问题,下次见。

凡能用JavaScript实现的,注定会被用JavaScript实现。        
                                               ---Jeff Atwood
 

标签:窗口,js,案例,Electron,remindWindow,进程,mainWindow
From: https://www.cnblogs.com/sddai/p/17471208.html

相关文章

  • 使用阿里云快速构建海外镜像实战案例
    目录一.github创建项目1.注册账号2.登录github账号3.创建仓库4.添加一个Dockerfile文件二.登录阿里云创建镜像仓库1.进入容器镜像服务页面2.创建命名空间3.创建镜像仓库3.1配置仓库信息3.2配置代码源3.2.1选择代码源3.2.2绑定github账号3.2.3绑定成功3.2.4配置代码源3.2.5......
  • Python使用Queue对象实现多线程同步小案例
    queue模块的Queue对象实现了多生产者/多消费者队列,尤其适合需要在多个线程之间进行信息交换的场合,实现了多线程编程所需要的所有锁语义。Queue对象主要实现了put()和get()方法,分别用来往队列尾部追加元素和在队列头部获取并删除元素。这两个方法都允许指定超时时间,其用法分别为put(......
  • Python字符串处理小案例
    连续5天30个小时的Python培训圆满结束,明天早上5点半出发赶飞机回烟台,晚上收拾行李的时候突然想起来20年前做过的一个C语言题目:假设有一个字符串,里面有若干字母o。要求如下:从前到后扫描,把每个字符删除并追加至字符串尾部,如果遇到字母o就删除,直至字符串处理结束。本文代码主要演示字......
  • Python 3.6+Django开发入门小案例(自动变化的问候)完整步骤
    第一步:在命令提示符环境使用pipinstalldjango命令安装django第二步:在命令提示符环境使用pythondjango-admin.pystartprojectdjango_greeting命令创建网站django_greeting第三步:利用资源管理器在网站目录django_greeting中创建子文件夹templates,并在该子文件夹中创建文件gree......
  • Python+tkinter动态创建与销毁组件小案例
    本文代码演示了如何在tkinter窗体上动态创建组件以及销毁组件的方法。importtkinterimporttkinter.messageboximporttkinter.simpledialogbtnList=[]#动态创建组件,并计算组件在窗体上的位置defplace(n):foriinrange(n):exec('btn'+str(i)+'=tkinter.B......
  • java集成chatGpt完整案例代码(效果和官网一样逐字输出)
    背景要集成chatGpt参考我上一篇文章即可。但是,如果要实现官网一样的效果,逐字输出,难度就提升了不少了。经过在官网的研究发现它应该是采用了SSE技术,这是一种最新的HTTP交互技术。SSE(Server-SentEvents):通俗解释起来就是一种基于HTTP的,以流的形式由服务端持续向客户端发送数据的......
  • 北京君正案例:数传网关的集大成者—积木式边缘网关
    外观介绍数传网关的集大成者USR-M300产品集成了数据的边缘采集、计算、主动上报和数据读写,联动控制,IO采集和控制等功能,采集协议包含标准Modbus协议和多种常见的PLC协议,以及行业专用协议;主动上报采用分组上报方式,自定义Json上报模版,快速实现服务器数据格式的对接。同时USR-M300产品......
  • 【刨根问底】BigDecimal 案例和部分源码分析
    本文总以下几个部分:前言Bigdecimal定义Bigdecimal创建方式Bigdecimal部分源码分析Bigdecimal坑Bigdecimal使用建议Bigdecimal工具类前言在咱们开发过程中很容易遇到计算的问题,普通计算其实也还好使用int、long、double、float基本上能应付。但是如果涉及到数据类型转后在处理等......
  • Java多态综合案例(包含接口,接口实现类)
    首先定义一个接口名为USB其次定义两个实现类分别名为KeyBorad和Mouse此时就可以使用多态了,因为实现类和接口某种意义上来说是继承关系。USBu=newKeyborad();USBu2 =newMouse();因为键盘和鼠标都具有插拔功能,所以为了方便,把这两个功能写入接口,然后实现类重写。pac......
  • 关于HTML 5文档结构的简单案例
    在HTML文档的开头,一般会有一个文档类型声明(DOCTYPE)。在HTML5中,文档类型声明为<!DOCTYPEhtml>(注意大小写)。因此,如果一个网页的文档类型声明为<!DOCTYPEhtml>,那么这个网页就是采用了HTML5。但需要注意的是,并不是所有的网页都需要文档类型声明。如果一个网页没有文档类型声明,也不......