[Programming]/[Language]

[Python] 나도코딩_활용편2 / Finished 22.09.17

김파고띠 2022. 8. 18. 21:09

나도 코딩 [활용편2]

공부 & 정리


| 공부 자료

gui_basic.zip
0.01MB
gui_project.zip
0.02MB
clean_code.zip
0.01MB


[1] 소개

 

활용편 1과 동일


[2] 활용편 2 소개

GUI(Graphical User Interface) 프로그래밍 :
 눈으로 볼 수 있는 프로그래밍
    
여러 GUI 라이브러리 중에서 tkinter를 공부할 것

프로젝트 소개 :
 여러 이미지를 합치는 툴을 만들 것

[3] 기본 프레임

 

파이썬을 설치할 때 tkinter 모듈이 포함이 되어있기 때문에 따로 설치가 필요하지 않다

BUT

구름IDE에서는 템플릿 지정을 tkinter 프로젝트로 설정을 한 후에 해야한다.

 

| CODE

 

from tkinter import *

root = Tk()
root.title("Hero GUI")
root.geometry("640x480") # 가로 * 세로
# root.geometry("640x480+100+300") # 가로 * 세로 + x좌표 + y좌표
root.resizable(False, False) # x(너비), y(높이) 값 변경 불가 ( 창 크기 변경 불가 )

root.mainloop()

[4] 버튼

 

| CODE

from tkinter import *

root = Tk()
root.title("Hero GUI")

btn1 = Button(root, text="버튼1")
btn1.pack()

btn2 = Button(root, padx=10, pady=5, text="버튼2")
btn2.pack()

btn3 = Button(root, padx=5, pady=10, text="버튼3")
btn3.pack()

btn4 = Button(root, width=10, height=3, text="버튼4")
btn4.pack()

# pad는 여백을 지정하는 느낌이기 때문에 글자에 맞춰서 변하지만
# width, height는 값이 고정되기 때문에 글자가 늘어나도 박스 크기 동일 (글자가 잘리더라도)

btn5 = Button(root, fg="red", bg="yellow", text="버튼5")
btn5.pack()

photo = PhotoImage(file="/workspace/NC_Utl_2_GUI2/gui_basic/image.png")
btn6 = Button(root, image=photo)
btn6.pack()

def btncmd():
    print("버튼이 클릭되었습니다")
    
btn7 = Button(root, text="버튼이 클릭되었습니다", command = btncmd )
btn7.pack()

root.mainloop()

[5] 레이블

 

| CODE

# 레이블은 글자나 이미지를 보여주는 것이고 실제 어떠한 행동을 하지는 않음

from tkinter import *

root = Tk()
root.title("Hero GUI")

label1 = Label(root, text="안녕하세요")
label1.pack()

photo = PhotoImage(file="/workspace/NC_Utl_2_GUI2/gui_basic/image.png")
label2 = Label(root, image=photo)
label2.pack()

def change():
    label1.config(text="또 만나요")
    
    global photo2
    photo2 = PhotoImage(file="/workspace/NC_Utl_2_GUI2/gui_basic/image2.png")
    label2.config(image=photo2)

btn = Button(root, text="클릭", command=change)
btn.pack()

root.mainloop()

# 함수 내에서 레이블의 이미지 값등을 바꾸기 위해서는 그 값은 전역 변수로 선언해야함

[6] 텍스트 & 엔트리

 

| CODE

from tkinter import *

root = Tk()
root.title("Hero GUI")
root.geometry = ("640*480") # 가로 * 세로

txt = Text(root, width=30, height=5)
txt.pack()
txt.insert(END, "글자를 입력하세요")

e = Entry(root, width=30)
e.pack()
e.insert(0, "한 줄만 입력하세요")

def btncmd():
    
    # 내용 출력
    print(txt.get("1.0", END)) # 1 : 첫번째 라인, 0 : 0번째 column 위치
    print(e.get())
    
    # 내용 삭제
    txt.delete("1.0", END)
    e.delete(0, END)
    
btn = Button(root, text="클릭", command=btncmd)
btn.pack()

root.mainloop()


# 여러줄이 필요한 것은 txt를 쓴다

# e는 엔터를 입력할 수 없다
# e는 한줄로 입력을 받을때, such as 아이디, 비밀번호등

[7] 리스트 박스

 

| CODE

from tkinter import *

root = Tk()
root.title("Hero GUI")
root.geometry = ("640*480") # 가로 * 세로

listbox = Listbox(root, selectmode="extended", height=0)
# height가 0일때는 리스트에 있는 값을 다 보여주고, 다른 수 (such as 3)일 때는 그 수만큼 보여주고 스크롤이 됌
listbox.insert(0,"Apple")
listbox.insert(1,"Strawberry")
listbox.insert(2,"Banana")
listbox.insert(END,"Watermelon")
listbox.insert(END,"Grape")
listbox.pack()


def btncmd():
    
    # 삭제
    # listbox.delete(0) # 맨 앞 항목을 삭제, END는 맨 뒤 항복을 삭제
    
    # 갯수 확인
    # print("리스트에는", listbox.size(), "개가 있어요")
    
    # 항목 확인 ( 시작 idx, 끝 idx )
    # print("1번째부터 3번째까지의 항목 : ", listbox.get(0,2))
    
    # 선택된 항목 확인
    # print("선택된 항목 : ", listbox.curselection())

btn = Button(root, text="클릭", command=btncmd)
btn.pack()

root.mainloop()

[8] 체크 버튼

 

| CODE

from tkinter import *

root = Tk()
root.title("Hero GUI")
root.geometry = ("640*480")

chkvar = IntVar() # chkvar에 int형으로 값을 저장한다
chkbox = Checkbutton(root, text="오늘 하루 보지 않기", variable=chkvar)
# chkbox.select() # 자동 선택 처리
# chkbox.deselect() # 선택 해제 처리
chkbox.pack()

chkvar2 = Intvar()
chkbox2 = Checkbutton(root, text="일주일동안 보지 않기", variable=chkvar2)
chkbox2.pack()

def btncmd():
    print(chkvar.get()) # 0 : 체크 해제, 1 : 체크
    print(chkvar2.get())
    
btn = Button(root, text="클릭", command=btncmd)
btn.pack()

root.mainloop()

[9] 라디오 버튼

 

| CODE

from tkinter import *

root = Tk()
root.title("Hero GUI")
root.geometry = ("640*480") # 가로 * 세로

Label(root, text="메뉴를 선택하세요").pack()

burger_var = IntVar() # 여기에 int형으로 값을 저장한다
btn_burger1 = Radiobutton(root, text="햄버거", value=1, variable=burger_var)
btn_burger2 = Radiobutton(root, text="치즈버거", value=2, variable=burger_var)
btn_burger3 = Radiobutton(root, text="치킨버거", value=3, variable=burger_var)

btn_burger1.pack()
btn_burger2.pack()
btn_burger3.pack()

Label(root, text="음료를 선택하세요").pack() # 기존에 pack()을 별도로 쓰는 것과 동일한 실행

drink_var = StringVar() # 여기에 String형으로 값을 저장한다
btn_drink1 = Radiobutton(root, text="콜라", value="콜라", variable=drink_var)
btn_drink1.select() # 기본값 선택
btn_drink2 = Radiobutton(root, text="사이다", value="사이다", variable=drink_var)

btn_drink1.pack()
btn_drink2.pack()

def btncmd():
    print(burger_var.get()) # 햄버거 중 선택된 라디오 항목의 값(Value)를 출력
    print(drink_var.get()) # 음료 중 선택된 라디오 항목의 값(Value)를 출력
    
btn = Button(root, text="클릭", command=btncmd)
btn.pack()

root.mainloop()

[10] 콤보 박스

 

import tkinter.ttk as ttk
from tkinter import *

root = Tk()
root.title("Hero GUI")
root.geometry = ("640*480") # 가로 * 세로

values = [ str(i) + "일" for i in range(1,32)] # 1~31까지의 숫자
combobox = ttk.Combobox(root, height=5, values=values) # height 값은 목록 보여주는 길이 차이
combobox.pack()
combobox.set("카드 결제일") # 최초 목록 제목 설정 (+ 버튼 클릭을 통한 값 설정도 가능)

readonly_combobox = ttk.Combobox(root, height=10, values=values, state="readonly") # state="readonly"는 박스에 텍스트 입력이 안되는 것
readonly_combobox.current(0) #0번째 인덱스 값 선택
readonly_combobox.pack()

def btncmd():
    print(combobox.get()) # 선택된 값 출력
    print(readonly_combobox.get())

btn = Button(root, text="클릭", command=btncmd)
btn.pack()

root.mainloop()

[11] 프로그레스 바

 

| CODE

import time
import tkinter.ttk as ttk
from tkinter import *

root = Tk()
root.title("Hero GUI")
root.geometry = ("640*480") # 가로 * 세로

# progressbar = ttk.Progressbar(root, maximum=100, mode="indeterminate")
# progressbar = ttk.Progressbar(root, maximum=100, mode="determinate")

# progressbar.start(5) # 5ms마다 움직임
# progressbar.pack()

# def btncmd():
#     progressbar.stop() # 작동 중지

# btn = Button(root, text="중지", command=btncmd)
# btn.pack()

p_var2 = DoubleVar()
progressbar2 = ttk.Progressbar(root, maximum=100, length=150, variable=p_var2)
progressbar2.pack()

def btncmd():
    for i in range(1,101) : # 1~2100
        time.sleep(0.01) # 0.01초 대기
        
        p_var2.set(i) # progressbar의 값 설정
        progressbar2.update() # ui 업데이트
        print(p_var2.get())
    
btn = Button(root, text="시작", command=btncmd)
btn.pack()

root.mainloop()

[12] 메뉴

 

| CODE

 

from tkinter import *

root = Tk()
root.title("Hero GUI")
root.geometry("640x480")

def create_new_file():
    print("새 파일이 만들어졌습니다")
    
menu = Menu(root)

# File 메뉴
menu_file = Menu(menu, tearoff=0)
menu_file.add_command(label="New File", command = create_new_file)
menu_file.add_command(label="New Window")
menu_file.add_separator()
menu_file.add_command(label="Open File")
menu_file.add_separator()
menu_file.add_command(label="Save All", state="disable") #비활성화
menu_file.add_separator()
menu_file.add_command(label="Exit", command=root.quit)
menu.add_cascade(label="File", menu=menu_file)

# Edit 메뉴 (빈값)
menu.add_cascade(label="Edit")

# Language 메뉴 추가 (radio 버튼을 통해서 택1)
menu_lang = Menu(menu, tearoff=0)
menu_lang.add_radiobutton(label="C++")
menu_lang.add_radiobutton(label="Python")
menu_lang.add_radiobutton(label="Java")
menu.add_cascade(label="Language", menu=menu_lang)

# View 메뉴
menu_view = Menu(menu, tearoff=0)
menu_view.add_checkbutton(label="Show Minimap")
menu.add_cascade(label="View", menu=menu_view)

root.config(menu=menu)
root.mainloop()

[13] 메시지 박스

 

| CODE

import tkinter.messagebox as msgbox
from tkinter import *

root = Tk()
root.title("Hero GUI")
root.geometry("640x480")

def info():
    msgbox.showinfo("알림", "정상적으로 예매 완료되었습니다")

def warn():
    msgbox.showwarning("경고", "해당 좌석은 매진되었습니다")

def error():
    msgbox.showerror("에러", "결제 오류가 발생했습니다")
    
def okcancel():
    msgbox.askokcancel("확인/취소", "해당 좌석은 유아동반석입니다. 예매하겠습니까?")
    
def retrycancel():
    msgbox.askretrycancel("확인/취소", "일시적인 오류입니다. 다시 시도하겠습니까?")

def yesno():
    msgbox.askyesno("확인/취소", "해당 좌석은 역방향입니다. 예매하겠습니까?")
    
def yesnocancel():
    response = msgbox.askyesnocancel(title=None, message="예매 내역이 저장되지 않았습니다 \n 저장 후 프로그램을 종료하겠습니까?")
    # 네 : 저장 후 종료
    # 아니오 : 저장하지 않고 종료
    # 취소 : 프로그램 종료 취소 (현재 화면에서 계속 작업)
    print("응답:", response) # True, False, None -> 예 1, 아니오 0, 그 외
    if response ==1: # 네
        print("예")
    elif response ==0: # 아니오
        print("아니오")
    else :
        print("취소")
        
    # True 값의 경우(Ex : 네, 재시도, 확인등)는 값이 1,
    # False 값의 경우(Ex : 아니오, 취소등)는 값이 0,
    # 그외의 값(Ex : 네/아니오/취소에서 취소 같은)은 else

    
Button(root, command=info, text="알림").pack()
Button(root, command=warn, text="경고").pack()
Button(root, command=error, text="에러").pack()

Button(root, command=okcancel, text="확인 취소").pack()
Button(root, command=retrycancel, text="재시도 취소").pack()
Button(root, command=yesno, text="예 아니오").pack()
Button(root, command=yesnocancel, text="예 아니오 취소").pack()

root.mainloop()

[14] 프레임

 

| CODE

from tkinter import *

root = Tk()
root.title("Hero GUI")
root.geometry("640x480")

Label(root, text="메뉴를 선택하세요").pack(side="top")

Button(root, text="주문하기").pack(side="bottom")

# 버거 프레임
frame_burger = Frame(root, relief="solid", bd=1)
frame_burger.pack(side="left", fill="both", expand=True)

Button(frame_burger, text="햄버거").pack()
Button(frame_burger, text="치즈버거").pack()
Button(frame_burger, text="치킨버거").pack()

# 음료 프레임
frame_drink = LabelFrame(root, text="음료")
frame_drink.pack(side="right", fill="both", expand=True)

Button(frame_drink, text="콜라").pack()
Button(frame_drink, text="사이다").pack()

root.mainloop()

[15] 스크롤 바

 

| CODE

from tkinter import *

root = Tk()
root.title("Hero GUI")
root.geometry("640x480")

# 스크롤바와 스크롤바 대상이 되는 위젯을 하나의 프레임에 넣는게 관리가 편함
frame = Frame(root)
frame.pack()

scrollbar = Scrollbar(frame)
scrollbar.pack(side="right", fill="y")

# set이 없으면 스크롤을 내려도 다시 올라움
listbox = Listbox(frame, selectmode="extended", height=10, yscrollcommand = scrollbar.set)
for i in range(1,32): # 1일 ~31일
    listbox.insert(END, str(i) + "일") # 1일, 2일, ...
listbox.pack(side="left")

# 서로 맵핑을 해줌으로써 스크롤바 움직임에 맞추어서 리스트도 움직임
scrollbar.config(command=listbox.yview)

root.mainloop()

[16] 그리드 기본 &

[17] 그리드 심화

 

| Explan

 

Greed : 격자

좌표값을 가지고 있는 격자 모양

| CODE

from tkinter import *

root = Tk()
root.title("Hero GUI")
root.geometry("640x480")

# btn1 = Button(root, text="버튼1")
# btn2 = Button(root, text="버튼2")

# pack이 쌓는 느낌이라면 greed는 지정된 좌표에 넣는 느낌이다

# btn1.pack()
# btn2.pack()

# btn1.pack(side="left")
# btn2.pack(side="right")

# btn1.grid(row=0, column=0)
# btn2.grid(row=1, column=1)

# sticky는 N(북), E(동), W(서), S(남) 내가 원하는 방향으로 붙여서 칸을 붙이는 것
# padx=..., pady=...,
	# Button에 pad를 넣으면 글자 기준으로 칸에 공간을 두는 것
    # grid에 pad를 넣으면 그리드 간에 공간을 두는 것
# width, height
    # padx, pady는 글자 기준으로 공간을 두는 것이기 때문에 크기가 살짝 다르다
    # BUT width, height을 쓰면 값이 고정

# 맨 윗줄
btn_f16 = Button(root, text="f16", width=5, height=2)
btn_f17 = Button(root, text="f17", width=5, height=2)
btn_f18 = Button(root, text="f18", width=5, height=2)
btn_f19 = Button(root, text="f19", width=5, height=2)

btn_f16.grid(row=0, column=0, sticky=N+E+W+S, padx=3, pady=3)
btn_f17.grid(row=0, column=1, sticky=N+E+W+S, padx=3, pady=3)
btn_f18.grid(row=0, column=2, sticky=N+E+W+S, padx=3, pady=3)
btn_f19.grid(row=0, column=3, sticky=N+E+W+S, padx=3, pady=3)

# clear 줄
btn_clear = Button(root, text="clear", width=5, height=2)
btn_equal = Button(root, text="=", width=5, height=2)
btn_div = Button(root, text="/", width=5, height=2)
btn_mul = Button(root, text="*", width=5, height=2)

btn_clear.grid(row=1, column=0, sticky=N+E+W+S, padx=3, pady=3)
btn_equal.grid(row=1, column=1, sticky=N+E+W+S, padx=3, pady=3)
btn_div.grid(row=1, column=2, sticky=N+E+W+S, padx=3, pady=3)
btn_mul.grid(row=1, column=3, sticky=N+E+W+S, padx=3, pady=3)

# 7 시작 줄
btn_7 = Button(root, text="7", width=5, height=2)
btn_8 = Button(root, text="8", width=5, height=2)
btn_9 = Button(root, text="9", width=5, height=2)
btn_sub = Button(root, text="-", width=5, height=2)

btn_7.grid(row=2, column=0, sticky=N+E+W+S, padx=3, pady=3)
btn_8.grid(row=2, column=1, sticky=N+E+W+S, padx=3, pady=3)
btn_9.grid(row=2, column=2, sticky=N+E+W+S, padx=3, pady=3)
btn_sub.grid(row=2, column=3, sticky=N+E+W+S, padx=3, pady=3)

# 4 시작 줄
btn_4 = Button(root, text="4", width=5, height=2)
btn_5 = Button(root, text="5", width=5, height=2)
btn_6 = Button(root, text="6", width=5, height=2)
btn_add = Button(root, text="+", width=5, height=2)

btn_4.grid(row=3, column=0, sticky=N+E+W+S, padx=3, pady=3)
btn_5.grid(row=3, column=1, sticky=N+E+W+S, padx=3, pady=3)
btn_6.grid(row=3, column=2, sticky=N+E+W+S, padx=3, pady=3)
btn_add.grid(row=3, column=3, sticky=N+E+W+S, padx=3, pady=3)

# 1 시작 줄
btn_1 = Button(root, text="1", width=5, height=2)
btn_2 = Button(root, text="2", width=5, height=2)
btn_3 = Button(root, text="3", width=5, height=2)
btn_enter = Button(root, text="enter", width=5, height=2) # 세로로 합쳐짐

btn_1.grid(row=4, column=0, sticky=N+E+W+S, padx=3, pady=3)
btn_2.grid(row=4, column=1, sticky=N+E+W+S, padx=3, pady=3)
btn_3.grid(row=4, column=2, sticky=N+E+W+S, padx=3, pady=3)
btn_enter.grid(row=4, column=3, rowspan=2, sticky=N+E+W+S, padx=3, pady=3) # 현재 위치로부터 아래쪽으로 몇줄을 더함

# 0 시작 줄
btn_0 = Button(root, text="0", width=5, height=2) # 가로로 합쳐짐
btn_point = Button(root, text=".", width=5, height=2)

btn_0.grid(row=5, column=0, columnspan=2, sticky=N+E+W+S, padx=3, pady=3) # 현재 위치로부터 오른쪽으로 몇칸 더함
btn_point.grid(row=5, column=2, sticky=N+E+W+S, padx=3, pady=3)

root.mainloop()

[18] QUIZ

'''
Quiz) tkinter를 이용한 메모장 프로그램 만들기

GUI 조건)
    	1. title : 제목 없음 - Windows 메모장
    	2. 메뉴 : 파일, 편집, 서식, 보기, 도움말
    	3. 실제 메뉴 구현 : 파일 메뉴 내에서 열기, 저장, 끝내기 3개만 처리
    	3-1. 열기 : mynote.txt 파일 내용 열어서 보여주기
    	3-2. 저장 : mynote.txt 파일에 현재 내용 저장하기
    	3-3. 끝내기 : 프로그램 종료
    	4. 프로그램 시작 시 본문은 비어 있는 상태
    	5. 하단 status 바는 필요 없음
    	6. 프로그램 크기, 위치는 자유롭게 하되, 크기 조절 가능해야 함
    	7. 본문 우측에 상하 스크롤바 넣기
'''

import os
from tkinter import *

root = Tk()
root.title("Windows 메모장")
root.geometry("640x480")
root.resizable(True, True)

# 열기, 저장 파일 이름
filename = "mynote.txt"

def open_file():
    if os.path.isfile(filename): # 파일 있으면 True, 없으면 False
        with open(filename, "r", encoding="utf8") as file :
            txt.delete("1.0", END) # 텍스트 위젯 본문 삭제
            txt.insert(END, file.read()) # 파일 내용을 본문에 입력

def save_file():
    with open(filename, "w", encoding="utf8") as file:
        file.write(txt.get("1.0", END)) # 모든 내용을 가져와서 저장

menu = Menu(root)

# 파일 메뉴
menu_file = Menu(menu, tearoff=0)
menu_file.add_command(label="열기", command = open_file )
menu_file.add_command(label="저장", command = save_file )
menu_file.add_command(label="끝내기", command = root.quit )
menu.add_cascade(label="파일", menu=menu_file)

# 편집 메뉴
menu_edit = Menu(menu, tearoff=0)
menu.add_cascade(label="편집", menu=menu_edit)

# 서식 메뉴
menu_form = Menu(menu, tearoff=0)
menu.add_cascade(label="서식", menu=menu_form)

# 보기 메뉴
menu_view = Menu(menu, tearoff=0)
menu.add_cascade(label="보기", menu=menu_view)

# 도움말 메뉴
menu_help = Menu(menu, tearoff=0)
menu.add_cascade(label="도움말", menu=menu_help)

# 스크롤바
scrollbar = Scrollbar(root)
scrollbar.pack(side="right", fill="y")

# 본문 영역
txt = Text(root, yscrollcommand = scrollbar.set)
txt.pack(side="left", fill="both", expand=True)
scrollbar.config(command=txt.yview)

root.config(menu=menu)
root.mainloop()

[19] 프로젝트 [이미지 합치기 프로그램 만들기]

 

| Explanation

[Project]
여러 이미지를 합치는 프로그램을 만드시오

[사용자 시나리오]
1. 사용자는 합치려는 이미지를 1개 이상 선택한다.
2. 합쳐진 이미지가 저장될 경로를 지정한다.
3. 가로넓이, 간격, 포맷 옵션을 지정한다.
4. 시작 버튼을 통해 이미지를 합친다.
5. 닫기 버튼을 통해 프로그램을 종료한다.

[기능 명세]
1. 파일 추가 : 리스트 박스에 파일 추가
2. 선택삭제 : 리스트 박스에서 선택된 항목 삭제
3. 찾아보기 : 저장 폴더를 선택하면 텍스트 위젯에 입력
4. 가로넓이 : 이미지 넓이 지정 ( 원본유지, 1024, 800, 640 )
5. 간격 : 이미지 간의 간격 지정 ( 없음, 좁게, 보통, 넓게 )
6. 포맷 : 저장 이미지 포맷 지정 ( PNG, JPG, BMP )
7. 시작 : 이미지 합치기 작업 실행
8. 진행상황 : 현재 진행중인 파일 순서에 맞게 반영
9. 닫기 : 프로그램 종료

| Example

구현된 모습


[20] 레이아웃 전반전 &

[21] 레이아웃 후반전 &

[22] 레이아웃 연장전

 

| CODE

import tkinter.ttk as ttk
from tkinter import *

root = Tk()
root.title("Hero GUI")

# 파일 프레임 ( 파일추가, 선택 삭제 )

file_frame = Frame(root)
file_frame.pack(fill="x", padx=5, pady=5) # 간격 띄우기

btn_add_file = Button(file_frame, padx=5, pady=5, width=12, text="파일추가")
btn_add_file.pack(side="left")

btn_del_file = Button(file_frame, padx=5, pady=5, width=12,text="선택삭제")
btn_del_file.pack(side="right")

# 리스트 프레임
list_frame = Frame(root)
list_frame.pack(fill="both", padx=5, pady=5)

scrollbar = Scrollbar(list_frame)
scrollbar.pack(side="right", fill="y")

list_file = Listbox(list_frame, selectmode="extended", height=15, yscrollcommand=scrollbar.set)
list_file.pack(side="left", fill="both", expand=True)
scrollbar.config(command=list_file.yview)

# 저장경로 프레임
path_frame = LabelFrame(root, text="저장경로")
path_frame.pack(fill="x", padx=5, pady=5, ipady=5)

txt_dest_path = Entry(path_frame)
txt_dest_path.pack(side="left", fill="x", expand=True, padx=5, pady=5, ipady=4) # 높이 변경

btn_dest_path = Button(path_frame, text="찾아보기", width=10)
btn_dest_path.pack(side="right", padx=5, pady=5)

# 옵션 프레임
frame_option = LabelFrame(root, text="옵션")
frame_option.pack(padx=5, pady=5, ipady=5)

# 1. 가로 넓이 옵션
# 가로 넓이 레이블
lbl_width = Label(frame_option, text="가로넓이", width=8)
lbl_width.pack(side="left", padx=5, pady=5)
# 가로 넓이 콤보
opt_width = ["원본유지", "1024", "800", "640"]
cmb_width = ttk.Combobox(frame_option, state="readonly", values=opt_width, width=10)
cmb_width.current(0)
cmb_width.pack(side="left", padx=5, pady=5)

# 2. 간격 옵션
# 간격 레이블
lbl_space = Label(frame_option, text="간격", width=8)
lbl_space.pack(side="left", padx=5, pady=5)
# 간격 콤보
opt_space = ["없음", "좁게", "보통", "넓게"]
cmb_space = ttk.Combobox(frame_option, state="readonly", values=opt_space, width=10)
cmb_space.current(0)
cmb_space.pack(side="left", padx=5, pady=5)

# 3. 포맷 옵션
# 포맷 레이블
lbl_format = Label(frame_option, text="포맷", width=8)
lbl_format.pack(side="left", padx=5, pady=5)
# 포맷 콤보
opt_format = ["PNG", "JPG", "BMP"]
cmb_format = ttk.Combobox(frame_option, state="readonly", values=opt_format, width=10)
cmb_format.current(0)
cmb_format.pack(side="left", padx=5, pady=5)

# 진행상황 Progress Bar
frame_progress = LabelFrame(root, text="진행상황")
frame_progress.pack(fill="x", padx=5, pady=5, ipady=5)

p_var = DoubleVar()
progress_bar = ttk.Progressbar(frame_progress, maximum=100, variable=p_var)
progress_bar.pack(fill="x", padx=5, pady=5)

# 실행 프레임
frame_run = Frame(root)
frame_run.pack(fill="x", padx=5, pady=5)

btn_close = Button(frame_run, padx=5, pady=5, text="닫기", width=10, command=root.quit)
btn_close.pack(side="right", padx=5, pady=5)

btn_start = Button(frame_run, padx=5, pady=5, text="시작", width=10)
btn_start.pack(side="right", padx=5, pady=5)


root.resizable(False, False) # x(너비), y(높이) 값 변경 불가 ( 창 크기 변경 불가 )
root.mainloop()

[23] 파일 추가 & 선택 삭제

 

| CODE

import tkinter.ttk as ttk
from tkinter import * # _all_
from tkinter import filedialog # 서브 모듈이기 때문에 별도로 improt

root = Tk()
root.title("Hero GUI")

# 파일 추가
def add_file():
    files = filedialog.askopenfilenames(title="이미지 파일을 선택하세요",\
            filetypes=(("PNG 파일", "*.png"), ("모든 파일", "*.*"),), \
            initialdir=r"C:/workspace/NC_Utl_2_GUI@/gui_basic") # 최초 위치 지정이 작동을 하지 않음. WHY?
    		# 최초에 사용자가 지정한 경로를 보여줌
    		# r은 에스케이프 문자를 무시하는, raw string
    for file in files:
        list_file.insert(END, file)

# 선택 삭제
def del_file():
    print(list_file.curselection())
    # 인덱스 뒤에 것부터 지워야 인덱스에 변화가 안 생김
    for index in reversed(list_file.curselection()):
        list_file.delete(index)
    
# 파일 프레임 ( 파일추가, 선택 삭제 )

file_frame = Frame(root)
file_frame.pack(fill="x", padx=5, pady=5) # 간격 띄우기

btn_add_file = Button(file_frame, padx=5, pady=5, width=12, text="파일추가", command=add_file)
btn_add_file.pack(side="left")

btn_del_file = Button(file_frame, padx=5, pady=5, width=12,text="선택삭제", command=del_file)
btn_del_file.pack(side="right")

# 리스트 프레임
list_frame = Frame(root)
list_frame.pack(fill="both", padx=5, pady=5)

scrollbar = Scrollbar(list_frame)
scrollbar.pack(side="right", fill="y")

list_file = Listbox(list_frame, selectmode="extended", height=15, yscrollcommand=scrollbar.set)
list_file.pack(side="left", fill="both", expand=True)
scrollbar.config(command=list_file.yview)

# 저장경로 프레임
path_frame = LabelFrame(root, text="저장경로")
path_frame.pack(fill="x", padx=5, pady=5, ipady=5)

txt_dest_path = Entry(path_frame)
txt_dest_path.pack(side="left", fill="x", expand=True, padx=5, pady=5, ipady=4) # 높이 변경

btn_dest_path = Button(path_frame, text="찾아보기", width=10)
btn_dest_path.pack(side="right", padx=5, pady=5)

# 옵션 프레임
frame_option = LabelFrame(root, text="옵션")
frame_option.pack(padx=5, pady=5, ipady=5)

# 1. 가로 넓이 옵션
# 가로 넓이 레이블
lbl_width = Label(frame_option, text="가로넓이", width=8)
lbl_width.pack(side="left", padx=5, pady=5)
# 가로 넓이 콤보
opt_width = ["원본유지", "1024", "800", "640"]
cmb_width = ttk.Combobox(frame_option, state="readonly", values=opt_width, width=10)
cmb_width.current(0)
cmb_width.pack(side="left", padx=5, pady=5)

# 2. 간격 옵션
# 간격 레이블
lbl_space = Label(frame_option, text="간격", width=8)
lbl_space.pack(side="left", padx=5, pady=5)
# 간격 콤보
opt_space = ["없음", "좁게", "보통", "넓게"]
cmb_space = ttk.Combobox(frame_option, state="readonly", values=opt_space, width=10)
cmb_space.current(0)
cmb_space.pack(side="left", padx=5, pady=5)

# 3. 포맷 옵션
# 포맷 레이블
lbl_format = Label(frame_option, text="포맷", width=8)
lbl_format.pack(side="left", padx=5, pady=5)
# 포맷 콤보
opt_format = ["PNG", "JPG", "BMP"]
cmb_format = ttk.Combobox(frame_option, state="readonly", values=opt_format, width=10)
cmb_format.current(0)
cmb_format.pack(side="left", padx=5, pady=5)

# 진행상황 Progress Bar
frame_progress = LabelFrame(root, text="진행상황")
frame_progress.pack(fill="x", padx=5, pady=5, ipady=5)

p_var = DoubleVar()
progress_bar = ttk.Progressbar(frame_progress, maximum=100, variable=p_var)
progress_bar.pack(fill="x", padx=5, pady=5)

# 실행 프레임
frame_run = Frame(root)
frame_run.pack(fill="x", padx=5, pady=5)

btn_close = Button(frame_run, padx=5, pady=5, text="닫기", width=10, command=root.quit)
btn_close.pack(side="right", padx=5, pady=5)

btn_start = Button(frame_run, padx=5, pady=5, text="시작", width=10)
btn_start.pack(side="right", padx=5, pady=5)


root.resizable(False, False) # x(너비), y(높이) 값 변경 불가 ( 창 크기 변경 불가 )
root.mainloop()

[24] 저장 경로

 

| CODE

import tkinter.ttk as ttk
import tkinter.messagebox as msgbox
from tkinter import * # _all_
from tkinter import filedialog # 서브 모듈이기 때문에 별도로 improt

root = Tk()
root.title("Hero GUI")

# 파일 추가
def add_file():
    files = filedialog.askopenfilenames(title="이미지 파일을 선택하세요",\
            filetypes=(("PNG 파일", "*.png"), ("모든 파일", "*.*"),), \
            initialdir=r"C:/workspace/NC_Utl_2_GUI@/gui_basic") # 최초 위치 지정이 작동을 하지 않음. WHY?
    		# 최초에 사용자가 지정한 경로를 보여줌
    		# r은 에스케이프 문자를 무시하는, raw string
    for file in files:
        list_file.insert(END, file)

# 선택 삭제
def del_file():
    print(list_file.curselection())
    # 인덱스 뒤에 것부터 지워야 인덱스에 변화가 안 생김
    for index in reversed(list_file.curselection()):
        list_file.delete(index)
        
# 저장 경로 (폴더)
def browse_dest_path():
    folder_selected = filedialog.askdirectory()
    if folder_selected == '': # 사용자가 취소를 누를 때
        return
    txt_dest_path.delete(0, END)
    txt_dest_path.insert(0, folder_selected)

# 시작
def start():
    # 각 옵션들 값 확인
    print("가로넓이 : ", cmb_width.get())
    print("간격 : ", cmb_space.get())
    print("포맷 : ", cmb_foramt.get())
    
    # 파일 목록 확인
    if list_file.size() == 0:
        msgbox.showwarning("경고", "이미지 파일을 추가하세요")
        return
    
    # 저장 경로 확인
    if len(txt_dest_path.get()) == 0:
        msgbox.showwarning("경고", "저장 경로를 선택하세요")
        return
        
    
# 파일 프레임 ( 파일추가, 선택 삭제 )

file_frame = Frame(root)
file_frame.pack(fill="x", padx=5, pady=5) # 간격 띄우기

btn_add_file = Button(file_frame, padx=5, pady=5, width=12, text="파일추가", command=add_file)
btn_add_file.pack(side="left")

btn_del_file = Button(file_frame, padx=5, pady=5, width=12,text="선택삭제", command=del_file)
btn_del_file.pack(side="right")

# 리스트 프레임
list_frame = Frame(root)
list_frame.pack(fill="both", padx=5, pady=5)

scrollbar = Scrollbar(list_frame)
scrollbar.pack(side="right", fill="y")

list_file = Listbox(list_frame, selectmode="extended", height=15, yscrollcommand=scrollbar.set)
list_file.pack(side="left", fill="both", expand=True)
scrollbar.config(command=list_file.yview)

# 저장경로 프레임
path_frame = LabelFrame(root, text="저장경로")
path_frame.pack(fill="x", padx=5, pady=5, ipady=5)

txt_dest_path = Entry(path_frame) # Entry로 만들었기 때문에 위에서 (0,END)로 쓰는 것이고 txt였다면 ("1.0", END)
txt_dest_path.pack(side="left", fill="x", expand=True, padx=5, pady=5, ipady=4) # 높이 변경

btn_dest_path = Button(path_frame, text="찾아보기", width=10, command=browse_dest_path)
btn_dest_path.pack(side="right", padx=5, pady=5)

# 옵션 프레임
frame_option = LabelFrame(root, text="옵션")
frame_option.pack(padx=5, pady=5, ipady=5)

# 1. 가로 넓이 옵션
# 가로 넓이 레이블
lbl_width = Label(frame_option, text="가로넓이", width=8)
lbl_width.pack(side="left", padx=5, pady=5)
# 가로 넓이 콤보
opt_width = ["원본유지", "1024", "800", "640"]
cmb_width = ttk.Combobox(frame_option, state="readonly", values=opt_width, width=10)
cmb_width.current(0)
cmb_width.pack(side="left", padx=5, pady=5)

# 2. 간격 옵션
# 간격 레이블
lbl_space = Label(frame_option, text="간격", width=8)
lbl_space.pack(side="left", padx=5, pady=5)
# 간격 콤보
opt_space = ["없음", "좁게", "보통", "넓게"]
cmb_space = ttk.Combobox(frame_option, state="readonly", values=opt_space, width=10)
cmb_space.current(0)
cmb_space.pack(side="left", padx=5, pady=5)

# 3. 포맷 옵션
# 포맷 레이블
lbl_format = Label(frame_option, text="포맷", width=8)
lbl_format.pack(side="left", padx=5, pady=5)
# 포맷 콤보
opt_format = ["PNG", "JPG", "BMP"]
cmb_format = ttk.Combobox(frame_option, state="readonly", values=opt_format, width=10)
cmb_format.current(0)
cmb_format.pack(side="left", padx=5, pady=5)

# 진행상황 Progress Bar
frame_progress = LabelFrame(root, text="진행상황")
frame_progress.pack(fill="x", padx=5, pady=5, ipady=5)

p_var = DoubleVar()
progress_bar = ttk.Progressbar(frame_progress, maximum=100, variable=p_var)
progress_bar.pack(fill="x", padx=5, pady=5)

# 실행 프레임
frame_run = Frame(root)
frame_run.pack(fill="x", padx=5, pady=5)

btn_close = Button(frame_run, padx=5, pady=5, text="닫기", width=10, command=root.quit)
btn_close.pack(side="right", padx=5, pady=5)

btn_start = Button(frame_run, padx=5, pady=5, text="시작", width=10, command=start)
btn_start.pack(side="right", padx=5, pady=5)


root.resizable(False, False) # x(너비), y(높이) 값 변경 불가 ( 창 크기 변경 불가 )
root.mainloop()

[25] 자동 스크린샷

 

| Explanation

"자동 스크린샷 프로그램"은 기존 메인 프로젝트인 "이미지 합치기 프로그램"과

별도인 프로그램이다. "이미지 합치기 프로그램"에서 쓰일 이미지를 위해서

만들어낸 유틸리티일 뿐이다.

 

| CODE

import time
from PIL import ImageGrab # Python Image Library

time.sleep(5) # 5초 대기 : 사용자가 준비하는 시간

for i in range(1,11): #2초 간격으로 10개 이미지 저장
    img = ImageGrab.grab() # 현재 스크린 이미지를 가져옴
    img.save("image{}.png".format(i)) # 파일로 저장 (image1.png ~ image10.png)
    time.sleep(2) # 2초 단위

[26] 이미지 합치기

 

| CODE

import os
import tkinter.ttk as ttk
import tkinter.messagebox as msgbox
from tkinter import * # _all_
from tkinter import filedialog # 서브 모듈이기 때문에 별도로 improt
from PIL import Image

root = Tk()
root.title("Hero GUI")

# 파일 추가
def add_file():
    files = filedialog.askopenfilenames(title="이미지 파일을 선택하세요",\
            filetypes=(("PNG 파일", "*.png"), ("모든 파일", "*.*"),), \
            initialdir=r"C:/workspace/NC_Utl_2_GUI") # 최초 위치 지정이 작동을 하지 않음. WHY?
    		# 최초에 사용자가 지정한 경로를 보여줌
    		# r은 에스케이프 문자를 무시하는, raw string
    for file in files:
        list_file.insert(END, file)

# 선택 삭제
def del_file():
    print(list_file.curselection())
    # 인덱스 뒤에 것부터 지워야 인덱스에 변화가 안 생김
    for index in reversed(list_file.curselection()):
        list_file.delete(index)
        
# 저장 경로 (폴더)
def browse_dest_path():
    folder_selected = filedialog.askdirectory()
    if folder_selected == '': # 사용자가 취소를 누를 때
        return
    txt_dest_path.delete(0, END)
    txt_dest_path.insert(0, folder_selected)

# 이미지 통합
def merge_image():
    # print(list_file.get(0, END)) # 모든 파일 목록을 가져오기
    images = [Image.open(x) for x in list_file.get(0, END)]
    # size -> size[0] : width, size[1] : height
    widths = [x.size[0] for x in images]
    height = [x.size[1] for x in images]
    
    # 최대 넓이, 전체 높이 구해옴
    max_width, total_height = max(widths), sum(heights)
    
    # 스케치북 준비
    result_img = Image.new("RGB", (max_width, total_height), (255,255,255)) # 배경 흰색
    y_offset = 0 # 이미지를 합치면서 변해가는 y 위치
    for img in images:
        result_img.paste(img, (0, y_offset))
        y_offset += img.size[1] # height 값만큼 더하기
    
    dest_path = os.path.join(txt_dest_path.get(), "hero_photo.jpg")
    result_img.save(dest_path)
    msgbox.showinfo("알림", "작업이 완료되었습니다.")

# 시작
def start():
    # 각 옵션들 값 확인
    print("가로넓이 : ", cmb_width.get())
    print("간격 : ", cmb_space.get())
    print("포맷 : ", cmb_foramt.get())
    
    # 파일 목록 확인
    if list_file.size() == 0:
        msgbox.showwarning("경고", "이미지 파일을 추가하세요")
        return
    
    # 저장 경로 확인
    if len(txt_dest_path.get()) == 0:
        msgbox.showwarning("경고", "저장 경로를 선택하세요")
        return
    
    # 이미지 통합 작업
    merge_image() # 함수가 길어질 것 같아서 별도로 빼서 함수 정의
        
    
# 파일 프레임 ( 파일추가, 선택 삭제 )

file_frame = Frame(root)
file_frame.pack(fill="x", padx=5, pady=5) # 간격 띄우기

btn_add_file = Button(file_frame, padx=5, pady=5, width=12, text="파일추가", command=add_file)
btn_add_file.pack(side="left")

btn_del_file = Button(file_frame, padx=5, pady=5, width=12,text="선택삭제", command=del_file)
btn_del_file.pack(side="right")

# 리스트 프레임
list_frame = Frame(root)
list_frame.pack(fill="both", padx=5, pady=5)

scrollbar = Scrollbar(list_frame)
scrollbar.pack(side="right", fill="y")

list_file = Listbox(list_frame, selectmode="extended", height=15, yscrollcommand=scrollbar.set)
list_file.pack(side="left", fill="both", expand=True)
scrollbar.config(command=list_file.yview)

# 저장경로 프레임
path_frame = LabelFrame(root, text="저장경로")
path_frame.pack(fill="x", padx=5, pady=5, ipady=5)

txt_dest_path = Entry(path_frame) # Entry로 만들었기 때문에 위에서 (0,END)로 쓰는 것이고 txt였다면 ("1.0", END)
txt_dest_path.pack(side="left", fill="x", expand=True, padx=5, pady=5, ipady=4) # 높이 변경

btn_dest_path = Button(path_frame, text="찾아보기", width=10, command=browse_dest_path)
btn_dest_path.pack(side="right", padx=5, pady=5)

# 옵션 프레임
frame_option = LabelFrame(root, text="옵션")
frame_option.pack(padx=5, pady=5, ipady=5)

# 1. 가로 넓이 옵션
# 가로 넓이 레이블
lbl_width = Label(frame_option, text="가로넓이", width=8)
lbl_width.pack(side="left", padx=5, pady=5)
# 가로 넓이 콤보
opt_width = ["원본유지", "1024", "800", "640"]
cmb_width = ttk.Combobox(frame_option, state="readonly", values=opt_width, width=10)
cmb_width.current(0)
cmb_width.pack(side="left", padx=5, pady=5)

# 2. 간격 옵션
# 간격 레이블
lbl_space = Label(frame_option, text="간격", width=8)
lbl_space.pack(side="left", padx=5, pady=5)
# 간격 콤보
opt_space = ["없음", "좁게", "보통", "넓게"]
cmb_space = ttk.Combobox(frame_option, state="readonly", values=opt_space, width=10)
cmb_space.current(0)
cmb_space.pack(side="left", padx=5, pady=5)

# 3. 포맷 옵션
# 포맷 레이블
lbl_format = Label(frame_option, text="포맷", width=8)
lbl_format.pack(side="left", padx=5, pady=5)
# 포맷 콤보
opt_format = ["PNG", "JPG", "BMP"]
cmb_format = ttk.Combobox(frame_option, state="readonly", values=opt_format, width=10)
cmb_format.current(0)
cmb_format.pack(side="left", padx=5, pady=5)

# 진행상황 Progress Bar
frame_progress = LabelFrame(root, text="진행상황")
frame_progress.pack(fill="x", padx=5, pady=5, ipady=5)

p_var = DoubleVar()
progress_bar = ttk.Progressbar(frame_progress, maximum=100, variable=p_var)
progress_bar.pack(fill="x", padx=5, pady=5)

# 실행 프레임
frame_run = Frame(root)
frame_run.pack(fill="x", padx=5, pady=5)

btn_close = Button(frame_run, padx=5, pady=5, text="닫기", width=10, command=root.quit)
btn_close.pack(side="right", padx=5, pady=5)

btn_start = Button(frame_run, padx=5, pady=5, text="시작", width=10, command=start)
btn_start.pack(side="right", padx=5, pady=5)


root.resizable(False, False) # x(너비), y(높이) 값 변경 불가 ( 창 크기 변경 불가 )
root.mainloop()

[27] 프로그레스 바 연동

 

| CODE

import os
import tkinter.ttk as ttk
import tkinter.messagebox as msgbox
from tkinter import * # _all_
from tkinter import filedialog # 서브 모듈이기 때문에 별도로 improt
from PIL import Image

root = Tk()
root.title("Hero GUI")

# 파일 추가
def add_file():
    files = filedialog.askopenfilenames(title="이미지 파일을 선택하세요",\
            filetypes=(("PNG 파일", "*.png"), ("모든 파일", "*.*"),), \
            initialdir=r"C:/workspace/NC_Utl_2_GUI") # 최초 위치 지정이 작동을 하지 않음. WHY?
    		# 최초에 사용자가 지정한 경로를 보여줌
    		# r은 에스케이프 문자를 무시하는, raw string
    for file in files:
        list_file.insert(END, file)

# 선택 삭제
def del_file():
    print(list_file.curselection())
    # 인덱스 뒤에 것부터 지워야 인덱스에 변화가 안 생김
    for index in reversed(list_file.curselection()):
        list_file.delete(index)
        
# 저장 경로 (폴더)
def browse_dest_path():
    folder_selected = filedialog.askdirectory()
    if folder_selected == '': # 사용자가 취소를 누를 때
        return
    txt_dest_path.delete(0, END)
    txt_dest_path.insert(0, folder_selected)

# 이미지 통합
def merge_image():
    # print(list_file.get(0, END)) # 모든 파일 목록을 가져오기
    images = [Image.open(x) for x in list_file.get(0, END)]
    # size -> size[0] : width, size[1] : height
    widths = [x.size[0] for x in images]
    height = [x.size[1] for x in images]
    
    # 최대 넓이, 전체 높이 구해옴
    max_width, total_height = max(widths), sum(heights)
    
    # 스케치북 준비
    result_img = Image.new("RGB", (max_width, total_height), (255,255,255)) # 배경 흰색
    y_offset = 0 # 이미지를 합치면서 변해가는 y 위치
    for img in images:
        result_img.paste(img, (0, y_offset))
        y_offset += img.size[1] # height 값만큼 더하기
        
    for idx, img in enumerate(images):
        result_img.paste(img, (0, y_offset))
        y_offset += img.size[1]
        
        progress = (idx+1) / len(images) * 100 # 실제 퍼센트 정보를 계산
        p_var.set(progress)
        progress_bar.update()
    
    dest_path = os.path.join(txt_dest_path.get(), "hero_photo.jpg")
    result_img.save(dest_path)
    msgbox.showinfo("알림", "작업이 완료되었습니다.")

# 시작
def start():
    # 각 옵션들 값 확인
    print("가로넓이 : ", cmb_width.get())
    print("간격 : ", cmb_space.get())
    print("포맷 : ", cmb_foramt.get())
    
    # 파일 목록 확인
    if list_file.size() == 0:
        msgbox.showwarning("경고", "이미지 파일을 추가하세요")
        return
    
    # 저장 경로 확인
    if len(txt_dest_path.get()) == 0:
        msgbox.showwarning("경고", "저장 경로를 선택하세요")
        return
    
    # 이미지 통합 작업
    merge_image() # 함수가 길어질 것 같아서 별도로 빼서 함수 정의
        
    
# 파일 프레임 ( 파일추가, 선택 삭제 )

file_frame = Frame(root)
file_frame.pack(fill="x", padx=5, pady=5) # 간격 띄우기

btn_add_file = Button(file_frame, padx=5, pady=5, width=12, text="파일추가", command=add_file)
btn_add_file.pack(side="left")

btn_del_file = Button(file_frame, padx=5, pady=5, width=12,text="선택삭제", command=del_file)
btn_del_file.pack(side="right")

# 리스트 프레임
list_frame = Frame(root)
list_frame.pack(fill="both", padx=5, pady=5)

scrollbar = Scrollbar(list_frame)
scrollbar.pack(side="right", fill="y")

list_file = Listbox(list_frame, selectmode="extended", height=15, yscrollcommand=scrollbar.set)
list_file.pack(side="left", fill="both", expand=True)
scrollbar.config(command=list_file.yview)

# 저장경로 프레임
path_frame = LabelFrame(root, text="저장경로")
path_frame.pack(fill="x", padx=5, pady=5, ipady=5)

txt_dest_path = Entry(path_frame) # Entry로 만들었기 때문에 위에서 (0,END)로 쓰는 것이고 txt였다면 ("1.0", END)
txt_dest_path.pack(side="left", fill="x", expand=True, padx=5, pady=5, ipady=4) # 높이 변경

btn_dest_path = Button(path_frame, text="찾아보기", width=10, command=browse_dest_path)
btn_dest_path.pack(side="right", padx=5, pady=5)

# 옵션 프레임
frame_option = LabelFrame(root, text="옵션")
frame_option.pack(padx=5, pady=5, ipady=5)

# 1. 가로 넓이 옵션
# 가로 넓이 레이블
lbl_width = Label(frame_option, text="가로넓이", width=8)
lbl_width.pack(side="left", padx=5, pady=5)
# 가로 넓이 콤보
opt_width = ["원본유지", "1024", "800", "640"]
cmb_width = ttk.Combobox(frame_option, state="readonly", values=opt_width, width=10)
cmb_width.current(0)
cmb_width.pack(side="left", padx=5, pady=5)

# 2. 간격 옵션
# 간격 레이블
lbl_space = Label(frame_option, text="간격", width=8)
lbl_space.pack(side="left", padx=5, pady=5)
# 간격 콤보
opt_space = ["없음", "좁게", "보통", "넓게"]
cmb_space = ttk.Combobox(frame_option, state="readonly", values=opt_space, width=10)
cmb_space.current(0)
cmb_space.pack(side="left", padx=5, pady=5)

# 3. 포맷 옵션
# 포맷 레이블
lbl_format = Label(frame_option, text="포맷", width=8)
lbl_format.pack(side="left", padx=5, pady=5)
# 포맷 콤보
opt_format = ["PNG", "JPG", "BMP"]
cmb_format = ttk.Combobox(frame_option, state="readonly", values=opt_format, width=10)
cmb_format.current(0)
cmb_format.pack(side="left", padx=5, pady=5)

# 진행상황 Progress Bar
frame_progress = LabelFrame(root, text="진행상황")
frame_progress.pack(fill="x", padx=5, pady=5, ipady=5)

p_var = DoubleVar()
progress_bar = ttk.Progressbar(frame_progress, maximum=100, variable=p_var)
progress_bar.pack(fill="x", padx=5, pady=5)

# 실행 프레임
frame_run = Frame(root)
frame_run.pack(fill="x", padx=5, pady=5)

btn_close = Button(frame_run, padx=5, pady=5, text="닫기", width=10, command=root.quit)
btn_close.pack(side="right", padx=5, pady=5)

btn_start = Button(frame_run, padx=5, pady=5, text="시작", width=10, command=start)
btn_start.pack(side="right", padx=5, pady=5)


root.resizable(False, False) # x(너비), y(높이) 값 변경 불가 ( 창 크기 변경 불가 )
root.mainloop()

[28] zip

 

| Explanation

zip 라이브러리에 대한 설명.

프로젝트에 필요한 부분, 보충 설명

기존에 있던 부분을 zip 라이브러리를 이용해서 교체함

 

| CODE

import os
import tkinter.ttk as ttk
import tkinter.messagebox as msgbox
from tkinter import * # _all_
from tkinter import filedialog # 서브 모듈이기 때문에 별도로 improt
from PIL import Image

root = Tk()
root.title("Hero GUI")

# 파일 추가
def add_file():
    files = filedialog.askopenfilenames(title="이미지 파일을 선택하세요",\
            filetypes=(("PNG 파일", "*.png"), ("모든 파일", "*.*"),), \
            initialdir=r"C:/workspace/NC_Utl_2_GUI") # 최초 위치 지정이 작동을 하지 않음. WHY?
    		# 최초에 사용자가 지정한 경로를 보여줌
    		# r은 에스케이프 문자를 무시하는, raw string
    for file in files:
        list_file.insert(END, file)

# 선택 삭제
def del_file():
    print(list_file.curselection())
    # 인덱스 뒤에 것부터 지워야 인덱스에 변화가 안 생김
    for index in reversed(list_file.curselection()):
        list_file.delete(index)
        
# 저장 경로 (폴더)
def browse_dest_path():
    folder_selected = filedialog.askdirectory()
    if folder_selected == '': # 사용자가 취소를 누를 때
        return
    txt_dest_path.delete(0, END)
    txt_dest_path.insert(0, folder_selected)

# 이미지 통합
def merge_image():
    # print(list_file.get(0, END)) # 모든 파일 목록을 가져오기
    images = [Image.open(x) for x in list_file.get(0, END)]
    # size -> size[0] : width, size[1] : height
    # widths = [x.size[0] for x in images] # zip을 사용하기 전 방법
    # heights = [x.size[1] for x in images] # zip을 사용하기 전 방법
    
    # [(10,10). (20,20), (30,30)]
    widths, heights = zip(*(x.size for x in images))
    
    # 최대 넓이, 전체 높이 구해옴
    max_width, total_height = max(widths), sum(heights)
    
    # 스케치북 준비
    result_img = Image.new("RGB", (max_width, total_height), (255,255,255)) # 배경 흰색
    y_offset = 0 # 이미지를 합치면서 변해가는 y 위치
    for img in images:
        result_img.paste(img, (0, y_offset))
        y_offset += img.size[1] # height 값만큼 더하기
        
    for idx, img in enumerate(images):
        result_img.paste(img, (0, y_offset))
        y_offset += img.size[1]
        
        progress = (idx+1) / len(images) * 100 # 실제 퍼센트 정보를 계산
        p_var.set(progress)
        progress_bar.update()
    
    dest_path = os.path.join(txt_dest_path.get(), "hero_photo.jpg")
    result_img.save(dest_path)
    msgbox.showinfo("알림", "작업이 완료되었습니다.")

# 시작
def start():
    # 각 옵션들 값 확인
    print("가로넓이 : ", cmb_width.get())
    print("간격 : ", cmb_space.get())
    print("포맷 : ", cmb_format.get())
    
    # 파일 목록 확인
    if list_file.size() == 0:
        msgbox.showwarning("경고", "이미지 파일을 추가하세요")
        return
    
    # 저장 경로 확인
    if len(txt_dest_path.get()) == 0:
        msgbox.showwarning("경고", "저장 경로를 선택하세요")
        return
    
    # 이미지 통합 작업
    merge_image() # 함수가 길어질 것 같아서 별도로 빼서 함수 정의
        
    
# 파일 프레임 ( 파일추가, 선택 삭제 )

file_frame = Frame(root)
file_frame.pack(fill="x", padx=5, pady=5) # 간격 띄우기

btn_add_file = Button(file_frame, padx=5, pady=5, width=12, text="파일추가", command=add_file)
btn_add_file.pack(side="left")

btn_del_file = Button(file_frame, padx=5, pady=5, width=12, text="선택삭제", command=del_file)
btn_del_file.pack(side="right")

# 리스트 프레임
list_frame = Frame(root)
list_frame.pack(fill="both", padx=5, pady=5)

scrollbar = Scrollbar(list_frame)
scrollbar.pack(side="right", fill="y")

list_file = Listbox(list_frame, selectmode="extended", height=15, yscrollcommand=scrollbar.set)
list_file.pack(side="left", fill="both", expand=True)
scrollbar.config(command=list_file.yview)

# 저장경로 프레임
path_frame = LabelFrame(root, text="저장경로")
path_frame.pack(fill="x", padx=5, pady=5, ipady=5)

txt_dest_path = Entry(path_frame) # Entry로 만들었기 때문에 위에서 (0,END)로 쓰는 것이고 txt였다면 ("1.0", END)
txt_dest_path.pack(side="left", fill="x", expand=True, padx=5, pady=5, ipady=4) # 높이 변경

btn_dest_path = Button(path_frame, text="찾아보기", width=10, command=browse_dest_path)
btn_dest_path.pack(side="right", padx=5, pady=5)

# 옵션 프레임
frame_option = LabelFrame(root, text="옵션")
frame_option.pack(padx=5, pady=5, ipady=5)

# 1. 가로 넓이 옵션
# 가로 넓이 레이블
lbl_width = Label(frame_option, text="가로넓이", width=8)
lbl_width.pack(side="left", padx=5, pady=5)
# 가로 넓이 콤보
opt_width = ["원본유지", "1024", "800", "640"]
cmb_width = ttk.Combobox(frame_option, state="readonly", values=opt_width, width=10)
cmb_width.current(0)
cmb_width.pack(side="left", padx=5, pady=5)

# 2. 간격 옵션
# 간격 레이블
lbl_space = Label(frame_option, text="간격", width=8)
lbl_space.pack(side="left", padx=5, pady=5)
# 간격 콤보
opt_space = ["없음", "좁게", "보통", "넓게"]
cmb_space = ttk.Combobox(frame_option, state="readonly", values=opt_space, width=10)
cmb_space.current(0)
cmb_space.pack(side="left", padx=5, pady=5)

# 3. 포맷 옵션
# 포맷 레이블
lbl_format = Label(frame_option, text="포맷", width=8)
lbl_format.pack(side="left", padx=5, pady=5)
# 포맷 콤보
opt_format = ["PNG", "JPG", "BMP"]
cmb_format = ttk.Combobox(frame_option, state="readonly", values=opt_format, width=10)
cmb_format.current(0)
cmb_format.pack(side="left", padx=5, pady=5)

# 진행상황 Progress Bar
frame_progress = LabelFrame(root, text="진행상황")
frame_progress.pack(fill="x", padx=5, pady=5, ipady=5)

p_var = DoubleVar()
progress_bar = ttk.Progressbar(frame_progress, maximum=100, variable=p_var)
progress_bar.pack(fill="x", padx=5, pady=5)

# 실행 프레임
frame_run = Frame(root)
frame_run.pack(fill="x", padx=5, pady=5)

btn_close = Button(frame_run, padx=5, pady=5, text="닫기", width=10, command=root.quit)
btn_close.pack(side="right", padx=5, pady=5)

btn_start = Button(frame_run, padx=5, pady=5, text="시작", width=10, command=start)
btn_start.pack(side="right", padx=5, pady=5)


root.resizable(False, False) # x(너비), y(높이) 값 변경 불가 ( 창 크기 변경 불가 )
root.mainloop()

[29] 옵션 전반전

 

| CODE

import os
import tkinter.ttk as ttk
import tkinter.messagebox as msgbox
from tkinter import * # _all_
from tkinter import filedialog # 서브 모듈이기 때문에 별도로 improt
from PIL import Image

root = Tk()
root.title("Hero GUI")

# 파일 추가
def add_file():
    files = filedialog.askopenfilenames(title="이미지 파일을 선택하세요",\
            filetypes=(("PNG 파일", "*.png"), ("모든 파일", "*.*"),), \
            initialdir=r"C:/workspace/NC_Utl_2_GUI") # 최초 위치 지정이 작동을 하지 않음. WHY?
    		# 최초에 사용자가 지정한 경로를 보여줌
    		# r은 에스케이프 문자를 무시하는, raw string
    for file in files:
        list_file.insert(END, file)

# 선택 삭제
def del_file():
    print(list_file.curselection())
    # 인덱스 뒤에 것부터 지워야 인덱스에 변화가 안 생김
    for index in reversed(list_file.curselection()):
        list_file.delete(index)
        
# 저장 경로 (폴더)
def browse_dest_path():
    folder_selected = filedialog.askdirectory()
    if folder_selected == '': # 사용자가 취소를 누를 때
        return
    txt_dest_path.delete(0, END)
    txt_dest_path.insert(0, folder_selected)

# 이미지 통합
def merge_image():
    # print("가로넓이 : ", cmb_width.get())
    # print("간격 : ", cmb_space.get())
    # print("포맷 : ", cmb_format.get())
    
    # 가로넓이
    img_width = cmb_width.get()
    if img_width == "원본 유지":
        img_width = -1 #-1일때는 원본 기준으로
    else:
        img_width = int(img_width)
    
    # 간격
    if img_space == "좁게":
        img_space = 30
    elif img_space == "보통":
        img_space = 60
    elif img_space == "넓게":
        img_space = 90
    else: # 없음
        img_space = 0
        
    # 포맷
    img_format = cmb_format.get().lower() # PNG, JPG, BMP 값을 받아와서 소문자로 변경
    
    ########################
    
    images = [Image.open(x) for x in list_file.get(0, END)]
    
    # 이미지 사이즈 리스트에 넣어서 하나씩 처리
    image_sizes = [] # [(width1, height1), (width2, height2), ...]
    if img_width > -1:
        # width 값 변경
        image_sizes = [(int(img_width), int(img_width * x.size[1] / x.size[0]))for x in images]
        
    else :
        # 원본 사이즈 사용
        image_sizes = [(x.size[0], x.size[1]) for x in images] # 원본 사이즈 사용
        
    '''
    
    계산식
    	100*60 이미지가 있음 -> width를 80으로 줄이면 height는?
        (원본 width) : (원본 height) = (변경 width) : (변경 height)
        100 : 60 = 80 : ?
        x : y = x' : y'
        yx' = xy'
        y' = yx' / x -> 이 식을 적용
        100:60=80:48
        
    우리 코드에 대입하려면?
    	x = width = size[0]
        x = width = size[0]
        x' = img_width -> 이 값으로 변경해야 함
        y' = x'y / x = img_width * size[1] / size[0]
        
    '''
    
    widths, heights = zip(*(image_sizes))
    
    # 최대 넓이, 전체 높이 구해옴
    max_width, total_height = max(widths), sum(heights)
    
    # 스케치북 준비
    result_img = Image.new("RGB", (max_width, total_height), (255,255,255)) # 배경 흰색
    y_offset = 0 # 이미지를 합치면서 변해가는 y 위치
    for img in images:
        result_img.paste(img, (0, y_offset))
        y_offset += img.size[1] # height 값만큼 더하기
        
    for idx, img in enumerate(images):
        result_img.paste(img, (0, y_offset))
        y_offset += img.size[1]
        
        progress = (idx+1) / len(images) * 100 # 실제 퍼센트 정보를 계산
        p_var.set(progress)
        progress_bar.update()
    
    dest_path = os.path.join(txt_dest_path.get(), "hero_photo.jpg")
    result_img.save(dest_path)
    msgbox.showinfo("알림", "작업이 완료되었습니다.")

# 시작
def start():
    # 각 옵션들 값 확인
    print("가로넓이 : ", cmb_width.get())
    print("간격 : ", cmb_space.get())
    print("포맷 : ", cmb_format.get())
    
    # 파일 목록 확인
    if list_file.size() == 0:
        msgbox.showwarning("경고", "이미지 파일을 추가하세요")
        return
    
    # 저장 경로 확인
    if len(txt_dest_path.get()) == 0:
        msgbox.showwarning("경고", "저장 경로를 선택하세요")
        return
    
    # 이미지 통합 작업
    merge_image() # 함수가 길어질 것 같아서 별도로 빼서 함수 정의
        
    
# 파일 프레임 ( 파일추가, 선택 삭제 )

file_frame = Frame(root)
file_frame.pack(fill="x", padx=5, pady=5) # 간격 띄우기

btn_add_file = Button(file_frame, padx=5, pady=5, width=12, text="파일추가", command=add_file)
btn_add_file.pack(side="left")

btn_del_file = Button(file_frame, padx=5, pady=5, width=12, text="선택삭제", command=del_file)
btn_del_file.pack(side="right")

# 리스트 프레임
list_frame = Frame(root)
list_frame.pack(fill="both", padx=5, pady=5)

scrollbar = Scrollbar(list_frame)
scrollbar.pack(side="right", fill="y")

list_file = Listbox(list_frame, selectmode="extended", height=15, yscrollcommand=scrollbar.set)
list_file.pack(side="left", fill="both", expand=True)
scrollbar.config(command=list_file.yview)

# 저장경로 프레임
path_frame = LabelFrame(root, text="저장경로")
path_frame.pack(fill="x", padx=5, pady=5, ipady=5)

txt_dest_path = Entry(path_frame) # Entry로 만들었기 때문에 위에서 (0,END)로 쓰는 것이고 txt였다면 ("1.0", END)
txt_dest_path.pack(side="left", fill="x", expand=True, padx=5, pady=5, ipady=4) # 높이 변경

btn_dest_path = Button(path_frame, text="찾아보기", width=10, command=browse_dest_path)
btn_dest_path.pack(side="right", padx=5, pady=5)

# 옵션 프레임
frame_option = LabelFrame(root, text="옵션")
frame_option.pack(padx=5, pady=5, ipady=5)

# 1. 가로 넓이 옵션
# 가로 넓이 레이블
lbl_width = Label(frame_option, text="가로넓이", width=8)
lbl_width.pack(side="left", padx=5, pady=5)
# 가로 넓이 콤보
opt_width = ["원본유지", "1024", "800", "640"]
cmb_width = ttk.Combobox(frame_option, state="readonly", values=opt_width, width=10)
cmb_width.current(0)
cmb_width.pack(side="left", padx=5, pady=5)

# 2. 간격 옵션
# 간격 레이블
lbl_space = Label(frame_option, text="간격", width=8)
lbl_space.pack(side="left", padx=5, pady=5)
# 간격 콤보
opt_space = ["없음", "좁게", "보통", "넓게"]
cmb_space = ttk.Combobox(frame_option, state="readonly", values=opt_space, width=10)
cmb_space.current(0)
cmb_space.pack(side="left", padx=5, pady=5)

# 3. 포맷 옵션
# 포맷 레이블
lbl_format = Label(frame_option, text="포맷", width=8)
lbl_format.pack(side="left", padx=5, pady=5)
# 포맷 콤보
opt_format = ["PNG", "JPG", "BMP"]
cmb_format = ttk.Combobox(frame_option, state="readonly", values=opt_format, width=10)
cmb_format.current(0)
cmb_format.pack(side="left", padx=5, pady=5)

# 진행상황 Progress Bar
frame_progress = LabelFrame(root, text="진행상황")
frame_progress.pack(fill="x", padx=5, pady=5, ipady=5)

p_var = DoubleVar()
progress_bar = ttk.Progressbar(frame_progress, maximum=100, variable=p_var)
progress_bar.pack(fill="x", padx=5, pady=5)

# 실행 프레임
frame_run = Frame(root)
frame_run.pack(fill="x", padx=5, pady=5)

btn_close = Button(frame_run, padx=5, pady=5, text="닫기", width=10, command=root.quit)
btn_close.pack(side="right", padx=5, pady=5)

btn_start = Button(frame_run, padx=5, pady=5, text="시작", width=10, command=start)
btn_start.pack(side="right", padx=5, pady=5)


root.resizable(False, False) # x(너비), y(높이) 값 변경 불가 ( 창 크기 변경 불가 )
root.mainloop()

[30] 옵션 후반전

 

| CODE

import os
import tkinter.ttk as ttk
import tkinter.messagebox as msgbox
from tkinter import * # _all_
from tkinter import filedialog # 서브 모듈이기 때문에 별도로 improt
from PIL import Image

root = Tk()
root.title("Hero GUI")

# 파일 추가
def add_file():
    files = filedialog.askopenfilenames(title="이미지 파일을 선택하세요",\
            filetypes=(("PNG 파일", "*.png"), ("모든 파일", "*.*"),), \
            initialdir=r"C:/workspace/NC_Utl_2_GUI") # 최초 위치 지정이 작동을 하지 않음. WHY?
    		# 최초에 사용자가 지정한 경로를 보여줌
    		# r은 에스케이프 문자를 무시하는, raw string
    for file in files:
        list_file.insert(END, file)

# 선택 삭제
def del_file():
    print(list_file.curselection())
    # 인덱스 뒤에 것부터 지워야 인덱스에 변화가 안 생김
    for index in reversed(list_file.curselection()):
        list_file.delete(index)
        
# 저장 경로 (폴더)
def browse_dest_path():
    folder_selected = filedialog.askdirectory()
    if folder_selected == '': # 사용자가 취소를 누를 때
        return
    txt_dest_path.delete(0, END)
    txt_dest_path.insert(0, folder_selected)

# 이미지 통합
def merge_image():
    # print("가로넓이 : ", cmb_width.get())
    # print("간격 : ", cmb_space.get())
    # print("포맷 : ", cmb_format.get())
    
    # 가로넓이
    img_width = cmb_width.get()
    if img_width == "원본 유지":
        img_width = -1 #-1일때는 원본 기준으로
    else:
        img_width = int(img_width)
    
    # 간격
    if img_space == "좁게":
        img_space = 30
    elif img_space == "보통":
        img_space = 60
    elif img_space == "넓게":
        img_space = 90
    else: # 없음
        img_space = 0
        
    # 포맷
    img_format = cmb_format.get().lower() # PNG, JPG, BMP 값을 받아와서 소문자로 변경
    
    ########################
    
    images = [Image.open(x) for x in list_file.get(0, END)]
    
    # 이미지 사이즈 리스트에 넣어서 하나씩 처리
    image_sizes = [] # [(width1, height1), (width2, height2), ...]
    if img_width > -1:
        # width 값 변경
        image_sizes = [(int(img_width), int(img_width * x.size[1] / x.size[0]))for x in images]
        
    else :
        # 원본 사이즈 사용
        image_sizes = [(x.size[0], x.size[1]) for x in images] # 원본 사이즈 사용
        
    '''
    
    계산식
    	100*60 이미지가 있음 -> width를 80으로 줄이면 height는?
        (원본 width) : (원본 height) = (변경 width) : (변경 height)
        100 : 60 = 80 : ?
        x : y = x' : y'
        yx' = xy'
        y' = yx' / x -> 이 식을 적용
        100:60=80:48
        
    우리 코드에 대입하려면?
    	x = width = size[0]
        x = width = size[0]
        x' = img_width -> 이 값으로 변경해야 함
        y' = x'y / x = img_width * size[1] / size[0]
        
    '''
    
    widths, heights = zip(*(image_sizes))
    
    # 최대 넓이, 전체 높이 구해옴
    max_width, total_height = max(widths), sum(heights)
    
    # 스케치북 준비
    
    if img_space > 0: # 이미지 간격 옵션 적용
        total_height += (img_space * (len(images)-1))
        
    result_img = Image.new("RGB", (max_width, total_height), (255,255,255)) # 배경 흰색
    y_offset = 0 # 이미지를 합치면서 변해가는 y 위치
    for img in images:
        result_img.paste(img, (0, y_offset))
        y_offset += img.size[1] # height 값만큼 더하기
        
    for idx, img in enumerate(images):
        # width가 원본 유지가 아닐 때에는 이미지 크기 조정
        if img_width > -1:
            img = img.resize(image_sizes[idx])
        
        result_img.paste(img, (0, y_offset))
        y_offset += (img.size[1] + img_space) # height 값 + 사용자가 지정한 간격
        
        progress = (idx+1) / len(images) * 100 # 실제 퍼센트 정보를 계산
        p_var.set(progress)
        progress_bar.update()
    
    # 포맷 옵션 처리
    file_name = "hero_photo." + img_format
    dest_path = os.path.join(txt_dest_path.get(), "file_name")
    result_img.save(dest_path)
    msgbox.showinfo("알림", "작업이 완료되었습니다.")

# 시작
def start():
    # 각 옵션들 값 확인
    print("가로넓이 : ", cmb_width.get())
    print("간격 : ", cmb_space.get())
    print("포맷 : ", cmb_format.get())
    
    # 파일 목록 확인
    if list_file.size() == 0:
        msgbox.showwarning("경고", "이미지 파일을 추가하세요")
        return
    
    # 저장 경로 확인
    if len(txt_dest_path.get()) == 0:
        msgbox.showwarning("경고", "저장 경로를 선택하세요")
        return
    
    # 이미지 통합 작업
    merge_image() # 함수가 길어질 것 같아서 별도로 빼서 함수 정의
        
    
# 파일 프레임 ( 파일추가, 선택 삭제 )

file_frame = Frame(root)
file_frame.pack(fill="x", padx=5, pady=5) # 간격 띄우기

btn_add_file = Button(file_frame, padx=5, pady=5, width=12, text="파일추가", command=add_file)
btn_add_file.pack(side="left")

btn_del_file = Button(file_frame, padx=5, pady=5, width=12, text="선택삭제", command=del_file)
btn_del_file.pack(side="right")

# 리스트 프레임
list_frame = Frame(root)
list_frame.pack(fill="both", padx=5, pady=5)

scrollbar = Scrollbar(list_frame)
scrollbar.pack(side="right", fill="y")

list_file = Listbox(list_frame, selectmode="extended", height=15, yscrollcommand=scrollbar.set)
list_file.pack(side="left", fill="both", expand=True)
scrollbar.config(command=list_file.yview)

# 저장경로 프레임
path_frame = LabelFrame(root, text="저장경로")
path_frame.pack(fill="x", padx=5, pady=5, ipady=5)

txt_dest_path = Entry(path_frame) # Entry로 만들었기 때문에 위에서 (0,END)로 쓰는 것이고 txt였다면 ("1.0", END)
txt_dest_path.pack(side="left", fill="x", expand=True, padx=5, pady=5, ipady=4) # 높이 변경

btn_dest_path = Button(path_frame, text="찾아보기", width=10, command=browse_dest_path)
btn_dest_path.pack(side="right", padx=5, pady=5)

# 옵션 프레임
frame_option = LabelFrame(root, text="옵션")
frame_option.pack(padx=5, pady=5, ipady=5)

# 1. 가로 넓이 옵션
# 가로 넓이 레이블
lbl_width = Label(frame_option, text="가로넓이", width=8)
lbl_width.pack(side="left", padx=5, pady=5)
# 가로 넓이 콤보
opt_width = ["원본유지", "1024", "800", "640"]
cmb_width = ttk.Combobox(frame_option, state="readonly", values=opt_width, width=10)
cmb_width.current(0)
cmb_width.pack(side="left", padx=5, pady=5)

# 2. 간격 옵션
# 간격 레이블
lbl_space = Label(frame_option, text="간격", width=8)
lbl_space.pack(side="left", padx=5, pady=5)
# 간격 콤보
opt_space = ["없음", "좁게", "보통", "넓게"]
cmb_space = ttk.Combobox(frame_option, state="readonly", values=opt_space, width=10)
cmb_space.current(0)
cmb_space.pack(side="left", padx=5, pady=5)

# 3. 포맷 옵션
# 포맷 레이블
lbl_format = Label(frame_option, text="포맷", width=8)
lbl_format.pack(side="left", padx=5, pady=5)
# 포맷 콤보
opt_format = ["PNG", "JPG", "BMP"]
cmb_format = ttk.Combobox(frame_option, state="readonly", values=opt_format, width=10)
cmb_format.current(0)
cmb_format.pack(side="left", padx=5, pady=5)

# 진행상황 Progress Bar
frame_progress = LabelFrame(root, text="진행상황")
frame_progress.pack(fill="x", padx=5, pady=5, ipady=5)

p_var = DoubleVar()
progress_bar = ttk.Progressbar(frame_progress, maximum=100, variable=p_var)
progress_bar.pack(fill="x", padx=5, pady=5)

# 실행 프레임
frame_run = Frame(root)
frame_run.pack(fill="x", padx=5, pady=5)

btn_close = Button(frame_run, padx=5, pady=5, text="닫기", width=10, command=root.quit)
btn_close.pack(side="right", padx=5, pady=5)

btn_start = Button(frame_run, padx=5, pady=5, text="시작", width=10, command=start)
btn_start.pack(side="right", padx=5, pady=5)


root.resizable(False, False) # x(너비), y(높이) 값 변경 불가 ( 창 크기 변경 불가 )
root.mainloop()

[31] 버그 수정

 

| CODE

import os
import tkinter.ttk as ttk
import tkinter.messagebox as msgbox
from tkinter import * # _all_
from tkinter import filedialog # 서브 모듈이기 때문에 별도로 improt
from PIL import Image

root = Tk()
root.title("Hero GUI")

# 파일 추가
def add_file():
    files = filedialog.askopenfilenames(title="이미지 파일을 선택하세요",\
            filetypes=(("PNG 파일", "*.png"), ("모든 파일", "*.*"),), \
            initialdir=r"C:/workspace/NC_Utl_2_GUI") # 최초 위치 지정이 작동을 하지 않음. WHY?
    		# 최초에 사용자가 지정한 경로를 보여줌
    		# r은 에스케이프 문자를 무시하는, raw string
    for file in files:
        list_file.insert(END, file)

# 선택 삭제
def del_file():
    print(list_file.curselection())
    # 인덱스 뒤에 것부터 지워야 인덱스에 변화가 안 생김
    for index in reversed(list_file.curselection()):
        list_file.delete(index)
        
# 저장 경로 (폴더)
def browse_dest_path():
    folder_selected = filedialog.askdirectory()
    if folder_selected == '': # 사용자가 취소를 누를 때
        print("폴더 선택 취소")
        return
    txt_dest_path.delete(0, END)
    txt_dest_path.insert(0, folder_selected)

# 이미지 통합
def merge_image():
    # print("가로넓이 : ", cmb_width.get())
    # print("간격 : ", cmb_space.get())
    # print("포맷 : ", cmb_format.get())
    
    try:
        # 가로넓이
        img_width = cmb_width.get()
        if img_width == "원본 유지":
            img_width = -1 #-1일때는 원본 기준으로
        else:
            img_width = int(img_width)

        # 간격
        if img_space == "좁게":
            img_space = 30
        elif img_space == "보통":
            img_space = 60
        elif img_space == "넓게":
            img_space = 90
        else: # 없음
            img_space = 0

        # 포맷
        img_format = cmb_format.get().lower() # PNG, JPG, BMP 값을 받아와서 소문자로 변경

        ########################

        images = [Image.open(x) for x in list_file.get(0, END)]

        # 이미지 사이즈 리스트에 넣어서 하나씩 처리
        image_sizes = [] # [(width1, height1), (width2, height2), ...]
        if img_width > -1:
            # width 값 변경
            image_sizes = [(int(img_width), int(img_width * x.size[1] / x.size[0]))for x in images]

        else :
            # 원본 사이즈 사용
            image_sizes = [(x.size[0], x.size[1]) for x in images] # 원본 사이즈 사용

        '''

        계산식
            100*60 이미지가 있음 -> width를 80으로 줄이면 height는?
            (원본 width) : (원본 height) = (변경 width) : (변경 height)
            100 : 60 = 80 : ?
            x : y = x' : y'
            yx' = xy'
            y' = yx' / x -> 이 식을 적용
            100:60=80:48

        우리 코드에 대입하려면?
            x = width = size[0]
            x = width = size[0]
            x' = img_width -> 이 값으로 변경해야 함
            y' = x'y / x = img_width * size[1] / size[0]

        '''

        widths, heights = zip(*(image_sizes))

        # 최대 넓이, 전체 높이 구해옴
        max_width, total_height = max(widths), sum(heights)

        # 스케치북 준비

        if img_space > 0: # 이미지 간격 옵션 적용
            total_height += (img_space * (len(images)-1))

        result_img = Image.new("RGB", (max_width, total_height), (255,255,255)) # 배경 흰색
        y_offset = 0 # 이미지를 합치면서 변해가는 y 위치
        for img in images:
            result_img.paste(img, (0, y_offset))
            y_offset += img.size[1] # height 값만큼 더하기

        for idx, img in enumerate(images):
            # width가 원본 유지가 아닐 때에는 이미지 크기 조정
            if img_width > -1:
                img = img.resize(image_sizes[idx])

            result_img.paste(img, (0, y_offset))
            y_offset += (img.size[1] + img_space) # height 값 + 사용자가 지정한 간격

            progress = (idx+1) / len(images) * 100 # 실제 퍼센트 정보를 계산
            p_var.set(progress)
            progress_bar.update()

        # 포맷 옵션 처리
        file_name = "hero_photo." + img_format
        dest_path = os.path.join(txt_dest_path.get(), "file_name")
        result_img.save(dest_path)
        msgbox.showinfo("알림", "작업이 완료되었습니다.")
    except Exception as err: # 예외처리
        msgbox.showerror("에러", err)

# 시작
def start():
    # 각 옵션들 값 확인
    print("가로넓이 : ", cmb_width.get())
    print("간격 : ", cmb_space.get())
    print("포맷 : ", cmb_format.get())
    
    # 파일 목록 확인
    if list_file.size() == 0:
        msgbox.showwarning("경고", "이미지 파일을 추가하세요")
        return
    
    # 저장 경로 확인
    if len(txt_dest_path.get()) == 0:
        msgbox.showwarning("경고", "저장 경로를 선택하세요")
        return
    
    # 이미지 통합 작업
    merge_image() # 함수가 길어질 것 같아서 별도로 빼서 함수 정의
        
    
# 파일 프레임 ( 파일추가, 선택 삭제 )

file_frame = Frame(root)
file_frame.pack(fill="x", padx=5, pady=5) # 간격 띄우기

btn_add_file = Button(file_frame, padx=5, pady=5, width=12, text="파일추가", command=add_file)
btn_add_file.pack(side="left")

btn_del_file = Button(file_frame, padx=5, pady=5, width=12, text="선택삭제", command=del_file)
btn_del_file.pack(side="right")

# 리스트 프레임
list_frame = Frame(root)
list_frame.pack(fill="both", padx=5, pady=5)

scrollbar = Scrollbar(list_frame)
scrollbar.pack(side="right", fill="y")

list_file = Listbox(list_frame, selectmode="extended", height=15, yscrollcommand=scrollbar.set)
list_file.pack(side="left", fill="both", expand=True)
scrollbar.config(command=list_file.yview)

# 저장경로 프레임
path_frame = LabelFrame(root, text="저장경로")
path_frame.pack(fill="x", padx=5, pady=5, ipady=5)

txt_dest_path = Entry(path_frame) # Entry로 만들었기 때문에 위에서 (0,END)로 쓰는 것이고 txt였다면 ("1.0", END)
txt_dest_path.pack(side="left", fill="x", expand=True, padx=5, pady=5, ipady=4) # 높이 변경

btn_dest_path = Button(path_frame, text="찾아보기", width=10, command=browse_dest_path)
btn_dest_path.pack(side="right", padx=5, pady=5)

# 옵션 프레임
frame_option = LabelFrame(root, text="옵션")
frame_option.pack(padx=5, pady=5, ipady=5)

# 1. 가로 넓이 옵션
# 가로 넓이 레이블
lbl_width = Label(frame_option, text="가로넓이", width=8)
lbl_width.pack(side="left", padx=5, pady=5)
# 가로 넓이 콤보
opt_width = ["원본유지", "1024", "800", "640"]
cmb_width = ttk.Combobox(frame_option, state="readonly", values=opt_width, width=10)
cmb_width.current(0)
cmb_width.pack(side="left", padx=5, pady=5)

# 2. 간격 옵션
# 간격 레이블
lbl_space = Label(frame_option, text="간격", width=8)
lbl_space.pack(side="left", padx=5, pady=5)
# 간격 콤보
opt_space = ["없음", "좁게", "보통", "넓게"]
cmb_space = ttk.Combobox(frame_option, state="readonly", values=opt_space, width=10)
cmb_space.current(0)
cmb_space.pack(side="left", padx=5, pady=5)

# 3. 포맷 옵션
# 포맷 레이블
lbl_format = Label(frame_option, text="포맷", width=8)
lbl_format.pack(side="left", padx=5, pady=5)
# 포맷 콤보
opt_format = ["PNG", "JPG", "BMP"]
cmb_format = ttk.Combobox(frame_option, state="readonly", values=opt_format, width=10)
cmb_format.current(0)
cmb_format.pack(side="left", padx=5, pady=5)

# 진행상황 Progress Bar
frame_progress = LabelFrame(root, text="진행상황")
frame_progress.pack(fill="x", padx=5, pady=5, ipady=5)

p_var = DoubleVar()
progress_bar = ttk.Progressbar(frame_progress, maximum=100, variable=p_var)
progress_bar.pack(fill="x", padx=5, pady=5)

# 실행 프레임
frame_run = Frame(root)
frame_run.pack(fill="x", padx=5, pady=5)

btn_close = Button(frame_run, padx=5, pady=5, text="닫기", width=10, command=root.quit)
btn_close.pack(side="right", padx=5, pady=5)

btn_start = Button(frame_run, padx=5, pady=5, text="시작", width=10, command=start)
btn_start.pack(side="right", padx=5, pady=5)


root.resizable(False, False) # x(너비), y(높이) 값 변경 불가 ( 창 크기 변경 불가 )
root.mainloop()

| Problem

프로그램 실행과 기타 모든 기능은 정상이나,

 합쳐지는 것이 안됌. 문제 해결 요망


[32] BONUS : 스크린 샷 프로그램

 

| CODE

import time
import keyboard
from PIL import ImageGrab

def screenshot():
    # 2020년 9월 17일 23시 44분 30초 -> _20200917_234430
    curr_time = time.strftime("_%Y%m%d_%H%M%S")
    img = ImageGrab.grab()
    img.save("image{}.png".format(curr_time)) # ex) image_20200917_234315.png
    
keyboard.add_hotkey("F9", screenshot) # 사용자가 F9 키를 누르면 스크린샷 저장
# keyboard.add_hotkey("a", screenshot) # 사용자가 a를 누르면 스크린샷 저장
# keyboard.add_hotkey("ctrl+shift+s", screenshot)
# 사용자가 'ctrl+shift+s'키를 누르면 스크린샷 저장

keyboard.wait("esc") # 사용자가 esc를 누를 때까지 프로그램 수행

Reference

[나도 코딩] 파이썬 코딩 활용편 2 - GUI 프로그래밍, 이미지 합치기 / https://www.youtube.com/watch?v=bKPIcoou9N8