Electronic development from scratch – main process – window closing and tray handling

Time:2021-3-23

Window closing and tray handling

This issue mainly deals with the closing of windows and the simple handling of pallets.
Let’s talk about the realization of a target function in this issue: take Netease cloud music as an example. In the windows environment, we click Close in the upper right corner. At this time, a pop-up window will appear (if no reminder is checked before), asking whether to exit directly or shrink to the system tray. After selecting OK, we will actually close it. When no reminder is checked, click OK to next time Close no longer prompt direct processing, if it is reduced to the tray, the tray click exit will really close. In the MAC environment, click Close in the upper right corner to shrink to the dock directly, right-click to exit the dock or the tray in the upper right corner or the menu in the upper left corner to exit, then the software will be closed.
Of course, each software has different exit logic. This paper introduces how to realize the above functions, and describes various events of electron exit, hoping to help you find the exit mode you want.
Here is an upgrade to the version of electron: 12.0.0

Electronic development from scratch - main process - window closing and tray handling

The concept of closure

We’re using itOfficial examplesAfter packaging and installing, you will find that MAC and win are different in closing. MAC is directly reduced to the dock, and right-click to exit the dock to close it. Win is directly closing the software. Why?
Here, I’d like to briefly talk about the concept of closing. Many people confuse the closing of software with the closing of windows. I’d like to distinguish windows from software

Window closing:

Win: browserwindow instance
win.destroy (): forcing this window to close will trigger the closed event of win instead of the close event
win.close (): close the window and trigger the close and closed events of win

Note: the closing of a window does not necessarily trigger the closing of the software, but usually we only have one window. If this window is closed, the event of window all closed of the app will be triggered. In this event, we can call the closing of the software app.quit (), so most of the time, we close the window, soft And then I quit.
Then the reason for the difference comes to the surface

app.on('window-all-closed', () => {
  if (!isMac) {
    app.quit()
  }
})

Software shutdown:

app.quit (): the call will first trigger the before quit event of the app, and then trigger the closing event of all windows. All windows are closed (call app.quit () closing a window will not trigger window all closed, but will still trigger the quit event of app. But if you use the event.preventDefault () the default behavior (close event of win, before quit and will quit of APP) is blocked, but the software will not close.
app.exit (): it's easy to understand that the most rude way is to force all windows to close and trigger the quit event of the app, so the close event of win and the before quit and will quit events of the app will not be triggered

To sum up, there are two conditions for software shutdown

  • All the windows are closed
  • Called app.quit ()

So software shutdown is generally the following several cases

  1. All windows close to trigger window all closed, which is called in window all closed app.quit ()
  2. call app.quit (), trigger the close event of all windows
  3. app.exit()

So to achieve our goal, we have to use method 2.

Process communication configuration

Process communication, put it later, here is just the configuration of process communication
If I want to use some methods of electron in the rendering process, use the following

const { ipcRenderer } = require('electron')
ipcRenderer.send ('asynchronous message ','ping') // sends a message to the main process

It’s no problem to use it in this way, but if we have multiple pages to use, then we need to require each page, which is troublesome. Moreover, if we want to package both electronic and web, we can also use it process.env.IS_ The introduction of electron is useless. Electronic windowwebPreferencesProvidedpreloadJS can be injected. Here we can mount ipcrender under window.

vue.config.js:
electronBuilder: {
  Nodeintegration: true, // setting here is actually setting process.env.ELECTRON_ NODE_ The value of integration
  preload: 'src/renderer/preload/ipcRenderer.js',
  ......
}

ipcRenderer.js:
import { ipcRenderer } from 'electron'
window.ipcRenderer = ipcRenderer
Main process:
win = createWindow({
    ....
    webPreferences: {
      contextIsolation: false,
      nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
      preload: path.join(__dirname, 'preload.js'),
      scrollBounce: isMac
    }
  }, '', 'index.html')

Rendering process:
if (process.env.IS_ELECTRON) {
  window.ipcRenderer.send('asynchronous-message', 'ping')
}

Here’s an explanationcontextIsolationBefore 12.0.0, the default value of this value is false. This example is version 12.0.0, and the default value is true. The difference is that if it is true, it is injected preload.js It can be seen as an independent running environment, and the rendering process is invisible. Simply put, we mount ipcrender to the window, and the corresponding rendering process cannot be obtained, so it is set to false here.

Function realization

How to achieve it? There are two ways to trigger the close event of win

  1. One is that we click Close trigger. At this time, we don’t want to close the window, so we should use ite.preventDefault()Prevent windows from closing.
  2. The other is that we take the initiative to use itapp.quit()When the close event is triggered, no processing is performed in the close event.

Then, a global variable is declared by switching a variable flagwillQuitApp, inonAppReadyWhen we click close to trigger the close event, thee.preventDefault()The closing of the window is prohibited, and we send a closing notice to the rendering process through the main process.
Our process is as follows:
Main process detection closed – > judge whether it isapp.quit()trigger
– > No, notify the rendering process to close the message. After receiving the message, the rendering process notifies the main process to close or shrink the software to the tray according to the user operation or local storage
– > Yes, close the software

Main process:

let willQuitApp = false

onAppReady:
win.on('close', (e) => {
  console.log('close', willQuitApp)
  if (!willQuitApp) {
    win.webContents.send('win-close-tips', { isMac })
    e.preventDefault()
  }
})

We use it on our own initiative` app.quit ()'When triggering close, set willquitapp to true, and then trigger the close event of win to close the window. Method 2 is achieved.
app.on ('activate', () =>  win.show ()) // click Mac to display the dock window
app.on('before-quit', () => {
  console.log('before-quit')
  willQuitApp = true
})

Rendering process:

<a-modal
    v-model:visible="visible"
    :destroyOnClose="true"
   
    OK text = "confirm"
    Cancel text = "Cancel"
    @ok="hideModal"
  >
    <a-radio-group v-model:value="closeValue">
      < a-radio: style = "radiostyle": value = "1" > minimize to tray < / a-radio >
      < a-radio: style = "radiostyle": value = "2" > Exit Vue cli electron < / a-radio >
      <a-checkbox v- model:checked= "Closechecked" > no more reminders < / a-checkbox >
    </a-radio-group>
  </a-modal>

import { defineComponent, reactive, ref, onMounted, onUnmounted } from 'vue'
import { LgetItem, LsetItem } from '@/utils/storage'

export default defineComponent({
  setup() {
    const closeChecked = ref(false)
    const closeValue = ref(1)
    const visible = ref(false)
    const radioStyle = reactive({
      display: 'block',
      height: '30px',
      lineHeight: '30px',
    })
    onMounted(() => {
      window.ipcRenderer.on ('win close tips', (event, data) = > {// accept the closing notification of the main process
        const closeChecked = LgetItem('closeChecked')
        const isMac = data.isMac
        If (closechecked | ismac) {// Mac and win
          event.sender.invoke ('win close ', lgetitem ('closevalue') // when MAC or no more prompt is checked, send a message to the main process
        } else {
          visible.value = true
          event.sender.invoke ('win-focus',  closeValue.value )// display close pop-up and focus
        }
      })
    })
    onUnmounted(() => {
      window.ipcRenderer.removeListener('win-close-tips')
    })
    async function hideModal() {
      if (closeChecked.value) {
        LsetItem('closeChecked', true)
        LsetItem('closeValue', closeValue.value)
      }
      await  window.ipcRenderer.invoke ('win-close',  closeValue.value )// push the result of our selection to the main process
      visible.value = false
    }
    return {
      closeChecked,
      closeValue,
      radioStyle,
      visible,
      hideModal
    }
  }
})

The main process receives the render process message,initWindowAfter calling win, we should pay attention to Mac processing. If Mac is hidden in full screen, then there will be software white screen or black screen. We need to exit the full screen and hide it again.

import { ipcMain, app } from 'electron'
import global from '../config/global'

export default function () {
  const win = global.sharedObject.win
  const isMac = process.platform === 'darwin'
  ipcMain.handle('win-close', (event, data) => {
    if (isMac) {
      if ( win.isFullScreen ()) {// special processing in full screen state
        win.once('leave-full-screen', function () {
          win.setSkipTaskbar(true)
          win.hide()
        })
        win.setFullScreen(false)
      } else {
        win.setSkipTaskbar(true)
        win.hide()
      }
    } else {
      If (data = = = 1) {// win shrink to tray
        win.setSkipTaskbar (true) // make the window not appear in the taskbar
        win.hide () // hide window
      } else {
        app.quit () // win exit
      }
    }
  })
  ipcMain.handle ('win focus', () = > {// focus window
    if (win.isMinimized()) {
      win.restore()
      win.focus()
    }
  })
}

Electronic development from scratch - main process - window closing and tray handling

Tray settings

The tray setting here is only to complete the exit function of the software, so it is only a brief introduction, and the rest of the functions will be described in detail in the following chapters.
Right click the tray to exit directly, so call directlyapp.quit()Trigger exit process

After initWindow assigns win, setTray (win) is called.

import { Tray, nativeImage, Menu, app } from 'electron'
const isMac = process.platform === 'darwin'
const path = require('path')
let tray = null

export default function (win) {
  const iconType = isMac ? '16x16.png' : 'icon.ico'
  const icon = path.join(__static, `./icons/${iconType}`)
  const image = nativeImage.createFromPath(icon)
  if (isMac) {
    image.setTemplateImage(true)
  }
  tray = new Tray(image)
  let contextMenu = Menu.buildFromTemplate([
    {
      Label: 'display Vue cli electron',
      click: () => {
        winShow(win)
      }
    }, {
      Label: 'exit',
      click: () => {
        app.quit()
      }
    }
  ])
  if (!isMac) {
    tray.on('click', () => {
      winShow(win)
    })
  }
  tray.setToolTip('vue-cli-electron')
  tray.setContextMenu(contextMenu)
}

function winShow(win) {
  if (win.isVisible()) {
    if (win.isMinimized()) {
      win.restore()
      win.focus()
    } else {
      win.focus()
    }
  } else {
    !isMac && win.minimize()
    win.show()
    win.setSkipTaskbar(false)
  }
}

The logic here is relatively simple, and the only doubt may be thatwin.show()Why do you have one beforewin.minimize()The processing here is because if there is a visible change in the rendering process before hide (we have closed the pop-up window of the close prompt here), there will be a flickering problem when we show it later. Interested students canwin.minimize()Comment and see the effect. Of course, you can also use the following methods:

win.on('show', () => {
  setTimeout(() => {
    win.setOpacity(1)
  }, 200)
})
win.on('hide', () => {
  win.setOpacity(0)
})

supplement

There are some logic differences between MAC and windows in processing, although there is no hard and fast rule to deal with it in this way. It depends more on personal preferences and conventions.
For example, in the click processing of the tray, left-click on win directly opens the software and right-click on the menu, while left-click on MAC not only triggers click, but also opens the menu, which is not suitable if it is processed the same as in win.
Let’s add another one on Mac. When the mac software is in full screen, most of the software disable the “zoom out” button. So how can electronic achieve this

win.on('enter-full-screen', () => {
  isMac && app.commandLine.appendSwitch('disable-pinch', true)
})
win.on('leave-full-screen', () => {
  isMac && app.commandLine.appendSwitch('disable-pinch', false)
})

Because our window is actually chromium, we can set chromium parameters to achieve it. For more parameters, please refer tolinkset up.

Address:link
Address of this article:link

Recommended Today

What is “hybrid cloud”?

In this paper, we define the concept of “hybrid cloud”, explain four different cloud deployment models of hybrid cloud, and deeply analyze the industrial trend of hybrid cloud through a series of data and charts. 01 introduction Hybrid cloud is a computing environment that integrates multiple platforms and data centers. Generally speaking, hybrid cloud is […]