ColorButton
A color-selector tool for PyQt

Below is a short snippet to implement a color-picker attached to a button in Qt. Clicking on the button pops up a dialog (native) to select a color. The color is shown by the color of the button face. You can provide a default color which will be the initial state of the button when it is created -- right-clicking on the button will reset the color to this default value (or None if no default is set)

To get the currently set color, use button.color().

python
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt, pyqtSignal

class ColorButton(QtWidgets.QPushButton):
    '''
    Custom Qt Widget to show a chosen color.

    Left-clicking the button shows the color-chooser, while
    right-clicking resets the color to None (no-color).
    '''

    colorChanged = pyqtSignal(object)

    def __init__(self, *args, color=None, **kwargs):
        super(ColorButton, self).__init__(*args, **kwargs)

        self._color = None
        self._default = color
        self.pressed.connect(self.onColorPicker)

        # Set the initial/default state.
        self.setColor(self._default)

    def setColor(self, color):
        if color != self._color:
            self._color = color
            self.colorChanged.emit(color)

        if self._color:
            self.setStyleSheet("background-color: %s;" % self._color)
        else:
            self.setStyleSheet("")

    def color(self):
        return self._color

    def onColorPicker(self):
        '''
        Show color-picker dialog to select color.

        Qt will use the native dialog by default.

        '''
        dlg = QtWidgets.QColorDialog(self)
        if self._color:
            dlg.setCurrentColor(QtGui.QColor(self._color))

        if dlg.exec_():
            self.setColor(dlg.currentColor().name())

    def mousePressEvent(self, e):
        if e.button() == Qt.RightButton:
            self.setColor(self._default)

        return super(ColorButton, self).mousePressEvent(e)

python
from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtCore import Qt, Signal


class ColorButton(QtWidgets.QPushButton):
    '''
    Custom Qt Widget to show a chosen color.

    Left-clicking the button shows the color-chooser, while
    right-clicking resets the color to None (no-color).
    '''

    colorChanged = Signal(object)

    def __init__(self, *args, color=None, **kwargs):
        super(ColorButton, self).__init__(*args, **kwargs)

        self._color = None
        self._default = color
        self.pressed.connect(self.onColorPicker)

        # Set the initial/default state.
        self.setColor(self._default)

    def setColor(self, color):
        if color != self._color:
            self._color = color
            self.colorChanged.emit(color)

        if self._color:
            self.setStyleSheet("background-color: %s;" % self._color)
        else:
            self.setStyleSheet("")

    def color(self):
        return self._color

    def onColorPicker(self):
        '''
        Show color-picker dialog to select color.

        Qt will use the native dialog by default.

        '''
        dlg = QtWidgets.QColorDialog(self)
        if self._color:
            dlg.setCurrentColor(QtGui.QColor(self._color))

        if dlg.exec_():
            self.setColor(dlg.currentColor().name())

    def mousePressEvent(self, e):
        if e.button() == Qt.RightButton:
            self.setColor(self._default)

        return super(ColorButton, self).mousePressEvent(e)

This custom widget is also available in the LearnPyQt qtwidgets library.

Create GUI Applications with Python & Qt5
The easy way to create desktop applications

The complete guide to building GUI applications with PySide2. From the basics of creating a desktop window to the key features you need to build real apps.

Downloadable ebook (PDF, ePub) & Complete Source code

To support developers in [[ countryRegion ]] I give a [[ localizedDiscount[couponCode] ]]% discount with the code [[ couponCode ]] — Enjoy!

For [[ activeDiscount.description ]] I'm giving a [[ activeDiscount.discount ]]% discount with the code [[ couponCode ]] — Enjoy!

Continue reading

Simple threading in PyQt/PySide apps with .start() of QThreadPool  PySide

In PyQt version 5.15.0 and PySide 6.2.0, the .start() method of QThreadPool was extended to take a Python function, a Python method, or a PyQt/PySide slot, besides taking only a QRunnable object. This simplifies running Python code in the background, avoiding the hassle of creating a QRunnable object for each task. For more information about creating a QRunnable object for multithreading, see the multithreading tutorial. The .start() method schedules the execution of a function/method/slot on a separate thread using QThreadPool, so it avoids blocking the main GUI thread of your app. Therefore, if you have one or more long-running tasks that need to be completed or be running in the background, pass them to .start() and be done. We'll build a simple demo app that simulates a long-running task to show how .start() can move a user-defined Python function/method or a PyQt/PySide slot onto a separate thread. But first, let’s begin with a flawed approach. Blocking the GUI Our demo app is a sheep counter that counts upwards from 1. While this is happening, you can press a button to pick a sheep. And since picking a sheep is hard, it takes some time to complete. This is how our demo app looks like. Make sure you're using PyQt 5.15.0+ or PySide 6.2.0+; otherwise, the demo app won’t work for you. PySide6 PyQt6 PyQt5 python import time from PySide6.QtCore import Slot, QTimer from PySide6.QtWidgets import ( QLabel, QWidget, QMainWindow, QPushButton, QVBoxLayout, QApplication, ) class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.setFixedSize(250, 100) self.setWindowTitle("Sheep Picker") self.sheep_number = 1 self.timer = QTimer() self.picked_sheep_label = QLabel() self.counted_sheep_label = QLabel() self.layout = QVBoxLayout() self.main_widget = QWidget() self.pick_sheep_button = QPushButton("Pick a sheep!") self.layout.addWidget(self.counted_sheep_label) self.layout.addWidget(self.pick_sheep_button) self.layout.addWidget(self.picked_sheep_label) self.main_widget.setLayout(self.layout) self.setCentralWidget(self.main_widget) self.timer.timeout.connect(self.count_sheep) self.pick_sheep_button.pressed.connect(self.pick_sheep) self.timer.start() @Slot() def count_sheep(self): self.sheep_number += 1 self.counted_sheep_label.setText(f"Counted {self.sheep_number} sheep.") @Slot() def pick_sheep(self): self.picked_sheep_label.setText(f"Sheep {self.sheep_number} picked!") time.sleep(5) # This function represents a long-running task! if __name__ == "__main__": app = QApplication([]) main_window = MainWindow() main_window.show() app.exec() python import time from PyQt6.QtCore import pyqtSlot, QTimer from PyQt6.QtWidgets import ( QLabel, QWidget, QMainWindow, QPushButton, QVBoxLayout, QApplication, ) class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.setFixedSize(250, 100) self.setWindowTitle("Sheep Picker") self.sheep_number = 1 self.timer = QTimer() self.picked_sheep_label = QLabel() self.counted_sheep_label = QLabel() self.layout = QVBoxLayout() self.main_widget = QWidget() self.pick_sheep_button = QPushButton("Pick a sheep!") self.layout.addWidget(self.counted_sheep_label) self.layout.addWidget(self.pick_sheep_button) self.layout.addWidget(self.picked_sheep_label) self.main_widget.setLayout(self.layout) self.setCentralWidget(self.main_widget) self.timer.timeout.connect(self.count_sheep) self.pick_sheep_button.pressed.connect(self.pick_sheep) self.timer.start() @pyqtSlot() def count_sheep(self): self.sheep_number += 1 self.counted_sheep_label.setText(f"Counted {self.sheep_number} sheep.") @pyqtSlot() def pick_sheep(self): self.picked_sheep_label.setText(f"Sheep {self.sheep_number} picked!") time.sleep(5) # This function represents a long-running task! if __name__ == "__main__": app = QApplication([]) main_window = MainWindow() main_window.show() app.exec() python import time from PyQt5.QtCore import pyqtSlot, QTimer from PyQt5.QtWidgets import ( QLabel, QWidget, QMainWindow, QPushButton, QVBoxLayout, QApplication, ) class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.setFixedSize(250, 100) self.setWindowTitle("Sheep Picker") self.sheep_number = 1 self.timer = QTimer() self.picked_sheep_label = QLabel() self.counted_sheep_label = QLabel() self.layout = QVBoxLayout() self.main_widget = QWidget() self.pick_sheep_button = QPushButton("Pick a sheep!") self.layout.addWidget(self.counted_sheep_label) self.layout.addWidget(self.pick_sheep_button) self.layout.addWidget(self.picked_sheep_label) self.main_widget.setLayout(self.layout) self.setCentralWidget(self.main_widget) self.timer.timeout.connect(self.count_sheep) self.pick_sheep_button.pressed.connect(self.pick_sheep) self.timer.start() @pyqtSlot() def count_sheep(self): self.sheep_number += 1 self.counted_sheep_label.setText(f"Counted {self.sheep_number} sheep.") @pyqtSlot() def pick_sheep(self): self.picked_sheep_label.setText(f"Sheep {self.sheep_number} picked!") time.sleep(5) # This function represents a long-running task! if __name__ == "__main__": app = QApplication([]) main_window = MainWindow() main_window.show() app.exec() When you run the demo app and press the Pick a sheep! button, you’ll notice that for 5 seconds, the GUI is completely unresponsive. That's not good. The delay in GUI responsiveness comes from the line time.sleep(5) which pauses execution of Python code for 5 seconds. This was added to simulate a long-running task. We can, however, improve that by threading, as you’ll see later on. Feel free to experiment by increasing the length of the delay – pass a number greater than 5 to .sleep() – and you may notice that your operating system starts complaining about the demo app not responding. Run a task on a separate thread So, how can we improve the responsiveness of our demo app? This is where the extended .start() method of QThreadPool comes in! First, we need to import QThreadPool, so let’s do that. PySide6 PyQt6 PyQt5 python from PySide6.QtCore import QThreadPool python from PyQt6.QtCore import QThreadPool python from PyQt5.QtCore import QThreadPool Next, we need to create a QThreadPool instance. Let’s add python self.thread_manager = QThreadPool() to the __init__ block of the MainWindow class. Now, let’s create a pick_sheep_safely() slot. It will use the .start() method to call the long-running pick_sheep() slot and move it from the main GUI thread onto a separate thread. PySide PyQt python @Slot() def pick_sheep_safely(self): self.thread_manager.start(self.pick_sheep) # This is where the magic happens! python @pyqtSlot() def pick_sheep_safely(self): self.thread_manager.start(self.pick_sheep) # This is where the magic happens! Also, make sure that you connect the pick_sheep_safely() slot with the pressed signal of self.pick_sheep_button. So, in the __init__ block of the MainWindow class, you should have python self.pick_sheep_button.pressed.connect(self.pick_sheep_safely) And if you followed along, the code of our improved demo app should now be: PySide6 PyQt6 PyQt5 python import time from PySide6.QtCore import Slot, QThreadPool, QTimer from PySide6.QtWidgets import ( QLabel, QWidget, QMainWindow, QPushButton, QVBoxLayout, QApplication, ) class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.setFixedSize(250, 100) self.setWindowTitle("Sheep Picker") self.sheep_number = 1 self.timer = QTimer() self.picked_sheep_label = QLabel() self.counted_sheep_label = QLabel() self.layout = QVBoxLayout() self.main_widget = QWidget() self.thread_manager = QThreadPool() self.pick_sheep_button = QPushButton("Pick a sheep!") self.layout.addWidget(self.counted_sheep_label) self.layout.addWidget(self.pick_sheep_button) self.layout.addWidget(self.picked_sheep_label) self.main_widget.setLayout(self.layout) self.setCentralWidget(self.main_widget) self.timer.timeout.connect(self.count_sheep) self.pick_sheep_button.pressed.connect(self.pick_sheep_safely) self.timer.start() @Slot() def count_sheep(self): self.sheep_number += 1 self.counted_sheep_label.setText(f"Counted {self.sheep_number} sheep.") @Slot() def pick_sheep(self): self.picked_sheep_label.setText(f"Sheep {self.sheep_number} picked!") time.sleep(5) # This function doesn't affect GUI responsiveness anymore... @Slot() def pick_sheep_safely(self): self.thread_manager.start(self.pick_sheep) # ...since .start() is used! if __name__ == "__main__": app = QApplication([]) main_window = MainWindow() main_window.show() app.exec() python import time from PyQt6.QtCore import pyqtSlot, QThreadPool, QTimer from PyQt6.QtWidgets import ( QLabel, QWidget, QMainWindow, QPushButton, QVBoxLayout, QApplication, ) class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.setFixedSize(250, 100) self.setWindowTitle("Sheep Picker") self.sheep_number = 1 self.timer = QTimer() self.picked_sheep_label = QLabel() self.counted_sheep_label = QLabel() self.layout = QVBoxLayout() self.main_widget = QWidget() self.thread_manager = QThreadPool() self.pick_sheep_button = QPushButton("Pick a sheep!") self.layout.addWidget(self.counted_sheep_label) self.layout.addWidget(self.pick_sheep_button) self.layout.addWidget(self.picked_sheep_label) self.main_widget.setLayout(self.layout) self.setCentralWidget(self.main_widget) self.timer.timeout.connect(self.count_sheep) self.pick_sheep_button.pressed.connect(self.pick_sheep_safely) self.timer.start() @pyqtSlot() def count_sheep(self): self.sheep_number += 1 self.counted_sheep_label.setText(f"Counted {self.sheep_number} sheep.") @pyqtSlot() def pick_sheep(self): self.picked_sheep_label.setText(f"Sheep {self.sheep_number} picked!") time.sleep(5) # This function doesn't affect GUI responsiveness anymore... @pyqtSlot() def pick_sheep_safely(self): self.thread_manager.start(self.pick_sheep) # ...since .start() is used! if __name__ == "__main__": app = QApplication([]) main_window = MainWindow() main_window.show() app.exec() python import time from PyQt5.QtCore import pyqtSlot, QThreadPool, QTimer from PyQt5.QtWidgets import ( QLabel, QWidget, QMainWindow, QPushButton, QVBoxLayout, QApplication, ) class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.setFixedSize(250, 100) self.setWindowTitle("Sheep Picker") self.sheep_number = 1 self.timer = QTimer() self.picked_sheep_label = QLabel() self.counted_sheep_label = QLabel() self.layout = QVBoxLayout() self.main_widget = QWidget() self.thread_manager = QThreadPool() self.pick_sheep_button = QPushButton("Pick a sheep!") self.layout.addWidget(self.counted_sheep_label) self.layout.addWidget(self.pick_sheep_button) self.layout.addWidget(self.picked_sheep_label) self.main_widget.setLayout(self.layout) self.setCentralWidget(self.main_widget) self.timer.timeout.connect(self.count_sheep) self.pick_sheep_button.pressed.connect(self.pick_sheep_safely) self.timer.start() @pyqtSlot() def count_sheep(self): self.sheep_number += 1 self.counted_sheep_label.setText(f"Counted {self.sheep_number} sheep.") @pyqtSlot() def pick_sheep(self): self.picked_sheep_label.setText(f"Sheep {self.sheep_number} picked!") time.sleep(5) # This function doesn't affect GUI responsiveness anymore... @pyqtSlot() def pick_sheep_safely(self): self.thread_manager.start(self.pick_sheep) # ...since .start() is used! if __name__ == "__main__": app = QApplication([]) main_window = MainWindow() main_window.show() app.exec() When you press the Pick a sheep! button now, the pick_sheep() slot is executed on a separate thread and no longer blocks the main GUI thread. The sheep counting goes on, and the GUI remains responsive – even though our demo app still has to complete a long-running task in the background. Try increasing the length of the delay now – for example, time.sleep(10) – and notice that it doesn’t affect the GUI anymore. Conclusion And that’s it! I hope you’ll find the extended .start() method of QThreadPool helpful in any of your PyQt/PySide GUI apps that have long-running tasks to be executed in the background. More