Search code examples
pythonpython-3.xpyqt5qpainterqlabel

How to draw with QPainter on top of already placed QLabel or QPixmap?


While experimenting with Python and PyQt5 I got stuck on a problem. I have in my GUI few labels (QLabel) and images (QPixmap) and I want to draw something on them, depending on what the main program does. I can't figure out how though. For example, I change text on labels calling setLabels() from class BinColUI and I would like to draw something on them (i.e. QPainter.drawLine()) just after that. What I tried is not working, there's nothing drawn. My unsuccesful attempt is commented out in setLabels(). How do I do it?

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter, QPen
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QVBoxLayout, QWidget


class BinColUI(QMainWindow):

    def __init__(self):
        super().__init__()
        self.initUi()
        self.createLabelTop()
        self.createLabelBot()

    def initUi(self):
        self.setWindowTitle('Bin Collection')
        self.setFixedSize(500, 500)
        # self.setStyleSheet('background-color: white')
        self.generalLayout = QVBoxLayout()
        self._centralWidget = QWidget(self)
        self.setCentralWidget(self._centralWidget)
        self._centralWidget.setLayout(self.generalLayout)

    def paintEvent(self, event):
        self.qp = QPainter()
        self.qp.begin(self)
        self.drawLine(event, self.qp)
        self.qp.end()

    def drawLine(self, event, qp):
        pen = QPen(Qt.red, 3, Qt.SolidLine)
        qp.setPen(pen)
        qp.drawLine(5, 5, 495, 5)
        qp.drawLine(495, 5, 495, 495)
        qp.drawLine(495, 495, 5, 495)
        qp.drawLine(5, 495, 5, 5)

    def createLabelTop(self):
        self.label_top = QLabel('PLEASE WAIT')
        self.label_top.setAlignment(Qt.AlignCenter)
        self.label_top.setFixedSize(450, 60)
        self.label_top.setStyleSheet("font: 14pt Bahnschrift; color: black; background-color: yellow")
        self.generalLayout.addWidget(self.label_top, alignment=Qt.AlignCenter)

    def createLabelBot(self):
        self.label_bot = QLabel('PLEASE WAIT')
        self.label_bot.setAlignment(Qt.AlignCenter)
        self.label_bot.setFixedSize(450, 60)
        self.label_bot.setStyleSheet("font: 14pt Bahnschrift; color: black; background-color: yellow")
        self.generalLayout.addWidget(self.label_bot, alignment=Qt.AlignCenter)

    def setLabels(self, texttop, textbot):
        # qp = QPainter(self.label_top)
        self.label_top.setText(texttop)
        self.label_bot.setText(textbot)
        # pen = QPen(Qt.red, 3)
        # qp.setPen(pen)
        # qp.drawLine(10, 10, 50, 50)
        # self.label_top.repaint()


class BinColCtrl:

    def __init__(self, view: BinColUI):
        self._view = view
        self.calculateResult()

    def calculateResult(self):
        line_top = 'NEW LABEL TOP'
        line_bottom = 'NEW LABEL BOTTOM'
        self._view.setLabels(line_top, line_bottom)


def main():
    """Main function."""
    # Create an instance of `QApplication`
    bincol = QApplication(sys.argv)
    window = BinColUI()
    window.show()
    BinColCtrl(view=window)
    sys.exit(bincol.exec_())


if __name__ == '__main__':
    main()

Solution

  • In general, the painting of a QWidget (QLabel, QPushButton, etc.) should only be done in the paintEvent method as the OP seems to know. And that painting depends on the information that the widget has, for example QLabel uses a text and draws the text, OR uses a QPixmap and draws based on that pixmap. So in this case you must create a QPixmap where the line is painted, and pass that QPixmap to the QLabel to paint it.

    def setLabels(self, texttop, textbot):
        pixmap = QPixmap(self.label_top.size())
        pixmap.fill(Qt.transparent)
        qp = QPainter(pixmap)
        pen = QPen(Qt.red, 3)
        qp.setPen(pen)
        qp.drawLine(10, 10, 50, 50)
        qp.end()
        self.label_top.setPixmap(pixmap)
        self.label_bot.setText(textbot)
    

    enter image description here

    Update:

    I can't have text and drawn line on the label?

    As I already pointed out in the initial part of my answer: Either you paint a text or you paint a QPixmap, you can't do both in a QLabel.

    Can I draw line then text on it using QPainter.drawText()?

    Yes, you can use all the methods to paint the text in the QPixmap: be creative :-). For example:

    def setLabels(self, texttop, textbot):
        pixmap = QPixmap(self.label_top.size())
        pixmap.fill(Qt.transparent)
        qp = QPainter(pixmap)
        pen = QPen(Qt.red, 3)
        qp.setPen(pen)
        qp.drawLine(10, 10, 50, 50)
    
        qp.drawText(pixmap.rect(), Qt.AlignCenter, texttop)
    
        qp.end()
        self.label_top.setPixmap(pixmap)
        self.label_bot.setText(textbot)
    

    enter image description here