사용자가 선택한 폴더 안의 하위 폴더 크기를 시각화하는 데스크톱 애플리케이션입니다. CustomTkinter 라이브러리로 사용자 인터페이스(UI)를 만들고, Matplotlib으로 막대 그래프를 생성하여 시각화합니다.
애플리케이션의 뼈대
1. 창 설정: "Folder size visualization"이라는 제목의 창을 생성하고, 초기 크기를 1000x600 픽셀로 설정합니다.
2. UI 요소:
- 제목: "Folder size visualization"이라는 제목 레이블을 보여줍니다.
- 진행 바: 폴더 크기를 계산하는 동안 작동하는 진행 바입니다. 작업이 진행 중임을 시각적으로 알려줍니다.
- 폴더 선택: 폴더 경로를 입력하는 창(entry)과 폴더를 선택하는 버튼(button)으로 구성되어 있습니다.
- 시각화 영역: Canvas 위젯과 Scrollbar를 사용하여 그래프가 창보다 커질 경우 스크롤할 수 있도록 만듭니다. inner_frame은 실제 그래프가 그려지는 곳입니다.
import os
import customtkinter as ctk
from tkinter import filedialog, Canvas, Scrollbar
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import threading
class App(ctk.CTk):
def __init__(self):
super().__init__()
self.title("Folder size visualization")
self.geometry("1000x600")
# Title label
title_label = ctk.CTkLabel(self, text="Folder size visualization", font=("Arial", 20))
title_label.pack(pady=10)
# Progress bar
self.progress = ctk.CTkProgressBar(self, orientation="horizontal", width=800, mode="indeterminate")
self.progress.pack(pady=10)
# Folder selection frame
frame = ctk.CTkFrame(self)
frame.pack(pady=10)
self.entry = ctk.CTkEntry(frame, width=500)
self.entry.pack(side="left", padx=5)
button = ctk.CTkButton(frame, text="검색 폴더 선택", command=self.select_folder)
button.pack(side="left")
# Custom scrollable frame with horizontal scroll
self.canvas = Canvas(self, bg="white", width=800, height=400)
self.h_scrollbar = Scrollbar(self, orient="horizontal", command=self.canvas.xview)
self.canvas.configure(xscrollcommand=self.h_scrollbar.set)
self.h_scrollbar.pack(side="bottom", fill="x")
self.canvas.pack(pady=10)
self.inner_frame = ctk.CTkFrame(self.canvas)
self.canvas.create_window((0, 0), window=self.inner_frame, anchor="nw")
self.inner_frame.bind("<Configure>", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")))
def select_folder(self):
folder = filedialog.askdirectory()
if folder:
self.entry.delete(0, "end")
self.entry.insert(0, folder)
self.progress.start()
thread = threading.Thread(target=self.compute_sizes, args=(folder,))
thread.start()
def compute_sizes(self, parent_folder):
subfolders = [f for f in os.listdir(parent_folder) if os.path.isdir(os.path.join(parent_folder, f))]
sizes = {}
for subfolder in subfolders:
subfolder_path = os.path.join(parent_folder, subfolder)
size = self.get_folder_size(subfolder_path)
sizes[subfolder] = size
self.after(0, self.update_ui, sizes)
def update_ui(self, sizes):
# Clear previous content
for widget in self.inner_frame.winfo_children():
widget.destroy()
if not sizes:
self.progress.stop()
return
# Find max size for normalization
max_size = max(sizes.values())
if max_size > 0:
normalized_sizes = {name: (size / max_size) * 100 for name, size in sizes.items()}
names = list(normalized_sizes.keys())
values = list(normalized_sizes.values())
# Reduce figure size by 50%
fig = plt.Figure(figsize=(6, 2.5)) # Original 12x5 reduced by 50%
ax = fig.add_subplot(111)
ax.barh(names, values, color='skyblue')
ax.set_xlabel('Relative Size (%)')
ax.set_ylabel('Subfolders')
ax.set_title('Relative Sizes of Subfolders (Max = 100%)')
# Highlight the max size bar
max_index = names.index(next(name for name, size in sizes.items() if size == max_size))
ax.patches[max_index].set_color('yellow')
# Reduce font size to improve readability with smaller size
plt.xticks(fontsize=8)
plt.yticks(fontsize=8)
# Adjust layout to prevent label cutoff
plt.tight_layout()
canvas = FigureCanvasTkAgg(fig, master=self.inner_frame)
canvas.draw()
canvas.get_tk_widget().pack(side="left", fill="y")
self.progress.stop()
def get_folder_size(self, folder_path):
total_size = 0
for root, dirs, files in os.walk(folder_path):
for file in files:
file_path = os.path.join(root, file)
if os.path.exists(file_path):
total_size += os.path.getsize(file_path)
return total_size
if __name__ == "__main__":
app = App()
app.mainloop()
▣ Progress bar
# Progress bar
self.progress = ctk.CTkProgressBar(self, orientation="horizontal", width=400, mode="indeterminate")
self.progress.pack(pady=10)
CustomTkinter 라이브러리의 진행 바(Progress Bar)를 생성하는 코드입니다. 이 진행 바의 주요 기능과 속성은 다음과 같습니다.
주요 기능
- 작업 진행 상태 시각화:
시간이 오래 걸리는 작업(예: 폴더 크기 계산)이 진행 중임을 사용자에게 시각적으로 보여줍니다.
이를 통해 애플리케이션이 멈춘 것이 아니라, 작업을 처리하고 있다는 것을 알 수 있습니다. - 사용자 경험 개선:
작업이 백그라운드에서 진행되는 동안에도 사용자는 애플리케이션이 응답하고 있음을 알 수 있어 답답함을 줄이고 긍정적인 경험을 제공합니다.
코드 속성별 설명
- ctk.CTkProgressBar(self, ...):
App 클래스의 인스턴스인 self에 새로운 진행 바 위젯을 생성합니다. - orientation="horizontal":
진행 바가 수평 방향으로 표시되도록 설정합니다. - width=800:
진행 바의 너비를 800픽셀로 설정합니다. - mode="indeterminate":
진행 바의 모드를 **불확정 모드(indeterminate)**로 설정합니다.
이 모드는 작업의 총 진행률을 알 수 없을 때 사용됩니다.
진행 바의 막대가 왼쪽에서 오른쪽으로 반복적으로 움직이며 작업이 진행 중임을 나타냅니다.
확정 모드 (determinate)
1. 용도: 작업의 전체 진행 상태를 정확히 알고 있을 때 사용합니다.
2. 표현: 진행 바의 막대가 0%에서 100%까지 순차적으로 채워지며, 작업이 얼마나 완료되었는지 직관적으로 보여줍니다3. 예시:
- 파일 다운로드 시 총 파일 크기와 현재 다운로드된 용량을 모두 알 경우.
- 여러 파일의 압축을 해제할 때, 완료된 파일의 개수를 기준으로 진행률을 계산할 경우.
만약 확정 모드를 사용하려면 CTkProgressBar의 mode를 "determinate"로 설정해야 합니다. 그리고 set() 또는 step() 메서드를 사용하여 진행률을 업데이트해야 합니다. 이 예시에서 progress_bar.set(value) 메서드는 value에 해당하는 값(0.0에서 1.0 사이)으로 진행률을 설정합니다.
import customtkinter as ctk
app = ctk.CTk()
# 확정 모드로 진행 바 생성
progress_bar = ctk.CTkProgressBar(app, mode="determinate")
progress_bar.pack(pady=20)
# 진행 바의 초기 값 설정 (예: 0.1은 10%)
progress_bar.set(0.1)
# 또는 step()을 사용하여 조금씩 진행
# progress_bar.step()
# 진행률을 50%로 설정
progress_bar.set(0.5)
# 작업이 완료되면 100%로 설정
# progress_bar.set(1.0)
▣ Folder selection frame
# Folder selection frame
frame = ctk.CTkFrame(self)
frame.pack(pady=10)
self.entry = ctk.CTkEntry(frame, width=300)
self.entry.pack(side="right", padx=5)
button = ctk.CTkButton(frame, text="검색 폴더 선택", command=self.select_folder)
button.pack(side="left")
두 개의 위젯(버튼과 입력 상자)을 하나의 컨테이너(프레임) 안에 배치하여, 기능적으로 묶고 깔끔하게 정렬하는 역할을 합니다.
코드 기능 설명
1. frame = ctk.CTkFrame(self)
- CTkFrame은 다른 UI 위젯들을 담는 컨테이너 역할을 합니다. 마치 박스처럼, 여러 위젯들을 그룹화하는 데 사용됩니다.
- 여기서 self는 메인 애플리케이션 창(App 클래스의 인스턴스)을 의미하며, 이 프레임이 메인 창 안에 위치하게 됨을 나타냅니다.
- frame.pack(pady=10)는 이 프레임을 메인 창에 배치하고, 위아래로 10픽셀의 여백(pady)을 줍니다.
2. self.entry = ctk.CTkEntry(frame, width=500)
- CTkEntry는 사용자가 텍스트를 입력하거나, 코드로 텍스트를 표시할 수 있는 입력 상자 위젯입니다.
- frame을 부모 위젯으로 지정하여, 이 입력 상자가 프레임 안에 속하도록 만듭니다.
- width=500은 입력 상자의 너비를 500픽셀로 설정합니다.
3. self.entry.pack(side="left", padx=5)
- pack()은 위젯을 부모 컨테이너에 배치하는 가장 간단한 방법입니다.
- side="left"는 입력 상자를 프레임의 왼쪽에 배치하도록 지시합니다.
- padx=5는 위젯의 좌우에 5픽셀의 내부 여백을 추가하여, 다른 위젯과 너무 가깝게 붙지 않도록 합니다.
4. button = ctk.CTkButton(frame, text="검색 폴더 선택", command=self.select_folder)
- CTkButton은 사용자가 클릭할 수 있는 버튼 위젯입니다.
- 이 버튼 역시 frame 안에 위치합니다.
- text="검색 폴더 선택"는 버튼에 표시될 텍스트를 지정합니다.
- command=self.select_folder는 버튼이 클릭되었을 때 self.select_folder라는 메서드를 실행하도록 연결합니다.
5. button.pack(side="left")
- pack(side="left")를 통해, 이 버튼도 프레임의 왼쪽에 배치됩니다. entry 위젯이 이미 왼쪽에 있기 때문에, 이 버튼은 그 바로 옆(오른쪽)에 자연스럽게 위치하게 됩니다.
▣ Custom scrollable frame with horizontal scroll
# Custom scrollable frame with horizontal scroll
self.canvas = Canvas(self, bg="white", width=800, height=400)
self.h_scrollbar = Scrollbar(self, orient="horizontal", command=self.canvas.xview)
self.canvas.configure(xscrollcommand=self.h_scrollbar.set)
self.h_scrollbar.pack(side="bottom", fill="x")
self.canvas.pack(pady=10)
self.inner_frame = ctk.CTkFrame(self.canvas)
self.canvas.create_window((0, 0), window=self.inner_frame, anchor="nw")
self.inner_frame.bind("<Configure>", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")))
CustomTkinter와 Tkinter의 Canvas 위젯을 활용하여 가로 스크롤이 가능한 영역을 만드는 방법입니다. 특히, 그래프와 같이 크기가 유동적인 내용을 담는 데 유용합니다.
주요 기능
이 코드는 다음과 같은 세 가지 주요 위젯을 조합하여 스크롤 가능한 컨테이너를 만듭니다.
- Canvas: 그림이나 그래프와 같이 동적인 콘텐츠를 그릴 수 있는 영역입니다.
- Scrollbar: Canvas와 연동되어 사용자가 콘텐츠를 가로로 스크롤할 수 있게 해줍니다.
- CTkFrame: 스크롤될 실제 내용(여기서는 Matplotlib 그래프)을 담는 보이지 않는 컨테이너 역할을 합니다.
by korealionkk@gmail.com

'업무 자동화 > python & CAD' 카테고리의 다른 글
| Qt Designer 학습] python 코드와 연결 하는 방법? (0) | 2025.09.09 |
|---|---|
| Python 개발] 하위 폴더의 용량 비교 #2 (0) | 2025.09.08 |
| Python 학습] 가상환경 설정 (0) | 2025.09.08 |
| Get dimensions from a Creo model #1 (0) | 2025.09.07 |
| Get Feature Names from Creo Models (0) | 2025.09.06 |