[Electron] 11.타이틀바 추가(feat. Modal window 재사용 이슈) - 레시피 일렉트론 앱

개요

일렉트론의 기본 타이틀바를 제거하고, 최대화(Maximize), 최소화(minimize), 닫기(close) 기능만 있는 타이틀바를 새로 추가한다.

Modal Window 재사용 이슈

모달 창에 데이터 표시는 잘되지만, 모달 창을 닫은 후 다시 열려고 하면 에러가 발생한다.

Error decompressing a compressed file: TypeError: Object has been destroyed
    at viewRcpDetail (D:\\vscode workspace\\recipe.electron\\.webpack\\main\\index.js:13610:39)
    at D:\\vscode workspace\\recipe.electron\\.webpack\\main\\index.js:13411:7
    at D:\\vscode workspace\\recipe.electron\\.webpack\\main\\index.js:13378:9

위와 같이 Object가 파괴되었다는 에러가 발생하여, 압축 해제를 실행하지 못한다. 이는 모달 창을 닫을 시에 Modal Window 인스턴스가 아예 삭제되기 때문으로 추측한다.

 

해결법

따라서 일렉트론 앱의 기본 타이틀바를 삭제하고, 임의의 타이틀바 및 닫기 버튼을 추가한다. 닫기 버튼을 클릭할 시에 요청을 main으로 보내고, main에서는 모달 창을 삭제하지 않고 숨김 처리하도록 한다.

타이틀바 기능 구현

//preload.js
...
contextBridge.exposeInMainWorld('apis', {
    minimize: (thisWindow) => ipcRenderer.invoke('minimize', thisWindow),
    maximize: (thisWindow) => ipcRenderer.invoke('maximize', thisWindow),
    unmaximize: (thisWindow) => ipcRenderer.invoke('unmaximize', thisWindow),
    close: (thisWindow) => ipcRenderer.invoke('close', thisWindow),

    viewMaximizeBtn: (callback) => ipcRenderer.on('viewMaximizeBtn', callback),
...

preload.js: renderer에서 사용할 ipc 함수를 window 전역 변수에 설정한다.

  • viewMaimizeBtn 함수는 Maximize(최대화) 상태일 때는 Unmaximize(최대화 해제) 버튼을 표시하고, Maximize 상태가 아닐 때는 Maximize 버튼을 표시하도록 하는 함수이다.
//rcp_ipc.js
...
addTitleBar() {
    ipcMain.handle('minimize', (event, thisWindow) => {
      if (thisWindow == 'main') {
        this.mainWindow.minimize();
      } else {
        this.modalWindow.minimize();
      }
    });

    ipcMain.handle('maximize', (event, thisWindow) => {
      if (thisWindow == 'main') {
        this.mainWindow.maximize();
      } else {
        this.modalWindow.maximize();
      }
    });

    ipcMain.handle('unmaximize', (event, thisWindow) => {
      if (thisWindow == 'main') {
        this.mainWindow.unmaximize();
      } else {
        this.modalWindow.unmaximize();
      }
    });

    ipcMain.handle('close', (event, thisWindow) => {
      if (thisWindow == 'main') {
        this.mainWindow.close();
      } else {
        // fade out effect
        /*
        * If you close modalWindow, 
        * it cannot be reused again, so hide it.
        */
       
        let opacity = 1;
        const interval = setInterval(() => {
          opacity -= 0.25;
          this.modalWindow.setOpacity(opacity);

          if (opacity <= 0) {
            clearInterval(interval);
            this.modalWindow.setHasShadow(false);
            this.modalWindow.hide();
            this.modalWindow.unmaximize();
            this.mainWindow.focus();
          }
        }, 25);
      }
    });
}
...

rcp_ipc.js: 타이틀바 관련 IPC 채널 모두 어떤 window에서 온 요청인지 인자로 받아 확인한다. 그리고 해당 window에 알맞는 함수를 호출한다. 단 Modal window에서는 close 함수를 호출하지 않는다. 재사용 이슈를 해결하기 위해 창을 닫지 않고 숨겨두었다가 다시 사용할 수 있도록한다.

//title_bar.js
window.$ = window.jQuery = require('jquery');

/**
 * A listener that determines which 'maximize' or 'unmaximize' buttons 
 * to display on the title bar
 */
window.apis.viewMaximizeBtn((event, value) => {
  if (value == 'unmaximize') {
    $('#unmaximize').hide();
    $('#maximize').show();
  } else if (value == 'maximize') {
    $('#unmaximize').show();
    $('#maximize').hide();
  }
});

export class Titlebar {
  isMaximized = false;

  constructor(_thisWindow) {
    this.initTitlebar(_thisWindow);
  }

  /**
   * A function that sets the appropriate click listener f
   * or the title bar buttons of '_thisWindow'.
   * @param {*} _thisWindow 
   */
  initTitlebar(_thisWindow) {
    $('#minimize').on('click', function () {
      window.apis.minimize(_thisWindow);
    });

    $('#unmaximize').on('click', function () {
      this.isMaximized = !this.isMaximized;
      window.apis.unmaximize(_thisWindow);

      $('#unmaximize').hide();
      $('#maximize').show();
    });

    $('#maximize').on('click', function () {
      this.isMaximized = !this.isMaximized;
      window.apis.maximize(_thisWindow);

      $('#unmaximize').show();
      $('#maximize').hide();
    });

    $('#close').on('click', function () {
      window.apis.close(_thisWindow);
    });

  }
}

title_bar.js: 타이틀바가 필요한 각 renderer.js 에서 import 하여 사용할 js 파일. TitleBar 클래스의 함수는 타이틀바 각 버튼 클릭 리스너에 알맞은 IPC 함수를 매핑해준다.

//main.js
...
  mainWindow.on('unmaximize', () => {
    mainWindow.webContents.send('viewMaximizeBtn', 'unmaximize');
  });
  
  mainWindow.on('maximize', () => {
    mainWindow.webContents.send('viewMaximizeBtn', 'maximize');
  });
...

main.js: renderer의 타이틀바 Maximize 버튼 표시를 위해 최대화 상태의 여부를 리스너로 받아, renderer로 송신한다.

결과 화면

사진1. 타이틀바가 추가된 일렉트론 앱.