개요
최근에 GUI 애플리케이션을 개발하면서 문득 든 생각인데,
백그라운드에서 시간이 많이 걸리는 작업(예: 웹 스크래핑)을 수행할 때
사용자에게 현재 진행 상황을 실시간으로 보여주는 Progress bar가 UI에 있으면 좋겠다라는 생각을 했다.
그래서 이번 포스팅에서는 PySide6에서 QProgressBar를 어떤 식으로 활용할 수 있을지 예제 코드들을 작성해보려고 한다.
기본 예제
이번 챕터에서는 QProgressBar
의 기본 응용 방법에 대해서 알아보도록 하겠다.
1. QProgressBar 사용방법
- 최소/최대 값 설정:
setMinimum()
과setMaximum()
메서드를 사용하여 진행률의 범위를 정할 수 있음. - 현재 진행률 업데이트:
setValue()
를 통해 현재 진행 상황을 업데이트.
QProgressBar
를 응용하는 예제 코드는 다음과 같다.
import sys
from PySide6.QtWidgets import QApplication, QMainWindow, QProgressBar, QPushButton, QVBoxLayout, QWidget
from PySide6.QtCore import QBasicTimer
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("기본 Progress Bar 예제")
# QProgressBar 생성 및 최소, 최대값 설정
self.progressBar = QProgressBar()
self.progressBar.setMinimum(0)
self.progressBar.setMaximum(100)
# 진행 시작/중지를 위한 버튼 생성
self.button = QPushButton("진행 시작")
self.button.clicked.connect(self.startProgress)
# 레이아웃 설정
layout = QVBoxLayout()
layout.addWidget(self.progressBar)
layout.addWidget(self.button)
container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)
self.timer = QBasicTimer()
self.step = 0
def startProgress(self):
if self.timer.isActive():
self.timer.stop()
self.button.setText("진행 시작")
else:
self.timer.start(100, self)
self.button.setText("진행 중지")
def timerEvent(self, event):
if self.step >= 100:
self.timer.stop()
self.button.setText("진행 시작")
return
self.step += 1
self.progressBar.setValue(self.step)
if __name__ == "__main__":
app = QApplication([])
window = MainWindow()
window.show()
sys.exit(app.exec())
이 예제는 버튼을 클릭할 때마다 타이머를 시작하거나 중지하며,
타이머 이벤트를 통해 progressBar
의 값이 점진적으로 증가한다.
2. 사용자 친화적인 진행률 표시하기
시각적 피드백 강화
- 애니메이션 효과: 진행률이 부드럽게 증가하는 애니메이션 효과를 주면 사용자가 진행 상황을 더 직관적으로 이해할 수 있음.
- 색상과 스타일 커스터마이징: Qt의 스타일 시트(QSS)를 활용하여 진행률 막대의 색상, 경계선, 배경 등을 변경하여 사용자에게 매력적인 UI를 제공해보기
기본적으로 PySide6에서 제공하는 QProgressBar
는 다음과 같이 밋밋한 디자인을 띄고있다.
스타일 시트를 간단하게 적용하면
self.progressBar.setStyleSheet("""
QProgressBar {
border: 2px solid grey;
border-radius: 5px;
text-align: center;
}
QProgressBar::chunk {
background-color: #05B8CC;
width: 20px;
}
""")
심화 예제
이번 챕터에서는 조금 더 심화된 버전으로 QProgressBar
를 응용하는 방법에 대해서 알아보겠다.
QThread 응용 (1)
크롤링 작업을 별도의 QThread
에서 처리하고,
작업 도중 발생하는 데이터를 dict
형태로 Signal을 통해 메인 UI로 전달할거다.
메인 UI에서는 전달받은 데이터를 QLabel
에 표시해주고,
QProgressBar
의 값도 업데이트하여 사용자에게 현재 작업 상태를 명확하게 표시해준다.
아래 코드는 위 아이디어를 반영하여 작성해본 예제 코드이다.
import sys
import time
from PySide6.QtCore import QThread, Signal, Qt
from PySide6.QtWidgets import QApplication, QMainWindow, QProgressBar, QLabel, QVBoxLayout, QWidget, QMessageBox
# QThread를 상속받은 Worker 클래스
class Worker(QThread):
# 데이터를 dict 형태로 전달하는 Signal
data = Signal(dict)
# 진행률을 나타내는 int 값을 전달하는 Signal
progress = Signal(int)
def run(self):
total_items = 100 # 전체 작업 수 예시
for i in range(total_items):
# 실제 크롤링 작업이 이루어지는 부분 (여기서는 시간 지연으로 대체)
time.sleep(0.05)
# 예시로 dict 형태의 데이터 생성
result = {"index": i + 1, "message": f"Item {i + 1} 크롤링 완료"}
# 데이터 emit
self.data.emit(result)
# 진행률 emit
self.progress.emit(i + 1)
# 모든 작업 완료 후, 진행률을 확실하게 100%로 업데이트
self.progress.emit(total_items)
# 메인 UI 클래스
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("크롤링 진행률 예제")
# Progress Bar 생성 (최소 0, 최대 100 설정)
self.progressBar = QProgressBar()
self.progressBar.setMinimum(0)
self.progressBar.setMaximum(100)
self.progressBar.setAlignment(Qt.AlignCenter)
# 데이터를 표시할 라벨 생성
self.dataLabel = QLabel("데이터 수신 대기 중...")
self.dataLabel.setAlignment(Qt.AlignCenter)
# 레이아웃 설정
layout = QVBoxLayout()
layout.addWidget(self.progressBar)
layout.addWidget(self.dataLabel)
container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)
# Worker 인스턴스 생성 및 Signal 연결
self.worker = Worker()
self.worker.data.connect(self.handle_data)
self.worker.progress.connect(self.progressBar.setValue)
# Worker 스레드 시작 (백그라운드 작업 수행)
self.worker.start()
# QThread에서 emit된 데이터를 받는 슬롯
def handle_data(self, data: dict):
self.dataLabel.setText(f"{data['message']} (#{data['index']})")
# 모든 작업이 완료되면 사용자에게 메시지 박스 표시
if data["index"] == 100:
QMessageBox.information(self, "완료", "모든 크롤링 작업이 완료되었습니다!")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.resize(400, 200)
window.show()
sys.exit(app.exec())
QThread 응용 (2)
Worker
에서 전체 추출할 데이터 개수(혹은 예상 개수)를 미리 알려주는 시그널을 추가MainUI
에서QProgressBar.setMaximum()
으로 최대값 설정- 데이터가 올 때마다
QProgressBar.setValue()
로 현재값 업데이트
1. Worker 클래스에 총 개수 전달용 시그널 추가
class Worker(QThread):
data = Signal(dict)
total = Signal(int) # 전체 개수 전달용
def __init__(self):
super().__init__()
self.page = 10
def run(self) -> None:
payloads = [{"page": page} for page in range(1, self.page + 1)]
total_count = 0
all_data = []
for payload in payloads:
# 크롤링 로직
resp = rq.get(url="", params=payload)
resp_json = resp.json()
datas = resp_json.get("data", [])
all_data.extend(datas)
self.total.emit(len(all_data)) # 총 개수 전달
for data in all_data:
title = data.get("title")
self.data.emit({"제목": title})
2. MainUI에 프로그래스바와 카운터 추가
class MainUI(QWidget, main_ui):
def __init__(self):
super().__init__()
self.setupUi(self)
self.worker = Worker()
self.worker.data.connect(self.update_data)
self.worker.total.connect(self.set_total)
self.progress_count = 0
self.total_count = 0
self.worker.start()
def set_total(self, count: int) -> None:
self.total_count = count
self.progressBar.setMaximum(count)
self.progressBar.setValue(0)
def update_data(self, data: dict) -> None:
self.progress_count += 1
self.progressBar.setValue(self.progress_count)
# 추가적인 데이터 UI 처리 가능
print(f"받은 데이터: {data}")
무한 진행 상태
이번에는 설치 프로그램에서 흔히 보는 움직이는 막대 효과처럼 애니메이션을 설정하는 방법에 대해서 알아보겠다.
self.progressBar.setMinimum(0)
self.progressBar.setMaximum(0)
이렇게 설정하면 진행률을 따로 지정하지 않아도, 프로그레스바가 자동으로 반복해서 움직이는 애니메이션을 보여준다.
만약 백그라운드 작업이 완료되었다면
다음과 같이 정상적인 바 형태로 변경해주면 된다.
self.progressBar.setMinimum(0)
self.progressBar.setMaximum(100) # 또는 원하는 최대값
self.progressBar.setValue(0) # 초기값 설정