배치 관리자

이번 절에서는 tkinter의 배치 관리자에 대하여 살펴봅시다. 파이썬은 3 종류의 배치 관리자를 제공합니다.

압축(pack) 배치 관리자, 격자(grid) 배치 관리자, 절대(place) 배치 관리자가 바로 그것입니다.

우리는 이미 압축 배치 관리자와 격자 배치 관리자는 어느 정도 사용해 보았습니다. 이번 절에서 복습하면서 정리합시다.

 

배치 관리자

버튼이나 레이블 등의 위젯은 컨테이너 내부에 배치됩니다. 컨테이저 내부의 어떤 위치에 어떤 크기로 배치되는가를 프로그래머가 절대 좌표값으로 구체적으로 지정할 수도 있습니다.

그러나 이 방법은 단점을 가지고 있습니다. 파이썬 프로그램은 다양한 플랫폼에서 실행될 수 있고 따라서 플랫폼마다 위젯의 크기가 다를 수 있습니다.

따라서 절대 위치를 사용하여 위젯이 배치될 경우, 프로그래머가 의도한 바와는 다르게 사용자가 볼 수 있고 프로그램의 외관이 플랫폼마다 달라질 수 있습니다.

 

이런 문제점을 해결하기 위하여 파이썬에서는 위젯의 배치를 배치 관리자(layout manager)를 사용하여 자동화할 수 있습니다.

배치 관리자는 컨테이너 안에 존재하는 위젯의 크기와 위치를 자동으로 관리하는 객체입니다.

파이썬에서는 여러 가지의 배치를 제공하는 배치 관리자가 제공되며 같은 개수의 버튼을 가지고 있더라도 배치 관리자에 따라 상당히 달라 보일 수 있습니다.

  • 압축(pack) 배치 관리자
  • 격자(grid) 배치 관리자
  • 절대(place) 배치 관리자

 

격자 배치 관리자

격자 배치 관리자(grid geometry manager)는 위젯(버튼, 레이블 등)을 테이블 형태로 배치합니다. 격자 배치 관리자를 사용하면 부모 윈도우는 행 및 열로 분할되고, 각 위젯은 소정의 셀에 배치됩니다.

격자 배치 관리자는 얼마나 많은 행과 열이 실제로 필요한지를 추적하고 그에 따라 윈도우를 채웁니다. 또한 각 행은 해당 행 또는 열에서 가장 큰 위젯을 수용할 수 있어야 합니다.

따라서 행과 열의 크기를 결정합니다. 모든 행이 같은 높이가 될 필요는 없습니다. 모든 열도 동일한 폭을 가질 필요가 없습니다.

 

하나의 예로 2 X 2 격자 형태로 버튼을 배치해봅시다.

from tkinter import *

window = Tk()
b1 = Button(window, text = "One")
b2 = Button(window, text = "Two")

# 격자의 0번째 행과 0번째 열에 배치합니다.
# 행 번호와 열 번호는 0부터 시작합니다.
b1.grid(row = 0, column = 0)
b2.grid(row = 1, column = 1)

mainloop()

 

<실행 결과>

 

위의 실행 결과를 보면 약간의 빈 공간이 있는데 이것은 0번째 행과 1번째 열, 1번째 행과 0번째 열에 아무것도 들어 있지 않기 때문입니다. 레이블 위젯을 윈도우에 배치해봅시다.

from tkinter import *

window = Tk()
b1 = Button(window, text = "One")
b2 = Button(window, text = "Two")

# 격자의 0번째 행과 0번째 열에 배치합니다.
# 행 번호와 열 번호는 0부터 시작합니다.
b1.grid(row = 0, column = 0)
b2.grid(row = 1, column = 1)

l = Label(window, text = "이것은 레이블입니다.")
l.grid(row = 1, column = 0)

mainloop()

 

<실행 결과>

 

압축 배치 관리자

압축 배치 관리자(pack geometry manager)는 위젯을 최대한 붙여서 배치합니다. 위젯들을 세로로 차례대로 배치하는데 사용됩니다. 제일 간단한 배치 관리자입니다.

from tkinter import *

window = Tk()

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

mainloop()

 

<실행 결과>

 

fill 옵션을 사용하면 위젯이 전체 컨테이너를 채우도록 할 수 있습니다. fill 옵션은 X, Y, BOTH가 가능합니다. fill을 X로 지정하면 X 방향으로 채우라는 의미가 됩니다.

from tkinter import *

window = Tk()

w = Label(window, text = "박스 #1", bg = "red", fg = "white")
w.pack(fill = X)

w = Label(window, text = "박스 #2", bg = "green", fg = "black")
w.pack(fill = X)

w = Label(window, text = "박스 #3", bg = "blue", fg = "white")
w.pack(fill = X)

mainloop()

 

<실행 결과>

 

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

from tkinter import *

window = Tk()

w = Label(window, text = "박스 #1", bg = "red", fg = "white")
w.pack(padx = 5, pady = 0, side = LEFT)

w = Label(window, text = "박스 #2", bg = "green", fg = "black")
w.pack(padx = 5, pady = 0, side = LEFT)

w = Label(window, text = "박스 #3", bg = "blue", fg = "white")
w.pack(padx = 5, pady = 0, side = LEFT)

mainloop()

 

<실행 결과>

 

절대 위치 배치 관리자

절대 위치 배치 관리자(place geometry manager)는 절대 위치를 사용하여 위젯을 배치합니다. x와 y 매개변수를 사용합니다.

from tkinter import *

window = Tk()

w = Label(window, text = "박스 #1", bg = "red", fg = "white")
w.place(x = 0, y = 0)

w = Label(window, text = "박스 #2", bg = "green", fg = "black")
w.place(x = 20, y = 20)

w = Label(window, text = "박스 #3", bg = "blue", fg = "white")
w.place(x = 40, y = 40)

mainloop()

 

<실행 결과>

 

여러 배치 관리자 혼용하기

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

예를 들어 레이블을 3개의 버튼 위에 배치하려면 먼저 프레임 안에 수평으로 버튼을 배치하고 레이블을 배치한 후에 루트 윈도우에 수직으로 프레임을 배치하면 됩니다.

이런 방법을 사용하면 어떤 배치도 가능합니다.

from tkinter import *

window = Tk()
f = Frame(window)   # 윈도우 안에 프레임을 만듭니다.

b1 = Button(f, text = "One")    # 프레임 안에 버튼을 만듭니다.
b2 = Button(f, text = "Two")
b3 = Button(f, text = "Three")

b1.pack(side = LEFT)    # 프레임은 압축 배치 관리자를 사용합니다.
b2.pack(side = LEFT)
b3.pack(side = LEFT)

l = Label(window, text = "이 레이블은 버튼들 위에 배치됩니다.")
l.pack()    # 레이블이 윈도우 상단에 배치됩니다.
f.pack()    # 프레임이 수직으로 쌓이게 됩니다.

mainloop()

 

<실행 결과>

 

도전 과제

1. 우리나라의 오목과 유사한 외국의 게임이 Tic-Tac-Toe 입니다. Tic-Tac-Toe 게임은 유아들을 위한 게임으로 잘 알려져 있습니다.

Tic-Tac-Toe는 3 X 3칸을 가지는 게임판을 만들고, 경기자 2명이 동그라미 심볼(O)와 가위표 심볼(X)을 고릅니다. 경기자는 번갈아 가며 게임판에 동그라미나 가위표를 놓습니다.

가로, 세로, 대각선으로 동일한 심볼을 먼저 만들면 승리하게 됩니다. 다음과 같이 두 사람이 컴퓨터를 통하여 Tic-Tac-Toe 게임을 할 수 있는 프로그램을 작성해봅시다.

 

<실행 결과>

 

우리는 버튼을 9개 생성하여 화면에 배치합니다. 사용자가 버튼을 클릭하면 버튼의 텍스트를 "X"나 "O"로 변경합시다.

9개의 버튼은 격자 배치 관리자를 이용하여 화면에 격자 모양으로 배치하면 될 것입니다. 반복문을 이용하여 9번 반복하면서 버튼 객체를 생성합니다.

생성된 버튼 객체는 리스트에 순서대로 저장해둡시다.

window = Tk()   # 윈도우를 생성합니다.
player = "X"    # 시작은 플레이어 X입니다.
list = []

# 9개의 버튼을 생성하여 격자 형태로 윈도우에 배치합니다.
for i in range(9):
    b = Button(window, text = "                 ", command = lambda k = i:checked(k))
    b.grid(row=i//3, column=i%3)
    list.append(b)  # 버튼 객체를 리스트에 저장합니다.

 

버튼이 눌려졌을 때 checkde() 함수가 호출되도록 합시다. 그런데 문제가 있습니다.

우리가 버튼을 생성할 때 다음과 같이 하면 파이썬 인터프리터는 checked(i)를 호출하고 그 반환값을 command에 저장합니다. 이것은 우리가 원하는 동작이 명백히 아닙니다.

b = Button(window, text = "                 ", command = checked(i))

 

많은 방법이 있겠지만 람다식을 사용하는 것이 가장 쉽습니다. 람다식은 함수를 객체로 만들어서 저장하는 것입니다.

변수 i의 값을 k로 전달하고 이것을 가지고 checked(k)를 호출하도록 함수를 생성하여 command에 저장하는 것입니다.

버튼이 클릭되면 버튼의 번호에 따라서 check(0), check(1), ... 등이 자동으로 호출됩니다.

b = Button(window, text = "                 ", command = lambda k = i:checked(k))

 

함수 checked()는 아래와 같이 정의됩니다. global은 전역 변수 player를 사용한다는 의미입니다.

# i번째 버튼을 누를 수 있는지 검사합니다. 누를 수 있으면 X나 O를 표시합니다.
def checked(i):
    global player
    button = list[i]        # 리스트에서 i번째 버튼 객체를 가져옵니다.

    # 버튼이 초기 상태가 아니면 이미 누른 버튼이므로 아무것도 하지 않고 리턴합니다.
    if button["text"] != "                 ":
        return

    button["text"] = "      " + player + "       "
    button["bg"] = "yellow"

    if player == "X":
        player = "O"
        button["bg"] = "yellow"
    else:
        player = "X"
        button["bg"] = "lightgreen"

 

checked() 함수에서는 리스트에서 i번째 버튼 객체를 찾아서 버튼의 텍스트를 "X" 또는 "O"로 변경합니다. 버튼의 배경색도 적절하게 변경합니다.

한번 호출될 때마다 player 변수는 토글됩니다.

 


2. 아래와 같은 파이썬 프로그램을 작성해봅시다. 레이블과 버튼을 사용하였습니다. 격자 배치 관리자를 사용하여 레이블과 버튼을 배치합니다.

색상의 개수만큼 반복하면서 레이블과 버튼을 생성하고 격자 형태로 배치하면 됩니다.

 

<실행 결과>

 


3. 레이블을 사용하여 간단한 스톱워치를 작성하여 봅시다. 시작 버튼을 누르면 시작되고 중지 버튼을 누르면 스톱워치가 중지됩니다.

 

<실행 결과>

 

스톱워치를 구현하려면 주기적으로 호출되는 함수가 필요합니다. 파이썬에서는 윈도우 객체의 after() 함수를 이용하면 주기적으로 특정한 함수가 호출되게 할 수 있습니다.

예를 들어서 10ms 주기로 startTimer() 함수를 호출하게 만드려면 아래와 같은 문장을 사용합니다.

def startTimer():
    if RuntimeWarning:
        global timer    # 전역 변수 timer를 사용합니다.
        timer += 1
        timeText.configure(text = str(timer))
    window.after(10, startTimer)    # 10ms 후에 startTimer()가 호출되도록 합니다.

window = tk.Tk()
...

 


4. 파이썬을 이용하여 버튼을 가지는 계산기를 작성해봅시다. 적절한 배치 관리자를 선택하여 사용합니다.

 

<실행 결과>

 

물론 격자 배치 관리자를 사용하는 것이 좋습니다. 격자 배치 관리자의 맨 위에 엔트리 위젯을 배치합니다.

엔트리 위젯은 5개의 셀을 차지합니다. 이것은 다음과 같은 columnspan 이라고 하는 매개변수를 설정하면 됩니다.

entry = Entry(window, width = 33, bg = "yellow")
entry.grid(row = 0, column = 0, columnspan = 5)

 

"=" 키가 눌려지면 엔트리 위젯에 저장된 수식을 꺼내서 eval() 을 사용하여 수식의 값을 계산합니다. 계산의 결과는 다시 엔트리 위젯에 표시합니다.

엔트리 위젯에서 문자열을 가져오는 메소드는 get() 입니다. 결과를 표시할 때는 insert(END, s) 를 사용합니다. 결과를 표시하기 전에 delete() 메소드를 사용하여 엔트리 위젯의 내용을 지워야 합니다.