Tkinter를 활용한 UI 프로그래밍1

6 분 소요

tkinter는 GUI에 대한 표준 Python 인터페이스이며 Window UI 창을 생성할 수 있습니다.

< 기본 과정 >

  1. 윈도우 생성: tk.Tk()
  2. 위젯 생성: tk.Label(), tk.Button() => 윈도우에 붙일 버튼, 레이블, 입력박스 등 생성
  3. 이벤트 핸들러 등록.command 속성에 이벤트 발생시 호출된 함수 등록
  4. 생성한 위젯을 윈도우에 붙임: 위젯.pack()
  5. window.mainloop()를 호출하여 ui 실행
import tkinter as tk

window = tk.Tk()  #Tk 객체 생성, 기본 윈도우 객체
#tk.Label(이 레이블을 배치할 윈도우, )
label = tk.Label(window, text = 'hello', width='100', height='5') # 뷰 위젯의 하나인 레이블 객체 생성
btn1 = tk.Button(window, text = 'btn1')
label.pack() # 위젯(레이블, 버튼...)을 윈도우에 붙임
btn1.pack()
window.mainloop() #ui 스레드를 실행하여 화면에 출력
1

위와 같이 window 창이 실행되고 label 하나와 버튼 한개가 기본 윈도우에 붙는 것을 확인 할 수 있습니다.



다음은 이벤트 핸들러를 등록해 보겠습니다.

import tkinter as tk

def f1():
    print('button click')

window = tk.Tk()
label = tk.Label(window, text = 'hello', width='100', height='5')
btn1 = tk.Button(window, text = 'btn1', command=f1) # command: 이벤트 핸들러 등록.
label.pack() 
btn1.pack()
window.mainloop()

button click

2

command 에 f1 함수를 등록 해서 함수 btn1 을 클릭하면 f1()함수의 print(‘button click’)이 실행되는 것을 확인 할 수 있습니다.



이번에는 버튼의 이벤트 규칙을 좀 더 넣어 보겠습니다. 뷰위젯.cget(속성이름) 을 통해서 속성값을 읽어와서 출력해 보겠습니다. 위와 동일 하게 버튼을 클릭하면 이벤트 핸들러가 동작하고 .cget() 을 통해서 text 속성의 값을 읽어와 출력 합니다.

import tkinter as tk

def f1():
    label_text = label.cget('text') # 뷰위젯.cget(속성이름): 속성값 읽음
    btn_text = btn1.cget('text')
    print(label_text, ' / ', btn_text)

window = tk.Tk()
label = tk.Label(window, text = 'hello', width='100', height='5')
btn1 = tk.Button(window, text = 'btn1', command=f1)
label.pack() 
btn1.pack()
window.mainloop()

hello / btn1




다음은 윈도우의 타이틀명, 크기 및 위치, 위젯의 위치를 조정해 보겠습니다.

  1. 위젯.pack(side=위치) 위치 옵션: ‘right’, ‘left’, ‘top’, ‘bottom’ 등등
  2. .title(타이틀명) => 타이틀명 지정
  3. .geometry(‘300x200+100+100’) => 300x300 크기, 윈도우 출력 위치 100, 100
  4. .resizable(False, False) => False일 경우 크기조정이 되지 않게 함
win = tk.Tk()
win.title('window title') # 윈도우 타이틀
win.geometry('300x200+100+100') # 300x300 크기, 윈도우 출력 위치 100, 100
win.resizable(False, False) # 크기 조정 방지

b1 = tk.Button(win, text='right', width=10)
b1.pack(side='right') # sid: 윈도우에 붙일 방향
b2 = tk.Button(win, text='left', width=10)
b2.pack(side='left')
b3 = tk.Button(win, text='top', width=10)
b3.pack(side='top')
b4 = tk.Button(win, text='bottom', width=10)
b4.pack(side='bottom')

win.mainloop()
3




배치를 해보니 아래와 같이 격자로 정렬된 위치 조정이 필요 할 수 있습니다.

4

이때 grid 를 이용하여 간격을 조정 할 수 있습니다.

win = tk.Tk()
win.title('window title') 
win.geometry('300x200+100+100') 
win.resizable(False, False)

b1 = tk.Button(win, text='1', width=10)
b1.grid(row=0, column=0) # grid: 격자형 배치
b2 = tk.Button(win, text='2', width=10)
b2.grid(row=0, column=1)
b3 = tk.Button(win, text='3', width=10)
b3.grid(row=0, column=2)
b4 = tk.Button(win, text='4', width=10)
b4.grid(row=1, column=0)
b5 = tk.Button(win, text='5', width=10)
b5.grid(row=1, column=1)
b6 = tk.Button(win, text='6', width=10)
b6.grid(row=1, column=2)

win.mainloop()
5




내가 원하는 위치에 위젯을 조정하고 싶을때도 있습니다. 이럴때 .pace(x, y)를 이용하여 x, y 축의 위치를 조정해 줍니다.

win = tk.Tk()
win.title('window title')
win.geometry('300x200+100+100')
win.resizable(False, False)

b1 = tk.Button(win, text='1', width=10)
b1.place(x=30, y=30)  # .pace(x, y) 원하는 x, y축에 붙이기
b2 = tk.Button(win, text='2', width=10)
b2.place(x=100, y=100)
b3 = tk.Button(win, text='3', width=10)
b3.place(x=170, y=170)

win.mainloop()
6




이번에는 이벤트 핸들러 동작을 좀 더 이용해 보겠습니다.

버튼을 클릭 했을때 이벤트 핸들러를 발생시키고 이번에는 함수에 파라미터를 지정해 줍니다.
이때 파라미터 값을 지정해 주기위해lambda를 사용합니다.
또한 label 화면에 출력하여 그 값을 버튼 클릭 시 마다 변경해주기 위해 .config()를 이용 하겠습니다.

def f1(label, val):
    s = label.cget('text')
    print(s)
    s += val
    label.config(text=s) # config(): 위젯 속성의 값을 설정

def main():
    win = tk.Tk()
    win.title('window title')
    win.geometry('300x200+100+100')
    win.resizable(False, False)
    
    label = tk.Label(win, height=4)
    label.grid(row=0, column=0)
    b1 = tk.Button(win, text='1', width=7, height=4, command=lambda:f1(label, '1')) # 파라미터를 지정하기 위해 lambda 사용
    b1.grid(row=1, column=0) 
    b2 = tk.Button(win, text='2', width=7, height=4, command=lambda:f1(label, '2'))
    b2.grid(row=1, column=1) 
    b3 = tk.Button(win, text='3', width=7, height=4, command=lambda:f1(label, '3'))
    b3.grid(row=1, column=2) 
    b4 = tk.Button(win, text='+', width=7, height=4, command=lambda:f1(label, '+'))
    b4.grid(row=1, column=3) 
    win.mainloop()

main()

1
12
123
123+
123+3

7




마지막으로 UI를 만들때 꼭 빠지지 않는게 있습니다.
바로 바로….. 계산기 입니다 !
지금까지의 기능들을 통해 계산기를 만들어 보겠습니다.

import tkinter as tk

# 버튼 클릭 이벤트시에 버튼의 종류가 숫자인지 그 외 인지 확인
def click(val):
    try:
        num = int(val)
        numberop(num)
    except:
        operatorop(val)

# 숫자 클릭 이벤트처리
def numberop(num):
    global opflag
    global num1
    global num2
    global op

    # 연산자 버튼 클릭 후 숫자버튼 클릭시 처리
    if label1.cget('text') != '':
        if opflag:
            opflag = False
            label2.config(text='0')
            
    # label2에 '0으로 나눌 수 없습니다' 출력 후 숫자 버튼 클릭시 처리
    if label2.cget('text') == '0으로 나눌 수 없습니다':
        label2.config(text='0')  # ㅣabel2의 값을 초기화
        if num >= 1:  # 0 이 아닌 버튼이 클릭되었을때
            label2.config(text=str(num))  # label2의 값을 입력된 값으로 변경
    
    # 초기 0값일때 에러발생을 막기위한 처리
    elif label2.cget('text') == '0':  # label2의 값이 0이면
        if num >= 1:   # 0 이 아닌 버튼이 클릭되었을때 숫자 교환
            label2.config(text=str(num))
    else:   # label2의 값이 0이 아니면 값을 추가
        s = label2.cget('text')
        s += str(num)
        label2.config(text=s)

# 숫자 외 버튼 처리 함수
def operatorop(val):
    global opflag
    global num1
    global num2
    global op

    # 0으로 나눌 수 없습니다. 출력시 숫자외 버튼 오류 처리
    if label2.cget('text') == '0으로 나눌 수 없습니다':
        calcul_init()

    # 초기화 버튼 이벤트 처리
    if val == 'C':
        calcul_init()

    # '=' 버튼 이벤트 처리
    elif val == '=':
        s1 = label1.cget('text')
        if s1 != '':  # label1의 값이 있을때
            # '=' 결과 처리후 '=' 버튼을 연속으로 누를시 반복 계산
            if s1[-1] == '=':  
                num1 = int(label2.cget('text'))
            else: # 일반적인 상황
                num1 = int(s1[:-1])
                num2 = int(label2.cget('text'))

            fcVal = four_calcul()  # 사칙연산 계산
            if fcVal == 'Fail':
                calcul_init()
                label2.config(text='0으로 나눌 수 없습니다')
                return

            lab1txt = str(num1) + op + str(num2) + '='
            label1.config(text=lab1txt)
            label2.config(text=str(fcVal))

        else:  # label1의 값이 없을때
            s2 = label2.cget('text')
            s2 += '='
            label1.config(text=s2)

        if opflag == False:
            opflag = True

    # 사칙연산 버튼 이벤트 처리
    else:
        s1 = label1.cget('text')
        if s1 != '':  # label1의 값이 있을때
            # 숫자가 아닌 버튼을 연속으로 눌렀을때 처리
            if opflag:
                num1 = int(label2.cget('text'))
                op = val  # op변수에 사칙연산 기호 변경
                label1.config(text=str(num1) + op)

            # '=' 버튼으로 계산 결과를 보는 것이 아닌 사칙연산 버튼으로 결과 처리
            else:
                num1 = int(s1[:-1])
                num2 = int(label2.cget('text'))

                fcVal = four_calcul()  # 사칙연산 계산
                if fcVal == 'Fail':
                    calcul_init()
                    label2.config(text='0으로 나눌 수 없습니다')
                    return
                num1 = fcVal
                num2 = fcVal
                op = val  # op변수에 사칙연산 기호 변경
                label1.config(text=str(num1) + op)
                label2.config(text=str(num2))
                
        else:  # label1의 값이 없을때
            op = val  # op변수에 사칙연산 기호 변경
            s2 = label2.cget('text')
            label1.config(text=str(s2) + op)
            num1 = str(s2)
            num2 = str(s2)

        if opflag == False:
            opflag = True

# 전체 변수 및 label 초기화 함수
def calcul_init():
    global opflag
    global num1
    global num2
    global op

    label1.config(text='')
    label2.config(text='0')
    num1 = 0
    num2 = 0
    op = ''
    opflag = False


# 사칙연산 계산
def four_calcul():
    if op == '+':
        return num1 + num2
    elif op == '-':
        return num1 - num2
    elif op == '*':
        return num1 * num2
    elif op == '/':
        if num2 == 0:
            return 'Fail'
        else:
            return num1 / num2

# 전역 변수 설정
opflag = False  # 직전 입력이 연산자 입력일 경우
num1 = 0
num2 = 0
op = ''

# tkinter 화면 출력부
win = tk.Tk()
win.title('계산기')
win.resizable(False, False)

label1 = tk.Label(win, height=2, wraplength=220)  # wraplength : 자동 줄내림 설정 너비
label1.grid(row=0, column=0, columnspan=4)  # columnspan : 가로 컬럼의 길이
label2 = tk.Label(win, text='0', height=2, wraplength=220)
label2.grid(row=1, column=0, columnspan=4)

vals = [['1', '2', '3', '+'], ['4', '5', '6', '-'], ['7', '8', '9', '*'], ['0', 'C', '=', '/']]
for i in range(0, 4):
    for j in range(0, 4):
        val = vals[i][j]
        cmd = lambda bt=val: click(bt)
        b = tk.Button(text=val, width=7, height=4, command=cmd)
        b.grid(row=(i + 2), column=j)
win.mainloop()
8

모양은 위와 같고 윈도우 계산기와 비슷하게 돌아가게 최대한 이벤트 처리를 해 보았는데….
의식의 흐름대로 짜다보니 코드가 길어 졌네요… ㅠㅠ;;
아직 잘잘한 오류도 남아 있는거 같은데 다음에 다시 손 봐야 겠습니다. ㅎㅎ

지금까지 Tkinter를 이용한 Python UI 프로그래밍을 해보았습니다.
앞으로의 포스팅에서도 자주 등장할 거 같으니 그때는 더 보강해 보겠습니다.