버튼은 아주 많이 사용되는 위젯입니다. 이번 절에서는 버튼을 생성하고 버튼이 발생하는 이벤트를 처리하는 방법을 자세히 살펴봅시다.
버튼은 다양한 용도로 사용되는 표준 tkinter 위젯입니다. 버튼은 사용자와 상호작용을 할 목적으로 설계된 위젯입니다.
우리가 마우스로 버튼을 누르면 어떤 동작이 시작됩니다. 버튼은 텍스트와 이미지를 포함할 수 있습니다.
레이블은 다양한 글꼴로 텍스트를 표시할 수 있지만, 버튼은 하나의 글꼴만 사용할 수 있습니다. 버튼의 텍스트는 여러 줄 이상에 걸쳐 있을 수 있습니다.
우리는 특정한 함수를 버튼에 등록할 수 있습니다. 버튼이 눌리면 등록된 함수가 호출됩니다.
간단한 버튼 프로그램을 작성해봅시다.
from tkinter import *
window = Tk()
b1 = Button(window, text = "이것이 파이썬 버튼입니다.")
b1.pack()
window.mainloop()
<실행 결과>

파이썬 버튼은 Button 클래스로 생성할 수 있습니다. 첫 번째 인수는 루트 윈도우이고 text 인수는 버튼의 텍스트를 나타냅니다.
버튼을 생성한 후에는 반드시 pack()을 호춯아여야 화면에 버튼이 표시됩니다.
이번에도 한 줄씩 자세히 분석해봅시다.
배치 관리자는 차후에 자세히 설명할 것입니다. 여기서는 버튼을 가지고 간단하게 소개합니다. 다음과 같이 버튼을 하나 더 생성해봅시다.
from tkinter import *
window = Tk()
b1 = Button(window, text = "첫 번째 버튼")
b2 = Button(window, text = "두 번째 버튼")
b1.pack()
b2.pack()
window.mainloop()
<실행 결과>

b1.pack() 이 실행되면 버튼 b1이 윈도우에 배치되고 윈도우 자체가 버튼의 크기로 축소되는 것을 알 수 있습니다.
두 번째 버튼 b2가 배치되면 윈도우가 이것을 수용할 수 있도록 확장됩니다. 기본적으로 pack 배치 관리자는 위젯을 추가된 순서에 따라 수직으로 쌓습니다.
이제 위의 프로그램에서 일부를 변경하여 실행해봅시다.
...
b1 = Button(window, text = "첫 번째 버튼")
b2 = Button(window, text = "두 번째 버튼")
b1.pack(side = LEFT)
b2.pack(side = LEFT)
...
<실행 결과>

이제 버튼은 오른쪽과 같이 보일 것입니다.
압축 배치 관리자는 위젯을 수직으로 배치하거나 수평으로 배치하는데 사용됩니다. 현재 우리의 버튼은 너무 압축되어 보입니다.
우리는 약간의 패딩을 추가하여 이 문제를 해결할 수 있습니다. "pady"는 상단과 하단에 픽셀을 추가하고, "padx"는 왼쪽 및 오른쪽에 픽셀을 추가합니다.
...
b1 = Button(window, text = "첫 번째 버튼")
b2 = Button(window, text = "두 번째 버튼")
b1.pack(side = LEFT, padx = 10)
b2.pack(side = LEFT, padx = 10)
...
<실행 결과>

버튼의 텍스트는 다음과 같은 문장으로 언제든지 변경할 수 있습니다.
...
b1.pack(side = LEFT, padx = 10)
b2.pack(side = LEFT, padx = 10)
b1["text"] = "One"
b2["text"] = "Two"
...
<실행 결과>

tkinter 프로그램은 이벤트에 기반을 두고 동작됩니다. 이벤트에 대해서는 이번 장의 후반부에서 자세히 설명합니다.
여기서는 버튼의 이벤트를 처리하는 방법만 간략하게 설명하고 지나가도록 하겠습니다. 실제로 버튼의 이벤트가 가장 많이 이용됩니다.
이벤트가 발생하면 라이브러리에서 사용자가 지정한 콜백 함수를 호출하는 개념입니다.
사용자가 버튼을 클릭하면 버튼에서는 이벤트가 발생합니다. 우리는 버튼에 이벤트를 처리하는 함수를 붙일 수 있습니다.
버튼에 이벤트를 처리하는 함수가 연결되어 있으면 이벤트가 발생했을 때, 그 함수가 호출됩니다.
이벤트가 발생하였을 때 호출되는 이러한 함수를 콜백함수(callback function), 또는 핸들러(handler)라고 합니다.
버튼에 콜백 함수를 등록하려면 버튼의 생성자를 호출할 때, command 매개변수에 이벤트를 처리하는 함수의 이름을 지정하면 됩니다.
버튼이 클릭될 때 command에서 지정한 함수가 호출됩니다.
button = Button(window, text="one", command = 함수 이름)
버튼을 하나 생성하고 버튼을 클릭하면 버튼의 텍스트가 변경되는 프로그램을 작성해봅시다.
from tkinter import *
def callback():
button["text"] = "버튼이 클릭되었습니다."
window = Tk()
button = Button(window, text = "클릭", command = callback)
button.pack(side = LEFT)
window.mainloop()
<실행 결과>

위의 프로그램을 실행하면 오른쪽과 같은 윈도우가 나타납니다. 버튼을 클릭하면 버튼의 텍스트가 "버튼이 클릭되었습니다." 로 변경됩니다.
프로그램의 첫 부분을 보면 callback() 이라는 함수를 정의합니다. 이 함수는 Button()의 생성자 호출에서 이벤트 처리함수로 등록됩니다.
button = Button(window, text="버튼을 클릭하세요", command = callback)
이 버튼이 클릭되면 callback() 함수가 호출되도록 버튼과 함수가 연결된 것입니다.
이번에는 레이블과 버튼을 동시에 표시하는 코드를 살펴봅시다.
from tkinter import *
window = Tk()
label = Label(window, text = "안녕하세요!")
label.pack()
button = Button(window, text = "tkinter로 버튼을 쉽게 만들 수 있습니다.")
button.pack()
window.mainloop()
<실행 결과>

위의 코드를 한 줄씩 설명해봅시다.
from tkinter import *
tkinter를 사용하려면 tkinter 모듈을 포함시켜야 합니다. *은 tkinter 모듈에서 모든 클래스와 함수를 포함하겠다는 의미입니다.
window = Tk()
Tk() 를 호출하여 Tk 클래스의 객체(인스턴스)를 생성합니다. 이 객체가 윈도우를 나타냅니다.
label = Label(window, text = "안녕하세요!")
Label은 텍스트를 표시하는 위젯입니다. 위젯 클래스의 첫 번째 인수는 항상 윈도우(부모 컨테이너)가 됩니다. 여기서는 window를 전달합니다.
label = Label(window, text = "안녕하세요!")
위의 문장은 "안녕하세요!"를 저장하고 있는 레이블 객체를 생성하여 window 내부에 저장합니다.
label.pack()
이 문장은 압축 배치 관리자(pack manager)를 이용하여 윈도우에 레이블을 배치합니다. 압축 관리자는 한 행에 위젯을 하나씩 배치합니다.
큰 프로그램을 작성할 때는 코드를 클래스로 감싸는 것이 좋습니다. 아래 예제에서는 윈도우를 생성하는 부분을 클래스로 작성한 것입니다.
from tkinter import *
class App:
def __init__(self):
window = Tk()
helloB = Button(window, text = "Hello", command = self.hello, fg = "red")
helloB.pack(side = LEFT)
quitB = Button(window, text = "Quit", command = self.quit)
quitB.pack(side = LEFT)
window.mainloop()
def hello(self):
print("Hello 버튼이 클릭되었습니다.")
def quit(self):
print("Quit 버튼이 클릭되었습니다.")
App()
<실행 결과>

Hello 버튼이 클릭되었습니다.
Quit 버튼이 클릭되었습니다.
Hello 버튼이 클릭되었습니다.
Quit 버튼이 클릭되었습니다.
위의 예제는 클래스로 작성되었습니다. 생성자(__init__() 메소드)가 호출되면 윈도우를 생성하고 이어서 Button을 하나 생성하여 윈도우의 왼쪽에 배치합니다.
def __init__(self):
window = Tk()
...
윈도우 인스턴스는 window 지역 변수에 저장됩니다. 위젯을 생성한 후에는 pack() 메소드가 호출됩니다.
tkinter에서는 pack()을 호출하지 않으면 화면에 표시되지 않습니다. 주의하여야 합니다.
이어서 우리는 2개의 버튼 위젯을 생성합니다. 이들 위젯은 윈도우의 자식이 됩니다.
helloB = Button(window, text = "Hello", command = self.hello, fg = "red")
helloB.pack(side = LEFT)
quitB = Button(window, text = "Quit", command = self.quit)
quitB.pack(side = LEFT)
이번에는 생성자에 키워드 인수로 많은 선택사항을 전달하였습니다. 첫 번째 버튼은 "Hello" 라는 텍스트를 가지고 있으며 빨간색을 사용합니다. (fg는 foreground의 약자입니다.)
두 번째 버튼은 "Quit"이라는 텍스트를 가지고 있습니다. 첫 번째 버튼의 이벤트는 hello() 함수가 처리합니다. 두 번째 버튼의 이벤트는 quit() 함수가 처리합니다.
hello()와 quit()은 객체의 메소드이기 때문에 self.hello()와 self.quit()로 버튼에 등록하여야 합니다.
이어서 버튼들의 pack() 메소드가 호출됩니다. 이번에는 키워드 인수로 side = LEFT 를 가집니다. 이것의 의미는 윈도우 안에서 가능하면 왼쪽에 배치하라는 것입니다.
첫 번째 버튼은 윈도우의 왼쪽 경계에 붙어서 배치되고 두 번째 버튼은 첫 번째 버튼에 붙어서 배치됩니다. 기본적으로 위젯들은 그들의 부모 위젯에 상대적으로 배치됩니다.
만약 side 인수가 주어지지 않으면 기본값은 TOP 입니다. 위젯 생성이 끝나면 mainloop() 함수가 호출됩니다.
window.mainloop()
mainloop() 호출은 Tk 이벤트 루프를 시작합니다. 여기서 애플리케이션은 버튼이 클릭되거나 윈도우가 닫힐 때까지 기다립니다.
생성자 정의가 끝나면 process()와 quit() 메소드가 정의됩니다.
def hello(self):
print("Hello 버튼이 클릭되었습니다.")
def quit(self):
print("Quit 버튼이 클릭되었습니다.")
클래스 외부에서는 App 클래스의 인스턴스를 하나 생성합니다.
App()
이렇게 GUI를 처리하는 부분을 클래스로 정의하는 것은 어떤 장점이 있을까요? 첫째는 클래스를 재활용할 수 있다는 점입니다.
위와 같이 버튼이 2개 있는 윈도우가 필요하면 언제든지 클래스를 가져다가 사용하면 됩니다.
둘째는 클래스의 메소드에서 인스턴스 변수들을 제한없이 접근하여 사용할 수 있다는 점입니다. 즉 함수의 매개변수로 전달하지 않아도 됩니다.
앞의 예제에서 윈도우 위젯은 window라는 지역 변수에 저장됩니다. 여기서 문제가 발생하지 않을까요? 즉 __init__() 메소드 호출이 종료되면 지역 변수 window는 사라지지 않을까요?
위젯 인스턴스에 대해서는 참조 변수를 유지시킬 필요는 없습니다. tkinter는 자동적으로 위젯 트리를 관리합니다. 따라서 위젯은 응용 프로그램에서 참조 변수가 없어졌다고 해서 사라지지 않습니다.
위젯은 destroy() 메소드를 사용해서 명시적으로 삭제되어야 없어집니다. 물론 차후에 위젯으로 다른 작업을 하기 위해서는 참조 변수를 어딘가에 저장하는 것이 좋을 것입니다.
만약 위젯에 대한 참조를 저장할 필요가 없다면 다음과 같이 한 줄에 생성과 pack()을 넣어도 됩니다.
Button(window, text="Hello", command = self.process).pack(side=LEFT)
그러나 좀 더 안전하게 하려면 다음과 같이 두 줄로 하는 것이 일반적입니다.
button = Button(window, text="Hello", command = self.process)
button.pack(side=LEFT)