#01.
😟처음 코드
from tkinter import *
def process():
label["text"]="clicked"
window=Tk()
label=Label(window, text="Hi!").pack(side=LEFT)
button=Button(window, text="click me", command=process).pack(side=LEFT)
window.mainloop()
오류 메시지를 보면, 'NoneType' 객체에는 항목 할당을 지원하지 않는다고 나와 있습니다. 이는 label 객체가 NoneType으로 설정되었음을 의미하며, 이는 변수를 초기화하는 동안 무언가 잘못되었음을 나타냅니다.
이 경우의 문제는 pack() 메서드의 반환 값이 None이기 때문에 발생합니다. pack() 메서드는 위젯을 화면에 배치하며 반환 값이 없습니다. 따라서 이 메서드를 호출한 후 결과를 label 변수에 할당하려고 하면, label 변수에는 None 값이 할당됩니다.
Tkinter의 pack() 함수는 위젯(여기서는 레이블과 버튼)을 화면에 배치하는 역할을 합니다. 즉, 이 함수는 '이 위젯을 화면에 어디에 어떻게 보여줄 것인지'를 정하는 역할을 합니다.
하지만, pack() 함수는 '실행 결과'를 반환하지 않습니다. 다른 많은 함수들은 실행한 결과를 반환하므로, 그 결과를 변수에 저장할 수 있습니다. 예를 들어 result = 3 + 4와 같이 쓰면, 3 + 4의 결과인 7이 result라는 변수에 저장됩니다.
그러나 pack()은 그런 결과를 내지 않습니다. 그래서 label = Label(window, text="Hi!").pack(side=LEFT)라고 쓰면, label 변수에는 pack()의 결과, 즉 None이 저장됩니다. 그리고 나서 label["text"]="clicked"라고 쓰려고 하면, label이 None이므로 오류가 발생하는 것입니다.
따라서 pack()을 호출하는 부분과 변수에 할당하는 부분을 분리해야 합니다. label=Label(window, text="Hi!")와 같이 먼저 레이블을 만들고, 그 결과를 label 변수에 저장합니다. 그 다음에 label.pack(side=LEFT)라고 써서 이 레이블을 화면에 배치합니다. 이렇게 하면 label 변수는 None이 아니라, 실제 레이블 객체를 가리키게 되므로, 나중에 label["text"]="clicked"와 같이 쓸 수 있게 됩니다.
🙂최종 코드
#01.
from tkinter import *
def process():
label["text"]="clicked"
window=Tk()
label=Label(window, text="Hi!")
label.pack(side=LEFT)
button=Button(window, text="click me", command=process)
button.pack(side=LEFT)
window.mainloop()
#05.
😟처음 코드
#05.
from tkinter import *
window=Tk()
def click(key):
if key=="감소":
label["text"]-=1
else:
label["text"]+=1
num=0
label=Label(window, text=f" {num} ").grid(row=0, column=1)
minus=Button(window, text="감소", command=click(minus["text"])).grid(row=0, column=0)
plus=Button(window, text="증가", command=click(plus["text"])).grid(row=0, column=2)
🙂최종 코드
from tkinter import *
window=Tk()
def click(key):
global num
if key=="감소":
num -= 1
else:
num += 1
label['text'] = num
num = 0
label=Label(window, text=num)
label.grid(row=0, column=1)
minus=Button(window, text="감소", command=lambda: click("감소"))
minus.grid(row=0, column=0)
plus=Button(window, text="증가", command=lambda: click("증가"))
plus.grid(row=0, column=2)
window.mainloop()
1. global num : 이를 통해 함수 내부에서 전역 변수 num을 수정할 수 있습니다. 전역 변수를 함수 내부에서 수정하려면 이렇게 선언해야 합니다.
2. command=lambda: click("감소") 와 command=lambda: click("증가") : 이는 각각 "감소"와 "증가" 문자열을 click 함수로 전달하도록 합니다. 여기서 lambda는 이름 없는 함수를 생성하는데 사용되며, 이 경우에는 특정 문자열을 click 함수에 전달하기 위해 사용됩니다.
3. label['text'] = num : 이 라인은 click 함수 내에서 num의 값을 변경한 후에 Label 위젯의 text 속성을 업데이트합니다. 이는 num의 값이 변경될 때마다 이 변경사항이 즉시 사용자 인터페이스에 반영되도록 합니다.
이러한 차이점들 때문에 제가 수정한 코드는 예상대로 동작합니다. 다시 말해, "감소" 버튼을 클릭하면 num의 값이 1씩 감소하고, "증가" 버튼을 클릭하면 num의 값이 1씩 증가합니다.
label['text'] = num 라인이 없다면 num의 값이 변해도 사용자 인터페이스에서는 이 변화를 볼 수 없습니다. 이 라인은 num의 값을 사용자 인터페이스에 반영하도록 합니다.
label['text'] = num 코드가 없다면, '증가' 또는 '감소' 버튼을 클릭하더라도 num의 값이 변경되는 것을 볼 수 없습니다. 실제로 num 값은 증가 또는 감소하지만, 그 값의 변화가 레이블에 업데이트되지 않아 사용자 인터페이스에서는 그대로 보일 것입니다.
따라서, 이 라인은 사용자 인터페이스가 num의 현재 값을 정확하게 반영하도록 합니다.
#06.
label 길이 버튼 길이만큼 하려면 width 값 설정
#06.
from tkinter import *
from random import randint
window=Tk()
def dice():
global num
num=randint(1,6)
label['text']=num
num=0
label=Label(window, text=num, width=6)
label.pack(side=LEFT)
button=Button(window, text="굴리기", command=dice)
button.pack(side=LEFT)
window.mainloop()
#08.
😟처음 코드
#08.
from tkinter import *
window=Tk()
def click(key):
if key=="로그인":
login['text']="로그아웃"
elif key=="취소":
login['text']="로그인"
id=Label(window, text="아이디").grid(row=0, column=0)
password=Label(window, text="비밀번호").grid(row=1, column=0)
id_e=Entry(window, width=20).grid(row=0, column=1)
ps_e=Entry(window, width=20).grid(row=1, column=1)
login=Button(window, text="로그인", command=click("로그인")).grid(row=3, column=0)
cancel=Button(window, text="취소", command=click("취소")).grid(row=3, column=1)
💡함수 작동 안 하는 이유
이 코드에서 command=click("로그인")와 command=click("취소") 부분이 문제입니다. command 인자에는 함수를 참조해야 하는데, 여기서는 click("로그인")와 click("취소")가 함수 호출로 인식되어 버튼을 클릭하기 전에 이미 실행됩니다. 따라서 버튼이 클릭되었을 때 실행되어야 하는 click 함수가 클릭 이벤트 없이도 바로 실행되는 문제가 발생합니다.
일반적으로 command 인자는 인수가 없는 함수를 참조해야 합니다. 하지만 여기서는 인수를 넘겨주어야 하는 상황이므로 lambda를 이용하여 간단한 익명 함수를 만들어서 command 인자에 전달하는 방식을 사용해야 합니다.
💡람다 함수를 안 쓰면 바로 호출 되는 이유
파이썬에서 함수를 참조하려면 함수 이름만 사용해야 합니다. 예를 들어, click라는 함수를 참조하려면 그냥 click라고 써야 합니다.
그러나 만약 click("로그인")처럼 쓰면, 파이썬은 이를 "함수 호출"로 해석합니다. 즉, 이 코드는 click 함수를 즉시 실행하라는 의미가 됩니다. 그리고 이 함수는 "로그인"이라는 인수를 받아서 실행됩니다.
command=click("로그인")이라고 쓴다면, command 인자에는 click("로그인")의 반환 값이 들어가게 됩니다. 만약 click 함수가 어떤 값을 반환하지 않는다면 (즉, None을 반환한다면), 실질적으로 command=None이 됩니다. 그 결과, 버튼을 클릭해도 아무런 동작도 하지 않게 됩니다.
람다 함수를 사용하면 이 문제를 해결할 수 있습니다. 람다 함수를 사용하면, 함수 호출을 미룰 수 있습니다. 즉, command=lambda: click("로그인")이라고 쓰면, click("로그인")은 버튼을 클릭할 때까지 실행되지 않습니다. 이렇게 하면, 버튼 클릭 이벤트가 발생할 때마다 click("로그인")이 호출되는 것이죠. 이것이 바로 람다 함수를 사용해야 하는 이유입니다.
🙂최종 코드
#08.
from tkinter import *
window=Tk()
def click(key):
if key=="로그인":
login['text']="로그아웃"
elif key=="취소":
login['text']="로그인"
id=Label(window, text="아이디").grid(row=0, column=0)
password=Label(window, text="비밀번호").grid(row=1, column=0)
id_e=Entry(window, width=20).grid(row=0, column=1)
ps_e=Entry(window, width=20).grid(row=1, column=1)
login=Button(window, text="로그인", command=lambda: click("로그인"))
login.grid(row=3, column=0)
cancel=Button(window, text="취소", command=lambda: click("취소"))
cancel.grid(row=3, column=1)
#09.
😟처음 코드
#09.
from tkinter import *
window=Tk()
name=Label(window, text="이름", width=20)
name.grid(row=0, column=0)
job=Label(window, text="직업", width=20)
job.grid(row=1, column=0)
country=Label(window, text="국적", width=20)
country.grid(row=2, column=0)
e1=Entry(window, width=30)
e1.grid(row=0, column=2)
e2=Entry(window, width=30)
e2.grid(row=1, column=2)
e3=Entry(window, width=30)
e3.grid(row=2, column=2)
b1=Button(window, text="Show")
b1.grid(row=3,column=0)
b2=Button(window, text="Quit")
b2.grid(row=3, column=1)
🙂최종 코드
from tkinter import *
fields = '이름', '직업', '국적'
def fetch(entries):
for entry in entries:
field = entry[0]
text = entry[1].get()
print('%s: "%s"' % (field, text))
def makeform(root, fields):
entries = []
for field in fields:
row = Frame(root)
lab = Label(row, width=15, text=field)
ent = Entry(row)
row.pack(side=TOP, fill=X)
lab.pack(side=LEFT)
ent.pack(side=RIGHT, expand=YES, fill=X)
entries.append((field, ent))
return entries
root = Tk()
ents = makeform(root, fields)
root.bind('<Return>', (lambda event, e=ents: fetch(e)))
b1 = Button(root, text='Show',
command=(lambda e=ents: fetch(e)))
b1.pack(side=LEFT, padx=5, pady=5)
b2 = Button(root, text='Quit', command=root.quit)
b2.pack(side=LEFT, padx=5, pady=5)
root.mainloop()
함수 사용: 코드의 일부를 다른 함수로 분리했습니다. 이를 통해 코드의 구조를 더욱 명확하게 만들고, 특정 작업을 수행하는 코드를 재사용할 수 있게 했습니다. 특히 fetch, makeform 함수를 정의하고 사용하였습니다.
프레임 사용: 각 입력 필드를 담는 프레임(Frame)을 사용하였습니다. 이를 통해 각 입력 필드가 독립적인 레이아웃을 가질 수 있게 되어 화면 배치가 더욱 유연해집니다.
엔트리 저장: makeform 함수에서 각 필드와 해당하는 엔트리 위젯을 튜플로 묶어 entries 리스트에 저장하였습니다. 이를 통해 나중에 fetch 함수에서 각 엔트리의 값을 쉽게 가져올 수 있게 되었습니다.
이벤트 바인딩: root.bind('<Return>', (lambda event, e=ents: fetch(e))) 코드를 통해 엔터키(Return)를 누르는 이벤트에 fetch 함수를 연결하였습니다. 이를 통해 사용자가 엔터키를 누르면 입력한 값이 출력되도록 하였습니다.
람다 함수: 'Show' 버튼과 'Quit' 버튼의 command 속성에 각각 lambda e=ents: fetch(e)와 root.quit를 사용하여, 버튼이 클릭될 때 실행할 함수를 지정하였습니다. 이 람다 함수를 통해 fetch 함수에 필요한 인자를 전달할 수 있게 하였습니다.
#10.
#10.
from tkinter import *
window=Tk()
def callback(event):
rect_id = 1 # the id of the rectangle, usually it starts from 1
coords = w.coords(rect_id)
# increase the size of rectangle by doubling the distance from the origin
new_coords = [x*2 for x in coords]
w.coords(rect_id, *new_coords)
def callback1(event):
rect_id = 1 # the id of the rectangle, usually it starts from 1
coords = w.coords(rect_id)
# increase the size of rectangle by doubling the distance from the origin
new_coords = [x//2 for x in coords]
w.coords(rect_id, *new_coords)
w=Canvas(window, width=600, height=600)
w.pack()
w.create_rectangle(50, 100, 150, 200)
window.bind("<Button-1>", callback)
window.bind("<Button-3>", callback1)
window.mainloop()
마우스 클릭 시 사각형이 두 배가 되게 하려면 존재하는 사각형의 좌표를 가져와서 2배로 확장해야 합니다.
Tkinter의 coords() 메서드를 사용하면, Canvas의 특정 항목에 대한 현재 좌표를 가져올 수 있습니다. 그런 다음 이 좌표를 두 배로 확장하고, 사각형의 좌표를 업데이트하기 위해 다시 coords() 메서드를 사용할 수 있습니다.
이 코드에서 rect_id는 사각형 항목의 ID입니다. Canvas에 첫 번째로 추가된 항목의 ID는 1이며, 이후 추가되는 각 항목은 이전 항목 ID에 1이 더해진 값을 가집니다. 그런데 이 ID 값은 Canvas에 여러 항목이 있는 경우에만 의미가 있으며, 현재 코드에서는 사각형 하나만 존재하므로 이 ID는 사실상 변하지 않습니다. 만약 여러 사각형을 조작하려면 각 사각형에 대한 ID를 관리해야 합니다.
참고로, 위 코드에서 *new_coords 부분은 리스트를 언패킹하여 coords() 메서드에 각각의 좌표 값을 전달합니다. 이 방식은 파이썬에서 리스트나 튜플 등의 시퀀스 데이터를 함수의 인자로 전달할 때 사용하는 방법입니다.
그러나, 사각형의 크기를 두 배로 늘리는 것이 목표라면, 단순히 모든 좌표 값을 2배로 하는 것이 아니라, 사각형의 중심점을 구하고, 그 중심점을 기준으로 사각형의 가로, 세로 길이를 2배로 늘려야 합니다. 그렇지 않으면 사각형은 단지 오른쪽 아래로 이동할 뿐 크기가 늘어나지 않습니다. 따라서 사각형의 중심점을 계산하는 로직이 추가로 필요합니다.
#11.
#11.
from tkinter import *
class Marquee(Canvas):
def __init__(self, parent, text, margin=2, speed=2, **kwargs):
Canvas.__init__(self, parent, **kwargs)
self.margin = margin
self.speed = speed
self.length = int(self['width'])
self.text = self.create_text(self.length, self.length / 2,
text=text, tags="Marquee",
anchor='w')
self.after(self.speed, self.tick)
def tick(self):
text_width = self.bbox("Marquee")[2]
if text_width < 0:
self.move("Marquee", self.length, 0)
else:
self.move("Marquee", -1, 0)
self.after(self.speed, self.tick)
window=Tk()
label=Marquee(window, text="파이썬 커피샵으로 오세요!", speed=10)
label.pack(side=LEFT)
window.mainloop()
텍스트를 흘러가게 하려면 일반적으로는 텍스트의 위치를 시간에 따라 변경하는 방식을 사용합니다. 이를 위해
after()
메서드를 사용하여 일정 시간 간격으로 텍스트의 위치를 업데이트하는 함수를 호출할 수 있습니다.
이 코드에서 Marquee 클래스는 Canvas를 상속받아서 스크롤하는 텍스트를 생성합니다. __init__() 메서드에서는 Canvas를 초기화하고, 텍스트를 생성하며, tick() 메서드를 호출하여 스크롤을 시작합니다.
tick() 메서드에서는 텍스트의 너비를 가져와서 이를 바탕으로 텍스트의 위치를 업데이트합니다. 텍스트가 화면에서 사라지면 (text_width < 0인 경우), 텍스트를 오른쪽으로 이동시켜 스크롤을 계속합니다. 그렇지 않으면 텍스트를 왼쪽으로 1픽셀씩 이동시킵니다. 그리고 after() 메서드를 사용하여 tick() 메서드를 계속 호출합니다. 이를 통해 텍스트가 계속 스크롤되는 효과를 만들 수 있습니다.
마지막으로, root.mainloop()를 호출하여 Tkinter 애플리케이션을 시작합니다. 이 애플리케이션을 실행하면, "This is a scrolling marquee"라는 문구가 화면에 스크롤되는 것을 볼 수 있습니다.
#12.
화면의 하단에 버튼을 4개 배치하고 이 버튼을 누르면 화면의 사각형의 상하좌우로 움직이는 애플리케이션을 작성해보자.
화면의 하단에 4개의 버튼을 배치하고, 이 버튼들을 누르면 화면의 사각형이 상하좌우로 움직이는 애플리케이션을 작성하려면, 각 버튼의 command를 해당 방향으로 사각형을 이동하는 함수에 연결해야 합니다.
from tkinter import *
class MovingRectangle:
def __init__(self, root):
self.canvas = Canvas(root, width=500, height=500)
self.rectangle = self.canvas.create_rectangle(225, 225, 275, 275, fill='orange')
self.canvas.pack()
frame = Frame(root)
frame.pack(side=BOTTOM)
Button(frame, text='←', command=self.move_left).pack(side=LEFT)
Button(frame, text='↑', command=self.move_up).pack(side=LEFT)
Button(frame, text='↓', command=self.move_down).pack(side=LEFT)
Button(frame, text='→', command=self.move_right).pack(side=LEFT)
def move_left(self):
self.canvas.move(self.rectangle, -20, 0)
def move_up(self):
self.canvas.move(self.rectangle, 0, -20)
def move_down(self):
self.canvas.move(self.rectangle, 0, 20)
def move_right(self):
self.canvas.move(self.rectangle, 20, 0)
root = Tk()
mr = MovingRectangle(root)
root.mainloop()
이 예제에서 MovingRectangle 클래스는 Tkinter의 root 위젯을 입력으로 받아서 이 위에 Canvas를 생성하고, 이 Canvas에 사각형을 추가합니다. 그리고 Frame 위젯을 생성하여 하단에 배치하고, 이 위에 4개의 버튼을 추가합니다. 각 버튼의 command는 사각형을 상하좌우로 이동하는 메서드로 연결됩니다.
각 이동 메서드에서는 Canvas의 move 메서드를 호출하여 사각형의 위치를 변경합니다. move 메서드는 첫 번째 인수로 아이템의 ID, 두 번째와 세 번째 인수로 이동할 x와 y의 양을 받습니다.
마지막으로, root.mainloop()를 호출하여 Tkinter 애플리케이션을 시작합니다. 이 애플리케이션을 실행하면, 화면의 중앙에 주황색 사각형이 나타나고, 하단에 4개의 버튼이 나타납니다. 이 버튼들을 누르면 사각형이 상하좌우로 이동하는 것을 볼 수 있습니다.
#13.
화면에 사각형을 그리고, 화살표 키로 사각형을 움직이는 프로그램을 작성해보자.
from tkinter import *
class MovingRectangle:
def __init__(self, root):
self.canvas = Canvas(root, width=500, height=500)
self.rectangle = self.canvas.create_rectangle(225, 225, 275, 275, fill='green')
self.canvas.pack()
root.bind('<Left>', self.move_left)
root.bind('<Up>', self.move_up)
root.bind('<Down>', self.move_down)
root.bind('<Right>', self.move_right)
def move_left(self, event):
self.canvas.move(self.rectangle, -20, 0)
def move_up(self, event):
self.canvas.move(self.rectangle, 0, -20)
def move_down(self, event):
self.canvas.move(self.rectangle, 0, 20)
def move_right(self, event):
self.canvas.move(self.rectangle, 20, 0)
root = Tk()
mr = MovingRectangle(root)
root.mainloop()
화살표 키로 사각형을 움직이려면 키보드 이벤트를 처리하는 방법을 사용해야 합니다. Tkinter에서는 bind 메서드를 사용하여 특정 이벤트에 대해 처리 함수를 등록할 수 있습니다.
이 예제에서는 MovingRectangle 클래스의 생성자에서 화살표 키 이벤트에 대한 핸들러를 등록합니다. 각 핸들러는 이벤트를 입력으로 받아서 사각형을 움직입니다.
이 프로그램을 실행하면, 화면의 중앙에 초록색 사각형이 표시되고, 화살표 키를 누르면 사각형이 이동합니다. 각 이동 방향에 대한 핸들러는 Canvas의 move 메서드를 호출하여 사각형의 위치를 변경합니다.
#14.
윈도우를 하나 만들고 여기에 랜덤한 크기의 사각형을 여러 개 그려보자. 위치도 랜덤이어야 하고 크기, 색상도 랜덤으로 하여본다.
from tkinter import *
import random
def displayRect():
x1 = random.randint(0, 400)
y1 = random.randint(0, 400)
x2 = x1 + random.randint(50, 100)
y2 = y1 + random.randint(50, 100)
color = random.choice(["red", "orange", "yellow", "green", "blue", "violet"])
canvas.create_rectangle(x1, y1, x2, y2, fill=color)
window.after(500, displayRect) # 500 milliseconds 후에 다시 displayRect 함수를 호출
window = Tk()
canvas = Canvas(window, width=500, height=500)
canvas.pack()
displayRect()
window.mainloop()
이 프로그램은 랜덤한 위치, 크기 및 색상으로 500 밀리초마다 사각형을 그립니다.
window.after(500, displayRect)
호출은
displayRect
함수를 500 밀리초마다 계속 호출하도록 합니다.
'프로그래밍 > Python' 카테고리의 다른 글
| 📔파워 유저를 위한 파이썬 Express 11. 내장함수, 람다식, 제너레이터, 모듈 (0) | 2023.07.13 |
|---|---|
| 📔파워 유저를 위한 파이썬 Express 10. 파일과 예외처리 (0) | 2023.06.18 |
| 📔파워 유저를 위한 파이썬 Express09. GUI 프로그래밍 (0) | 2023.06.02 |
| 📔파워 유저를 위한 파이썬 Express08. 객체와 클래스 Programming 문제풀이 (1) | 2023.05.29 |
| 📔파워 유저를 위한 파이썬 Express08. 객체와 클래스 (0) | 2023.05.29 |