Scaling can only be set on initialization (or restart)
# Enable High DPI display with PyQt
os.environ.setdefault("QT_SCALE_FACTOR", str(rcParams["scale_factor"]))
if hasattr(QApplication, "setAttribute"):
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
Use qdarkstyle and implement it with:
import qdarkstyle
app = QApplication(sys.argv)
app.setStyleSheet(qdarkstyle.load_stylesheet(palette=qdarkstyle.LightPalette))
# simple method to enable/disable darkmode for the MainWindow
def enable_dark_mode(self, state=True):
"""Enable or disable dark mode."""
if state:
self.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
else:
self.setStyleSheet(self.default_style_sheet)
Recommend using a status bar to show progress using a QProgressBar. Also, change the cursor to busy when running.
def cursor_busy(self, busy):
"""Show a busy cursor or revert it to the default cursor.
Parameters
----------
busy : bool
When ``True``, show a busy cursor. When ``False``, show the normal cursor.
"""
if busy:
QApplication.setOverrideCursor(Qt.WaitCursor)
else:
QApplication.restoreOverrideCursor()
Here's the status bar class:
class SystemUsageStatusBar(QStatusBar):
"""
A custom QStatusBar to show real-time system usage.
This status bar shows real-time CPU and memory usage at the bottom
of a QMainWindow. The CPU usage and memory usage are shown in
percentage and GB, respectively. The values are updated every second.
Parameters
----------
parent : Optional[QWidget]
Parent widget.
"""
_disable_progress_bar_signal = Signal()
_increment_progress_signal = Signal(int)
_initialize_progress_bar_signal = Signal(int, str)
_set_progress_signal = Signal(int)
_show_progress_bar_executing_signal = Signal()
def __init__(self, parent=None):
"""Initialize this statusbar."""
super().__init__(parent)
# Initialize Progress bar
self._progress_bar = QProgressBar()
self._progress_bar.setRange(0, 100) # default range 0-100%
self._progress_bar.setValue(0)
self._progress_bar.setFormat(f"Not Executing")
self._progress_bar.setTextVisible(True)
self._progress_bar.setEnabled(False)
self._progress_bar.setMinimumWidth(200)
self.addPermanentWidget(self._progress_bar, 1)
# Add an empty widget with stretch factor to push CPU and Memory labels to the right
self.addPermanentWidget(QLabel(), 1)
# Initialize CPU usage label
self._cpu_label = QLabel()
self._cpu_label.setFrameStyle(QLabel.Panel | QLabel.Sunken) # Add border
self._cpu_label.setMinimumWidth(120) # Set fixed width
self.addPermanentWidget(self._cpu_label)
# Initialize Memory usage label
self._memory_label = QLabel()
self._memory_label.setFrameStyle(QLabel.Panel | QLabel.Sunken) # Add border
self._memory_label.setMinimumWidth(180) # Set fixed width
self.addPermanentWidget(self._memory_label)
# Initialize timer for updating the status bar
self._status_timer = QTimer()
self._status_timer.timeout.connect(self._update_status)
self._status_timer.start(1000) # update every second
# Update the status bar for the first time
self._update_status()
# Connect signals
self._disable_progress_bar_signal.connect(self._disable_progress_bar)
self._increment_progress_signal.connect(self._increment_progress)
self._initialize_progress_bar_signal.connect(self._initialize_progress_bar)
self._set_progress_signal.connect(self._set_progress)
self._show_progress_bar_executing_signal.connect(
self._show_progress_bar_executing
)
def show_progress_bar_executing(self):
"""Show the status bar executing using a signal."""
self._show_progress_bar_executing_signal.emit()
def _show_progress_bar_executing(self):
"""Show the status bar executing."""
self._progress_bar.setRange(0, 0)
def initialize_progress_bar(self, value, label=""):
"""Set the maximum value and label of the progress bar using a signal."""
self._initialize_progress_bar_signal.emit(value, label)
def _initialize_progress_bar(self, max_value, label):
"""Set the maximum value of the progress bar."""
self._progress_bar.setValue(0)
if len(label) > 23:
label = f"{label[:20]}..."
# %p is replaced by the percentage completed.
self._progress_bar.setFormat(f"{label} %p%")
self._progress_bar.setMaximum(max_value)
self._progress_bar.setTextVisible(True)
self._progress_bar.setEnabled(True)
def increment_progress(self, value):
"""Increments the progress bar by a specified value using a signal."""
self._increment_progress_signal.emit(value)
def _increment_progress(self, value):
"""Increments the progress bar by one tick."""
if self._progress_bar.value() + value > self._progress_bar.maximum():
self._progress_bar.setValue(self._progress_bar.maximum())
else:
self._progress_bar.setValue(self._progress_bar.value() + value)
def set_progress(self, value):
"""Set the value of the progress bar to a specified value using a signal."""
self._set_progress_signal.emit(value)
def _set_progress(self, value):
"""Set the value of the progress bar to a specified value."""
self._progress_bar.setValue(value)
def disable_progress_bar(self):
"""Reset and disable the progress bar using a signal."""
self._disable_progress_bar_signal.emit()
def _disable_progress_bar(self):
"""Disable the progress bar."""
self._progress_bar.setValue(0)
self._progress_bar.setMaximum(1)
self._progress_bar.setFormat(f"Not Executing")
self._progress_bar.setEnabled(False)
def disable_progress_bar_executing(self):
"""Disable the progress bar executing."""
if self._progress_bar.maximum() == 0:
self.disable_progress_bar()
def _update_status(self):
"""
Update the status bar with the current system usage.
This method fetches the current system usage (CPU and memory) and
updates the status bar with these values. It is connected to
_status_timer's timeout signal and is called every second.
"""
cpu_usage, used_memory_gb, total_memory_gb = get_system_usage()
memory_usage_percent = (used_memory_gb / total_memory_gb) * 100
self._cpu_label.setText(f"CPU Usage: {cpu_usage:5.1f}%")
self._memory_label.setText(
f"Memory Usage: {used_memory_gb:.1f}GB/{total_memory_gb:.1f}GB ({memory_usage_percent:.1f}%)"
)
def reset(self):
"""
Reset this status bar.
For now, this just disables the progress bar.
"""
self.disable_progress_bar()
One way to avoid adding modifying each method that is long running is to use a protected_thread
decorator and then setting the progress bar progress and cursor to "busy."
def protected_thread(fn):
"""Call a function using a thread.
Reports error under the assumption the first argument is an object
containing a ``main_window``.
"""
@wraps(fn)
def wrapper(*args, **kwargs):
self = args[0]
def protected_fn():
try:
if hasattr(self, "main_window"):
self.main_window.cursor_busy(True)
self.main_window.pbar_show_executing()
...