본문 바로가기
  • You find inspiration to create your own path !
업무 자동화/python & CAD

Python 개발] 하위 폴더의 용량 비교 #1

by ToolBOX01 2025. 9. 8.
반응형

사용자가 선택한 폴더 안의 하위 폴더 크기를 시각화하는 데스크톱 애플리케이션입니다. 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")))

CustomTkinterTkinter의 Canvas 위젯을 활용하여 가로 스크롤이 가능한 영역을 만드는 방법입니다. 특히, 그래프와 같이 크기가 유동적인 내용을 담는 데 유용합니다.

주요 기능

이 코드는 다음과 같은 세 가지 주요 위젯을 조합하여 스크롤 가능한 컨테이너를 만듭니다.

  1. Canvas: 그림이나 그래프와 같이 동적인 콘텐츠를 그릴 수 있는 영역입니다.
  2. Scrollbar: Canvas와 연동되어 사용자가 콘텐츠를 가로로 스크롤할 수 있게 해줍니다.
  3. CTkFrame: 스크롤될 실제 내용(여기서는 Matplotlib 그래프)을 담는 보이지 않는 컨테이너 역할을 합니다.

 

by korealionkk@gmail.com


반응형