Of course! The QProgressBar is a fundamental widget in PyQt (and PySide) used to give visual feedback to the user about the progress of a long-running operation. It's an essential part of creating a user-friendly application.
Here’s a comprehensive guide covering everything from basic usage to advanced features.
Basic QProgressBar
Let's start with the simplest example. We'll create a progress bar and a button. When the button is clicked, the progress bar will fill up gradually.
Key Concepts:
QProgressBar(QWidget): The constructor. It's a widget, so it needs a parent.setValue(int): Sets the progress bar's value. The range is defined bysetMinimum()andsetMaximum(). By default, it's 0 to 100.setMinimum(int)/setMaximum(int): Sets the lower and upper bounds of the progress bar.setRange(int, int): A convenient way to set both minimum and maximum at once.
Code:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton, QProgressBar
from PyQt5.QtCore import Qt, QThread, pyqtSignal
# --- Basic Example ---
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("QProgressBar Basic Example")
self.setGeometry(100, 100, 400, 150)
# Create a central widget and a layout
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
# Create the progress bar
self.progress_bar = QProgressBar()
# Set the range (e.g., 0 to 100)
self.progress_bar.setRange(0, 100)
# Optional: Set text format
self.progress_bar.setFormat("%p%") # Shows percentage (e.g., 50%)
# Create a button to start the "process"
self.start_button = QPushButton("Start Process")
self.start_button.clicked.connect(self.start_progress)
# Add widgets to the layout
layout.addWidget(self.progress_bar)
layout.addWidget(self.start_button)
def start_progress(self):
"""Simulates a process by incrementing the progress bar."""
# Disable the button to prevent multiple clicks
self.start_button.setEnabled(False)
# Reset the progress bar
self.progress_bar.setValue(0)
# Simulate work in a loop
for i in range(101):
self.progress_bar.setValue(i)
# A small delay to make the progress visible
# In a real app, you would not use sleep in the main thread!
# This is just for demonstration.
import time
time.sleep(0.02)
# Re-enable the button
self.start_button.setEnabled(True)
# --- Main Execution ---
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Handling Long-Running Tasks (The Correct Way)
The example above is bad practice because it uses time.sleep() in the main GUI thread. This will freeze the application. The user cannot move the window, click other buttons, or even close it.
The correct way to handle long-running tasks is to use a QThread. The main thread (GUI) will not be blocked.
Key Concepts:
QThread: Provides a way to run a task in a separate thread.pyqtSignal: A mechanism for communication between the worker thread and the main GUI thread. The worker thread emits a signal, and the main thread connects it to a slot (a function) that updates the progress bar.
Code (The Recommended Approach):
We'll create a Worker class that inherits from QThread and a pyqtSignal to send progress updates.
import sys
import time
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton, QProgressBar
from PyQt5.QtCore import QThread, pyqtSignal
# 1. Create a Worker class that inherits from QThread
class Worker(QThread):
# Define a signal to send the progress value
progress_signal = pyqtSignal(int)
def run(self):
"""This method runs in the separate thread."""
print("Worker thread started.")
for i in range(101):
# Simulate some work
time.sleep(0.05)
# Emit the signal with the current progress
self.progress_signal.emit(i)
print("Worker thread finished.")
# 2. Modify the MainWindow to use the Worker
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("QProgressBar with QThread")
self.setGeometry(100, 100, 400, 150)
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
self.progress_bar = QProgressBar()
self.progress_bar.setRange(0, 100)
self.progress_bar.setFormat("%p%")
self.start_button = QPushButton("Start Long Task")
self.start_button.clicked.connect(self.start_task)
layout.addWidget(self.progress_bar)
layout.addWidget(self.start_button)
# We will store the worker instance here
self.worker = None
def start_task(self):
# Disable the button to prevent multiple tasks
self.start_button.setEnabled(False)
self.progress_bar.setValue(0)
# Create a new worker instance
self.worker = Worker()
# Connect the worker's signal to the progress bar's update slot
self.worker.progress_signal.connect(self.update_progress)
# Optional: Connect a signal to know when the thread has finished
self.worker.finished.connect(self.task_finished)
# Start the thread
self.worker.start()
def update_progress(self, value):
"""This slot is called from the main thread when the signal is emitted."""
self.progress_bar.setValue(value)
def task_finished(self):
"""This slot is called when the worker thread is finished."""
print("Task finished in main window.")
self.start_button.setEnabled(True)
# --- Main Execution ---
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Customizing the QProgressBar
QProgressBar has several properties to change its appearance.
Key Properties:
setFormat(QString): Defines the text displayed on the progress bar.%p: Percentage (e.g., "50%").%v: Current value (e.g., "50").%m: Maximum value (e.g., "100")."%p% (%v/%m)": "50% (50/100)".
setAlignment(Qt.Alignment): Aligns the text inside the bar (Qt.AlignLeft,Qt.AlignCenter,Qt.AlignRight).setInvertedAppearance(bool): Reverses the direction of the fill.setTextVisible(bool): Hides or shows the text. Useful if you only want the visual bar.setOrientation(Qt.Orientation): Changes the orientation toQt.Vertical.
Code Example (Customization):
# ... (imports are the same as the basic example)
class CustomizedWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Customized QProgressBar")
self.setGeometry(100, 100, 400, 250)
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
# --- Horizontal Bar 1 ---
self.h_bar1 = QProgressBar()
self.h_bar1.setRange(0, 1000)
self.h_bar1.setFormat("Value: %v / 1000")
self.h_bar1.setAlignment(Qt.AlignCenter)
# --- Horizontal Bar 2 (Inverted) ---
self.h_bar2 = QProgressBar()
self.h_bar2.setRange(0, 100)
self.h_bar2.setFormat("%p%")
self.h_bar2.setInvertedAppearance(True) # Fills from right to left
self.h_bar2.setTextVisible(False) # Hide text for a cleaner look
# --- Vertical Bar ---
self.v_bar = QProgressBar()
self.v_bar.setRange(0, 100)
self.v_bar.setOrientation(Qt.Vertical)
self.v_bar.setFormat("%p%")
self.v_bar.setAlignment(Qt.AlignHCenter) # Text alignment for vertical bar
# Button to start a demo
self.start_button = QPushButton("Run Demo")
self.start_button.clicked.connect(self.run_demo)
layout.addWidget(self.h_bar1)
layout.addWidget(self.h_bar2)
layout.addWidget(self.v_bar)
layout.addWidget(self.start_button, alignment=Qt.AlignCenter)
def run_demo(self):
self.start_button.setEnabled(False)
# A simple way to update multiple bars
for i in range(101):
self.h_bar1.setValue(i * 10)
self.h_bar2.setValue(i)
self.v_bar.setValue(i)
QApplication.processEvents() # Keep the UI responsive
time.sleep(0.02)
self.start_button.setEnabled(True)
# --- Main Execution ---
if __name__ == '__main__':
app = QApplication(sys.argv)
# To run this example, you would create an instance of CustomizedWindow
# window = CustomizedWindow()
# window.show()
# sys.exit(app.exec_())
Styling with QSS (Qt Style Sheets)
You can give your progress bar a completely custom look using Qt Style Sheets, similar to CSS for web pages.
Key CSS Selectors:
QProgressBar: The widget itself.QProgressBar::chunk: The part of the bar that is filled.
Code Example (Styling):
# ... (imports are the same as the basic example)
class StyledWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Styled QProgressBar")
self.setGeometry(100, 100, 400, 150)
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
self.progress_bar = QProgressBar()
self.progress_bar.setRange(0, 100)
self.start_button = QPushButton("Start Process")
self.start_button.clicked.connect(self.start_progress)
layout.addWidget(self.progress_bar)
layout.addWidget(self.start_button)
# Apply the style sheet
self.setStyleSheet("""
QProgressBar {
border: 2px solid grey;
border-radius: 5px;
text-align: center;
background-color: #f0f0f0;
}
QProgressBar::chunk {
background-color: #05B8CC; /* A nice blue color */
width: 20px; /* Optional: set a fixed width for chunks */
margin: 1px; /* Optional: add space between chunks */
}
""")
def start_progress(self):
# (Same logic as the basic example, but with QThread in a real app)
self.start_button.setEnabled(False)
self.progress_bar.setValue(0)
for i in range(101):
self.progress_bar.setValue(i)
QApplication.processEvents()
time.sleep(0.02)
self.start_button.setEnabled(True)
# --- Main Execution ---
if __name__ == '__main__':
app = QApplication(sys.argv)
# window = StyledWindow()
# window.show()
# sys.exit(app.exec_())
Summary
| Feature | Method/Property | Description |
|---|---|---|
| Basic Setup | QProgressBar() |
Create the widget. |
setRange(min, max) |
Define the progress scale. | |
setValue(value) |
Set the current progress. | |
| Text | setFormat("...%p%...") |
Customize the displayed text. |
setTextVisible(True/False) |
Show or hide the text. | |
| Orientation | setOrientation(Qt.Vertical) |
Change to a vertical progress bar. |
| Long Tasks | QThread + pyqtSignal |
The correct way to avoid freezing the GUI. |
| Styling | setStyleSheet(...) |
Use CSS-like syntax for custom appearance. |
QProgressBar (selector) |
Styles the main widget (border, background). | |
QProgressBar::chunk (selector) |
Styles the filled part of the bar. |
