Home > PyQt5, python > PyQt5: QPen

PyQt5: QPen

14 Giugno 2019

Torna all’indice degli appunti

QPen

La classe QPen definisce come un QPainter dovrebbe disegnare linee e contorni di figure.
Il costruttore QPen prende come argomenti il colore, la larghezza del tratto e lo stile dello stesso, es: QPen(Qt.black, 2, Qt.SolidLine), ma anche il Cap Style ed il Join Style.
E’ possibile però costruire l’oggetto senza parametri e poi settarli in un secondo momento.
Un oggetto QPen ha 5 metodi fondamentali che lo identificano:

style(): indica il tipo di linea utilizzata;
brush(): indica il tipo di riempimento dei tratti generati da QPen;
width(): indica la larghezza del tratto della penna in pixel;
capStyle: indica la terminazione della linea, la parte finale che interrompe il tratto;
joinStyle: indica come viene effettuata l’unione di 2 linee (join);

I corrispondenti setter, che modificano le caratteristiche dell’oggetto QPen, sono ovviamente setStyle(Qt.PenStyle), setBrush(QBrush), setWidth(int), setCapStyle(Qt.PenCapStyle) e setJoinStyle(Qt.PenJoinStyle).

Useremo questo codice per vedere le varie differenze nell’oggetto QPen:

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


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("QPen 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)
        self.painter = None

    def paintEvent(self, event):
        self.painter = QPainter()
        self.painter.begin(self)
        self.draw_line(10, 50, 180, 50)
        self.painter.end()

    def draw_line(self, start_x, start_y, end_x, end_y):
        pen = QPen(Qt.black, 2, Qt.SolidLine)
        self.painter.setPen(pen)
        self.painter.drawLine(start_x, start_y, end_x, end_y)


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

PEN STYLE

Per sapere lo stile di QPen, come già detto, si usa il metodo style(), invece setStyle(style) setta il tipo di linea utilizzata;
style è un enum di tipo Qt.PenStyle e può assumere i seguenti valori:

Costante valore descrizione
Qt.NoPen 0 Nessuna linea. Esempio: QPainter.drawRect() riempie ma non disegna nessuna linea di contorno
Qt.SolidLine 1 Una linea continua
Qt.DashLine 2 Linea tratteggiata
Qt.DotLine 3 Linea punteggiata
Qt.DashDotLine 4 Trattini alternati da punti
Qt.DashDotDotLine 5 Un trattino e 2 punti alternati
Qt.CustomDashLine 6 Linea con un pattern personalizzato definito usando il metodo QPainterPathStroker.setDashPattern()

In caso di creazione dell’oggetto QPen senza parametri, di default lo stile sarà Qt.SolidLine:

>>> from PyQt5.QtGui import QPen
>>> pen = QPen()
>>> pen.style()
1

Nota:

Come si nota dal codice di esempio, per vedere QPen in azione, è necessario reimplementare il metodo paintEvent(), che chiama a sua volta un metodo designato a creare un oggetto QPen e a tracciare un linea con lo stile desiderato.

Volendo cambiare ad esempio lo stile del tratto, in Qt.DashLine, basterà modificare la linea di codice:

...
    def draw_line(self, start_x, start_y, end_x, end_y):
        pen = QPen(Qt.black, 2, Qt.SolidLine)
        self.painter.setPen(pen)
        self.painter.drawLine(start_x, start_y, end_x, end_y)

in

...
    def draw_line(self, start_x, start_y, end_x, end_y):
        pen = QPen(Qt.black, 2, Qt.DashLine)
        self.painter.setPen(pen)
        self.painter.drawLine(start_x, start_y, end_x, end_y)

oppure utilizzare il setter, che sovrascriverà le impostazioni di creazione:

...
    def draw_line(self, start_x, start_y, end_x, end_y):
        pen = QPen(Qt.red, 5, Qt.SolidLine)
        pen.setStyle(Qt.DashLine)
        self.painter.setPen(pen)
        self.painter.drawLine(start_x, start_y, end_x, end_y)

Le altre opzioni sono:

Qt.DotLine

Qt.DashDotLine

Qt.DashDotDotLine

PEN WIDTH

Di default la larghezza del tratto di QPen equivale a 1 e si ottiene con il metodo width().
Con i setter setWidth(int), o setWidthF(float), in caso di numero decimale, si può modificare lo spessore del tratto:

...
    def draw_line(self, start_x, start_y, end_x, end_y):
        pen = QPen(Qt.red, 5, Qt.SolidLine)
        pen.setStyle(Qt.DashLine)
        pen.setWidth(1)
        self.painter.setPen(pen)
        self.painter.drawLine(start_x, start_y, end_x, end_y)

PEN BRUSH/COLOR

Con il metodo brush() si risale all’oggetto QBrush associato a QPen. QBrush è il responsabile della colorazione data alla linea tracciata dall’oggetto QPen.
Di default il colore associato a QPen è il nero, infatti:

>>> brush = pen.brush()
>>> pen_color = brush.color()
>>> pen_color.getRgb()
(0, 0, 0, 255)

In realtà esiste una scorciatoia nell’oggetto QPen, data da color, per raggiungere direttamente l’oggetto QColor associato al QBrush di QPen:

>>> pen.color().getRgb()
(0, 0, 0, 255)

Per settare una colorazione differente, utilizziamo il setter setBrush(QBrush) o direttamente la scorciatoia setColor(QColor).
I due metodi sono equivalenti:

>>> from PyQt5.QtGui import QColor
>>> pen.setColor(QColor("#008000"))
>>> pen.color().getRgb()
(0, 128, 0, 255)

o con l’oggetto QBrush:

>>> from PyQt5.QtGui import QBrush
>>> brush = QBrush(QColor("#008500"))
>>> pen.setBrush(brush)
>>> pen.color().getRgb()
(0, 133, 0, 255)

Nel codice di esempio è sufficiente aggiungere una riga di codice con uno dei due setter appena visti:

...
    def draw_line(self, start_x, start_y, end_x, end_y):
        pen = QPen(Qt.red, 5, Qt.SolidLine)
        pen.setStyle(Qt.DashLine)
        pen.setWidth(5)
        pen.setColor(QColor("#008000"))
        self.painter.setPen(pen)
        self.painter.drawLine(start_x, start_y, end_x, end_y)

PEN CAPSTYLE

Il capstyle, decide come l’oggetto QPen deve disegnare il terminale della linea.
Tale terminale può essere di tre tipologie: Square, Flat e Round. Sono deinibili con i soliti enum di tipo Qt.PenCapStyle:

Costante valore descrizione
Qt.FlatCap 0x00 una linea “quadrata” che non copre il terminale della linea
Qt.SquareCap 0x10 una linea “quadrata” che copre il terminale della linea e si estende oltre la metà dello spessore della linea
Qt.RoundCap 0x20 il terminale della linea è arrotondato

I metodi getter e setter, sono ovviamente capStyle() per ottenere il valore associato all’oggetto QPen, che di default è Qt.SquareCap e setCapStyle(Qt.PenCapStyle), per modificare il valore:

>>> pen.capStyle()
16
>>> from PyQt5.QtCore import Qt
>>> pen.setCapStyle(Qt.RoundCap)
>>> pen.capStyle()
32

Per vedere la differenza, settiamo una larghezza del tratto importante.

...
    def draw_line(self, start_x, start_y, end_x, end_y):
        pen = QPen(Qt.red, 20, Qt.SolidLine)
        pen.setCapStyle(Qt.SquareCap)
        self.painter.setPen(pen)
        self.painter.drawLine(start_x, start_y, end_x, end_y)

lo stile Flat invece:

        pen.setCapStyle(Qt.FlatCap)

Anche se con difficoltà, si nota che la linea in realtà è più corta, perchè la modalità FLAT, non copre il terminale della linea come nella SQUARE, ma si ferma a metà.

infine il Round:

        pen.setCapStyle(Qt.RoundCap)

PEN JOINSTYLE

Il joinstyle, decide come l’oggetto QPen deve raccordare più linee tra loro.
Tale raccordo può essere di tre tipologie: Bevel, Miter e Round. Sono definibili con gli enum di tipo Qt.PenJoinStyle:

Costante valore descrizione
Qt.MiterJoin 0x00 i bordi più esterni delle linee vengono prolungati fino ad incontrarsi. L’angolo creatosi crea un’area che viene riempita
Qt.BevelJoin 0x40 il triangolo formato dalle linee viene riempito
Qt.RoundJoin 0x80 Un arco circolare raccorda le due linee e viene riempito

I metodi getter e setter, sono joinStyle() per ottenere il valore associato all’oggetto QPen, che di default è Qt.BevelJoin e setJoinStyle(Qt.PenJoinStyle), per modificare il valore:

>>> pen.joinStyle()
64
>>> pen.setJoinStyle(Qt.RoundJoin)
>>> pen.joinStyle()
128

ecco un esempio:

...
    def paintEvent(self, event):
        self.painter = QPainter()
        self.painter.begin(self)
        self.draw_symbol()
        self.painter.end()

    def draw_symbol(self):
        pen = QPen(Qt.red, 30, Qt.SolidLine, Qt.SquareCap, Qt.MiterJoin)
        self.painter.setPen(pen)
        self.painter.drawLine(20, 40, 50, 40)
        self.painter.drawLine(70, 40, 90, 80)
...

In caso di Round invece:

        pen = QPen(Qt.red, 30, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)

Nota:

Il Qt.RoundJoin funziona in coppia con il cap style Qt.RoundCap, ma bisogna anche raccordare le linee in modo che il join, sia ben fatto.
Correggendo le coordinate infatti si otterrà un effetto migliore:

        self.painter.drawLine(20, 40, 55, 40)
        self.painter.drawLine(60, 40, 90, 80)

DRAWING EXAMPLE

Ecco un esempio di come può essere utilizzato un oggetto QPen.
Sfruttiamo gli event handlers mousePressEvent, mouseMoveEvent e mouseReleaseEvent, per disegnare su un widget che mostra semplicemente un Pixmap.
Semplicemente reimplementandoli, controlliamo che venga premuto il tasto sinistro del mouse (event.button() == Qt.LeftButton), in tal caso, quando il mouse si muove (mouseMoveEvent), se il tasto è ancora premuto, settiamo l’oggetto QPen e, tenendo traccia dell’ultima posizione del mouse, disegnamo la linea.
Quando il pulsante del mouse viene rilasciato (mouseReleaseEvent), l’oggetto QPainter smette di disegnare poichè il flag is_drawable è stato settato a False.
Il flag is_drawable serve quindi per non continuare a disegnare senza che il pulsante sia premuto, durante i movimenti del mouse.

from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtGui import QPainter, QPen, QPixmap
import sys
from PyQt5.QtCore import Qt, QPoint


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("Drawaing Example")
        self.image = QPixmap("sheet.png")
        self.central_widget = FormWidget(self)
        self.setCentralWidget(self.central_widget)
        self.setMouseTracking(True)
        self.resize(self.image.width(), self.image.height())


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        self.last_mouse_pos = QPoint()
        self.is_drawable = False

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.drawPixmap(self.rect(), self.parent().image)

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.is_drawable = True
            self.last_mouse_pos = event.pos()

    def mouseMoveEvent(self, event):
        if event.buttons() and Qt.LeftButton and self.is_drawable:
            painter = QPainter(self.parent().image)
            painter.setPen(QPen(Qt.red, 3, Qt.SolidLine))
            painter.drawLine(self.last_mouse_pos, event.pos())
            self.last_mouse_pos = event.pos()
            self.update()  # it calls paintEvent

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.is_drawable = False


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.