[πŸ₯§|Py] μŒˆλ½•ν•œ GUI <CustomTkinter> νŠœν† λ¦¬μ–Ό ν•œκ΅­μ–΄ λ²ˆμ—­

minseok128Β·2024λ…„ 10μ›” 29일
2
post-thumbnail

파이썬 GUI의 κ³ λ‡Œ

파이썬의 μ‰¬μš΄ λ‚œμ΄λ„μ— λΉ„ν•΄ GUIλ₯Ό λ§Œλ“€ λ•ŒλŠ” 막막함을 λŠλ‚€λ‹€.

κ°€μž₯ 많이 μ‚¬μš©λ˜λŠ” GUI 라이브러리 쀑 ν•˜λ‚˜μΈ tkinter와 pyqtλŠ” 쉽고 λΉ λ₯΄κ²Œ λ°μŠ€ν¬ν†± μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ μ‚¬μš©μž μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•  수 μžˆλ„λ‘ λ‹€μ–‘ν•œ μœ„μ ―κ³Ό ν”Œλž«νΌ κ°„ ν˜Έν™˜μ„±μ„ μ œκ³΅ν•œλ‹€.

tkintertkinterpyqt

κ·ΈλŸ¬λ‚˜ μ΄λ“€λ‘œ μŒˆλ½•ν•œ UIλ₯Ό λ§Œλ“€κΈ°μ—” 역뢀쑱이닀. κ·Έλž˜μ„œ 맀번 GUI ν”„λ‘œμ νŠΈλ₯Ό 맑을 λ•Œλ§ˆλ‹€ μƒˆλ‘œμš΄ GUI 라이브러리λ₯Ό μ°Ύμ•„ ν—€λ§€λŠ” 여정을 λ– λ‚˜λ‹€κ°€, κ·Έλƒ₯ 포기해버린 적도 λΉˆλ²ˆν•˜λ‹€.

κΈ΄ μ—¬μ • 끝에, 기쑴의 tkinter와 거의 μœ μ‚¬ν•œ λ°©μ‹μœΌλ‘œ GUIλ₯Ό μŒˆλ½•ν•˜κ²Œ λ§Œλ“€ 수 μžˆλŠ” CustomTkinter ν”„λ‘œμ νŠΈλ₯Ό 찾게 λ˜μ—ˆλ‹€. 비ꡐ적 μƒˆλ‘œμš΄ λΌμ΄λΈŒλŸ¬λ¦¬μ΄μ§€λ§Œ, tkinter에 μ΅μˆ™ν•˜λ‹€λ©΄ 곡식 νŠœν† λ¦¬μ–Όμ„ 톡해 μ†μ‰½κ²Œ 읡힐 수 μžˆμ„ 것이닀.

아직 곡식 νŠœν† λ¦¬μ–Όμ„ ν•œκ΅­μ–΄λ‘œ λ²ˆμ—­ν•œ 글이 없기에, κ³΅λΆ€ν•˜λ©΄μ„œ 이λ₯Ό 직접 λ²ˆμ—­ν•΄λ³΄μ•˜λ‹€.

좜처:곡식 ν™ˆνŽ˜μ΄μ§€

μ„€μΉ˜ 방법과 λ‹€μ–‘ν•œ μ˜ˆμ œλŠ” 곡식 ν™ˆνŽ˜μ΄μ§€μ—μ„œ 확인할 수 μžˆλ‹€.


κ°œμš”


CustomTkinterλŠ” Tkinterλ₯Ό 기반으둜 ν•œ 파이썬 λ°μŠ€ν¬νƒ‘ UI 라이브러리둜, ν˜„λŒ€μ μ΄κ³  μ™„μ „ν•œ μ»€μŠ€ν„°λ§ˆμ΄μ§•μ΄ κ°€λŠ₯ν•œ μœ„μ ―μ„ μ œκ³΅ν•©λ‹ˆλ‹€. CustomTkinterλ₯Ό μ‚¬μš©ν•˜λ©΄ λͺ¨λ“  λ°μŠ€ν¬νƒ‘ ν”Œλž«νΌ(Windows, macOS, Linux)μ—μ„œ μΌκ΄€λœ UIλ₯Ό κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


import customtkinter

def button_callback():
    print("button clicked")

app = customtkinter.CTk()
app.geometry("400x150")

button = customtkinter.CTkButton(app, text="my button", command=button_callback)
button.pack(padx=20, pady=20)

app.mainloop()

λͺ‡ μ€„μ˜ μ½”λ“œλ§ŒμœΌλ‘œλ„ μ™„μ „νžˆ μž‘λ™ν•˜λŠ” ν”„λ‘œκ·Έλž¨μ„ λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€:


νŠœν† λ¦¬μ–Ό

CustomTkinter의 곡식 νŠœν† λ¦¬μ–Ό μ„Ήμ…˜μœΌλ‘œ, 라이브러리의 μ£Όμš” κΈ°λŠ₯을 κ°•μ‘°ν•˜λŠ” μ΄ˆκΈ‰ 및 κ³ κΈ‰ νŠœν† λ¦¬μ–Όμ„ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€. 이 νŠœν† λ¦¬μ–Ό μ„Ήμ…˜μ€ 라이브러리의 전체 κ°œμš”λ₯Ό μ œκ³΅ν•˜λŠ” 것이 λͺ©μ μ€ μ•„λ‹ˆλ©°, μ•žμœΌλ‘œ 더 λ§Žμ€ νŠœν† λ¦¬μ–Όμ΄ 좔가될 μ˜ˆμ •μž…λ‹ˆλ‹€.

μ΄ˆμ‹¬μž νŠœν† λ¦¬μ–Ό

κ·Έλ¦¬λ“œ(Grid) μ§€μ˜€λ©”νŠΈλ¦¬ μ‹œμŠ€ν…œμ— μ΅μˆ™ν•΄μ§€κ³ , ν”„λ ˆμž„κ³Ό 슀크둀 κ°€λŠ₯ν•œ ν”„λ ˆμž„μ„ λ§Œλ“€κ³ , ν”„λ ˆμž„μ„ 기반으둜 μž¬μ‚¬μš© κ°€λŠ₯ν•œ μ»΄ν¬λ„ŒνŠΈλ₯Ό λ§Œλ“€μ–΄λ³΄μ„Έμš”.

1. Grid System

κΈ°λ³Έ μ•±

μš°μ„ , μ΅œμ‹  λ²„μ „μ˜ CustomTkinterκ°€ μ„€μΉ˜λ˜μ–΄ μžˆλŠ”μ§€ ν™•μΈν•˜μ„Έμš”. 그런 λ‹€μŒ, λ‹€μŒκ³Ό 같은 κ°€μž₯ 기본적인 ν”„λ‘œκ·Έλž¨μœΌλ‘œ μ„€μΉ˜κ°€ μ œλŒ€λ‘œ λ˜μ—ˆλŠ”μ§€ ν…ŒμŠ€νŠΈν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이 ν”„λ‘œκ·Έλž¨μ€ λ‹¨μˆœνžˆ 창을 λ§Œλ“­λ‹ˆλ‹€:

import customtkinter

app = customtkinter.CTk()
app.mainloop()

이 ν”„λ‘œκ·Έλž¨μ΄ μ •μƒμ μœΌλ‘œ μž‘λ™ν•œλ‹€λ©΄, 창의 제λͺ©κ³Ό 크기(geometry) λ“±μ˜ 속성을 μ„€μ •ν•˜κ³  λ²„νŠΌμ„ μΆ”κ°€ν•˜κΈ° μ‹œμž‘ν•  수 μžˆμŠ΅λ‹ˆλ‹€. λͺ¨λ“  μœ„μ ―μ˜ 첫 번째 λ§€κ°œλ³€μˆ˜λŠ” master λ§€κ°œλ³€μˆ˜μ΄λ©°, 이 κ²½μš°μ—λŠ” app이 λ©λ‹ˆλ‹€. λ˜ν•œ ν‚€μ›Œλ“œ μΈμˆ˜λ‘œλ„ 전달할 수 μžˆμŠ΅λ‹ˆλ‹€(master=app).

import customtkinter

def button_callback():
    print("button pressed")

app = customtkinter.CTk()
app.title("my app")
app.geometry("400x150")

button = customtkinter.CTkButton(app, text="my button", command=button_callback)
button.grid(row=0, column=0, padx=20, pady=20)

app.mainloop()

λ²„νŠΌ 쀑앙 배치

Grid μ§€μ˜€λ©”νŠΈλ¦¬ κ΄€λ¦¬μž

이 μ˜ˆμ œμ—μ„œλŠ” μœ„μ ―μ˜ μœ„μΉ˜μ™€ νŒ¨λ”©μ„ μ„€μ •ν•˜κΈ° μœ„ν•΄ κ·Έλ¦¬λ“œ μ§€μ˜€λ©”νŠΈλ¦¬ κ΄€λ¦¬μžλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. placeλ‚˜ pack λŒ€μ‹  grid μ§€μ˜€λ©”νŠΈλ¦¬ κ΄€λ¦¬μžλ₯Ό μ‚¬μš©ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€. μ΄λŠ” λ°˜μ‘ν˜•μ΄κ³  ν™•μž₯ κ°€λŠ₯ν•œ μ‚¬μš©μž μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ‰½κ²Œ λ§Œλ“€ 수 있기 λ•Œλ¬Έμž…λ‹ˆλ‹€.

κ·Έλ¦¬λ“œλŠ” μ°½ λ˜λŠ” ν”„λ ˆμž„μ„ μ—΄κ³Ό ν–‰μœΌλ‘œ λ‚˜λˆ„λ©°, 빈 μ—΄μ΄λ‚˜ 행은 μžλ™μœΌλ‘œ μΆ•μ†Œλ˜μ§€λ§Œ, κ·Έ μ•ˆμ— 배치된 μœ„μ ―μ˜ 크기에 맞좰 μ‘°μ •λ©λ‹ˆλ‹€. 이전 μ˜ˆμ œμ—μ„œ λ²„νŠΌμ„ κ°€μš΄λ°λ‘œ λ°°μΉ˜ν•˜λ €λ©΄ 첫 번째 열에 0이 μ•„λ‹Œ weight 값을 λΆ€μ—¬ν•΄μ•Ό ν•©λ‹ˆλ‹€. κ·Έλ ‡μ§€ μ•ŠμœΌλ©΄ 열이 λ²„νŠΌμ˜ 크기둜 μΆ•μ†Œλ©λ‹ˆλ‹€(grid_rowconfigure()λ₯Ό μ‚¬μš©ν•˜μ—¬ 행도 μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€):

app.grid_columnconfigure(0, weight=1)

이제 μ—΄ 0이 weight=1둜 μ„€μ •λ˜μ—ˆκΈ° λ•Œλ¬Έμ— μ°½ 전체에 걸쳐 ν™•μž₯λ©λ‹ˆλ‹€. λ˜ν•œ, κ·Έλ¦¬λ“œ μ…€κ³Ό λ²„νŠΌμ΄ ν•¨κ»˜ ν™•μž₯λ˜λ„λ‘ ν•˜λ €λ©΄ sticky 인수λ₯Ό grid ν˜ΈμΆœμ— μΆ”κ°€ν•΄μ•Ό ν•©λ‹ˆλ‹€:

button.grid(row=0, column=0, padx=20, pady=20, sticky="ew")

이제 λ²„νŠΌμ΄ 동μͺ½κ³Ό μ„œμͺ½μ— μžˆλŠ” κ·Έλ¦¬λ“œ 셀에 κ³ μ •λ©λ‹ˆλ‹€. μ°½ 크기λ₯Ό μ‘°μ •ν•˜λ©΄ κ·Έλ¦¬λ“œ μ…€κ³Ό λ²„νŠΌ 크기가 μžλ™μœΌλ‘œ μ‘°μ •λ˜λŠ” 것을 λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€.


μ²΄ν¬λ°•μŠ€ μΆ”κ°€

λ ˆμ΄μ•„μ›ƒμ„ 쑰금 더 λ³΅μž‘ν•˜κ²Œ λ§Œλ“€κΈ° μœ„ν•΄ 두 번째 행에 두 개의 μ²΄ν¬λ°•μŠ€λ₯Ό μΆ”κ°€ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€:

checkbox_1 = customtkinter.CTkCheckBox(app, text="checkbox 1")
checkbox_1.grid(row=1, column=0, padx=20, pady=(0, 20), sticky="w")
checkbox_2 = customtkinter.CTkCheckBox(app, text="checkbox 2")
checkbox_2.grid(row=1, column=1, padx=20, pady=(0, 20), sticky="w")

μ—¬κΈ°μ„œ pady μΈμˆ˜λŠ” νŠœν”Œμ„ λ°›μŠ΅λ‹ˆλ‹€. 이 νŠœν”Œμ€ μœ„μͺ½μ—λŠ” 0, μ•„λž˜μͺ½μ—λŠ” 20의 νŒ¨λ”©μ„ μ„€μ •ν•œλ‹€λŠ” λœ»μž…λ‹ˆλ‹€. λ˜ν•œ μ²΄ν¬λ°•μŠ€λŠ” κ·Έλ¦¬λ“œ μ…€μ˜ μ„œμͺ½μ—λ§Œ κ³ μ •λ©λ‹ˆλ‹€. λ²„νŠΌμ΄ λ‹€μ‹œ μ°½ 전체에 걸쳐 ν™•μž₯λ˜λ„λ‘ ν•˜λ €λ©΄ columnspan=2λ₯Ό μ„€μ •ν•΄μ•Ό ν•©λ‹ˆλ‹€(rowspan은 행에 μ μš©λ©λ‹ˆλ‹€).

button.grid(row=0, column=0, padx=20, pady=20, sticky="ew", columnspan=2)

그리고 μ²΄ν¬λ°•μŠ€λ₯Ό λ™μΌν•œ κ°„κ²©μœΌλ‘œ λ°°μΉ˜ν•˜λ €λ©΄ 두 μ—΄ λͺ¨λ‘ λ™μΌν•œ weight 값을 μ€˜μ•Ό ν•©λ‹ˆλ‹€:

app.grid_columnconfigure((0, 1), weight=1)

이제 전체 ν”„λ‘œκ·Έλž¨μ€ λ‹€μŒκ³Ό 같이 λ³΄μž…λ‹ˆλ‹€:

def button_callback():
    print("button pressed")

app = customtkinter.CTk()
app.title("my app")
app.geometry("400x150")
app.grid_columnconfigure((0), weight=1)

button = customtkinter.CTkButton(app, text="my button", command=button_callback)
button.grid(row=0, column=0, padx=20, pady=20, sticky="ew", columnspan=2)
checkbox_1 = customtkinter.CTkCheckBox(app, text="checkbox 1")
checkbox_1.grid(row=1, column=0, padx=20, pady=(0, 20), sticky="w")
checkbox_2 = customtkinter.CTkCheckBox(app, text="checkbox 2")
checkbox_2.grid(row=1, column=1, padx=20, pady=(0, 20), sticky="w")

app.mainloop()

클래슀 μ‚¬μš©

λ§ˆμ§€λ§‰μœΌλ‘œ, 이 ν”„λ‘œκ·Έλž¨μ„ 클래슀 ꡬ쑰둜 μž¬κ΅¬μ„±ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€. CTkμ—μ„œ 상속받은 ν΄λž˜μŠ€λ‚˜ CTkFrame을 μ‚¬μš©ν•˜λŠ” 것이 μ½”λ“œμ˜ 가독성을 높이고 ν™•μž₯성을 μ‰½κ²Œ λ§Œλ“œλŠ” 데 도움이 되기 λ•Œλ¬Έμ— ꢌμž₯λ©λ‹ˆλ‹€. 클래슀λ₯Ό μ‚¬μš©ν•˜λ©΄ μ½”λ“œλ₯Ό μ—¬λŸ¬ 파일둜 λ‚˜λˆ„κΈ°λ„ μ‰¬μ›Œμ§‘λ‹ˆλ‹€.

주의: μ†Œκ·œλͺ¨ ν”„λ‘œκ·Έλž¨μ΄λ‚˜ ν…ŒμŠ€νŠΈκ°€ μ•„λ‹ˆλΌλ©΄, 항상 CTk, CTkToplevel, CTkFrame에 λŒ€ν•΄ λ³„λ„μ˜ 클래슀λ₯Ό μƒμ„±ν•˜μ„Έμš”. 클래슀λ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šκ³  λ§Žμ€ UI μ½”λ“œλ₯Ό ν•œ νŒŒμΌμ— μž‘μ„±ν•˜λŠ” 것은 읽기 νž˜λ“€κ³  μ’‹μ§€ μ•Šμ€ μ½”λ”© μŠ€νƒ€μΌμž…λ‹ˆλ‹€.

class App(customtkinter.CTk):
    def __init__(self):
        super().__init__()

        self.title("my app")
        self.geometry("400x150")
        self.grid_columnconfigure((0, 1), weight=1)

        self.button = customtkinter.CTkButton(self, text="my button", command=self.button_callback)
        self.button.grid(row=0, column=0, padx=20, pady=20, sticky="ew", columnspan=2)
        self.checkbox_1 = customtkinter.CTkCheckBox(self, text="checkbox 1")
        self.checkbox_1.grid(row=1, column=0, padx=20, pady=(0, 20), sticky="w")
        self.checkbox_2 = customtkinter.CTkCheckBox(self, text="checkbox 2")
        self.checkbox_2.grid(row=1, column=1, padx=20, pady=(0, 20), sticky="w")
        
    def button_callback(self):
        print("button pressed")

app = App()
app.mainloop()

이제 ν”„λ‘œκ·Έλž¨μ€ 클래슀둜 κ΅¬μ‘°ν™”λ˜μ–΄ λ”μš± ν™•μž₯ κ°€λŠ₯ν•˜κ³ , μœ μ§€λ³΄μˆ˜ν•˜κΈ°λ„ μ‰¬μ›Œμ‘ŒμŠ΅λ‹ˆλ‹€.


2. Using Frames

ν”„λ ˆμž„ μƒμ„±ν•˜κΈ°

μΈνŠΈλ‘œμ™€ μœ μ‚¬ν•œ 예제둜 μ‹œμž‘ν•˜μ—¬, λ²„νŠΌκ³Ό λͺ‡ 개의 μ²΄ν¬λ°•μŠ€κ°€ ν¬ν•¨λœ 창을 λ§Œλ“€μ–΄ λ΄…μ‹œλ‹€:

class App(customtkinter.CTk):
    def __init__(self):
        super().__init__()

        self.title("my app")
        self.geometry("400x180")
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure((0, 1), weight=1)

        self.checkbox_1 = customtkinter.CTkCheckBox(self, text="checkbox 1")
        self.checkbox_1.grid(row=0, column=0, padx=10, pady=(10, 0), sticky="w")
        self.checkbox_2 = customtkinter.CTkCheckBox(self, text="checkbox 2")
        self.checkbox_2.grid(row=1, column=0, padx=10, pady=(10, 0), sticky="w")
        self.button = customtkinter.CTkButton(self, text="my button", command=self.button_callback)
        self.button.grid(row=3, column=0, padx=10, pady=10, sticky="ew")

    def button_callback(self):
        print("button pressed")

app = App()
app.mainloop()

λ§Œμ•½ 2행에 μ²΄ν¬λ°•μŠ€λ₯Ό ν•˜λ‚˜ 더 μΆ”κ°€ν•˜λ €κ³  ν•œλ‹€λ©΄, λ²„νŠΌμ˜ ν–‰ λ²ˆν˜Έλ„ λ³€κ²½ν•΄μ•Ό ν•©λ‹ˆλ‹€. 이처럼 λ ˆμ΄μ•„μ›ƒ 변경이 ν•„μš”ν•œ 경우, 영ν–₯을 쀄이기 μœ„ν•΄ ν”„λ ˆμž„μ„ μ‚¬μš©ν•˜μ—¬ ꡬ쑰λ₯Ό μ •λ¦¬ν•©λ‹ˆλ‹€.

ν”„λ ˆμž„μ„ 0행에, λ²„νŠΌμ„ 1행에 λ°°μΉ˜ν•˜κ³ , 0ν–‰κ³Ό 0열이 ν™•μž₯λ˜λ„λ‘ μ„€μ •ν•©λ‹ˆλ‹€. ν”„λ ˆμž„μ—λŠ” sticky λ§€κ°œλ³€μˆ˜λ₯Ό 'nsw'둜 μ„€μ •ν•˜μ—¬ μ…€μ—μ„œ 뢁μͺ½, 남μͺ½, μ„œμͺ½μœΌλ‘œ ν™•μž₯되게 ν•©λ‹ˆλ‹€. 이제 μ²΄ν¬λ°•μŠ€μ˜ master μΈμˆ˜λŠ” self λŒ€μ‹  ν”„λ ˆμž„μ΄ λ©λ‹ˆλ‹€.

class App(customtkinter.CTk):
    def __init__(self):
        super().__init__()

        self.title("my app")
        self.geometry("400x180")
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)

        self.checkbox_frame = customtkinter.CTkFrame(self)
        self.checkbox_frame.grid(row=0, column=0, padx=10, pady=(10, 0), sticky="nsw")
        self.checkbox_1 = customtkinter.CTkCheckBox(self.checkbox_frame, text="checkbox 1")
        self.checkbox_1.grid(row=0, column=0, padx=10, pady=(10, 0), sticky="w")
        self.checkbox_2 = customtkinter.CTkCheckBox(self.checkbox_frame, text="checkbox 2")
        self.checkbox_2.grid(row=1, column=0, padx=10, pady=(10, 0), sticky="w")

        self.button = customtkinter.CTkButton(self, text="my button", command=self.button_callback)
        self.button.grid(row=3, column=0, padx=10, pady=10, sticky="ew")

    def button_callback(self):
        print("button pressed")

클래슀 μ‚¬μš©ν•˜κΈ°

그리고 μ½”λ“œμ˜ λ ˆμ΄μ•„μ›ƒμ„ λ°˜μ˜ν•˜κ³ , μ„œλ‘μ—μ„œ μ–ΈκΈ‰λœ 항상 클래슀λ₯Ό μ‚¬μš©ν•˜λŠ” 원칙을 λ”°λ₯΄κΈ° μœ„ν•΄, ν”„λ ˆμž„κ³Ό μ²΄ν¬λ°•μŠ€ μ½”λ“œλ₯Ό λ³„λ„μ˜ 클래슀둜 이동할 κ²ƒμž…λ‹ˆλ‹€. 이 ν΄λž˜μŠ€λŠ” CTkFrame을 μƒμ†ν•˜λ©°, 메인 App ν΄λž˜μŠ€μ—μ„œ 이 클래슀의 μΈμŠ€ν„΄μŠ€κ°€ μƒμ„±λ©λ‹ˆλ‹€. __init__ λ©”μ„œλ“œμ—λŠ” master 인자만 μ „λ‹¬λ˜μ–΄, μƒμœ„ 클래슀인 CTkFrame의 __init__에 전달될 수 μžˆλ„λ‘ ν•  κ²ƒμž…λ‹ˆλ‹€.

class MyCheckboxFrame(customtkinter.CTkFrame):
    def __init__(self, master):
        super().__init__(master)

        self.checkbox_1 = customtkinter.CTkCheckBox(self, text="checkbox 1")
        self.checkbox_1.grid(row=0, column=0, padx=10, pady=(10, 0), sticky="w")
        self.checkbox_2 = customtkinter.CTkCheckBox(self, text="checkbox 2")
        self.checkbox_2.grid(row=1, column=0, padx=10, pady=(10, 0), sticky="w")

class App(customtkinter.CTk):
    def __init__(self):
        super().__init__()

        self.title("my app")
        self.geometry("400x180")
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)

        self.checkbox_frame = MyCheckboxFrame(self)
        self.checkbox_frame.grid(row=0, column=0, padx=10, pady=(10, 0), sticky="nsw")

        self.button = customtkinter.CTkButton(self, text="my button", command=self.button_callback)
        self.button.grid(row=3, column=0, padx=10, pady=10, sticky="ew")

    def button_callback(self):
        print("button pressed")

app = App()
app.mainloop()

이제 MyCheckboxFrame ν΄λž˜μŠ€μ— μ²΄ν¬λ°•μŠ€λ₯Ό 좔가해도 λ²„νŠΌμ˜ λ ˆμ΄μ•„μ›ƒμ—λŠ” 영ν–₯을 μ£Όμ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

class MyCheckboxFrame(customtkinter.CTkFrame):
    def __init__(self, master):
        super().__init__(master)

        self.checkbox_1 = customtkinter.CTkCheckBox(self, text="checkbox 1")
        self.checkbox_1.grid(row=0, column=0, padx=10, pady=(10, 0), sticky="w")
        self.checkbox_2 = customtkinter.CTkCheckBox(self, text="checkbox 2")
        self.checkbox_2.grid(row=1, column=0, padx=10, pady=(10, 0), sticky="w")
        self.checkbox_3 = customtkinter.CTkCheckBox(self, text="checkbox 3")
        self.checkbox_3.grid(row=2, column=0, padx=10, pady=(10, 0), sticky="w")

ν”„λ ˆμž„ 클래슀 ν™•μž₯ν•˜κΈ°

λ¬Όλ‘  메인 App ν΄λž˜μŠ€μ—μ„œ μ²΄ν¬λ°•μŠ€μ˜ 값을 읽을 수 μžˆλŠ” 방법이 ν•„μš”ν•©λ‹ˆλ‹€. λ”°λΌμ„œ MyCheckboxFrame ν΄λž˜μŠ€μ— get λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•˜μ—¬, 체크된 μ²΄ν¬λ°•μŠ€μ˜ text 속성을 λ¬Έμžμ—΄ 리슀트둜 λ°˜ν™˜ν•˜λ„λ‘ ν•  κ²ƒμž…λ‹ˆλ‹€.

class MyCheckboxFrame(customtkinter.CTkFrame):
    def __init__(self, master):
        super().__init__(master)

        self.checkbox_1 = customtkinter.CTkCheckBox(self, text="checkbox 1")
        self.checkbox_1.grid(row=0, column=0, padx=10, pady=(10, 0), sticky="w")
        self.checkbox_2 = customtkinter.CTkCheckBox(self, text="checkbox 2")
        self.checkbox_2.grid(row=1, column=0, padx=10, pady=(10, 0), sticky="w")
        self.checkbox_3 = customtkinter.CTkCheckBox(self, text="checkbox 3")
        self.checkbox_3.grid(row=2, column=0, padx=10, pady=(10, 0), sticky="w")
        
    def get(self):
        checked_checkboxes = []
        if self.checkbox_1.get() == 1:
            checked_checkboxes.append(self.checkbox_1.cget("text"))
        if self.checkbox_2.get() == 1:
            checked_checkboxes.append(self.checkbox_2.cget("text"))
        if self.checkbox_3.get() == 1:
            checked_checkboxes.append(self.checkbox_3.cget("text"))
        return checked_checkboxes

App 클래슀의 button_callback λ©”μ„œλ“œμ—μ„œ 이 λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ 체크된 μ²΄ν¬λ°•μŠ€ λͺ©λ‘μ„ 좜λ ₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

    def button_callback(self):
        print("checked checkboxes:", self.checkbox_frame.get())

이제 λ²„νŠΌμ„ λˆ„λ₯΄λ©΄, λ‹€μŒκ³Ό 같은 μ„ νƒλœ μ²΄ν¬λ°•μŠ€μ˜ λͺ©λ‘μ΄ 좜λ ₯λ˜λŠ” 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

checked checkboxes: ['checkbox 1', 'checkbox 3']

동적 ν”„λ ˆμž„ 클래슀

ν•˜μ§€λ§Œ ν˜„μž¬ MyCheckboxFrame의 μ²΄ν¬λ°•μŠ€ 값듀은 μ½”λ“œμ— ν•˜λ“œμ½”λ”©λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. MyCheckboxFrame 클래슀λ₯Ό 보닀 λ™μ μœΌλ‘œ μ‚¬μš©ν•  수 μžˆλ„λ‘, μ²΄ν¬λ°•μŠ€μ˜ text 값이 될 λ¬Έμžμ—΄ 리슀트λ₯Ό MyCheckboxFrame에 전달할 κ²ƒμž…λ‹ˆλ‹€. 이제 μ²΄ν¬λ°•μŠ€μ˜ κ°œμˆ˜λ„ μž„μ˜λ‘œ μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

class MyCheckboxFrame(customtkinter.CTkFrame):
    def __init__(self, master, values):
        super().__init__(master)
        self.values = values
        self.checkboxes = []

        for i, value in enumerate(self.values):
            checkbox = customtkinter.CTkCheckBox(self, text=value)
            checkbox.grid(row=i, column=0, padx=10, pady=(10, 0), sticky="w")
            self.checkboxes.append(checkbox)

    def get(self):
        checked_checkboxes = []
        for checkbox in self.checkboxes:
            if checkbox.get() == 1:
                checked_checkboxes.append(checkbox.cget("text"))
        return checked_checkboxes

μ²΄ν¬λ°•μŠ€λ“€μ€ for λ£¨ν”„μ—μ„œ μƒμ„±λ˜μ–΄ checkboxesλΌλŠ” λ¦¬μŠ€νŠΈμ— μ €μž₯λ©λ‹ˆλ‹€. get λ©”μ„œλ“œμ—μ„œλŠ” 이 리슀트λ₯Ό λ°˜λ³΅ν•˜μ—¬ μ–΄λ–€ μ²΄ν¬λ°•μŠ€κ°€ μ„ νƒλ˜μ—ˆλŠ”μ§€ ν™•μΈν•˜κ³ , ν•΄λ‹Ή text 값을 λ°˜ν™˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이제 MyCheckboxFrame의 μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜λŠ” App ν΄λž˜μŠ€μ—μ„œλŠ” κ°’ 리슀트λ₯Ό 전달해야 ν•©λ‹ˆλ‹€.

self.checkbox_frame = MyCheckboxFrame(self, values=["value 1", "value 2", "value 3"])
self.checkbox_frame.grid(row=0, column=0, padx=10, pady=(10, 0), sticky="nsw")

닀쀑 μΈμŠ€ν„΄μŠ€ μƒμ„±ν•˜κΈ°

이제 μ²΄ν¬λ°•μŠ€μ˜ 값을 λ™μ μœΌλ‘œ μ œμ–΄ν•  수 있게 λ˜μ—ˆμœΌλ―€λ‘œ, MyCheckboxFrame 클래슀의 μΈμŠ€ν„΄μŠ€λ₯Ό 두 개 이상 맀우 μ‰½κ²Œ 생성할 수 μžˆμŠ΅λ‹ˆλ‹€. λ²„νŠΌμ—λŠ” columnspan을 2둜 μ§€μ •ν•˜κ³ , 1번 열에 κ°€μ€‘μΉ˜ 1을 μΆ”κ°€ν•΄μ•Ό ν•©λ‹ˆλ‹€.

class App(customtkinter.CTk):
    def __init__(self):
        super().__init__()

        self.title("my app")
        self.geometry("400x180")
        self.grid_columnconfigure((0, 1), weight=1)
        self.grid_rowconfigure(0, weight=1)

        self.checkbox_frame_1 = MyCheckboxFrame(self, values=["value 1", "value 2", "value 3"])
        self.checkbox_frame_1.grid(row=0, column=0, padx=10, pady=(10, 0), sticky="nsew")
        self.checkbox_frame_2 = MyCheckboxFrame(self, values=["option 1", "option 2"])
        self.checkbox_frame_2.grid(row=0, column=1, padx=(0, 10), pady=(10, 0), sticky="nsew")

        self.button = customtkinter.CTkButton(self, text="my button", command=self.button_callback)
        self.button.grid(row=3, column=0, padx=10, pady=10, sticky="ew", columnspan=2)

    def button_callback(self):
        print("checkbox_frame_1:", self.checkbox_frame_1.get())
        print("checkbox_frame_2:", self.checkbox_frame_2.get())

λ²„νŠΌμ„ λˆŒλ €μ„ λ•Œμ˜ 좜λ ₯:

checkbox_frame_1: ['value 1', 'value 3']
checkbox_frame_2: ['option 2']

ν”„λ ˆμž„μ— 제λͺ© μΆ”κ°€ν•˜κΈ°

MyCheckboxFrame ν΄λž˜μŠ€μ— ν”„λ ˆμž„μ˜ 제λͺ© 역할을 ν•˜λŠ” λ ˆμ΄λΈ”μ„ μΆ”κ°€ν•˜μ—¬ λ”μš± ν–₯μƒμ‹œν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€.

class MyCheckboxFrame(customtkinter.CTkFrame):
    def __init__(self, master, title, values):
        super().__init__(master)
        self.grid_columnconfigure(0, weight=1)
        self.values = values
        self.title = title
        self.checkboxes = []

        self.title = customtkinter.CTkLabel(self, text=self.title, fg_color="gray30", corner_radius=6)
        self.title.grid(row=0, column=0, padx=10, pady=(10, 0), sticky="ew")

        for i, value in enumerate(self.values):
            checkbox = customtkinter.CTkCheckBox(self, text=value)
            checkbox.grid(row=i+1, column=0, padx=10, pady=(10, 0), sticky="w")
            self.checkboxes.append(checkbox)

    def get(self):
        checked_checkboxes = []
        for checkbox in self.checkboxes:
            if checkbox.get() == 1:
                checked_checkboxes.append(checkbox.cget("text"))
        return checked_checkboxes

이제 ν”„λ ˆμž„ λ‚΄λΆ€μ˜ 0번 열에 κ°€μ€‘μΉ˜ 1이 μ„€μ •λ˜μ–΄ μžˆμœΌλ―€λ‘œ, λ ˆμ΄λΈ”μ˜ sticky 값을 'ew'둜 μ§€μ •ν•˜μ—¬ λ ˆμ΄λΈ”μ΄ ν”„λ ˆμž„ 전체에 걸쳐 ν™•μž₯λ©λ‹ˆλ‹€. CTkLabelμ—λŠ” fg_color와 corner_radius 인자λ₯Ό μ „λ‹¬ν–ˆλŠ”λ°, 기본적으둜 λ ˆμ΄λΈ”μ΄ '투λͺ…'이며 corner_radius 값이 0이기 λ•Œλ¬Έμž…λ‹ˆλ‹€. λ˜ν•œ, 첫 번째 행에 제λͺ© λ ˆμ΄λΈ”μ΄ μΆ”κ°€λ˜μ—ˆκΈ° λ•Œλ¬Έμ— κ·Έλ¦¬λ“œ ν–‰ μœ„μΉ˜λŠ” 이제 i+1이 λ©λ‹ˆλ‹€.

이제 App ν΄λž˜μŠ€μ—μ„œ MyCheckboxFrame μΈμŠ€ν„΄μŠ€λ₯Ό 생성할 λ•Œ 제λͺ©μ— ν•΄λ‹Ήν•˜λŠ” title λ§€κ°œλ³€μˆ˜μ˜ 값을 전달해야 ν•©λ‹ˆλ‹€.

self.checkbox_frame_1 = MyCheckboxFrame(self, "Values", values=["value 1", "value 2", "value 3"])
self.checkbox_frame_2 = MyCheckboxFrame(self, "Options", values=["option 1", "option 2"])

창의 높이λ₯Ό 220px둜 μ¦κ°€μ‹œν‚€λ©΄ λ‹€μŒκ³Ό 같은 κ²°κ³Όλ₯Ό μ–»μŠ΅λ‹ˆλ‹€:

이제 μ²΄ν¬λ°•μŠ€ ν”„λ ˆμž„μ— 제λͺ©μ΄ μƒκ²ΌμŠ΅λ‹ˆλ‹€. 그리고 MyCheckboxFrame은 CTkFrame을 μƒμ†ν•˜λ―€λ‘œ, 예λ₯Ό λ“€μ–΄ 투λͺ…ν•œ fg_color와 같이 CTkFrame처럼 ꡬ성할 수 μžˆμŠ΅λ‹ˆλ‹€.

self.checkbox_frame_2.configure(fg_color="transparent")

λΌλ””μ˜€λ²„νŠΌ ν”„λ ˆμž„

MyCheckboxFrameκ³Ό μœ μ‚¬ν•˜κ²Œ, μ²΄ν¬λ°•μŠ€ λŒ€μ‹  λΌλ””μ˜€ λ²„νŠΌμœΌλ‘œ κ΅¬μ„±λœ MyRadiobuttonFrame을 λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.

class MyRadiobuttonFrame(customtkinter.CTkFrame):
    def __init__(self, master, title, values):
        super().__init__(master)
        self.grid_columnconfigure(0, weight=1)
        self.values = values
        self.title = title
        self.radiobuttons = []
        self.variable = customtkinter.StringVar(value="")

        self.title = customtkinter.CTkLabel(self, text=self.title, fg_color="gray30", corner_radius=6)
        self.title.grid(row=0, column=0, padx=10, pady=(10, 0), sticky="ew")

        for i, value in enumerate(self.values):
            radiobutton = customtkinter.CTkRadioButton(self, text=value, value=value, variable=self.variable)
            radiobutton.grid(row=i + 1, column=0, padx=10, pady=(10, 0), sticky="w")
            self.radiobuttons.append(radiobutton)

    def get(self):
        return self.variable.get()

    def set(self, value):
        self.variable.set(value)

그리고 Appμ—μ„œ MyCheckboxFrameκ³Ό μœ μ‚¬ν•˜κ²Œ MyRadiobuttonFrame의 μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.

class App(customtkinter.CTk):
    def __init__(self):
        super().__init__()

        self.title("my app")
        self.geometry("400x220")
        self.grid_columnconfigure((0, 1), weight=1)
        self.grid_rowconfigure(0, weight=1)

        self.checkbox_frame = MyCheckboxFrame(self, "Values", values=["value 1", "value 2", "value 3"])
        self.checkbox_frame.grid(row=0, column=0, padx=10, pady=(10, 0), sticky="nsew")
        self.radiobutton_frame = MyRadiobuttonFrame(self, "Options", values=["option 1", "option 2"])
        self.radiobutton_frame.grid(row=0, column=1, padx=(0, 10), pady=(10, 0), sticky="nsew")

        self.button = customtkinter.CTkButton(self, text="my button", command=self.button_callback)
        self.button.grid(row=3, column=0, padx=10, pady=10, sticky="ew", columnspan=2)

    def button_callback(self):
        print("checkbox_frame:", self.checkbox_frame.get())
        print("radiobutton_frame:", self.radiobutton_frame.get())

이제 μ‚¬μš©μžλ‘œλΆ€ν„° μž…λ ₯을 받을 수 μžˆλŠ” 두 개의 λ™μ μœΌλ‘œ μƒμ„±λœ μ„œλ‘œ λ‹€λ₯Έ ν”„λ ˆμž„μ΄ μƒκ²ΌμŠ΅λ‹ˆλ‹€.


3. Scrollable frames

ν”„λ ˆμž„ λ‚΄ μ²΄ν¬λ°•μŠ€

이전 μ±•ν„°μ—μ„œ μš°λ¦¬λŠ” μ£Όμ–΄μ§„ κ°’ λͺ©λ‘μ— λŒ€ν•΄ μ²΄ν¬λ°•μŠ€λ₯Ό μƒμ„±ν•˜λŠ” λ‹€μŒ ν”„λ ˆμž„μ„ λ§Œλ“€μ—ˆμŠ΅λ‹ˆλ‹€.

class MyCheckboxFrame(customtkinter.CTkFrame):
    def __init__(self, master, values):
        super().__init__(master)
        self.values = values
        self.checkboxes = []

        for i, value in enumerate(self.values):
            checkbox = customtkinter.CTkCheckBox(self, text=value)
            checkbox.grid(row=i, column=0, padx=10, pady=(10, 0), sticky="w")
            self.checkboxes.append(checkbox)

    def get(self):
        checked_checkboxes = []
        for checkbox in self.checkboxes:
            if checkbox.get() == 1:
                checked_checkboxes.append(checkbox.cget("text"))
        return checked_checkboxes

슀크둀 κ°€λŠ₯ν•œ ν”„λ ˆμž„

λ ˆμ΄μ•„μ›ƒμ΄λ‚˜ 화면에 곡간이 λΆ€μ‘±ν•˜μ—¬ 더 λ§Žμ€ μ²΄ν¬λ°•μŠ€λ₯Ό ν‘œμ‹œν•˜λ €λ©΄, 슀크둀 κ°€λŠ₯ν•œ ν”„λ ˆμž„μ„ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. CTkScrollableFrame은 μŠ€ν¬λ‘€λ°”λ₯Ό μžλ™μœΌλ‘œ μΆ”κ°€ν•˜μ—¬ ν”„λ ˆμž„ 내뢀에 μ‹€μ œλ‘œ λ“€μ–΄κ°€λŠ” 것보닀 더 λ§Žμ€ μœ„μ ―μ„ μΆ”κ°€ν•  수 있게 ν•΄μ€λ‹ˆλ‹€. ν”„λ ˆμž„μ€ λ‚΄λΆ€ μœ„μ ―μ΄ λŠ˜μ–΄λ‚˜λ”λΌλ„ ν™•μž₯λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. CTkScrollableFrameμ—λŠ” 제λͺ© μ˜΅μ…˜λ„ μžˆμ–΄μ„œ, label_text 인자λ₯Ό 톡해 μœ„μ˜ μ˜ˆμ œμ—μ„œ μˆ˜λ™μœΌλ‘œ λ§Œλ“  κ²ƒμ²˜λŸΌ 제λͺ© λ ˆμ΄λΈ”μ— λŒ€ν•œ ν…μŠ€νŠΈλ₯Ό 전달할 수 μžˆμŠ΅λ‹ˆλ‹€.

class MyScrollableCheckboxFrame(customtkinter.CTkScrollableFrame):
    def __init__(self, master, title, values):
        super().__init__(master, label_text=title)
        self.grid_columnconfigure(0, weight=1)
        self.values = values
        self.checkboxes = []

        for i, value in enumerate(self.values):
            checkbox = customtkinter.CTkCheckBox(self, text=value)
            checkbox.grid(row=i, column=0, padx=10, pady=(10, 0), sticky="w")
            self.checkboxes.append(checkbox)

    def get(self):
        checked_checkboxes = []
        for checkbox in self.checkboxes:
            if checkbox.get() == 1:
                checked_checkboxes.append(checkbox.cget("text"))
        return checked_checkboxes

이제 App ν΄λž˜μŠ€μ—μ„œ MyScrollableCheckboxFrame의 μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜κ³  더 λ§Žμ€ 값을 μΆ”κ°€ν•˜μ—¬ μŠ€ν¬λ‘€λ°” κΈ°λŠ₯을 ν…ŒμŠ€νŠΈν•  수 μžˆμŠ΅λ‹ˆλ‹€.

class App(customtkinter.CTk):
    def __init__(self):
        super().__init__()

        self.title("my app")
        self.geometry("400x220")
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)

        values = ["value 1", "value 2", "value 3", "value 4", "value 5", "value 6"]
        self.scrollable_checkbox_frame = MyScrollableCheckboxFrame(self, title="Values", values=values)
        self.scrollable_checkbox_frame.grid(row=0, column=0, padx=10, pady=(10, 0), sticky="nsew")

        self.button = customtkinter.CTkButton(self, text="my button", command=self.button_callback)
        self.button.grid(row=3, column=0, padx=10, pady=10, sticky="ew", columnspan=2)

    def button_callback(self):
        print("checkbox_frame:", self.checkbox_frame.get())
        print("radiobutton_frame:", self.radiobutton_frame.get())

app = App()
app.mainloop()

슀크둀 κ°€λŠ₯ν•œ ν”„λ ˆμž„μ— λŒ€ν•œ 더 λ§Žμ€ μ˜ˆμ œλŠ” GitHub의 λ‹€μŒ 예제 ν”„λ‘œκ·Έλž¨μ—μ„œ 찾을 수 μžˆμŠ΅λ‹ˆλ‹€: https://github.com/TomSchimansky/CustomTkinter/blob/master/examples/scrollable_frame_example.py


κ³ κΈ‰ νŠœν† λ¦¬μ–Ό

μƒˆ μœ„μ ―μ„ λ§Œλ“€κ³  더 λ³΅μž‘ν•œ UIλ₯Ό κ΅¬μ„±ν•˜λŠ” 방법에 λŒ€ν•œ κ³ κΈ‰ νŠœν† λ¦¬μ–Όμž…λ‹ˆλ‹€.

μŠ€ν•€λ°•μŠ€ μƒμ„±ν•˜κΈ°

ν”„λ ˆμž„μ—μ„œ μœ„μ ― μƒμ„±ν•˜κΈ°

CustomTkinter에 κ΅¬ν˜„λ˜μ§€ μ•Šμ€ κΈ°λŠ₯이 ν•„μš”ν•΄μ„œ 직접 μƒˆλ‘œμš΄ μœ„μ ―μ„ λ§Œλ“€κ³ μž ν•œλ‹€λ©΄, μ‰½κ²Œ ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ•„λž˜μ—μ„œλŠ” float 값을 기반으둜 ν•œ Spinboxλ₯Ό μƒμ„±ν•˜λŠ” 예제λ₯Ό λ³΄μ—¬λ“œλ¦¬κ² μŠ΅λ‹ˆλ‹€.

λ¨Όμ € customtkinter.CTkFrame을 μƒμ†ν•˜λŠ” μƒˆλ‘œμš΄ 클래슀λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.

import customtkinter

class WidgetName(customtkinter.CTkFrame):
    def __init__(self, *args,
                 width: int = 100,
                 height: int = 32,
                 **kwargs):
        super().__init__(*args, width=width, height=height, **kwargs)

μ΄λ ‡κ²Œ ν•˜λ©΄ 기본적으둜 width와 height μ†μ„±μ˜ 기본값에 μ˜ν•΄ μ§€μ •λœ 크기λ₯Ό κ°€μ§„ μœ„μ ―μ΄ μƒμ„±λ©λ‹ˆλ‹€. λ˜ν•œ fg_colorλ‚˜ corner_radius와 같은 CTkFrame의 λͺ¨λ“  속성도 μ§€μ›ν•©λ‹ˆλ‹€.


ν”„λ ˆμž„μ—μ„œ μŠ€ν•€λ°•μŠ€ μƒμ„±ν•˜κΈ°

float 기반의 μŠ€ν•€λ°•μŠ€λ₯Ό κ°„λ‹¨ν•˜κ²Œ κ΅¬ν˜„ν•œ μ˜ˆμ‹œλŠ” λ‹€μŒκ³Ό 같을 수 μžˆμŠ΅λ‹ˆλ‹€.

class FloatSpinbox(customtkinter.CTkFrame):
    def __init__(self, *args,
                 width: int = 100,
                 height: int = 32,
                 step_size: Union[int, float] = 1,
                 command: Callable = None,
                 **kwargs):
        super().__init__(*args, width=width, height=height, **kwargs)

        self.step_size = step_size
        self.command = command

        self.configure(fg_color=("gray78", "gray28"))  # set frame color

        self.grid_columnconfigure((0, 2), weight=0)  # buttons don't expand
        self.grid_columnconfigure(1, weight=1)  # entry expands

        self.subtract_button = customtkinter.CTkButton(self, text="-", width=height-6, height=height-6,
                                                       command=self.subtract_button_callback)
        self.subtract_button.grid(row=0, column=0, padx=(3, 0), pady=3)

        self.entry = customtkinter.CTkEntry(self, width=width-(2*height), height=height-6, border_width=0)
        self.entry.grid(row=0, column=1, columnspan=1, padx=3, pady=3, sticky="ew")

        self.add_button = customtkinter.CTkButton(self, text="+", width=height-6, height=height-6,
                                                  command=self.add_button_callback)
        self.add_button.grid(row=0, column=2, padx=(0, 3), pady=3)

        # default value
        self.entry.insert(0, "0.0")

    def add_button_callback(self):
        if self.command is not None:
            self.command()
        try:
            value = float(self.entry.get()) + self.step_size
            self.entry.delete(0, "end")
            self.entry.insert(0, value)
        except ValueError:
            return

    def subtract_button_callback(self):
        if self.command is not None:
            self.command()
        try:
            value = float(self.entry.get()) - self.step_size
            self.entry.delete(0, "end")
            self.entry.insert(0, value)
        except ValueError:
            return

    def get(self) -> Union[float, None]:
        try:
            return float(self.entry.get())
        except ValueError:
            return None

    def set(self, value: float):
        self.entry.delete(0, "end")
        self.entry.insert(0, str(float(value)))

λ¬Όλ‘  configure λ©”μ„œλ“œλ‚˜ __init__ λ©”μ„œλ“œμ—μ„œ 색상을 μ„€μ •ν•  수 μžˆλŠ” 더 λ§Žμ€ 속성을 μΆ”κ°€ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

ν•˜μ§€λ§Œ μœ„ κ΅¬ν˜„μ„ 톡해 μŠ€ν•€λ°•μŠ€λ₯Ό λ‹€μŒκ³Ό 같이 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

app = customtkinter.CTk()

spinbox_1 = FloatSpinbox(app, width=150, step_size=3)
spinbox_1.pack(padx=20, pady=20)

spinbox_1.set(35)
print(spinbox_1.get())

app.mainloop()

profile
μ„Έκ³„λŠ” λ‚˜μ˜ ν‘œμƒ

3개의 λŒ“κΈ€

comment-user-thumbnail
2024λ…„ 11μ›” 2일

GUI도 λ§Œλ“œμ‹œλŠ”κ΅°μš”!! ν™•μ‹€νžˆ κΈ°μ‘΄ Tkinter보닀 예쁜 것 κ°™μŠ΅λ‹ˆλ‹€γ…Žγ…Ž λ‚˜μ€‘μ— λ§Œλ“€ κΈ°νšŒκ°€ 생기면 λ―Όν‚€ κ³΅μ£Όλ‹˜μ˜ νŠœν† λ¦¬μ–Όμ„ μ°Έκ³ ν•˜κ² μŠ΅λ‹ˆλ‹€!!

λ‹΅κΈ€ 달기
comment-user-thumbnail
2024λ…„ 11μ›” 3일

μ œκ°€ 찾던 CustomTkinter 곡식 νŠœν† λ¦¬μ–Ό λ²ˆμ—­λ³Έ μ—¬κΈ° μžˆμ—ˆλ„€μš”. κ°μ‚¬ν•©λ‹ˆλ‹€πŸ™

λ‹΅κΈ€ 달기
comment-user-thumbnail
2024λ…„ 11μ›” 8일

μš°μ™€ μ •μ„±μŠ€λŸ½κ²Œ 정리λ₯Ό μ•„μ£Ό μž˜ν•˜μ…¨λ„€μš”~~πŸ‘
파이썬 GUI ν”„λ‘œμ νŠΈ ν•  λ•Œ 무쑰건 μ°Έκ³ ν•΄μ•Όν•˜λŠ” 필독 ν¬μŠ€νŒ…μΈ 것 κ°™μŠ΅λ‹ˆλ‹€!

λ‹΅κΈ€ 달기