Source code: github
< 코드 > < 이미지 > < 설명 > 순서로 제작되어 있으며,
상황에 따라 일부 내용이 생략되어 있습니다.
기본 정보
Contributors
- 코드: DeneV
- 아래 작성된 코드는 전체 프로젝트 중 일부를 편집하여 작성되었습니다.
- 프로젝트: Team Palette
Licenses & Copyrights
- Code
- 코드는 수정 / 배포 / 사적 목적으로 사용할 수 있습니다.
- 단, 프로젝트에 사용된 이미지 및 폰트의 저작권은 외부에 있으며 이에 대한 사용 책임은 코드 사용자에게 있습니다.
- img/logo.png
- Image by Cha_cha
- "tvN 즐거운이야기"가 적용되어 있습니다.
- "tvN 즐거운이야기체"의 지적 재산권은 CJ E&M에 있습니다.
- img/sample.png
- Photo by DeneV
- img/icon/*.png
- (c) 2013-2017 Cole Bemis, The MIT License
Files
Palette
|- main.py
|- palette
| |- __init__.py
| |- data.py
| |- functions.py
|- img
|- icon.ico
|- error.png
|- logo.png
|- design
| |- *.png
|- icon
| |- *.png
|- photo
Version
- python: 3.9.7
- pillow: 8.4.0 (보안문제로 인해 9.0.1 권장)
main.py
from tkinter import *
import tkinter.ttk as ttk
from tkinter.tix import *
from PIL import ImageTk
from palette.functions import *
tkinter
class App(Tk):
def __init__(self):
Tk.__init__(self)
Tk.title(self, "Palette")
Tk.geometry(self, "360x480+500+200")
Tk.resizable(self, 0, 0)
ico_path = get_path("img\icon.ico") # functions.py 참고
Tk.iconbitmap(self, ico_path)
self._frame = None
self.refresh_frame(IntroPage)
def refresh_frame(self, frame, *parameters):
if self._frame is not None:
self._frame.destroy()
self._frame = frame(self, *parameters)
self._frame.pack(expand=YES)
Tk를 상속받은 App 클래스를 생성하여 페이지를 만듭니다.
- title: 해당 UI 상단에 표시되는 제목을 설정합니다.
- geometry: '가로x세로' 크기를 설정하고 '+가로+세로'를 통해 화면에 생성될 위치를 지정합니다.
- resizable: 화면 크기를 사용자가 늘릴 수 있는 범위를 지정합니다.
- (0, 0)은 사용자가 화면 크기를 조정할 수 없도록 합니다.
- iconbitmap: 상단 좌측에 표시될 아이콘을 설정합니다.
__init__의 refresh_frame을 통해 코드를 실행할 때 보여질 첫 페이지를 설정합니다.
refresh_frame을 통해 window 창을 유지한 상태에서 화면의 내용을 변경할 수 있도록 합니다.
기본 설정
class MainFrame(Frame):
def __init__(self, master):
super().__init__(master)
# 기본 디자인 정보
self._font = (None, 13)
self._bg = "#ffffff"
self._green = "#349E71"
self._green2 = "#CDEFE0"
self.pack(fill="both", expand=True)
self._trend_text = crawl_naver_datalab() # functions.py 참고
style = ttk.Style()
# 스타일 설정
style.theme_use("clam")
style.map("TCombobox", fieldbackground=[("readonly", "#ffffff")])
style.map("TCombobox", selectbackground=[("readonly", "#ffffff")])
style.map("TCombobox", selectforeground=[("readonly", "#000000")])
style.map("TCombobox", background=[("readonly", "#ffffff")])
style.configure("TNotebook", background=self._green)
style.configure("TNotebook.Tab", font=(None, 10))
style.map("TNotebook.Tab", background=[("selected", "#FEDF51")])
페이지에서 사용할 기본 정보를 설정합니다.
- self._font = (폰트, 크기)
- self._bg: 배경색
- style.theme_use: tkinter에서 제공하는 위젯 스타일을 변경할 수 있습니다.
- "TCombobox": 선택 페이지의 combobox를 커스텀합니다.
- "TNotebook.Tab": 결과 페이지의 notebook 상단 tab을 커스텀합니다.
세부 기능
정보 제공
class InfoPage(Toplevel):
def __init__(self, master):
super().__init__(master)
self.title("INFO")
self.geometry("300x360+680+270")
Label(self, text="TEXT").pack()
새로운 window 창을 생성해 텍스트 정보를 제공합니다.
이미지 표시
class ImgPage(Toplevel):
def __init__(self, master, img_file_name, folder=None, extension="png"):
super().__init__(master)
image = get_img(img_file_name, folder, extension) # functions.py 참고
photo = ImageTk.PhotoImage(image)
width, height = image.size
self.title("img_file_name")
self.geometry(f"{width}x{height}+680+270")
self.resizable(0, 0)
label = Label(self, image=photo)
label.image = photo
label.pack()
새로운 window 창을 생성해 이미지 정보를 제공합니다.
image.size를 이용해 이미지의 가로, 세로 정보를 받고 geometry를 통해 자동으로 창의 크기가 바뀌도록 설계하였습니다.
마우스 오버
class InfoBalloon(Balloon):
def __init__(self, master):
super().__init__(master, initwait=60)
for i in self.subwidgets_all():
i.config(bg="#ffffff")
self.message.config(wraplength=100)
마우스를 지정한 위젯에 올렸을 때 정보창을 제공합니다.
표지
class IntroPage(MainFrame):
def __init__(self, master):
super().__init__(master)
self.config(bg="#ffffff")
Label(self, bg=self._bg, height=4).pack(fill="x") # 상단 여백을 위해 추가
# 로고 설정
image = get_img("logo")
photo_logo = ImageTk.PhotoImage(image)
label_logo = Label(self, image=photo_logo, bg=self._bg)
label_logo.image = photo_logo
label_logo.pack()
# 시작 버튼
image = get_img("button-start", "design")
photo_button = ImageTk.PhotoImage(image)
button = Button(
self,
image=photo_button,
relief="flat",
bg=self._bg,
command=lambda: master.refresh_frame(StartPage),
)
button.image = photo_button
button.pack(ipadx=3, ipady=2)
코드를 실행했을 때 보여질 첫 화면을 설정합니다.
선택 페이지
class StartPage(MainFrame):
def __init__(self, master):
super().__init__(master)
self.config(bg=self._bg)
combo_var = StringVar()
entry_1_var = DoubleVar()
entry_2_var = DoubleVar()
entry_3_var = DoubleVar()
해당 페이지에서 사용자로부터 입력 받을 내용을 선언합니다.
- 자료형에 따라 StringVar, IntVar, DoubleVar 등을 사용합니다.
f_title = Frame(self, bg=self._bg)
f_title.pack(fill="x", pady=10)
...
# 제목 옆 이미지
image = get_img("icon", extension="ico").resize((25, 25), Image.ANTIALIAS)
photo_icon = ImageTk.PhotoImage(image)
icon = Label(f_title, image=photo_icon, bg=self._bg)
icon.image = photo_icon
icon.pack(side="left")
# 제목 텍스트
Label(f_title, text="Basic Info").pack()
상단 아이콘 및 제목을 설정합니다.
f_left = Frame(self, bg=self._bg)
f_right = Frame(self, bg=self._bg)
f_btn = Frame(self, bg=self._bg)
f_btn.pack(side="bottom")
f_left.pack(side="left")
f_right.pack(side="right")
"좌측: 텍스트, 우측: 입력 창, 하단: 버튼" 형태로 제작하였으며, 제작하는 디자인에 따라 다양하게 변형할 수 있습니다.
Label(f_left, bg=self._bg, text="OPTION").pack()
Label(f_left, bg=self._bg, text="ENTRY_1").pack()
...
좌측 텍스트를 설정합니다.
# Combobox
combo = ttk.Combobox(
f_right, textvariable=combo_var, state="readonly"
) # readonly를 통해 사용자가 정보를 수정할 수 없도록 합니다.
combo["values"] = ["OPTION_A", "OPTION_B", "OPTION_C"]
combo.current(0)
combo.pack()
# Entry
entry_1 = Entry(f_right, textvariable=entry_1_var)
entry_1.pack()
...
entry_1.delete(0, END) # 내용 초기화
...
- Combobox: 사용자 정해진 선택지 내에서 선택할 수 있도록 합니다.
- Entry: 사용자가 자유롭게 내용을 입력할 수 있도록 합니다.
- 해당 값의 자료형에 따라 예외처리를 할 필요가 있습니다.
- tkinter.messagebox.showwarning('Title', 'message')를 사용하면 경고창을 화면에 띄울 수 있습니다.
# File Save 기능
image = get_img("save-mint", "icon").resize((25, 25), Image.ANTIALIAS)
photo_save = ImageTk.PhotoImage(image)
width_save, height_save = image.size
btn_save = Button(
f_btn,
bg=self._bg,
image=photo_save,
relief="flat",
width=width_save,
height=height_save,
activebackground=self._bg,
command=lambda: save_new_csv(entry_1, entry_2, entry_3),
)
btn_save.image = photo_save
btn_save.pack()
# File Open 기능
image = get_img("file-text-mint", "icon").resize((25, 25), Image.ANTIALIAS)
photo_file = ImageTk.PhotoImage(image)
width_file, height_file = image.size
btn_open = Button(
f_btn,
bg=self._bg,
image=photo_file,
relief="flat",
width=width_file,
height=height_file,
activebackground=self._bg,
command=lambda: open_csv(entry_1, entry_2, entry_3),
)
btn_open.image = photo_file
btn_open.pack()
Entry에 입력한 데이터를 저장하거나 불러오는 기능을 제공합니다.
# Menu
self._menu = Menu(master)
# Menu 1
self._menu_data = Menu(self._menu, tearoff=0)
self._menu_data.add_command(
label="OPEN", command=lambda: open_csv(entry_1, entry_2, entry_3)
)
self._menu_data.add_command(
label="SAVE", command=lambda: save_new_csv(entry_1, entry_2, entry_3)
)
self._menu_data.add_separator()
self._menu_data.add_command(label="INFO", command=lambda: InfoPage(master))
self._menu.add_cascade(label="DATA", menu=self._menu_data)
# Menu 2
self._menu_sample = Menu(self._menu, tearoff=0)
self._menu_sample.add_command(label="INFO", command=lambda: InfoPage(master))
self._menu.add_cascade(label="SAMPLE", menu=self._menu_sample)
master.config(menu=self._menu)
상단의 menu를 사용할 수 있습니다.
def disable_menu_file(self):
self._menu_data.entryconfig("OPEN", state="disable")
self._menu_data.entryconfig("SAVE", state="disable")
self._menu_data.entryconfig("INFO", state="disable")
더 이상 사용하지 않는 메뉴를 비활성화합니다.
- 특정 페이지에서만 사용가능한 기능을 다른 페이지에서 사용할 수 없도록 합니다.
def switch_to_ResultPage(self, master):
# Get data from ComboBox and Entry
...
self.disable_menu_file()
master.refresh_frame(ResultPage, texts, imgs, urls)
사용자가 입력한 정보를 가져올 수 있도록 코드를 설계합니다.
disable_menu_file을 통해 일부 메뉴를 비활성화합니다.
refresh_frame을 통해 다음 페이지로 넘어갈 수 있도록 합니다.
결과 페이지
class ResultPage(MainFrame):
def __init__(self, master, texts, imgs, urls):
super().__init__(master)
self.config(bg=self._green)
notebook = ttk.Notebook(self, width=300, height=370)
notebook.pack(expand=True)
f_1 = Frame(self, bg="#ffffff")
f_2 = Label(self, bg="#ffffff")
f_3 = Frame(self, bg="#ffffff")
notebook: 책갈피 형태로 frame을 선택하여 볼 수 있습니다.
notebook.add(f_1, text="sample_1")
Label(f_1, bg="#ffffff", height=2).pack(fill="x") # 상단 여백을 위해 입력
Button(
f_1, text=texts[0], command=lambda: show_ImgPage(ImgPage, master, imgs[0])
).pack()
...
# 하단 이미지와 텍스트 정보
f_ref_1 = Frame(f_1, bg="#ffffff")
f_ref_1.pack(side="bottom")
image = get_img("image-mint", "icon")
photo_img = ImageTk.PhotoImage(image)
f_image = Label(f_ref_1, image=photo_img, bg="#ffffff")
f_image.image = photo_img
f_image.pack(side="left")
Label(f_ref_1, text="* click to \nshow images.", bg="#ffffff").pack(side="left")
텍스트를 클릭하면 지정된 이미지 파일을 보여줍니다.
notebook.add(f_2, text="sample_2")
Label(f_2, bg="#ffffff", height=2).pack(fill="x")
Button(f_2, text=texts[0], command=lambda: open_url(urls[0])).pack()
...
# 하단 이미지와 텍스트 정보
f_ref_tip = Frame(f_2, bg=self._bg)
f_ref_tip.pack(side="bottom")
image = get_img("link-mint", "icon")
photo_link = ImageTk.PhotoImage(image)
f_image = Label(f_ref_tip, image=photo_link, bg="#ffffff")
f_image.image = photo_link
f_image.pack(side="left")
Label(f_ref_tip, text="* click to \nopen urls.", bg="#ffffff").pack(side="left")
텍스트를 클릭하면 지정된 url을 기본 브라우저를 통해 보여줍니다.
notebook.add(f_3, text="sample_3")
# y-Scrollbar
scrollbar = Scrollbar(f_3, orient="vertical")
scrollbar.pack(side="right", fill="y")
f_text = Text(f_3, yscrollcommand=scrollbar.set)
f_text.pack(side="left", fill="both")
scrollbar.config(command=f_text.yview)
# Text 초기화 & 입력
f_text.delete(1.0, END)
f_text.insert(1.0, self._trend_text)
f_text.configure(state="disabled") # 시용자가 내용을 수정할 수 없도록 합니다.
Naver DataLab 크롤링을 통해 최근 패션/의류 인기 검색어를 크롤링한 결과를 보여줍니다.
y축으로 스크롤할 수 있는 scrollbar를 제공합니다.
추가 입력 페이지
class OptionalPage(MainFrame):
def __init__(self, master, texts, imgs, urls):
super().__init__(master)
self.config(bg=self._bg)
combo_1 = StringVar()
combo_2 = StringVar()
combo_3 = StringVar()
해당 페이지에서 사용자로부터 받을 내용을 선언합니다.
# Question-Mark 이미지 불러오기
image = get_img("help-circle-black", "icon").resize((12, 12), Image.ANTIALIAS)
photo_help = ImageTk.PhotoImage(image)
tip_mark = Label(f_color, image=photo_help, bg=self._bg)
tip_mark.image = photo_help
tip_mark.pack(side="right")
...
# 마우스 오버 정보 제공
tip_box = InfoBalloon(master)
tip_box.bind_widget(tip_mark, balloonmsg="Here we have Info")
이미지(Label) 위에 마우스를 올리면 텍스트가 담긴 박스를 보여줍니다.
- 클릭하면 작동하는 Button과 달리, 마우스 오버로 작동합니다.
추가 결과 페이지
notebook.add(f_color, text="sample_2")
Label(f_color, text="Colors").pack()
Label(f_color, bg=colors[0]).pack()
...
입력 받는 색상을 확인할 수 있도록 합니다.
- 사용자가 색상을 입력하지 않을 경우, 배경색과 같은 색으로 설정합니다.
btn_quit = Button(f_btn, ... command=master.quit)
quit을 이용해 별도의 함수를 설정하지 않고 프로그램을 종료할 수 있습니다.
실행
if __name__ == "__main__":
app = App()
app.mainloop()
위에서 제작한 코드가 화면에 실행되도록 합니다.
functions.py
from tkinter import filedialog
from tkinter.constants import END
import tkinter.messagebox as msgbox
import os
import webbrowser
from PIL import Image
if __name__ == "__main__":
from data import *
else:
from palette.data import *
이미지
def get_path(path):
return os.path.join(os.getcwd(), path)
입력한 path의 절대경로를 반환합니다.
def get_img(img_file_name, folder=None, extension="png"):
if folder is None:
# 폴더가 없을 때
img_path = get_path(f"img\{img_file_name}.{extension}")
elif folder:
# 폴더가 있을 때
img_path = get_path(f"img\{folder}\{img_file_name}.{extension}")
try:
image = Image.open(img_path)
except:
# 오류 발생
img_path = get_path("img\error.png")
image = Image.open(img_path)
return image
"파일 이름 / 파일 경로(선택) / 확장자"를 입력 받아 이미지를 반환합니다.
오류 발생 시, 지정한 이미지(error.png)를 반환합니다.
def show_ImgPage(class_name, master, *parameters):
# class_name을 ImgPage로 설정
return class_name(master, *parameters)
class_name을 ImgPage로 설정하여 이미지를 보여줍니다.
- 이미지 표시를 참고해주세요.
URL 열기
def open_url(url):
try:
webbrowser.open(url)
except webbrowser.Error as exp:
msgbox.showerror("오류", f"브라우저를 정상적으로 열 수 없습니다.\n{exp}")
except Exception:
return
기본 브라우저를 통해 지정한 url을 보여줍니다.
오류 발생 시, 지정한 메시지를 보여줍니다.
data.py
sample = {
"key": "value"
}
데이터를 저장해 functions에서 사용할 수 있습니다.