[ Golang을 배워보자! ] go를 이용해 계산기 만들기 - 3 _기능 구현

in #kr7 years ago

안녕하세요.

주말에 포스팅을 했어야 되는데 노느라 바빠서 이제야 포스팅을 하네요 ㅎㅎ.

여기부터 보면 잘 모르실테니 기존 시리즈를 안보신분은 기존 시리즈를 훑어보고 오시면 좋을 것 같습니다.

[ Golang을 배워보자! ] golang 시작하기 ( 개발환경 구축하기 )
https://steemit.com/go/@jungmu/golang
[ Golang을 배워보자! ] go를 이용해 계산기 만들기 - 0 _ gui툴 고르기
https://steemit.com/kr/@jungmu/golang-go-0--gui
[ Golang을 배워보자! ] go를 이용해 계산기 만들기 - 1 _ Demo 실행해 보기
https://steemit.com/kr/@jungmu/golang-go-1--demo
[ Golang을 배워보자! ] go를 이용해 계산기 만들기 - 2 _UI 제작
https://steemit.com/kr/@jungmu/golang-go-2-ui

저번 포스팅에서 ui는 대충 만들어 두었기 때문에 오늘은 계산기로서 기능할 수 있도록 기능 구현을 해보겠습니다.

이번에 구현해볼 결과 영상입니다.

소스코드는
https://github.com/Jungmu/astilectron
이곳에 올려두었습니다.

우선적으로 html을 먼저 수정하겠습니다.

<body>
    <div class="number-area">
        <textarea id="view"></textarea>
        <textarea id="num"></textarea>
    </div>
    <div class="row btn-area">
        <div class="col-12">
            <div class="row">
                <div class="col-3 btn-padding">
                    <button type="button" id="clear" class="btn btn-primary" onclick="index.clear()">C</button>
                </div>
                <div class="col-3 btn-padding">
                    <button type="button" id="√" class="btn btn-primary" onclick="index.clickSymbol(this.id)">√</button>
                </div>
                <div class="col-3 btn-padding">
                    <button type="button" id="^2" class="btn btn-primary"onclick="index.clickSymbol(this.id)">x²</button>
                </div>
                <div class="col-3 btn-padding">
                    <button type="button" id="/" class="btn btn-primary" onclick="index.clickSymbol(this.id)">÷</button>                        
                </div>
            </div>
            <div class="row">
                <div class="col-3 btn-padding">
                    <button type="button" id="1" class="btn btn-secondary" onclick="index.clickNum(this.id)">1</button>
                </div>
                <div class="col-3 btn-padding">
                    <button type="button" id="2" class="btn btn-secondary" onclick="index.clickNum(this.id)">2</button>
                </div>
                <div class="col-3 btn-padding">
                    <button type="button" id="3" class="btn btn-secondary" onclick="index.clickNum(this.id)">3</button>
                </div>
                <div class="col-3 btn-padding">
                    <button type="button" id="x" class="btn btn-primary" onclick="index.clickSymbol(this.id)">×</button>                        
                </div>
            </div>
            <div class="row">
                <div class="col-3 btn-padding">
                    <button type="button" id="4" class="btn btn-secondary" onclick="index.clickNum(this.id)">4</button>
                </div>
                <div class="col-3 btn-padding">
                    <button type="button" id="5" class="btn btn-secondary" onclick="index.clickNum(this.id)">5</button>
                </div>
                <div class="col-3 btn-padding">
                    <button type="button" id="6" class="btn btn-secondary" onclick="index.clickNum(this.id)">6</button>
                </div>
                <div class="col-3 btn-padding">
                    <button type="button" id="+" class="btn btn-primary" onclick="index.clickSymbol(this.id)">+</button>                        
                </div>
            </div>
            <div class="row">
                <div class="col-3 btn-padding">
                    <button type="button" id="7" class="btn btn-secondary" onclick="index.clickNum(this.id)">7</button>
                </div>
                <div class="col-3 btn-padding">
                    <button type="button" id="8" class="btn btn-secondary" onclick="index.clickNum(this.id)">8</button>
                </div>
                <div class="col-3 btn-padding">
                    <button type="button" id="9" class="btn btn-secondary" onclick="index.clickNum(this.id)">9</button>
                </div>
                <div class="col-3 btn-padding">
                    <button type="button" id="-" class="btn btn-primary" onclick="index.clickSymbol(this.id)">-</button>                        
                </div>
            </div>
            <div class="row">
                <div class="col-6 btn-padding">
                    <button type="button" id="0" class="btn btn-secondary" onclick="index.clickNum(this.id)">0</button>
                </div>
                <div class="col-3 btn-padding">
                    <button type="button" id="." class="btn btn-secondary" onclick="index.clickNum(this.id)">.</button>
                </div>
                <div class="col-3 btn-padding">
                    <button type="button" id="result" class="btn btn-primary" onclick="index.reqResult()">=</button>                        
                </div>
            </div>
        </div>
    </div>

먼저 기존 코드에 onclick 속성을 추가해서 버튼 클릭시 이벤트가 발생하도록 구현하였습니다.
C 에는 계산기를 초기화 시키는 clear 함수를
숫자에는 숫자 클릭을 명시하는 clickNum 함수를
연산기호에는 clickSymbol 함수를
= 에는 결과를 나타나게 하는 reqResult 함수를 붙여두었습니다.

또한 버튼별로 id 속성이 추가되었습니다.

그리고 위쪽을 보시면

<div class="number-area">
        <textarea id="edit"></textarea>
 </div>

이렇게 작성되있던 부분이

<div class="number-area">
        <textarea id="view"></textarea>
        <textarea id="num"></textarea>
</div>

이렇게 바뀌었습니다.

그다음에는

css파일도 조금 바뀌었는데요

* {
    box-sizing:  border-box;
}

html, body {
    background-color: #333;    
    color: #fff;
    height: 100%;
    margin: 0;
    width: 100%;
    padding-right: 10px;
    padding-left: 10px;
    min-width: 300px;
}

.number-area {
    color: #fff;
    float: top;
    height: 50px;
    overflow:unset;
    padding-top: 20px;
    width: 100%;
}

#view {
    background-color: #777;
    resize: none;
    height: 100%;
    width: 100%;
    text-align: right;
}
#num {
    visibility: hidden;
}

.btn {
    width: 100%;
}

.btn-area {
    padding: 10px;
}

.btn-padding {    
    padding-left: 5px;
    padding-right: 5px;
    padding-top: 5px;
    padding-bottom: 5px;
}

이렇게 바뀌었습니다.
css를 강의하려는 목적이 아니기 때문에 이부분은 그냥 복사해 주시면 될 것 같네요.

크게 중요하지 않은 html과 css는 이정도로 설명하도록 하겠습니다.
( html과 css에 익숙치 않으신분들은 복사 붙여 넣기를 활용해 주세요! - 글 상단에 github주소에서 코드를 다운 받으시면 됩니다. )

우선 clickNum 함수를 살펴보겠습니다.

let index = {
...
clickNum: function(id) {
        let textarea = document.getElementById("view");
        textarea.value = textarea.value+id;

        let number = document.getElementById("num");
        number.value = number.value+id; 
    },
...
}

저같은 경우에는 함수가 let index 내부에 선언이 되어있는게 굉장히 이질적으로 느껴졋는데요...;
기존 demo를 제작한 개발자 스타일인건지 내부 스펙인건지 아리송해서 기존 형식을 유지하여 개발하였습니다.

코드는 복잡하지 않습니다.
id값으로 view를 가진 textarea와
id값으로 num을 가진 textarea의 값에 누른 숫자를 덧붙이는? 함수입니다.
** 1 버튼을 3번 클릭하면 111 이 되겠죠?

여기서 view는 11 + 12 와같은 수식 전체를 사용자에게 보여주기 위한 용도로 사용될 예정이며
num은 화면상에 보이지않지만, 연산을 위해 숫자값을 저장해 두었다가 go로 작성된 프로그램으로 숫자를 전송하기 위한 임시변수 같은 느낌으로 사용됩니다.

숫자를 클릭하는 함수를 만들었으니 이번엔 연산 기호를 클릭하는 함수를 만들어 보겠습니다.

clickSymbol: function(id) {
        let textarea = document.getElementById("view");
        textarea.value = textarea.value+" "+id+" ";
        index.sendNumber();
        index.sendSymbol(id);
    },

연산기호를 클릭하게 되면 view에 보여주기 위한 문자열 추가가 동작되며 go로 작성된 프로그램에 연산기호와 기호 앞에 입력했던 숫자를 전송하게 됩니다.

  • 여기서 알아두셔야 할 점은, 사실 이정도 기능 구현은 굳이 go로 작성된 프로그램으로 넘길 필요도 없이 js로만 작성을 해도 충분합니다.
  • 다만, 해당 포스팅은 go-astilectron 를 사용해 보는것이 목적이기 때문에 계산기의 연산은 golang 으로 하겠습니다.

다음 함수는 번호와 기호를 전송하는 함수 입니다.

sendNumber: function() {
        let message = {"name": "sendNum"};
        
        let number = document.getElementById("num");
        message.payload = number.value;
        
        astilectron.sendMessage(message, function(message) {
            // Check error
            if (message.name === "error") {
                asticode.notifier.error(message.payload);
                return
            }            
        })
        number.value = ""
    },
    sendSymbol: function(id) {
        let message = {"name": "sendSymbol"};
        message.payload = id;
        astilectron.sendMessage(message, function(message) {

            // Check error
            if (message.name === "error") {
                asticode.notifier.error(message.payload);
                return
            }
            
        })
    },

message에 이름과 payload를 넣은 뒤 go로 작성된 프로그램으로 전송합니다.
자, 그럼 이번에는 go에서 이 메세지를 어떻게 받는지 알아보도록 하죠.

message.go 파일을 보시면

func handleMessages(_ *astilectron.Window, m bootstrap.MessageIn) (payload interface{}, err error) {
    // var result float64
    
    switch m.Name {
        case "블라블라" :
              어쩌고 저쩌고

이런 코드가 보이실텐데요 이부분이 바로 메세지를 받아주는 부분입니다.

js에서 name으로 넣었던

let message = {"name": "sendNum"};

요부분의 name값을 key로 해서 switch 문이 동작이 되는 겁니다.

// handleMessages handles messages
func handleMessages(_ *astilectron.Window, m bootstrap.MessageIn) (payload interface{}, err error) {
    switch m.Name {
    case "sendNum":
        var s string
        if err := json.Unmarshal(m.Payload, &s); err != nil {
            astilog.Error(errors.Wrap(err, "json.Unmarshal failed"))
        }
        num, err := strconv.ParseFloat(s, 64)
        if err == nil {
            numbers = append(numbers,num)
        }
    case "sendSymbol":
        var s string
        if err := json.Unmarshal(m.Payload, &s); err != nil {
            astilog.Error(errors.Wrap(err, "json.Unmarshal failed"))
        }
        if err == nil {
            symbols = s
        }
    case "clear":
        numbers = numbers[:0]
        symbols = ""
    case "result":
        switch symbols {
        case "+":
            result := numbers[0] + numbers[1]
            bootstrap.SendMessage(w, "resResult", strconv.FormatFloat(result,'f',6,64)) 
        case "-":
            result := numbers[0] - numbers[1]
            bootstrap.SendMessage(w, "resResult", strconv.FormatFloat(result,'f',6,64)) 
        case "x":
            result := numbers[0] * numbers[1]
            bootstrap.SendMessage(w, "resResult", strconv.FormatFloat(result,'f',6,64)) 
        case "/":
            result := numbers[0] / numbers[1]
            bootstrap.SendMessage(w, "resResult", strconv.FormatFloat(result,'f',6,64)) 
        }
    default:
    }
    return
}

이렇게 말이죠.

현재 구현상태에서는 result 라는 메세지가 와야만 연산을 진행하며, 연산을 굉장히 단순하게 처리하고 있기 때문에 엉성한 부분이 많습니다... ㅎㅎ

  • 예로 1 + 1 + 2 라고 입력을 한 뒤에 = 버튼을 누르면 2라고 출렵됩니다...;
  • 4라는 값을 얻고 싶다면, 1 + 1 = 를 누른뒤 + 2 = 이라고 눌러야 4라는 결과가 나옵니다 ㅎㅎ;;
case "result" :
// 부분을 보시면
bootstrap.SendMessage(w, "resResult", strconv.FormatFloat(result,'f',6,64)) 
// 이런 부분이 보이실텐데요
// 이부분이 바로 go가 js에게 메세지를 보내는 함수입니다.
// 이렇게 go로 짜여진 프로그램과 js로 짜여진 프로그램이 통신을 하는 형태로 프로그램이 동작이 된답니다 ㅎㅎ

그렇다면 go가 보낸 메세지는 js에서 어떻게 받아낼까요?

listen: function() {
        astilectron.onMessage(function(message) {
            switch (message.name) {
                case "about":
                    index.about(message.payload);
                    return {payload: "payload"};
                    break;
                case "resResult":
                    index.clear();
                    let textarea = document.getElementById("view");
                    textarea.value = message.payload;
                    let number = document.getElementById("num");
                    number.value = message.payload;
                    break;
            }
        });
    }

바로 이 함수에서 받아내는데요
마찬가지로 message의 name으로 switch 분기를 사용하여 동작합니다.

이정도면 js와 go 사이에 메세지를 주고받는 동작이 이해가 되셨을지요?

메세지를 주고 받는 부분만 이해가 됬다면, 나머지 기능 구현은 크게 어렵지 않을거라 생각되네요.

더 심화된 내용은 질문하는 분이 계시다면 포스팅 하도록 하겠습니다.

참고로
main.go에서 창 크기를 조절하시면 좀 더 이쁜 계산기가 됩니다 ㅎㅎ

MessageHandler: handleMessages,
        RestoreAssets:  RestoreAssets,
        WindowOptions: &astilectron.WindowOptions{
            BackgroundColor: astilectron.PtrStr("#333"),
            Center:          astilectron.PtrBool(true),
            MinHeight:       astilectron.PtrInt(380),
            MaxHeight:       astilectron.PtrInt(380),
            Height:          astilectron.PtrInt(380),
            MinWidth:        astilectron.PtrInt(350),
            MaxWidth:        astilectron.PtrInt(350),
            Width:           astilectron.PtrInt(350),
        },

*** 주의사항

OnWait: func(_ *astilectron.Astilectron, iw *astilectron.Window, _ *astilectron.Menu, _ *astilectron.Tray, _ *astilectron.Menu) error {
            w = iw
            return nil
        },

demo프로젝트의 main.go 내부에 5초뒤에 팝업이 뜨는 코드가 있는데요.
그 코드를 지우면서 w=iw를 함께 지워버리시면 메세지를 주고받을때 프로그램이 죽어버립니다.
저 코드를 지우지 않도록 조심하세요 ㅎㅎ

  • 제가 뭣도 모르고 지웠다가 에러가 계속나서... 한동안 삽질을 했다는...
Sort:  

오랫만에 코딩하는걸 보니 c언어의 아픈 과거가 생각나네요ㅠㅠ

ㅎㅎ... C는 쓰기가 편하지는 않죠