Home > PyQt5, python > PyQt5: QPainter

PyQt5: QPainter

31 Maggio 2019

Torna all’indice degli appunti

QPainter

La classe QPainter è una delle tre classi cardine sulle quali si basa il painting system di Qt.
E’ la classe che esegue disegni di basso livello sui paint device, come ad esempio i widgets.
QPainter può disegnare di tutto, dalle semplici linee a forme complesse, testi e pixmaps.
Può genericamente operare su ogni oggetto che erediti dalla classe QPaintDevice

Le operazioni di disegno effettuate da QPainter devono avvenire all’interno del metodo paintEvent del widget.
L’iter è il seguente:

– si costruisce l’oggetto QPainter all’interno del metodo paintEvent(event);
– si personalizza l’oggetto QPainter, ad es. settando il tipo di QPen o QBrush;
– si disegnano gli elementi (linee, punti, forme complesse, ecc)

paintEvent

Quest è il metodo che deve essere reimplementato, quando si vuole personalizzare l’aspetto di un widget ed è ovviamente un event Handler.
Un paint event è una richiesta di ridisegnare tutto o parte di un widget. Questo evento si scatena ad esempio quando vengono invocati i metodi repaint() o update().
Per alcuni widget è molto semplice ridisegnare l’intera superficie (label, button), ma altri widget complessi possono aver bisogno di ridisegnare solo una sezione (QListWiew, QTableView).
Per questo si utilizzerà il metodo region() che ottimizzerà la velocità dell’operazione.

Nota:

Quando il metodo update() viene chiamato in successione o il window system manda molti paint events, Qt per comodità, unisce tutti questi eventi, sotto uno unico che riguarderà una regione vasta. Questa ottimizzazione non riguarda il metodo repaint(), pertanto è consigliato di utilizzare sempre il metodo update().

Vediamo un esempio pratico: costruiamo una label personalizzata dove poter agire su Font, colore, cornice e trama dello sfondo:

Il codice base è il seguente:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QWidget, QApplication, QBoxLayout, QLabel
import sys


class CustomLabel(QLabel):
    def __init__(self, text, parent):
        super(CustomLabel, self).__init__(text=text, parent=None)
        self.text = text


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("Qpainter Example")
        self.central_widget = FormWidget(self) 
        self.setCentralWidget(self.central_widget)
        self.resize(200, 100)


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.setLayout(layout)
        label = CustomLabel("Bancaldo", self)
        layout.addWidget(label)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())

FONT

Cominciamo ora a dare un aspetto personalizzato alla label reimplementando l’event Handler paintEvent:

...

class CustomLabel(QLabel):
    ...

    def paintEvent(self, event):
        painter = QPainter()
        painter.begin(self)
        pen = QPen(Qt.red, Qt.SolidLine)
        painter.setPen(pen)
        painter.setFont(QFont('Decorative', 20, italic=True))
        painter.drawText(event.rect(), Qt.AlignCenter, self.text)
        painter.end()

...

come detto in precedenza, creiamo l’oggetto QPainter, lo attiviamo con il metodo begin(), lo personalizziamo associando ad es. un oggetto QPen, settiamo il font desiderato e chiudiamo il painter con il metodo end().
Il risultato è:

CORNICE

Se invece volessimo aggiungere un riquadro tratteggiato alla label?
Possiamo creare un metodo che si occupi di modificare l’oggetto QPen e poi disegni un rettangolo tratteggiato sulla label.
L’importante è chiamare questo metodo prima che l’oggetto QPainter venga terminato con il metodo end():

...

class CustomLabel(QLabel):
    ...

    def paintEvent(self, event):
        ...
        self.paint_square(painter)
        painter.end()

    def paint_square(self, painter):
        sizer = self.size()
        pen = QPen(Qt.green, 5, Qt.DashLine)
        painter.setPen(pen)
        painter.drawRect(0, 0, sizer.width(), sizer.height())

...

Nota:

sizer serve per ricavarmi le dimensioni della label in caso di resizeEvent.
Il resizeEvent infatti, riscatena automaticamente il paintEvent, che ogni volta ridisegnerà la cornice.

BACKGROUND

Con questo principio possiamo anche aggiungere un background alla forma geometrica che abbiamo costruito.
Per questo scopo si utilizza la classe QBrush.

In pratica si crea un oggetto QBrush dello stile desiderato e lo si associa all’oggetto painter:

...

class CustomLabel(QLabel):
    ...

    def paintEvent(self, event):
        ...
        self.paint_square(painter)
        self.paint_background(painter)
        painter.end()

    def paint_background(self, painter):
        sizer = self.size()
        brush = QBrush(Qt.Dense1Pattern)
        brush.setColor(Qt.lightGray)
        painter.setBrush(brush)
        painter.drawRect(0, 0, sizer.width(), sizer.height())

...

Come mai non si legge più il testo?
Perchè l’ordine in cui disegnamo le cose, è importante.
Per prima cosa va disegnata la trama del background, poi si appone la cornice ed infine disegnamo il testo.
Per comodità di lettura codice, metteremo in un metodo a parte, anche la scrittura del testo:

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


class CustomLabel(QLabel):
    def __init__(self, text, parent):
        super(CustomLabel, self).__init__(text=text, parent=None)
        self.parent = parent
        self.text = text

    def paintEvent(self, event):
        painter = QPainter()
        painter.begin(self)
        self.paint_background(painter)
        self.paint_square(painter)
        self.paint_text(event, painter)
        painter.end()

    def paint_text(self, event, painter):
        pen = QPen(Qt.red, Qt.SolidLine)
        painter.setPen(pen)
        painter.setFont(QFont('Decorative', 25, italic=True))
        painter.drawText(event.rect(), Qt.AlignCenter, self.text)

    def paint_square(self, painter):
        sizer = self.size()
        pen = QPen(Qt.green, 5, Qt.DashLine)
        painter.setPen(pen)
        painter.drawRect(0, 0, sizer.width(), sizer.height())

    def paint_background(self, painter):
        sizer = self.size()
        brush = QBrush(Qt.Dense1Pattern)
        brush.setColor(Qt.lightGray)
        painter.setBrush(brush)
        painter.drawRect(0, 0, sizer.width(), sizer.height())


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("Qpainter Example")
        self.central_widget = FormWidget(self) 
        self.setCentralWidget(self.central_widget)
        self.resize(200, 100)


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.setLayout(layout)
        label = CustomLabel("Bancaldo", self)
        layout.addWidget(label)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,
I commenti sono chiusi.