Tkinter를 활용한 UI 프로그래밍1
tkinter는 GUI에 대한 표준 Python 인터페이스이며 Window UI 창을 생성할 수 있습니다.
< 기본 과정 >
- 윈도우 생성: tk.Tk()
- 위젯 생성: tk.Label(), tk.Button() => 윈도우에 붙일 버튼, 레이블, 입력박스 등 생성
- 이벤트 핸들러 등록.command 속성에 이벤트 발생시 호출된 함수 등록
- 생성한 위젯을 윈도우에 붙임: 위젯.pack()
- 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 스레드를 실행하여 화면에 출력
위와 같이 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
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
다음은 윈도우의 타이틀명, 크기 및 위치, 위젯의 위치를 조정해 보겠습니다.
- 위젯.pack(side=위치) 위치 옵션: ‘right’, ‘left’, ‘top’, ‘bottom’ 등등
- .title(타이틀명) => 타이틀명 지정
- .geometry(‘300x200+100+100’) => 300x300 크기, 윈도우 출력 위치 100, 100
- .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()
배치를 해보니 아래와 같이 격자로 정렬된 위치 조정이 필요 할 수 있습니다.
이때 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()
내가 원하는 위치에 위젯을 조정하고 싶을때도 있습니다. 이럴때 .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()
이번에는 이벤트 핸들러 동작을 좀 더 이용해 보겠습니다.
버튼을 클릭 했을때 이벤트 핸들러를 발생시키고 이번에는 함수에 파라미터를 지정해 줍니다.
이때 파라미터 값을 지정해 주기위해서 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
마지막으로 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()
모양은 위와 같고 윈도우 계산기와 비슷하게 돌아가게 최대한 이벤트 처리를 해 보았는데….
의식의 흐름대로 짜다보니 코드가 길어 졌네요… ㅠㅠ;;
아직 잘잘한 오류도 남아 있는거 같은데 다음에 다시 손 봐야 겠습니다. ㅎㅎ
지금까지 Tkinter를 이용한 Python UI 프로그래밍을 해보았습니다.
앞으로의 포스팅에서도 자주 등장할 거 같으니 그때는 더 보강해 보겠습니다.