cyanidem
cyanidem

Reputation: 103

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()

Upvotes: 2

Views: 10977

Answers (2)

Mladen Bruck
Mladen Bruck

Reputation: 3

I find out an answer with the help of this thread PySide6 app crashes when using QPainter.drawLine()

So modified draw_something should look like this:

    def draw_something(self):
        # Setup canvas for drawing
        canvas = QPixmap(self.ui.label_2.size()) # Make new pixmap and set size from the size of the label
        canvas.fill(Qt.GlobalColor.transparent) # Set color
        self.ui.label_2.setPixmap(canvas) # Set label pixmap to THIS canvas
        pm = self.ui.label_2.pixmap() # Make new instance label's pixmap
        painter = QPainter(canvas) # Setup painter to use it
        painter.drawLine(10, 10, 300, 200) # drawwing begins
        painter.end()
        self.ui.label_2.setPixmap(pm) # Set label pixmap to use this pixmap

Upvotes: 0

eyllanesc
eyllanesc

Reputation: 244282

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

Upvotes: 5

Related Questions