본문 바로가기
Python/GUI(tkinter)

독학 Python tkinter(GUI) - 05.pack

by To올라운더 2023. 11. 3.
반응형

1. 요소(위젯) 표시하기

 - 앞에서 Frame이나 Label과 같은 요소(위젯)들을 생성한뒤, 

   pack()을 통해 GUI상에 표시해주는 것을 기억해볼 수 있다.

   그런데, 실제 tkinter에서는 pack 뿐만 아니라 grid와 place까지 

   총 3가지 방법으로 UI를 표시할 수 있다.

 

 - 간략한 사용법 설명에 앞서 주의해야할 사항은

   하나의 Frame 내에서는 pack과 grid 중 하나의 표기 방법으로 통일해야한다.

   (place는 혼용 가능)

 

 - 예를 들어 지난 시간에 만들었던 주황색 영역의 Frame안에

   초록색과 파란색의 Frame을 만든 뒤,

   초록색 Frame 내부에 있는 요소들은 pack 을

   파란색 Frame 내부에 있는 요소들은 grid 를 이용해 나타낼 수 있다.

 - 하지만 하나의 Frame 내부에 있는 요소들을 pack과 grid를 혼용해 사용할 수는 없다.

   (아래의 코드 Error와 같이 body_right_frame 안에 grid와 pack을 혼용하면 아래와 같은 Error가 발생한다.)

from tkinter import *

root = Tk()
root.title('To올라운드의 알찬 GUI 강의')


root.geometry("300x800") # 가로 X 세로 / 대문자X 하면 실행안됨

head_frame = Frame(root, relief='solid', bd=2, background='red')
head_frame.pack(fill='both', expand=True)

id_lable = Label(root, text='Root 에 입력된 Lable 입니다.')
id_lable.pack()

body_frame = Frame(root, relief='solid', bd=2, background='orange', padx=5, pady=5)
body_frame.pack(fill='both', expand=True)


body_left_frame = Frame(body_frame, relief='raised', bd=2, background='green')
body_left_frame.pack(fill='both', expand=True)

title_lable = Label(body_left_frame, text='제목을 입력하세요.')
title_lable.pack()

anchor_lable_north = Label(body_left_frame, text='north', anchor='n', width=250, height=3, relief='solid', bd=2)
anchor_lable_north.pack()

anchor_lable_east = Label(body_left_frame, text='east', anchor='e', width=250, height=3, relief='solid', bd=2)
anchor_lable_east.pack()

anchor_lable_south = Label(body_left_frame, text='south', anchor='s', width=250, height=3, relief='solid', bd=2)
anchor_lable_south.pack()

anchor_lable_west = Label(body_left_frame, text='west', anchor='w', width=250, height=3, relief='solid', bd=2)
anchor_lable_west.pack()

anchor_lable_center = Label(body_left_frame, text='center', anchor='center', width=250, height=3, relief='solid', bd=2)
anchor_lable_center.pack()





body_right_frame = Frame(body_frame, relief='ridge', bd=2, background='blue')
body_right_frame.pack(fill='both', expand=True)


num_gird0_label = Label(body_right_frame, text='0')
num_gird0_label.grid(column=1, row=1)

num_gird1_label = Label(body_right_frame, text='1')
num_gird1_label.grid(column=1, row=2)

num_gird2_label = Label(body_right_frame, text='2')
num_gird2_label.grid(column=2, row=2)

num_gird3_label = Label(body_right_frame, text='3')
num_gird3_label.grid(column=3, row=2)

num_gird4_label = Label(body_right_frame, text='4')
num_gird4_label.grid(column=1, row=3)

num_gird5_label = Label(body_right_frame, text='5')
num_gird5_label.grid(column=2, row=3)

num_gird6_label = Label(body_right_frame, text='6')
num_gird6_label.grid(column=3, row=3)

num_gird7_label = Label(body_right_frame, text='7')
num_gird7_label.grid(column=1, row=4)

num_gird8_label = Label(body_right_frame, text='8')
num_gird8_label.grid(column=2, row=4)

num_gird9_label = Label(body_right_frame, text='9')
num_gird9_label.grid(column=3, row=4)

# body_right_frame 내 grid, pack 혼용 사용시 Error 발생
# result_lable = Label(body_right_frame, text='결과를 알려주세요.')
# result_lable.pack()


tail_frame = Frame(root, relief='solid', bd=2, background='purple')
tail_frame.pack(fill='both', expand=True)

root.resizable(False,False) # x너비, y 변경 허용 여부
root.mainloop()

반응형

 

2. pack

 - pack()은 위젯의 위치를 지정하는 방법 중 하나로 사용법은 위젯.pack(파라미터)를 적용해주면 된다.

 - pack에 위치 값을 할당해주지 않으면, 기본적으로 탑처럼 먼저 표시를 요청한 위젯을

   위에서 아래로 내려가는 형태로 보여주게된다.

from tkinter import *

root = Tk()
root.title('To올라운드의 알찬 GUI 강의')


root.geometry("600x500") # 가로 X 세로 / 대문자X 하면 실행안됨


body_frame = Frame(root, relief='solid', bd=2, background='orange', padx=5, pady=5)
body_frame.pack(fill='both', expand=True)


top1_label = Label(body_frame, text='top1', relief='solid', bd=2)
top1_label.pack()

top2_label = Label(body_frame, text='top2', relief='solid', bd=2)
top2_label.pack()

left1_label = Label(body_frame, text='left1', relief='solid', bd=2)
left1_label.pack()

left2_label = Label(body_frame, text='left2', relief='solid', bd=2)
left2_label.pack()

right1_label = Label(body_frame, text='right1', relief='solid', bd=2)
right1_label.pack()

right2_label = Label(body_frame, text='right2', relief='solid', bd=2)
right2_label.pack()

bottom1_label = Label(body_frame, text='bottom1', relief='solid', bd=2)
bottom1_label.pack()

bottom2_label = Label(body_frame, text='bottom2', relief='solid', bd=2)
bottom2_label.pack()

top1_label = Label(body_frame, text='top1', relief='solid', bd=2)
top1_label.pack()

top1_label = Label(body_frame, text='top1', relief='solid', bd=2)
top1_label.pack()

top1_label = Label(body_frame, text='top1', relief='solid', bd=2)
top1_label.pack()


root.resizable(False,False) # x너비, y 변경 허용 여부
root.mainloop()

 - 해당 코드에 pack의 파라미터들을 입력해보자. 

 - 기본적으로 자주 사용되는 파라미터 값은 위치를 지정하는 side, 크기를 변경하는 fill과 확장하는 expand가 있다.

 - 기본 값 및 사용가능한 속성은 다음과 같다.

이름 의미 기본값 속성
side 특정 위치로 공간 할당 top top, bottom, left, right
anchor 할당된 공간 내에서 위치 지정 center center, n, e, s, w, ne, nw, se, sw
fill 할당된 공간에 대한 크기 맞춤 none none, x, y, both
expand 미사용 공간 확보 False Boolean
ipadx 위젯에 대한 x 방향 내부 패딩 0 상수
ipady 위젯에 대한 y 방향 내부 패딩 0 상수
padx 위젯에 대한 x 방향 외부 패딩 0 상수
pady 위젯에 대한 y 방향 외부 패딩 0 상수

 

1) side 파라미터 적용

from tkinter import *

root = Tk()
root.title('To올라운드의 알찬 GUI 강의')


root.geometry("600x500") # 가로 X 세로 / 대문자X 하면 실행안됨


body_frame = Frame(root, relief='solid', bd=2, background='orange', padx=5, pady=5)
body_frame.pack(fill='both', expand=True)


top1_label = Label(body_frame, text='top1', relief='solid', bd=2)
top1_label.pack(side='top')

top2_label = Label(body_frame, text='top2', relief='solid', bd=2)
top2_label.pack(side='top')

left1_label = Label(body_frame, text='left1', relief='solid', bd=2)
left1_label.pack(side='left')

left2_label = Label(body_frame, text='left2', relief='solid', bd=2)
left2_label.pack(side='left')

right1_label = Label(body_frame, text='right1', relief='solid', bd=2)
right1_label.pack(side='right')

right2_label = Label(body_frame, text='right2', relief='solid', bd=2)
right2_label.pack(side='right')

bottom1_label = Label(body_frame, text='bottom1', relief='solid', bd=2)
bottom1_label.pack(side='bottom')

bottom2_label = Label(body_frame, text='bottom2', relief='solid', bd=2)
bottom2_label.pack(side='bottom')



root.resizable(False,False) # x너비, y 변경 허용 여부
root.mainloop()
 

  - 지정한 위치로 위젯들이 이동했지만,

    뭔가 우리가 원한 형태에 조금 미흡한 느낌이 강하게 든다면 맞다.

    대부분은 fill과 expand 파라미터를 함께 적용한다. 

    경우에 따라서 내, 외부의 여백을 추가하기도 한다.

 

2) fill, expand 파라미터 적용  

from tkinter import *

root = Tk()
root.title('To올라운드의 알찬 GUI 강의')


root.geometry("600x500") # 가로 X 세로 / 대문자X 하면 실행안됨


body_frame = Frame(root, relief='solid', bd=2, background='orange', padx=5, pady=5)
body_frame.pack(fill='both', expand=True)


top1_label = Label(body_frame, text='top1-anchor:w', relief='solid', bd=2)
top1_label.pack(side='top', anchor='w')

top2_label = Label(body_frame, text='top2-fill:x', relief='solid', bd=2)
top2_label.pack(side='top', fill='x')

top3_label = Label(body_frame, text='top3-expand', relief='solid', bd=2)
top3_label.pack(side='top', expand=True)


left1_label = Label(body_frame, text='left1-anchor:n', relief='solid', bd=2)
left1_label.pack(side='left', anchor='n')

left2_label = Label(body_frame, text='left2-fill:y', relief='solid', bd=2)
left2_label.pack(side='left', fill='y')

left3_label = Label(body_frame, text='left3-expand', relief='solid', bd=2)
left3_label.pack(side='left', expand=True)



right1_label = Label(body_frame, text='right1-anchor:s', relief='solid', bd=2)
right1_label.pack(side='right', anchor='s')

right2_label = Label(body_frame, text='right2-fill:y', relief='solid', bd=2)
right2_label.pack(side='right', fill='y')

right3_label = Label(body_frame, text='right3-expand', relief='solid', bd=2)
right3_label.pack(side='right', expand=True)



bottom1_label = Label(body_frame, text='bottom1-anchor:e', relief='solid', bd=2)
bottom1_label.pack(side='bottom', anchor='e')

bottom2_label = Label(body_frame, text='bottom2-fill:x', relief='solid', bd=2)
bottom2_label.pack(side='bottom', fill='x')

bottom3_label = Label(body_frame, text='bottom3-expand', relief='solid', bd=2)
bottom3_label.pack(side='bottom', expand=True)



root.resizable(False,False) # x너비, y 변경 허용 여부
root.mainloop()

 - 뭔가 처음 side 만 적용했을 때 보다 더 이상해졌다고 느낄 수 있는데, 

   원인을 알고 나면 이런 이상한 모양이 이해되기도 하고,

   frame(영역)을 왜 나눠야 하는지도 단번에 이해할 수 있다.

 

 - 먼저 top 부분을 먼저 생각해보면, 14~21 라인에 top1, top2, top3가 각각 다른 파라미터를 가지는 것을 볼 수 있다.

 - side 값이 top이었기 때문에 위에서 부터 아래로 쌓인것을 볼 수 있고,

 - top1은 빨간색 영역 전체의 공간을 할당 받았지만, 해당 영역을 채우는 fill 옵션이 없기 때문에 텍스트 크기에 맞게 

   크기가 설정되고, 위치는 anchor 파라미터에 따라 왼쪽으로 정렬되었다.

 - top2는 초록색 영역을 할당 받았고 fill 옵션이 있기 때문에 자신이 할당 받은 영역 전체로 크기가 증가했다.

   하지만 증가된 영역이 가로(x)만 설정했기 때문에 아래로 증가하지는 않았다.

 - top3는 파란색 영역을 할당 받았는데, 다른 위젯들이 사용하지 않는(고정으로 할당 받지 않은) 전체 공간을 사용하는

  expand 옵션이 활성화 된 상태이다. 

 - 이제 left와 right 부분을 확인해보면, 공통적인 사항은 위의 top 설명과 크게 다르지 않다.

 - 그러나 유의 깊게 볼 사항은 left와 right가 세로 영역까지 크기를 표시(fill='y')하도록 옵션을 선택하였을 때,

   먼저 표기한 top(보라색)영역을 넘어서지 못한다는 것이다. 

   (반면 아래쪽은 윈도우의 바닥까지 내려와 있다.)

 - top영역이 차지 하고 있는 공간을 제외한 나머지 부분에서 확장이 일어나고 있는데

   top3_label 위젯을 주석처리해보면 left와 right 영역이 확장되는 것을 볼 수 있다.

 - 맞다. pack은 먼저 표시된 영역이 우선적으로 배치된다.

 - 이제 마지막 bottom 영역이다.

 - 실제 bottom 의 영역이 top 영역과 left, right 영역으로 인해 매우 협소하다.

 - 원했던 UI 배치가 top 처럼 전체 가로 영역을 할당하고 싶어 fill=x와 expand 파라미터를 추가하였지만, 

   미리 설정한 다른 요소들로 인해 원하는 배치가 되지 않는다.

  - 그래서 실제로 한 Frame에 넣기 보다는 Frame 단위로 나눠서 설정할 수 있어야 한다.

  - Frame에 대한 포스팅을 진행하며 나타낸 것 처럼

    각 Frame의 위치를 먼저 할당하고 해당 영역에 추가해보겠다.

  - head, body, tail 이라는 Frame을 만들고, body Frame은 내부에 left와 right Frame을 추가로 생성하였다.

from tkinter import *

root = Tk()
root.title('To올라운드의 알찬 GUI 강의')


root.geometry("600x500") # 가로 X 세로 / 대문자X 하면 실행안됨


head_frame = Frame(root, relief='solid', bd=2, background='red')
head_frame.pack(fill='both', expand=True)


top1_label = Label(head_frame, text='top1-anchor:w', relief='solid', bd=2)
top1_label.pack(side='top', anchor='w')

top2_label = Label(head_frame, text='top2-fill:x', relief='solid', bd=2)
top2_label.pack(side='top', fill='x')

top3_label = Label(head_frame, text='top3-expand', relief='solid', bd=2)
top3_label.pack(side='top', expand=True)



body_frame = Frame(root, relief='solid', bd=2, background='orange', padx=5, pady=5)
body_frame.pack(fill='both', expand=True)


body_left_frame = Frame(body_frame, relief='raised', bd=2, background='green')
body_left_frame.pack(side='left', fill='both', expand=True)


body_right_frame = Frame(body_frame, relief='ridge', bd=2, background='blue')
body_right_frame.pack(side='right', fill='both', expand=True)


left1_label = Label(body_left_frame, text='left1-anchor:n', relief='solid', bd=2)
left1_label.pack(side='left', anchor='n')

left2_label = Label(body_left_frame, text='left2-fill:y', relief='solid', bd=2)
left2_label.pack(side='left', fill='y')

left3_label = Label(body_left_frame, text='left3-expand', relief='solid', bd=2)
left3_label.pack(side='left', expand=True)



right1_label = Label(body_right_frame, text='right1-anchor:s', relief='solid', bd=2)
right1_label.pack(side='right', anchor='s')

right2_label = Label(body_right_frame, text='right2-fill:y', relief='solid', bd=2)
right2_label.pack(side='right', fill='y')

right3_label = Label(body_right_frame, text='right3-expand', relief='solid', bd=2)
right3_label.pack(side='right', expand=True)


tail_frame = Frame(root, relief='solid', bd=2, background='purple')
tail_frame.pack(fill='both', expand=True)


bottom1_label = Label(tail_frame, text='bottom1-anchor:e', relief='solid', bd=2)
bottom1_label.pack(side='bottom', anchor='e')

bottom2_label = Label(tail_frame, text='bottom2-fill:x', relief='solid', bd=2)
bottom2_label.pack(side='bottom', fill='x')

bottom3_label = Label(tail_frame, text='bottom3-expand', relief='solid', bd=2)
bottom3_label.pack(side='bottom', expand=True)



root.resizable(False,False) # x너비, y 변경 허용 여부
root.mainloop()

 

 - 큰 내용은 없지만, 생각보다 배치가 생각처럼 되지 않는다면, 

   각각의 Frame의 배치를 나눠서 진행하는 방법이 가장 깔끔하게 정리할 수 있는 방법일 거라 생각된다.

- 현재는 expand 파라미터를 이용해 전체 윈도우 크기에 맞춰 자동으로 확장을 진행했지만,

  width 파라미터를 이용해 해당 영역을 미리 고정할 수도 있다.

  (해당 설정처럼 width 값이 정확히 적용하기 위해서는 자동 확장이 되지 않도록

   pack의 파라미터 중 expand를 사용해서는 안된다.)

 - 생각보다 내용을 길어져 grid와 place는 다음 포스팅에서 진행하도록 하겠습니다.

반응형