본문으로 바로가기

728x90

TCP Socket Router 예제를 통한 Electron Application 작성방법에 대한 연재가 완료되었습니다.

 

Electron + Vue.js 애플리케이션 만들기

1. Electron 프로젝트 생성하기(Create Electron Application Project)

2. Electron IPC 통신(Electron Architecture, IPC Main/IPC Renderer)

3. TCP Router 기능 구현(Implements TCP Router Communication)

4. 화면 UI 구성(Main Window UI)

5. 환경설정 구현하기(Preferences Window)

6. 메뉴 사용하기(Application Menu & Context Menu)

7. 시스템 트레이 사용 및 창 최소화(System Tray & Minimize to Tray)

8. Bootstrap Vue와 Font Awesome 사용하기(Using Bootstrap Vue & Font Awesome)

9. Dark Layout과 Frameless Window(Dark Layout & Frameless Window)

10. 빌드 및 배포, 자동 업데이트(Build, Auto Updater)

 

 

이번 포스트에서는 환경설정을 화면을 구성하고 환경설정을 처리하는 로직을 구현합니다. 환경설정정보를 간단하게 구성해보고 추후 GUI 라이브러리(Bootstrap Vue) 설치 후에 내용을 확대하여 적용하겠습니다.

 

환경설정 창 띄우기

환경설정 관리(main 프로세스) 객체생성하기

환경설정을 관리하는 객체를 아래와 같이 생성합니다. 환경설정 저장은 preferences 모듈을 추가하였습니다.

import { BrowserWindow, ipcMain, dialog } from "electron";
import CommonUtils from "../shared/common-utils";

const iswin = process.platform === "win32";

// https://www.npmjs.com/package/preferences
const Preferences = require("preferences");

const winURL = process.env.NODE_ENV === "development"
    ? `http://localhost:9080/#/preferences`
    : `file://${__dirname}/preferences.html`;

export default (() => {
    class PreferencesManager {
        constructor(win) {
            this.win = win;
            this.prefsWindow = null;

            this.preferences = new Preferences("kr.ejsoft.tcp.router", {
                common : {
                    autostartup: true,
                    minimizestart: false,
                    autoservice: true,
                    minimizeToTray: true,
                    closeToTray: true,
                },
                routers : [
                    {
                        listen : 3307,
                        host : "192.168.1.24",
                        port : 3306
                    },
                    {
                        listen : 9010,
                        host : "localhost",
                        port : 9090
                    },
                    {
                        listen : 9020,
                        host : "127.0.0.1",
                        port : 9090
                    }
                ]
            }, {
                encrypt: true
            });

            ipcMain.on("open-preperences", (event, data) => {
                this.show();
            });

            ipcMain.on("request-preferences", (event, data) => {
                // const target = (data.target === "prefs") ? this.prefsWindow : this.win;
                const target = event.sender;
                // console.log("REQUEST_PREFERENCES", prefs);
                target.webContents.send("response-preferences", this.preferences);
            });

            ipcMain.on("save-preferences", (event, data) => {
                Object.keys(data).forEach((key) => {
                    this.preferences[key] = data[key];
                });
                // console.log("SAVE_PREFERENCES", prefs);
                this.preferences.save();

                this.win.webContents.send("changed-preferences", this.preferences);
                this.prefsWindow.webContents.send("changed-preferences", this.preferences);
            });
        }

        get() {
            return this.preferences;
        }

        show() {
            if(this.visible === true && this.prefsWindow) {
                this.prefsWindow.show();
                return;
            }
            
            const appicon = CommonUtils.icon(64);
            const style = {
                parent: this.win,
                icon: appicon,
                modal: true,
                width: 600,
                height: 360,
                // frame: false,
                resizable: false,
                minimizable: false,
                maximizable: false,
                useContentSize: true,
                webPreferences : {
                    // devTools : false,
                }
            };
            switch(process.platform) {
            case "darwin":
                style.frame = true;
                style.maximizable = false;
                break;
            default:
            }

            this.prefsWindow = new BrowserWindow(style);

            this.prefsWindow.loadURL(winURL);
            // dialog.once('ready-to-show', () => {
            //     dialog.show();
            // });

            this.prefsWindow.on("closed", () => {
                // null;
                this.visible = false;
                this.prefsWindow = null;
            });

            this.visible = true;
        }
    }

    return {
        getInstance(win) {
            if(!PreferencesManager.instance) {
                if(!win) {
                    throw new Error("윈도우 객체의 인스턴스가 필요합니다.");
                }
                PreferencesManager.instance = new PreferencesManager(win);
            }
            return PreferencesManager.instance;
        },
        get() {
            if(!PreferencesManager.instance) {
                throw new Error("환경설정관리자의 초기화가 먼저 필요합니다.");
            }
            const inst = PreferencesManager.instance;
            return inst.get();
        },
        show() {
            if(!PreferencesManager.instance) {
                throw new Error("환경설정관리자의 초기화가 먼저 필요합니다.");
            }
            const inst = PreferencesManager.instance;
            return inst.show();
        }
    };
})();

환경설정 화면(renderer 프로세스) 생성하기

환경설정 읽기, 저장, 수정하는 화면을 아래와 같이 구성합니다.

<template>
  <div id="wrapper">
    <main>
      <div class="left-side">
        <span class="title">TCP Router</span>
        <div class="items">
          <div class="item" v-for="(item, index) in preferences.routers"
            :key="index"
            :class="{'selected': selectedIndex == index}"
            @click.prevent.stop="setselectedIndex(index)"
          >
            <div class="name">{{ item.listen }}, {{item.host}}:{{item.port}}</div>
          </div>
        </div>
      </div>

      <div class="right-side">
        <div class="title">Router Information</div>
        <div class="information" ref="information">
          <div class="info-row">
            <div class="info-title">Listen</div>
            <div class="info-value"><input type="text" name="listen" v-model="selectedItem.listen" /></div>
          </div>
          <div class="info-row">
            <div class="info-title">Host</div>
            <div class="info-value"><input type="text" name="host" v-model="selectedItem.host" /></div>
          </div>
          <div class="info-row">
            <div class="info-title">Port :</div>
            <div class="info-value"><input type="text" name="port" v-model="selectedItem.port" /></div>
          </div>
        </div>
        <div class="info-control">
          <button class="alt" @click="handleAdd" v-if="selectedIndex >= 0">Modify</button>
          <button class="alt" @click="handleAdd" v-else>Add</button>
          <button class="alt" @click="handleDelete" v-if="selectedIndex >= 0">Delete</button>
          <button @click="handleReset">Reset</button>
        </div>
      </div>
    </main>

    <footer class="controlbox">
      <div class="left-side">
        <div class="doc">
          <button class="alt" @click="handleNew">New Router</button>
        </div>
      </div>
      <div class="right-side">
        <div class="doc">
          <button @click="handleSave">Save</button>
          <button class="alt" @click="handleClose">Close</button>
        </div>
      </div>
    </footer>
  </div>
</template>

<script>
import { ipcRenderer, remote } from 'electron'

  export default {
    name: 'preferences-page',
    components: {

    },
    data() {
      return {
        selectedIndex: -1,
        selectedItem: {},
        preferences: {},
      }
    },
    created() {
        console.log("created....");
        ipcRenderer.on("response-preferences", (event, args) => {
          console.log(args);
          this.preferences = args;
        });
        ipcRenderer.on("changed-preferences", (event, args) => {
          console.log(args);
          this.preferences = args;
        });
        
        ipcRenderer.send("request-preferences");
    },
    mounted() {
    },
    destroyed() {
    },
    methods: {
      setselectedIndex(index) {
        this.selectedIndex = index;
        this.bindData(index);
      },
      clearselectedIndex() {
        this.selectedIndex = -1;
        this.selectedItem = {};
      },
      bindData(index) {
        let router = {};
        if(index >= 0) {
          router = this.preferences.routers ? this.preferences.routers[index] : null;
        }
        router = router ? router : {listen:0, host:"", port:0}
        const {listen, host, port} = router;
        this.selectedItem =  {listen, host, port};
      },
      handleNew() {
        this.selectedIndex = -1;
        this.selectedItem = {};
      },
      handleAdd() {
        if(!this.preferences.routers) {
          this.preferences.routers = [];
        }

        const {listen, host, port} = this.selectedItem;
        if(this.selectedIndex >= 0) {
          
          this.preferences.routers.splice(
            this.selectedIndex,
            1, {
              listen, host, port
            }
          );
        } else {
          this.preferences.routers.push({
            listen, host, port
          });
        }
        this.selectedIndex = -1;
        this.selectedItem = {};
      },
      handleDelete() {
        if(this.selectedIndex >= 0) {
          this.preferences.routers.splice(this.selectedIndex, 1);
        }
        this.selectedIndex = -1;
        this.selectedItem = {};
      },
      handleReset() {
        this.bindData(this.selectedIndex);
      },
      handleSave() {
        ipcRenderer.send("save-preferences", this.preferences);
        this.handleClose();
      },
      handleClose() {
        const window = remote.getCurrentWindow();
        window.close();
      },
    }
  }
</script>

<style scoped>
 
......(이하생략)......

라우팅경로 추가

환경설정화면을 라우팅경로에 추가합니다. /src/renderer/router/index.js 에 환경설정 화면을 등록하고 meta에 다이얼로그의 제목을 등록하여 창이름을 변경합니다.

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

let router = new Router({
  routes: [
    {
      path: '/',
      name: 'main-page',
      meta: {
          title : "TCP Socket Router"
      },
      component: require('@/views/MainPage').default
    },
    {
        path: "/preferences",
        name: "preferences-page",
        meta: {
            layout: "dialog",
            title : "Preferences"
        },
        component: require("@/views/Preperences").default
    },
  ]
})

router.beforeEach((to, from, next) => {
  document.title = to.meta.title
  next()
})

export default router;

환경설정 초기화 코드 등록

/src/main/index.js 에서 init() 함수에 환경설정 초기화코드를 삽입니다. 초기화이후에는 애플리케이션에서 싱글톤으로 환경설정 인스턴스가 존재합니다.

import { app, ipcMain } from 'electron'
import MainWindow from './window/main-window'

import PreferencesManager from "./window/preferences-window";
import launchTCPRouter from './shared/tcp-router-launcher'

let mainWindow = null;
function init() {
  mainWindow = MainWindow.create();
  mainWindow.on('closed', () => {
    mainWindow = null
  });

  // // 환경설정 관리자
  app.preference = PreferencesManager.getInstance(mainWindow);

  TCPRouterLauncher.getInstance().execute();
  
......(이하생략)......

환경설정 창 구성

아래의 그림과 같이 왼쪽에는 TCP Router 목록을 오른쪽에는 정보를 편집하는 화면으로 구성합니다. 일반적인 UI입니다.설명은 생략하도록 하겠습니다.

환경설정

 

참고자료

 

소스코드

본 포스트 관련 소스코드는 여기에서 다운로드 가능합니다.

728x90