프로그래밍/Python

📔파워 유저를 위한 파이썬 Express09. GUI 프로그래밍

카멜필름 2023. 6. 2. 22:34
from tkinter import *

def process():
  print("버튼이 클릭되었습니다.")

window=Tk()
button=Button(window, text="클릭하세요!", command=process)
button.pack()
window.mainloop()

tkinter 시작하기

tkinter: 그래픽 사용자 인터페이스(GUI: Graphical user interface)를 개발할 때 필요한 모듈

tkinter는 객체 지향 프로그래밍의 여러 개념을 쉽게 이해할 수 있는 교육 도구

 

from tkinter import * #tkinter 모듈을 포함

window = Tk() #루트 윈도우를 생성
label=Label(window, text="Hello tkinter") #레이블 위젯을 생성
label.pack() #레이블 위젯을 윈도우에 배치

window.mainloop() #윈도우가 사용자 동작을 대기

🖋from tkinter import *

제일 먼저 해야 할 일: tkinter 모듈을 포함시키기

tkinter 모듈을 Tk툴킷을 사용하는데 필요한 모든 클래스와 함수, 상수를 가지고 있음

*: tkinter 모듈에서 모든 것을 포함시키라는 의미

🖋window=Tk()

제일 먼저 루트 윈도우를 생성해야 함

Tk 클래스는 제목을 가지고 있는 일반적인 윈도우를 나타냄

Tk()을 호출하면 Tk 클래스의 객체가 생성되면서 화면에 하나의 윈도우가 생성됨

이 윈도우 안에 여러 가지 위젯 추가 가능

각 프로그램은 오직 하나의 루트 윈도우를 가져야 함

루트 윈도우는 다른 위젯보다 먼저 생성되어야 함

🖋label=Label(window, text="Hello tkinter")

이 문장으로 레이블 위젯 생성

Label은 레이블 위젯을 나타내는 클래스

라벨 위젯은 화면에 텍스트 및 이미지를 표시하는 역할

Label()의 첫 번째 매개 변수는 항상 부모 컨테이너

🍋컨테이너: 위젯을 내부에 가질 수 있는 객체

여기서는 루트 윈도우가 부모 컨테이너로 전달됨

🖋label.pack()

레이블의 pack() 메소드가 호출됨

pack(): 압축 배치 관리자를 이용해서 레이블를 컨테이너에 위치시킴

압축 배치 관리자는 위젯을 윈도우의 한 행에 하나씩 배치함

레이블을 생성하더라도 pack() 함수를 호출하지 않으면 윈도우에 레이블이 나타나지 않음

🖋window.mainloop()

루트 윈도우의 mainloop()는 이벤트 처리 루프로서 사용자로부터 오는 마우스나 키보드 이벤트를 처리함

프로그램은 이 함수 안에서 이벤트가 발생하기를 기다림

tkinter를 사용하는 프로그램은 반드시 맨 끝에 이 문장을 가져야 함

 

기본 위젯들

위젯  설명
Label 텍스트를 표시하는 데 사용되는 위젯
Button 버튼을 제공하는 위젯
Entry 한 줄의 텍스트를 입력받는 위젯
Text 여러 줄로 된 텍스트를 입력받는 위젯
Frame 컨테이너 위젯

버튼 위젯

🍋버튼(Button) 위젯: 클릭 가능한 버튼을 표시하는 데 사용됨

버튼은 클릭할 수 있는 레이블

from tkinter import *
window=Tk()

button=Button(window, text="클릭하세요!", bg="yellow", fg="blue", width=80, height=2) #전경색, 배경색, 크기 설정
button.pack()
window.mainloop()

fg나 bg 매개 변수를 사용해 색상 변경 가능

width와 height 매개 변수를 사용해 버튼이나 레이블의 너비와 높이 제어 가능

단위는 픽셀이 아님

tkinter에서는 너비와 높이가 글자 단위로 측정됨

너비의 크기가 80이라면 글자를 80개 표시할 수 있다는 의미가 됨, 높이도 마찬가지

 

엔트리 위젯

🍋엔트리(Engry)위젯: 사용자가 키보드로 입력한 내용을 우리에게 전달하는 위젯

사용자가 이메일 주소를 입력할 때 엔트리 위젯 사용 가능

get(): 사용자 입력 가져오기

delete(): 사용자 입력 삭제

insert(): 중간에 텍스트 삽입

엔트리 위젯 생성 코드


 

배치 관리자

버튼이나 레이블 등의 위젯은 윈도우 내부에 배치됨

좀 더 일반적으로는 컨테이너 안에 배치됨

프로그래머가 절대 좌표값으로 구체적으로 위치 지정 가능하지만, 플랫폼마다 해상도가 달라 위젯의 크기나 위치가 다르게 보일 수 있음

➡파이썬에서는 위젯의 배치를 배치 관리자(layout manager)에게 맡김

🍋배치 관리자: 컨테이너 안에 존재하는 위젯의 크기와 위치를 자동적으로 관리하는 객체

같은 개수의 버튼을 가지고 있더라도 배치 관리자에 따라 상당히 달라보일 수 있음

🖋Pack: 압축 배치 관리자(pack geometry manager)

위젯들을 사각형 블록으로 간주해서 컨테이너 안에 상하로 배치 pack()

🖋Grid: 격차 배치 관리자(grid geometry manager)

위젯들을 2차원적인 격자에 배치 grid()

🖋Place: 절대 배치 관리자(place geometry manager)

사용자가 지정한 위치에 위젯들을 배치 place()

 

압축 배치 관리자

압축 배치 관리자(pack geometry manager): 제일 간단한 배치 관리자로서 위젯을 최대한 압축하여 상하로 배치

from tkinter import *
window=Tk()

Button(window, text="박스 #1", bg="red", fg="white").pack()
Button(window, text="박스 #2", bg="green", fg="black").pack()
Button(window, text="박스 #3", bg="orange", fg="white").pack()

window.mainloop()

Button() 생성자는 버튼 객체 반환

여기에 .pack() 붙여도 됨

박스들을 왼쪽에서 오른쪽으로 배치하려면 side 매개 변수를 LEFT로 지정하면 됨

side는 LEFT, RIGHT, TOP, BOTTOM으로 지정 가능

Button(window, text="박스 #1", bg="red", fg="white").pack(side=LEFT)
Button(window, text="박스 #2", bg="green", fg="black").pack(side=LEFT)
Button(window, text="박스 #3", bg="orange", fg="white").pack(side=LEFT)

 

격자 배치 관리자

🍋격자 배치 관리자(grid geometry manager): 위젯을 테이블 형태로 배치

부모 윈도우는 테이블의 셀로 분할되고, 각 위젯은 특정한 셀에 배치됨

격자 배치 관리자는 얼마나 많은 행과 열이 실제로 필요한지를 추적함

가장 큰 위젯을 수용할 수 있도록 행과 열의 크기를 결정

#4개의 버튼을 격자 모양으로 배치하기
from tkinter import *
window=Tk()

b1=Button(window, text="박스 #1", bg="red", fg="white")
b2=Button(window, text="박스 #2", bg="green", fg="white")
b3=Button(window, text="박스 #3", bg="orange", fg="white")
b4=Button(window, text="박스 #4", bg="pink", fg="white")

b1.grid(row=0, column=0) #0행 0열
b2.grid(row=0, column=1)
b3.grid(row=1, column=0)
b4.grid(row=1, column=1)

window.mainloop()

 

절대 위치 배치 관리자

🍋절대 위치 배치 관리자(place geometry manager): 절대 위치를 사용하여 위젯을 배치

x와 y 매개 변수 사용

from tkinter import *

window=Tk()

b1=Button(window, text="박스 #1", bg="red", fg="white")
b1.place(x=9, y=0)
b2=Button(window, text="박스 #2", bg="green", fg="black")
b2.place(x=20, y=30)
b3=Button(window, text="박스 #3", bg="orange", fg="white")
b3.place(x=40, y=60)

window.mainloop()

place()함수의 매개 변수 x와 y 위젯이 배치될 x좌표와 y좌표를 전달하면 됨

 

 

여러 배치 관리자 혼용하기

하나의 컨테이너 안에 다른 컨테이너를 배치하고 컨테이너마다 배치 관리자를 다르게 할 수 있음

ex) 3개의 버튼을 수평으로 배치하고, 그 위에 레이블 배치

이때는 컨테이너를 만들어서 3개의 버튼을 컨테이너 안에 압축 배치 관리자로 배치하고 레이블을 윈도우에 수직으로 배치한 후에 그 아래에 컨테이너를 배치하면 됨

컨테이너로 가장 많이 사용되는 것: 프레임(Frame)

#여러 배치 관리자 혼용하기
from tkinter import *

window=Tk()
f=Frame(window) #윈도우 안에 프레임 만들기

#프레임 안에 버튼 만들기
b1=Button(f, text="박스 #1", bg="red", fg="white")
b2=Button(f, text="박스 #2", bg="green", fg="black")
b3=Button(f, text="박스 #3", bg="orange", fg="white")
b1.pack(side=LEFT)
b2.pack(side=LEFT)
b3.pack(side=LEFT)

l=Label(window, text="이 레이블은 버튼들 위에 배치된다.")
l.pack() #레이블을 루트 윈도우에 압축 배치 관리자로 배치
f.pack() #프레임을 루트 윈도우에 압축 배치 관리자로 배치

window.mainloop()

 

윈도우의 크기와 위젯의 크기 설정하기

단위: 픽셀

geometry("600x200")과 같이 설정 가능

x는 소문자로 써야함

from tkinter import *

window=Tk()
window.geometry("600x100") #width X Height

Button(window, text="박스 #1", width=10, height=1).pack()
Button(window, text="박스 #2", width=10, height=1).pack()
Button(window, text="박스 #3", width=10, height=1).pack()

window.mainloop()

-columnspan 옵션: 여러 개의 열을 합치는데 사용됨

-RoWSPAN 옵션: 여러 개의 행을 합치는데 사용됨

-sticky 값을 W+E+n+S로 설정하면 버튼이 셀 전체로 확장됨

 

 

버튼 이벤트 처리

버튼이 클릭되면 내부적으로 이벤트가 발생함

만약 이벤트가 발생됬을 때 자동적으로 호출되는 함수를 지정해 놓으면 버튼이 클릭되었을 때 어떤 작업을 할 수 있음

알람시계가 정해진 시간에 울리는 것과 유사

이벤트 구동 프로그래밍(event driven programming): 이벤트가 프로그램의 실행 순서를 결정하는 프로그래밍

 

(1) 사용자가 버튼 클릭

(2) 이벤트 발생

(3) 코드 실행

(4) 어떤 작업 실행

 

콜백함수(collback function)=핸들러(handler): 이벤트가 발생했을 때 호출되는 함수

버튼에 콜백함수를 등록하려면 버트의 생성자를 호출할 때 command 매개변수에 이벤트를 처리하는 함수의 이름을 지정하면 됨

#Syntax: 버튼 이벤트 처리

형식: Button = Button(window, command=함수 이름)
Button(window, text="클릭하세요!", command=process)
#버튼에서 이벤트가 발생하면 호출되는 함수 등록

버튼의 이벤트를 처리하기 위해서는 함수가 필요

 

process()라는 함수를 정의하고 버튼이 클릭되면 콘솔에 "안녕하세요?"를 출력하도록 정의

from tkinter import *

def process():
  print("버튼이 클릭되었습니다.")

window=Tk()
button=Button(window, text="클릭하세요!", command=process)
button.pack()
window.mainloop()

코드 분석

1. 첫 째로 해야 할 일: tkinter의 모듈 가져오기

3. process() 함수 정의, process() 함수가 호출되면 콘솔에 "버튼이 클릭되었습니다."를 출력

6. 루트 윈도우를 생성하고 변수 window에 할당

7. 파이썬 버튼은 Button 클래스로 생성 가능, command 인수에 함수 process()를 전달. 버튼에서 이벤트 발생하면 process() 호출

8. 압축 배치 관리자로 위젯 배치

9. 윈도우의 이벤트 루프 시작

 

LAB: 온도 변환기#2

#LAB: 온도 변환기 #2
#온도는 사용자가 엔트리 위젯을 통하여 입력하고 이것을 get()이라는 메소드로 가져오면 됨
#t=e1.get()
#반대로 엔트리 위젯에 어떤 값을 쓰려면 insert() 함수 사용
#엔트리에 기존의 문자열이 있다면 먼저 지우고 새로운 문자열 추가해야 함
#e2.delete(0, end)
#e2.insert(0, str(tc))
from tkinter import *

#이벤트 처리 함수 정의
def process():
  tf=float(e1.get()) #e1에서 문자열을 읽어서 부동소수점형으로 변경
  tc=(tf-32.0)*5.0/9.0 #화씨 온도를 섭씨 온도로 변환
  e2.delete(0, END) #처음부터 끝까지 지움
  e2.insert(0, str(tc)) #tc 변수의 값을 문자열로 변환하여 추가

window=Tk()

Label(window, text="화씨").grid(row=0, column=0)
Label(window, text="섭씨").grid(row=1, column=0)

e1=Entry(window)
e2=Entry(window)
e1.grid(row=0, column=1)
e2.grid(row=1, column=1)

Button(window, text="화씨->섭씨", command=process).grid(row=2, column=1)
window.mainloop()

delete 메소드의 경우, tkinter의 Entry 위젯에서 텍스트를 삭제하는 데 사용됩니다. 이 메소드는 두 개의 인자를 받습니다:

첫 번째 인자는 삭제하려는 텍스트의 시작 인덱스입니다. tkinter에서는 인덱스가 0부터 시작합니다. 따라서 0은 텍스트 필드의 맨 처음을 가리킵니다.

두 번째 인자는 삭제하려는 텍스트의 마지막 인덱스입니다. 'end'는 텍스트 필드의 끝을 가리킵니다.

따라서 e2.delete(0, 'end')는 텍스트 필드의 처음부터 끝까지 모든 텍스트를 삭제하라는 의미입니다.

이렇게 함으로써 기존에 Entry 위젯에 존재하는 모든 텍스트를 삭제하고, insert 메소드를 통해 새로운 텍스트를 삽입할 수 있습니다. 이는 주로 사용자가 새로운 값을 입력할 때마다 이전 값을 지우고 새로운 값을 표시하는 등의 상황에서 유용합니다.

 

insert 메소드는 Entry 위젯에 텍스트를 삽입하는 데 사용됩니다. 이 메소드는 두 개의 인자를 받습니다:

첫 번째 인자는 텍스트를 삽입하려는 위치의 인덱스입니다. 여기서도 인덱스는 0부터 시작합니다. 따라서 0은 텍스트 필드의 맨 앞을 가리킵니다.

두 번째 인자는 삽입하려는 텍스트 문자열입니다.

따라서 e2.insert(0, str(tc))는 텍스트 필드의 맨 앞에 tc를 문자열로 변환한 값을 삽입하라는 의미입니다.

이 메소드는 주로 사용자가 값을 입력한 후 그 값을 텍스트 필드에 표시할 때 사용됩니다. 기존 텍스트를 삭제한 후(delete 메소드 사용) 새로운 텍스트를 삽입하는 패턴이 흔히 사용됩니다.

 

e2.delete(0, END)를 호출하는 이유는 이전에 계산된 결과가 Entry 위젯 e2에 남아있을 수 있기 때문입니다.

예를 들어, 사용자가 여러 번 화씨->섭씨 버튼을 눌러 여러 번의 온도 변환을 수행한다고 가정해봅시다. 각 변환 후에는 변환된 섭씨 온도가 e2 Entry 위젯에 출력됩니다. 따라서 새로운 변환을 수행하기 전에 이전 변환 결과를 삭제해야 새로운 결과가 정확하게 표시됩니다.

따라서, e2.delete(0, END)는 이전 결과를 삭제하고 e2.insert(0, str(tc))는 새로운 결과를 삽입합니다. 이 두 줄의 코드가 없으면 이전 결과 위에 새 결과가 덮어씌워질 수 있습니다.

delete 메소드를 사용하면 기존의 텍스트를 지우고, insert 메소드를 사용하면 새로운 값을 추가하므로 항상 최신의 결과가 표시되는 것을 보장할 수 있습니다.

 

 

LAB: 가위, 바위, 보 게임

#LAB: 가위, 바위, 보 게임
#문자열 중에서 하나 랜덤하게 선택하기: computer=random.chooice(["가위", "바위", "보"])

import random
from tkinter import *
window=Tk()
Label(window, text="선택하세요", font=("Helvetica", "16")).pack()
frame=Frame(window)

#이미지를 읽어서 객체로 만들기
rock_image=PhotoImagge(file="rock.png")
paper_image=PhotoImage(file="papaer.png")
scissors_image=PhotoImage(file="scissors.png")

#이미지 버튼을 생성
rock.Button(frame, image=rock_image, command=pass_r)
rock.pack(side="left")
paper=Button(frame, image=paper_image, command=pass_p)
paper.pack(side="left")
scissors=Button(frame, image=scissors_image, command=pass_s)
scissors.pack(side="left")

#정보를 표시하는 레이블 생성
frame.pack()
Label(window, text="컴퓨터는 다음을 선택하였습니다.", font=("Helvetica", "16")).pack()
computer_image=Label(window, image=rock_image)
computer_iamge.pack()
output=Label(window, text="", font=("Helvetica", "16"))
output.pack()

#컴퓨터가 랜덤하게 하나를 고름. 레이블의 이미지를 적절하게 수행
def decide(human):
  computer=random.choice(["가위", "바위", "보"])
  if computer=="바위":
    computer_image["image"]=rock_image
  elif computer=="보":
    computer_image["image"]=paper_image
  else:
    computer_image["image"]=scissors_image

  if (computer=="바위" and human=="보") or (computer=="보" and human=="가위") or (computer=="가위" and human=="바위"):
    result="인간 승리!"
  elif computer==human:
    result="비겼습니다."
  else:
    result="컴퓨터 승리!"
  output.config(text="인간: "+human+" 컴퓨터:"+computer+" "+result) #승리 또는 패배 판단

#사용자가 버튼을 누르면 호출되는 콜백 메소드
def pass_s():
  decide("가위")
def pass_r():
  decide("바위")
def pass_p():
  decide("보")

window.mainloop()

config() 함수는 Tkinter에서 widget의 설정을 변경하는 데 사용됩니다. 다양한 widget의 속성을 변경할 수 있습니다. 예를 들어, Label의 텍스트, 배경색, 폰트 등을 변경할 수 있습니다.

output.config(text="인간: "+human+" 컴퓨터:"+computer+" "+result) 이 코드에서 config() 함수는 Label output의 텍스트 속성을 업데이트합니다. config()를 호출하면 output 라벨의 텍스트가 "인간: "+human+" 컴퓨터:"+computer+" "+result로 변경됩니다.

즉, 사용자와 컴퓨터의 선택 결과와 승패 결과를 출력하는 레이블의 내용을 업데이트하는 역할을 합니다.

 

 사용자가 가위, 바위, 보 중 하나의 버튼을 클릭하면 해당하는 콜백 함수 (pass_s(), pass_r(), pass_p())가 호출됩니다.

예를 들어 사용자가 가위 버튼을 클릭하면 pass_s() 함수가 호출되고, 이 함수는 decide("가위")를 호출합니다. 이렇게 하면 decide() 함수의 human 매개변수에 "가위"가 전달됩니다.

즉, 사용자가 선택한 가위, 바위, 보가 human 변수로 decide() 함수에 전달되어 게임의 결과를 결정하는데 사용됩니다.

 

사용자가 가위 버튼을 클릭하면, pass_s() 함수가 호출되고, 이 함수는 decide("가위")를 호출합니다. 이때, decide 함수의 human 매개변수에 "가위" 문자열이 전달됩니다. 따라서 decide 함수 내에서는 human 변수의 값이 "가위"가 됩니다. 이런 식으로 사용자의 선택이 decide 함수에 전달되어, 이 함수가 게임의 결과를 결정하는데 사용하게 됩니다.

 

 

 

#LAB: 계산기 프로그램

eva()함수

문자열 받아서 계산

eval("2+3")
>>>5

 

람다식 이용해서 각 버튼마다 별도의 함수 정의하기

Button(window, text=button_text, command=lambda t=button_text: click(t))

같은 식

def process(t=button_text):
	click(t)
#LAB: 계산기 프로그램
from tkinter import *

window=Tk()
window.title("My Calculator")
display=Entry(window, width=33, bg="yellow")
display.grid(row=0, column=0, columnspan=5)
button_list=[
    '7', '8', '9', '/', 'C',
    '4', '5', '6', '*', ' ',
    '1', '2', '3', '-', ' ',
    '0', '.', '=', '+', ' '
]

def click(key):
  if key=="=":
    result=eval(display.get())
    s=str(result)
    display.insert(END, "="+s)
  else:
    display.insert(END, key)

row_index=1
col_index=0
for button_text in button_list:
  Button(window, text=button_text, width=5, command=lambda t=button_text: click(t)).grid(row=row_index, column=col_index)
  col_index+=1
  if col_index>4:
    row_index+=1
    col_index=0
window.mainloop()

Tkinter에서 END는 문자열의 끝을 나타내는 상수입니다. 이는 문자열 또는 텍스트 위젯에서 마지막 위치를 지칭합니다.

display.insert(END, key)에서 END는 새로운 텍스트가 Entry 위젯의 현재 텍스트 뒤에 추가되어야 함을 나타냅니다. 즉, 새로운 키 입력이 사용자에 의해 이미 입력된 텍스트 뒤에 추가됩니다.

display.insert(END, "=" + s)에서 END는 계산 결과가 현재 표시되는 수식 뒤에 추가되어야 함을 나타냅니다. 이로 인해 사용자는 계산식과 그 결과를 같이 볼 수 있습니다.

이처럼, END는 새로운 내용이 기존 텍스트의 끝에 추가되어야 함을 나타내는데 사용됩니다.


END는 함수가 아닙니다. END는 tkinter에서 제공하는 특수한 문자열 상수로, 텍스트 위젯(예: Text, Entry 등)에서 마지막 위치를 나타냅니다. 이를 통해 새로운 내용을 위젯의 마지막 부분에 추가할 수 있습니다.

예를 들어, Entry 위젯에서 insert(END, 'some text') 메서드를 사용하면, 'some text'라는 문자열이 위젯의 현재 내용 끝에 추가됩니다. 마찬가지로 delete 메서드에서 END를 사용하면 위젯의 마지막 내용부터 삭제를 시작합니다.

따라서 END는 함수가 아니라 위치를 나타내는 특별한 상수입니다.


 

화면에 그림 그리기

캔버스를 생성하여 그림을 그릴 수 있다!

캔버스 위젯 사용

일반적으로 그래픽에서는 왼쪽 상단이(0,0)이 되는 좌표계 사용

 

캔버스에 사각형 그리는 코드

#캔버스에 사각형 그리는 코드

from tkinter import *

window=Tk()
w=Canvas(window, width=300, height=200)
w.pack()

w.create_rectangle(50, 25, 200, 100, fill="blue")
window.mainloop()

4. 캔버스 300픽셀의 폭과 200픽셀의 높이, 화면 왼쪽 상단의 좌표는 (0,0), 오른쪽 하단의 좌표는 (300, 200)

5. 캔버스 위젯도 루트 윈도우에 배치해야 됨

7. 캔버스 위젯 위에 사각형 그리기

create_rectangle()에 전달되는 매개 변수는 사각형의 왼쪽 상단의 좌표와 오른쪽 하단의 좌표

매개 변수 fill을 통해 채우기 색상을 파란색으로 지정

 

도형 관리

그리기 속성 변경하고 싶으면 coords(), itemconfig(), move()사용

-좌표 변경: coords()

-도형의 속성 변경: itemconfig()

캔버스 위에 그려진 도형을 변경하려면 도형을 생성하는 함수가 반환하는 식별자를 기억해야 함

create_rectangle(): 식별자 반환

이것을 변수에 기억했다가 삭제할 때 사용하면 됨

-delete(): 도형 삭제

from tkinter import *

window=Tk()

w=Canvas(window, width=300, height=200)
w.pack()

i=w.create_rectangle(50, 25, 200, 100, fill="red")

w.coords(i, 0, 0, 100, 100) #좌표 변경
w.itemconfig(i, fill="blue") #색상 변경

#w.delete(i) #삭제
#w.delete(ALL) #모든 항목 삭제
window.mainloop()

 

색상 설정하기

색상 이름(영어) 사용

16진수로 흰색: #FFFFFF

#기호는 16진수임을 알려주는 기호

w.create_rectangle(50, 24, 200, 100, fill="#FA88AB")

 

폰트

폰트 지정 가능

튜플로 지정 가능 (폰트 이름, 폰트 크기, 폰트 스타일)과 같은 형식 사용

("Times", 10, "bold")
("Helvetica", 10, "bold italic")

폰트는 문자열로도 지정 가능

from tkinter import *

window=Tk()
canvas=Canvas(window, width=600, height=200, bg="#afeeee")
canvas.create_text(200, 100, fill="darkblue", font="Times 30 italic bold", text="This is a text example")
canvas.pack()
window.mainloop()

 

기초 도형 그리기

도형의 종류 설명 그림
canvas.create_line(15, 25, 200, 25) 직선 그리는 메소드
canvas.create_rectangle(5, 25, 150, 75, fill="blue") 사각형을 그리는 메소드
canvas.create_arc(10, 10, 100, 150, extent=90) 사각형에 내접한 원이 그려지고 원 중에서 90도만 그려짐
canvas.create_oval(15, 25, 100, 125) 타원은 지정된 사각형 안에 그려짐
canvas.create_polygon(10, 10, 150, 110, 250, 20, fill="yellow") (10,10)에서 출발해서 (150,110)으로 가고 최종적으로 (250,20)에서 종료됨
canvas.create_text(100, 20, text="Sing Street", fill="blue", font=("Courier",20)) 텍스트의 중앙 위치를 나타내는 (x, y) 좌표, 색상을 표시하는 매개 변수 fill, 폰트를 나타내는 매개 변수 font

 

이미지 표시하기

이미지 먼저 적재

디렉토리에 이미지 저장

tkinter가 읽을 수 있는 이미지 파일은 PNG파일과 JPG파일 뿐

다른 형식의 이미지 파일을 읽는 기능이 필요하면 Python Imaging Library(http://www.pythonware.com/products/pil/) 사용하기

create_image() 함수 사용해서 캔버스에 그리면 됨

from tkinter import *
window=Tk()

canvas=Canvas(window, width=500, height=300)
canvas.pack()

img=PhotoImage(file="D:\\starship.png")
canvas.create_image(20, 20, anchor=NW, image=img)

window.mainloop()

create_image()함수의 첫 번째 매개 변수는 이미지가 표시되는 좌표

anchor=NW: 이미지 왼쪽 상단 (NW: NorthWest)을 기준점으로 사용하라는 것

이미지 왼쪽 상단이 좌표 (20, 20)에 놓여짐

마지막 매개 변수 image는 표시할 이미지가 저장된 변수


키보드와 마우스 이벤트 처리

이벤트-구동 방식: tkinter 응용 프로그램은 대부분의 시간을 이벤트 루프에서 소모함

즉 mainloop()에서 이벤트를 기다리면서 반복 루프를 실행

각 위젯에 대하여 파이썬 함수를 붙이는 것이 가능

#Syntax: tkinter 이벤트 처리

위젯.bind(이벤트지정자, 콜백함수)
frame.bind("<Button-1>", callback)
#첫번째 마우스 버튼이 눌리면 callback 함수가 호출됨

루트 윈도우에서 마우스 이벤트를 받아서 콘솔에 출력하기

<Button-1>이라 불리는 이벤트에 콜백 함수 callback()을 bind()를 사용하여 연결함

마우스 버튼을 누르면 콘솔에 "32 44 에서 마우스 이벤트 발생"과 같은 메시지 출력됨

from tkinter import *

window=Tk()
window.geometry("600x200")

def callback(event):
  print(event.x, event.y, "에서 마우스 이벤트 발생")

window.bind("<Button-1>", callback)
window.mainloop()

이 코드는 tkinter를 사용하여 GUI 애플리케이션을 만드는 것입니다. tkinter는 파이썬에서 GUI 애플리케이션을 만들기 위한 표준 Python 인터페이스입니다.

callback 함수는 사용자가 마우스의 왼쪽 버튼을 클릭했을 때 호출되는 함수입니다. 이 함수는 이벤트 객체를 매개변수로 받아서, 해당 이벤트가 발생한 위치를 출력합니다. 이때 event.x와 event.y는 이벤트가 발생한 위치의 x, y 좌표를 나타냅니다.

<Button-1>은 tkinter에서 정의한 이벤트 문자열입니다. 여기서 Button-1은 마우스의 왼쪽 버튼을 누르는 이벤트를 나타냅니다.

bind 메서드는 특정 이벤트와 그에 대응하는 함수(또는 메서드)를 연결하는 역할을 합니다. window.bind("<Button-1>", callback) 코드는 "<Button-1>" 이벤트, 즉 마우스 왼쪽 버튼을 클릭하는 이벤트가 발생하면 callback 함수를 호출하라는 것을 의미합니다. 이렇게 이벤트와 함수를 연결하는 과정을 이벤트 바인딩(event binding)이라고 합니다.

event 객체는 사용자로부터 발생한 이벤트를 나타내는 객체입니다. event 객체는 이벤트가 발생했을 때 bind 메서드에 의해 자동으로 생성되고, 연결된 함수에 인자로 전달됩니다.

예를 들어 callback 함수에서 event 매개변수는 마우스 클릭 이벤트 정보를 담고 있는 event 객체를 받습니다. 이 객체는 사용자의 마우스 클릭에 대한 정보를 담고 있으며, 이 객체의 x와 y 속성을 사용하여 마우스 클릭의 위치를 얻을 수 있습니다.

따라서 event 객체는 코드에 직접적으로 나타나지는 않지만, 사용자의 액션에 따라 tkinter에 의해 자동으로 생성되어 bind로 연결된 함수에 전달되는 것입니다.

 

 

이벤트 지정자

이벤트 지정자는 <...> 안에 이벤트의 이름을 적어주면 됨

<Button-1>

마우스가 버튼 위젯 위에서 눌려졌을 때 발생하는 이벤트

Button-1: 왼쪽 버튼, Button-2: 중간 버튼, Button-3: 오른쪽 버튼

<Button-1>,, <ButtonPress-1>, <1>은 모두 버튼 이벤트

<B1-Motion>

마우스 버튼 1이 눌려진 채로 움직일 때 발생

중간 버튼이면 B2, 오른쪽 버튼이면 B3가 됨

<ButtonRelease-1>

사용자가 Button1에서 손을 뗄 때 발생

<Double-Button-1>

마우스 버튼 1이 더블 클릭될 때 발생

Double이나 Triple을 접두사로 사용할 수 있음

만약 단일 클릭과 더블 클릭에 동시에 연결했다면 양쪽 콜백 메소드가 모두 호출됨

<Enter>

마우스 포인터가 위젯으로 진입했을 때 발생

사용자가 엔터키를 눌렀다는 것이 아님

<Leave>

마우스 포인터가 위젯을 떠났을 때 발생

<FocusIn>

키보드 포커스가 현재의 위젯으로 이동

<FocusOut>

키보드 포커스가 현재의 위젯에서 다른 위젯으로 이동

<return>

사용자가 엔터키 입력

개발자는 키보드에 존재하는 어떤 키에도 콜백 메소드를 연결할 수 있음

<Key>

사요앚가 어떤 키라도 누르면 발생

눌려진 키는 이벤트 객체의 char멤버에 저장됨

만약 F5와 같은 특수키라면 char멤버는 비어있음

a

사용자가 "a"를 입력했을 때 발생

대부분의 인쇄 가능한 문자는 이런 식으로 이벤트 연결 가능

예외)""(<space>)와 ""(<less>)

1은 키보드 바인딩이고 <1>은 버튼 바인딩

<Shift-Ip>

사용자가 시프트 키를 누른 상태로 위쪽 화살표키를 누르면 발생

Alt, Shift, Control과 같은 수식어 사용 가능

<Coonfigure>

위젯이 크기를 변경할 때 발생

위젯의 위치나 플랫폼을 변경해도 발생

새로운 크기는 콜백 메소드로 전달되는 이벤트 객체의 width와 height 속성에 저장됨

 

키보드 이벤트 처리

키보드 이벤트는 현재 키보드 포커스를 소유하고 있는 위젯으로 보내짐

focus_set()메소드를 이용하여 원하는 위젯으로 포커스 이동시킬 수 있음

 

repr() 함수는 파이썬의 내장 함수로, 객체를 문자열로 변환하는데 사용합니다. repr()은 가능한 '공식적'인 문자열을 생성하며, 생성된 문자열은 파이썬의 식으로 해석할 수 있습니다.

from tkinter import *

window=Tk()

def key(event):
  print(repr(event.char), "가 눌렸습니다.")

def callback(event):
  frame.focust_set()
  print(event.x, event.y,"에서 마우스 이벤트 발생")

frame=Frame(window, width=100, height=100)


예를 들어, 문자열 객체에 repr() 함수를 호출하면, 결과는 문자열을 나타내는 유효한 파이썬 표현식입니다. 특수 문자(개행 문자, 탭 등)는 escape 문자로 표현되기 때문에, 이 함수를 사용하면 문자열이 어떤 특수 문자를 포함하고 있는지 명확하게 알 수 있습니다.

따라서 print(repr(event.char), "가 눌렸습니다.")는 event.char를 표현할 수 있는 '공식적인' 문자열을 생성하고 이를 출력합니다. 만약 event.char가 개행 문자('\n')라면, repr()의 결과는 "'\n'"이 될 것입니다.

 

 

#LAB: 그림판 프로그램 만들기

상단의 버튼들: Button()생성자 호출, 격자 배치 관리자 사용, sticky=W+E로 지정해서 왼쪽과 오른쪽을 서로 붙도록 함

penButton=Button(window, text="펜", command=use_pen)
penButton.grid(row=0, column=0, stick=W+E)

brushButton=Button(window, text="브러쉬", command=use_brush)
brushButton.grid(row=0, column=1, stick=W+E)

colorButton=Button(window, text="색상선택", command=choose_color)
colorButton.grid(row=0, column=2, stick=W+E)

eraserButton=Button(window, text="지우개", command=use_eraser)
eraserButton.grid(row=0, column=3, stick=W+E)

clearButton=Button(window, text="모두삭제", command=clear_all)
clearButton.grid(row=0, column=4, stick=W+E)

오른쪽 펜의 너비를 설정하는 슬라이더가 있음

슬라이더는 Scale()로 생성해서 다음과 같이 수직으로 배치 가능

var=DoubleVar()
...
scale=Scale(window, variable=var, orient=VERTICAL)
scale.grid(row=1, column=5, sticky=N+S)

이때 var는 루트 윈도우가 생성된 후에 반드시 만들어야 함

객체가 슬라이더와 연결되어서 우리에게 현재의 슬라이더 값을 알려줌

 

캔버스에서는 2개의 이벤트를 처리함

첫 번째는 "<B1-Motion>"으로 왼쪽 버튼을 누른 채로 움직이면 발생하는 이벤트

두 번재는 "<ButtonRelease-1>"로 버튼을 놓았을 때 발생하는 이벤트

canvas.bind('<b1-Motion>', paint)
canvas.bind('<ButtonRelease-1', reset)

각 마우스 이벤트가 발생하면 다음과 같은 함수들이 호출됨

def paint(event):
	global var, erase_on, mode, old_x, old_y
    fill_color="white" if mode=="erase" else mycolor
    if old_x and old_y:
    	canvas.create_line(old_x, old_y, event.x, event.y, capstyle=ROUND, width=var.get(), fill=fill_color)
        old_x=event.x
        old_y=event.y
        
def reset(event):
	global old_x, old_y
    old_x, old_y=None, None

가장 핵심적인 함수는 paint()

paint(): 사용자가 마우스 버튼을 누른 채로 캔버스 위를 움직이면 발생

제일 먼저 점검하는 사항: "현재 모드가 지우개 모드인가"

지우개 모드라면 색상은 흰색이 됨

그렇지 않으면 사용자가 선택한 색상으로 직선이 그려짐

old_x, old_y 값이 있으면 (old_x, old_y)으로부터 (event.x, event.y)까지 직선을 그림

이때 직선의 너비는 슬라이더와 연결된 객체 var로 함

객체 아느이 값을 얻으려면 var의 get()메소드를 호출해야함

 

이제 화면 상단의 버튼과 붙은 이벤트들을 처리하는 함수만 만들면 됨

def use_pen(): #펜 버튼이 선택되면 모드를 "pen"으로 바꿈
  global mode
  mode="pen"

def use_brush():
  global mode
  mode="brush"

def choose_color():
  global mycolor
  mycolor=askcolor(color=mycolor)[1]
  

def use_erase():
  global mode
  mode="erase"

색상을 선택할 때는 askcolor()함수를 호출

반환되는 튜플의 2번째 요소는 색상을 16진수로 표현한 문자열

여기서 askcolor 함수는 tkinter 모듈의 colorchooser 모듈에 있는 함수입니다. 이 함수는 사용자에게 색상 선택 대화상자를 보여줍니다. 사용자가 색상을 선택하면, askcolor 함수는 선택한 색상의 RGB 값과 대응하는 hexadecimal 문자열을 튜플 형태로 반환합니다. 예를 들어, 사용자가 흰색을 선택하면, 함수는 ((255.0, 255.0, 255.0), '#ffffff')와 같은 튜플을 반환합니다.
color=mycolor는 askcolor 함수에 초기 색상 선택을 제공하는 역할을 합니다. 사용자가 색상 선택 대화상자를 열었을 때, 초기에 표시될 색상을 mycolor로 설정하는 것입니다.
mycolor=askcolor(color=mycolor)[1] 이 부분은 askcolor 함수가 반환하는 튜플의 두 번째 요소를 mycolor 변수에 저장하는 것을 의미합니다. 반환된 튜플의 첫 번째 요소는 RGB 색상 값이고, 두 번째 요소는 해당 색상의 hexadecimal 문자열입니다. [1]로 인덱싱하는 이유는 hexadecimal 문자열 색상 코드만 필요하기 때문입니다. 이 색상 코드는 그 후 그림판에서 사용됩니다.

 

 

전체 소스

#LAB: 그림판 프로그램 만들기
from tkinter import *
from tkinter.colorchooser import askcolor

DEFAULT_PEN_SIZE=1.0
DEFAULT_COLOR="black"

mode="pen"
old_x=None
old_y=None
mycolor=DEFAULT_COLOR
erase_on=False

#펜 버튼이 선택되면 모드를 "pen"으로 바꿈
def use_pen():
  global mode
  mode="pen"

def use_brush(): #브러쉬 버튼이 선택되면 모드를 "brush"으로 바꿈
  global mode
  mode="brush"

def choose_color(): #색상 버튼이 선택되면 사용자한테 색상 요구
  global mycolor
  mycolor=askcolor(color=mycolor)[1]

def use_eraser(): #지우개 버튼이 선택되면 모드를 "erase"로 바꿈
  global mode
  mode="erase"

def paint(event): #이전 점과 현재 점 사이를 직선으로 연결
  global var, erase_on, mode, old_x, old_y
  fill_color="white" if mode=="erase" else mycolor
  if old_x and old_y:
    canvas.create_line(old_x, old_y, event.x, event.y,
                       capstyle="round", width=var.get(), fill=fill_color)
  old_x=event.x
  old_y=event.y

def reset(event): #사용자가 마우스 버튼에서 손을 떼면 이전 점을 삭제
  global old_x, old_y
  old_x, old_y=None, None

def clear_all(): #캔버스에 그려진 모든 그림을 삭제
  global canvas
  canvas.delete('all')

window=Tk()
var=DoubleVar() #슬라이더와 연결될 객체를 생성

penButton=Button(window, text="펜", command=use_pen)
penButton.grid(row=0, column=0, sticky=W+E)

brushButton=Button(window, text="브러쉬", command=use_brush)
brushButton.grid(row=0, column=1, sticky=W+E)

colorButton=Button(window, text="색상선택", command=choose_color)
colorButton.grid(row=0, column=2, sticky=W+E)

eraserButton=Button(window, text="지우개", command=use_eraser)
eraserButton.grid(row=0, column=3, sticky=W+E)

clearButton=Button(window, text="모두삭제", command=clear_all)
clearButton.grid(row=0, column=4, sticky=W+E)

scale=Scale(window, variable=var, orient=VERTICAL)
scale.grid(row=1, column=5, sticky=N+S)

canvas=Canvas(window, bg='white', width=600, height=400)
canvas.grid(row=1, columnspan=5)

canvas.bind('<B1-Motion>', paint)
canvas.bind('<ButtonRelease-1>', reset)

window.mainloop()

 

#LAB: 공 애니메이션1

실제로 객체 지향 기법이 많이 사용되는 곳이 GUI 응용 프로그래밍

실제로는 tkinter를 사용할 때 클래스로 많이 정의함

클래스로 정의해야 만 GUI에서 나타나는 정보를 객체로 묶을 수 있기 때문

#LAB: 공 애니메이션
from tkinter import *
import time
import random

window=Tk()

canvas=Canvas(window, width=600, height=400)
canvas.pack()

class Ball():
  def __init__(self, color, size):
    self.id=canvas.create_oval(0, 0, size, size, fill=color)
    self.dx=random.randint(1, 10)
    self.dy=random.randint(1, 10)

  def move(self):
    canvas.move(self.id, self.dx,self.dy)
    x0, y0, x1, y1=canvas.coords(self.id)
    #원이 위쪽이나 아래쪽으로 벗어났으면 dy의 부호를 반전시킴
    if y1>canvas.winfo_height() or y0<0:
      self.dy=-self.dy
    #원이 왼쪽이나 오른쪽으로 벗어났으면 dx의 부호를 반전시킴
    if x1>canvas.winfo_width() or x0<0:
      self.dx=-self.dx

ball1=Ball("blue", 60)
ball2=Ball("green", 100)
ball3=Ball("orange", 80)

while True:
  ball1.move()
  ball2.move()
  ball3.move()
  window.update()
  time.sleep(0.05)

window.mainloop()

 

#LAB: 공 애니메이션2

공 30개 정도 만들어서 움직이게 하기

각 개별 변수 사용해서 각 객체 참조하는 것은 거의 불가능

리스트 생성하고 리스트에 객체를 저장해야 함

 

색상을 리스트에 저장하고 random.choice()사용

colors=["red", "orange", "yellow", "green", "blue", "indigo", "violet"]

ballList=[]
for i in range(30):
	ballList.append(Ball(random.choice(colors), 60))

ballList라는 공백 리스트 생성하고 여기에 Ball()을 호출하여 만든 객체 추가

이때 객체의 색상은 약간씩 다르게 함

 

공을 움직일 때도 리스트에서 개겣를 하나씩 꺼내서 객체가 가지고 있는 move()라는 함수를 호출해주기

while True:
	for i in range(30):
    	ballList[i].move()

전체 코드

#LAB: 공 애니메이션2
#공 30개 정도 만들어서 움직이게 하기

from tkinter import *
import time
import random

window=Tk()

canvas=Canvas(window, width=600, height=400)
canvas.pack()

class Ball():
  def __init__(self, color, size):
    self.id=canvas.create_oval(0, 0, size, size, fill=color)
    self.dx=random.randint(1, 10)
    self.dy=random.randint(1, 10)

  def move(self):
    canvas.move(self.id, self.dx, self.dy)
    x0, y0, x1, y1 = canvas.coords(self.id)
    if y1>canvas.winfo_height() or y0<0:
      self.dy=-self.dy
    if x1>canvas.winfo_width() or x0<0:
      self.dx=-self.dx

colors=["red", "orange", "yellow", "green", "blue", "indigo", "violet"]
ballList=[]
for i in range(30):
  ballList.append(Ball(random.choice(colors), 60))

while True:
  for i in range(30):
    ballList[i].move()
  window.update()
  time.sleep(0.05)

winow.mainloop()
728x90
LIST