[Python] 자동화 프로그램(GUI편-계산기)

보통 프로그래밍 언어를 배울 때 CLI(콘솔프로그램)

계산기를 한번 씩 만들어 본다.

 

파이썬 IDLE 인터프리터는

거의 모든 것이 가능한 계산기인데

센스가 좋은 사람은 파이썬을 조금 배우면

스크립트 파일과 함께 무적의 계산기로 사용할 수 있을 것이다.

 

파이썬 IDLE는 온갖 복잡한 연산까지 쉽게되므로

콘솔 계산기를 만들어야할 필요가 없다.

 

tkinter GUI 계산기 기본

기본 GUI 계산기를 만들어 보자.

윈도우10에 내장된 계산기는 아래와 같이 생겼다.

일반 계산기보다 좀 더 많은 기능이 있는 계산기다.

물론 파이썬으로도 똑같이 만들 수 있다.

윈도우10 내장 계산기

계산기를 만들 때  생각보다 많은 시간을 소모하는 것은

다름이 아니라 레이아웃과 디자인이다.

 

이거 생각보다 시간을 많이 잡아먹는데

웹페이지로 말하면 반응형도 있고

다양한 레이아웃이 있다.

 

레이아웃의 특징도 좀 이해할 필요가 있는데

웹을 개발하던 사람이라면 좀 불편하게 느낄 수도 있다.

 

tkinter 자체는 1991년에 개발된

GUI Toolkit Interface 으로

30년전의 디자인과 현재의 감각은 꽤 거리감이 있다.

 

웹이 조금 고급스러워진 것도 역사적으로 보면

극히 최근에 일어난 일이다.

 

tkinter 는 이렇게 학습용이나

급히 프로토타입을 만들어야 할 때

사용하기에 좋은 GUI toolkit 이다.

 

전체코드

import tkinter as tk
 
root = tk.Tk()
root.title("Tkinter Calc")
root.geometry("300x480")
 
upper_frame = tk.Frame(root, width=500, height=300)
upper_frame.pack(pady=40)
 
down_frame = tk.Frame(root, width=400, height=100)
down_frame.pack(padx=10, pady=10)
 
entry = tk.Entry(upper_frame, width=20, font=("Courier",25), borderwidth=5)
entry.pack()
entry.insert(0,"")
 
def button_clicked(number):
    current = entry.get()
    entry.delete(0, tk.END)
    entry.insert(0, str(current) + str(number))
 
def button_clear():
    entry.delete(0, tk.END)
 
def button_add():
    first_number = entry.get()
    global f_num
    global math
    math = 'addition'
    f_num = int(first_number)
    entry.delete(0, tk.END)
 
def button_sub():
    first_number = entry.get()
    global f_num
    global math
    math = 'subtraction'
    f_num = int(first_number)
    entry.delete(0, tk.END)
 
def button_mul():
    first_number = entry.get()
    global f_num
    global math
    math = 'multiplication'
    f_num = int(first_number)
    entry.delete(0, tk.END)
 
def button_div():
    first_number = entry.get()
    global f_num
    global math
    math = 'division'
    f_num = int(first_number)
    entry.delete(0, tk.END)
 
def button_equal():
    second_number = entry.get()
    entry.delete(0, tk.END)
 
    if math == 'addition':
        entry.insert(0,f_num + int(second_number))
 
    if math == 'subtraction':
        entry.insert(0,f_num - int(second_number))
 
    if math == 'multiplication':
        entry.insert(0,f_num * int(second_number))
 
    if math == 'division':
        entry.insert(0,f_num / int(second_number))
 
 
btn7 = tk.Button(down_frame,text='7', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked(7))
btn7.grid(column=0, row=0, padx=5, pady=5)
btn8 = tk.Button(down_frame,text='8', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked(8))
btn8.grid(column=1, row=0, padx=5, pady=5)
btn9 = tk.Button(down_frame,text='9', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked(9))
btn9.grid(column=2, row=0, padx=5, pady=5)
 
btn4 = tk.Button(down_frame,text='4', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked(4))
btn4.grid(column=0, row=1, padx=5, pady=5)
btn5 = tk.Button(down_frame,text='5', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked(5))
btn5.grid(column=1, row=1, padx=5, pady=5)
btn6 = tk.Button(down_frame,text='6', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked(6))
btn6.grid(column=2, row=1, padx=5, pady=5)
 
btn1 = tk.Button(down_frame,text='1', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked(1))
btn1.grid(column=0, row=2, padx=5, pady=5)
btn2 = tk.Button(down_frame,text='2', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked(2))
btn2.grid(column=1, row=2, padx=5, pady=5)
btn3 = tk.Button(down_frame,text='3', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked(3))
btn3.grid(column=2, row=2, padx=5, pady=5)
 
btn_pm = tk.Button(down_frame,text='+/-', padx=5, pady=10, font=("Courier",15),command=lambda: button_clicked('-'))
btn_pm.grid(column=0, row=3, padx=5, pady=5)
btn0 = tk.Button(down_frame,text='0', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked(0))
btn0.grid(column=1, row=3, padx=5, pady=5)
btn_p = tk.Button(down_frame,text='.', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked('.'))
btn_p.grid(column=2, row=3, padx=5, pady=5)
 
btn_mul = tk.Button(down_frame,text='X', padx=15, pady=10, font=("Courier",15),command=button_mul, bg='orange')
btn_mul.grid(column=3, row=0, padx=5, pady=5)
btn_sub = tk.Button(down_frame,text='-', padx=15, pady=10, font=("Courier",15),command=button_sub, bg='orange')
btn_sub.grid(column=3, row=1, padx=5, pady=5)
btn_add = tk.Button(down_frame, text='+', padx=15, pady=10, font=("Courier",15),command=button_add, bg='orange')
btn_add.grid(column=3, row=2, padx=5, pady=5)
btn_div = tk.Button(down_frame, text='/', padx=15, pady=10, font=("Courier",15),command=button_div, bg='orange')
btn_div.grid(column=3, row=3, padx=5, pady=5)
 
btn_c = tk.Button(down_frame, text='C', padx=15, pady=10, font=("Courier",15),command=button_clear, bg='orange')
btn_c.grid(column=2, row=4, padx=5, pady=5)
 
btn_res = tk.Button(down_frame, text='=', padx=15, pady=10, font=("Courier",15),command=button_equal, bg='orange')
btn_res.grid(column=3, row=4, padx=5, pady=5)
 
btn3 = tk.Button(root,text='9')
 
root.mainloop()

 

*코드 도입부

1. 처음에 창과 프레임을 설정하는 부분

2. 버튼 클릭시 동작하는 함수 정의

3. 엔트리, 버튼 등 컴포넌트 생성 및 설정(레이아웃 작업)

 

import tkinter as tk
 
root = tk.Tk()
root.title("Tkinter Calc")
root.geometry("300x480")
 
upper_frame = tk.Frame(root, width=500, height=300)
upper_frame.pack(pady=40)
 
down_frame = tk.Frame(root, width=400, height=100)
down_frame.pack(padx=10, pady=10)
 
entry = tk.Entry(upper_frame, width=20, font=("Courier",25), borderwidth=5)
entry.pack()
entry.insert(0,"")

도입 부분이다. 프레임을 위 아래 두개로 나누었다.

위쪽은 그냥 편하게 pack 으로 레이아웃을 하고,

아래쪽은 grid 레이아웃이다.

버튼을 나열할 때는 바둑판인 grid 가 좀 낫다.

 

위쪽에는 Entry 객체가 들어간다.

계산기의 숫자가 표시되는 부분이다.

나중에 추가 기능을 넣는다면

현재 Entry 에 표시되는 내용에 대한 정보를

Label 로 표시해준다면 좋을 것 같다.

 

예를 들어 현재 더하기 모드입니다,

뺄셈 모드입니다 등 표시. 누산된 숫자.

 

매개변수 중에 padx 와 pady 가 나오는데

쉽게 말해 글자와 박스의 경계(보더) 까지의 거리이다.

 

박스는 보이는게 아니라 프레임 처럼

위치만 잡고 있을 수도 있으니

테스트를 통해서 감을 잡는게 좋다.

 

함수 부분

def button_clicked(number):
    current = entry.get()
    entry.delete(0, tk.END)
    entry.insert(0, str(current) + str(number))
 
def button_clear():
    entry.delete(0, tk.END)
 
def button_add():
    first_number = entry.get()
    global f_num
    global math
    math = 'addition'
    f_num = int(first_number)
    entry.delete(0, tk.END)
 
def button_sub():
    first_number = entry.get()
    global f_num
    global math
    math = 'subtraction'
    f_num = int(first_number)
    entry.delete(0, tk.END)
 
def button_mul():
    first_number = entry.get()
    global f_num
    global math
    math = 'multiplication'
    f_num = int(first_number)
    entry.delete(0, tk.END)
 
def button_div():
    first_number = entry.get()
    global f_num
    global math
    math = 'division'
    f_num = int(first_number)
    entry.delete(0, tk.END)
 
def button_equal():
    second_number = entry.get()
    entry.delete(0, tk.END)
 
    if math == 'addition':
        entry.insert(0,f_num + int(second_number))
 
    if math == 'subtraction':
        entry.insert(0,f_num - int(second_number))
 
    if math == 'multiplication':
        entry.insert(0,f_num * int(second_number))
 
    if math == 'division':
        entry.insert(0,f_num / int(second_number))

함수는 간결한 것이 좋다.

 

단순한 계산기에 굳이 클래스를 만들어야 할 이유는 없다.

 

숫자 두개를 더하기 위해서

전역변수와 함수 하나만으로도 충분한데

인스턴스를 생성해야 한다면 좀 낭비같다.

 

사칙연산만 할거면 클래스 설계가 필요 없지만

기능이 많아지고 복잡해지면 고려해볼 만하다.

 

위의 함수들은 각 버튼이 눌렸을 때

이벤트 처리기능을 정의한다.

 

global 변수인 f_num 과 math를 사용해서

계산기의 기능을 수행한다.

 

math는 어떤 버튼이 눌렸는지 파악한다.

아래쪽을 보면 if 문으로 처리함을 알 수 있다.

 

부호가 표시된 버튼을 클릭하면

숫자와 함께 어떤 연산을 하는지

정보를 전역변수에 넣는다.

 

최종적으로 equal 버튼을 누르면 계산이 실행된다.

 

버튼과 레이아웃

tn7 = tk.Button(down_frame,text='7', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked(7))
btn7.grid(column=0, row=0, padx=5, pady=5)
btn8 = tk.Button(down_frame,text='8', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked(8))
btn8.grid(column=1, row=0, padx=5, pady=5)
btn9 = tk.Button(down_frame,text='9', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked(9))
btn9.grid(column=2, row=0, padx=5, pady=5)
 
btn4 = tk.Button(down_frame,text='4', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked(4))
btn4.grid(column=0, row=1, padx=5, pady=5)
btn5 = tk.Button(down_frame,text='5', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked(5))
btn5.grid(column=1, row=1, padx=5, pady=5)
btn6 = tk.Button(down_frame,text='6', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked(6))
btn6.grid(column=2, row=1, padx=5, pady=5)
 
btn1 = tk.Button(down_frame,text='1', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked(1))
btn1.grid(column=0, row=2, padx=5, pady=5)
btn2 = tk.Button(down_frame,text='2', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked(2))
btn2.grid(column=1, row=2, padx=5, pady=5)
btn3 = tk.Button(down_frame,text='3', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked(3))
btn3.grid(column=2, row=2, padx=5, pady=5)
 
btn_pm = tk.Button(down_frame,text='+/-', padx=5, pady=10, font=("Courier",15),command=lambda: button_clicked('-'))
btn_pm.grid(column=0, row=3, padx=5, pady=5)
btn0 = tk.Button(down_frame,text='0', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked(0))
btn0.grid(column=1, row=3, padx=5, pady=5)
btn_p = tk.Button(down_frame,text='.', padx=15, pady=10, font=("Courier",15),command=lambda: button_clicked('.'))
btn_p.grid(column=2, row=3, padx=5, pady=5)
 
btn_mul = tk.Button(down_frame,text='X', padx=15, pady=10, font=("Courier",15),command=button_mul, bg='orange')
btn_mul.grid(column=3, row=0, padx=5, pady=5)
btn_sub = tk.Button(down_frame,text='-', padx=15, pady=10, font=("Courier",15),command=button_sub, bg='orange')
btn_sub.grid(column=3, row=1, padx=5, pady=5)
btn_add = tk.Button(down_frame, text='+', padx=15, pady=10, font=("Courier",15),command=button_add, bg='orange')
btn_add.grid(column=3, row=2, padx=5, pady=5)
btn_div = tk.Button(down_frame, text='/', padx=15, pady=10, font=("Courier",15),command=button_div, bg='orange')
btn_div.grid(column=3, row=3, padx=5, pady=5)
 
btn_c = tk.Button(down_frame, text='C', padx=15, pady=10, font=("Courier",15),command=button_clear, bg='orange')
btn_c.grid(column=2, row=4, padx=5, pady=5)
 
btn_res = tk.Button(down_frame, text='=', padx=15, pady=10, font=("Courier",15),command=button_equal, bg='orange')
btn_res.grid(column=3, row=4, padx=5, pady=5)
 
btn3 = tk.Button(root,text='9')
 
root.mainloop()

 

마지막 부분은 계산기 버튼의 생성과 배치(레이아웃)다.

grid 레이아웃을 써서 열과 행이 맞춰진 것을 볼 수 있다.

 

grid 는 레이아웃은 바둑판의 좌표를 생각하면 된다.

 

버튼을 생성할 때 이벤트 처리기를 등록해야 하는데

함수라도 ( )를 넣지 않는다.

 

함수를 실행하는게 아니라 참조를 넘겨줘서

버튼클릭 이벤트가 발생할 때 해당 함수를 실행시킨다.

 

함수의 이름과 기능에 대해서 생각하면

이 버튼이 무슨 일을 하는지 알 수 있다

(+/- 는 배치만 한 것이니 의미없는 버튼이다.)

 

command 에 람다를 걸어놓은 함수들은

매개변수를 이 방법으로 전달해야 하기 때문이다.

매개변수가 없는 함수는 그럴 필요가 없다.

그냥 command = button_add 같이 사용한다.

 

이 계산기 코드는 간단하게 만든 것이라

기능을 더 추가해 보는 것을 추천한다