PyQt5: QPalette

giugno 25, 2019 Lascia un commento

Torna all’indice degli appunti

QPalette

In Qt, tutti i widgets contengono un palette che li disegna.
La classe QPalette contiene i 3 gruppi di colori che rappresentano i 3 stati di un widget: Active, Inactive e Disabled:

Active: gruppo di colori per la finestra che detiene il Focus;
Inactive: le altre finestre (che non hanno il Focus);
Disabled: gruppo di colori utilizzato per i widget (non le window) che sono disabilitati;

Sia le window attive, sia le inattive, possono contenere Disabled widgets.

Per ottenere un color/brush di un determinato ColorGroup, di un determinato ColorRole, si utilizza il metodo color(ColorGroup, ColorRole) o i metodi scorciatoia dell’oggetto QPalette: ad esempio window(), windowText(), base().

>>> from PyQt5.QtWidgets import QApplication, QPushButton
>>> from PyQt5.QtGui import QPalette
>>> app = QApplication([])
>>> button = QPushButton("OK")
>>> palette = button.palette()
>>> palette.brush(QPalette.Active, QPalette.Text) == palette.text()
True
>>> palette.text().color().name()
'#000000'
>>> palette.base().color().name()
'#ffffff'

Per settare un color/brush invece, si utilizza il metodo setColor(ColorGroup, ColorRole, QColor) o setBrush(ColorGroup, ColorRole, QBrush).
Se volessimo ad esempio settare il colore del testo di un bottone nel suo stato Active:

>>> from PyQt5.QtGui import QColor
>>> color = QColor(130, 111, 132)
>>> palette.setColor(QPalette.Normal, text_role, color)
>>> button.setPalette(palette)
>>> new_color = palette.color(QPalette.Normal, button.foregroundRole())
>>> new_color.getRgb()
(130, 111, 132, 255)

ColorRole

I ColorRole

Gli enum ColorRole che definiscono i differenti colori utilizzati nella GUI, sono:

Costante Valore Descrizione
QPalette.Window 10 Colore di background generico
QPalette.Background Window Obsoleto, usare Window
QPalette.WindowText 0 Colore di Foreground generico
QPalette.Foreground WindowText Obsoleto. Usare WindowText
QPalette.Base 9 Usato principalmente per il background color dei textentry widgets, ma anche come background dei combobox drop down lists. Generalmente bianco o di colore chiaro
QPalette.AlternateBase 16 background alternativo in views con righe di colore alternato(QAbstractItemView.setAlternatingRowColors())
QPalette.ToolTipBase 18 background per QToolTip e QWhatsThis. I Tool tips usano il color group Inactive di QPalette, perchè non sono active windows
QPalette.ToolTipText 19 foreground color per QToolTip e QWhatsThis. I Tool tips usano il color group Inactive di QPalette, perchè non sono active windows
QPalette.PlaceholderText 20 colore per il placeholder dei widget in inserimento testo
QPalette.Text 6 Il colore di foreground usato con Base
QPalette.Button 1 Il colore di background dei bottoni
QPalette.ButtonText 8 Il colore di foreground usato nei bottoni
QPalette.BrightText 7 Il colore del testo quando è necessario un contrasto elevato

ColorGroup

I ColorRole

Gli enum ColorGroup che definiscono i differenti stati di un widget, sono:

Costante Valore Descrizione
QPalette.Disabled 1 stato disabled
QPalette.Active 0 stato Active
QPalette.Inactive 2 stato Inactive
QPalette.Normal Active sinonimo di Active

Per sapere quale ColorGroup è settato per l’oggetto QPalette, si utilizza il metodo currentColorGroup() che restituisce l’enum corrispondente.
Di default, il color group è Active/Normal:

>>> from PyQt5.QtWidgets import QApplication, QPushButton
>>> from PyQt5.QtGui import QPalette
>>> app = QApplication([])
>>> button = QPushButton()
>>> palette = button.palette()
>>> palette.currentColorGroup()
0

Per settare un color group differente, si utilizza il metodo setCurrentColorGroup(ColorGroup), dove ColorGroup è uno dei 4 enum della tabella precedente:

>>> palette.setCurrentColorGroup(QPalette.Disabled)
>>> palette.currentColorGroup()
1

Come anticipato, è possibile ottenere il color/brush di un determinato role, o con il metodo color(ColorGroup, role), o con i metodi specifici per il ColorGroup corrente, che sono:

base(): ritorna l’oggetto brush responsabile del base (background);

>>> palette.base()
<PyQt5.QtGui.QBrush object at 0x035FCD70>
>>> palette.base().texture()
<PyQt5.QtGui.QPixmap object at 0x035FCC70>
>>> palette.base().color()
<PyQt5.QtGui.QColor object at 0x035FCD30>
>>> palette.base().color().value()
240

alternateBase(): ritorna il brush dell’alternate base (background) per il ColorGroup corrente;
brightText(): ritorna il brush del bright text (foreground) per il ColorGroup corrente;
text(): ritorna l’oggetto brush del text (foreground) del ColorGroup corrente;
button(): ritorna l’oggetto brush responsabile del button del ColorGroup corrente;
buttonText(): ritorna l’oggetto brush responsabile del testo del button (foreground) del ColorGroup corrente;
dark(): ritorna il dark brush del ColorGroup corrente;
light(): ritorna il light brush del ColorGroup corrente;
highlight(): ritorna l’highlight brush del ColorGroup corrente;
highlightedText(): ritorna il brush di testi evidenziati del ColorGroup corrente;
link(): ritorna il brush per i link non visitati del ColorGroup corrente;
linkVisited(): ritorna il brush per i link visitati del ColorGroup corrente;
mid(): ritorna il mid brush del ColorGroup corrente;
placeHolderText(): ritorna il brush del place holder text (suggerimento) del ColorGroup corrente;
shadow(): ritorna il brush dell’ombra del ColorGroup corrente;
toolTipBase(): ritorna il brush del background del tooltip del ColorGroup corrente;
toolTipText(): ritorna il brush del testo del tooltip del ColorGroup corrente;
window(): ritorna il brush del backgorund generale della window del ColorGroup corrente;
windowText(): ritorna il brush del foreground generale della window del ColorGroup corrente;

Ecco un esempio di codice per la modifica del testo e del colore del bottone.

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QWidget, QApplication, QBoxLayout, QPushButton
from PyQt5.QtGui import QColor, QPalette
import sys


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("QButton 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)
        t_color = QColor(190, 49, 54)
        b_color = QColor(90, 192, 54)
        button = QPushButton("OK", minimumHeight=48)
        palette = button.palette()
        text = button.foregroundRole()
        base = button.backgroundRole()
        button.setAutoFillBackground(True)
        palette.setColor(QPalette.Active, text, t_color)
        palette.setColor(QPalette.Active, base, b_color)
        button.setPalette(palette)
        layout.addWidget(button, 0)


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

BACKGROUND BUTTON

Se il colore del background del bottone cambia solo nella cornice, bisogna utilizzare il metodo setStyleSheet(string) del widget.
E’ possibile infatti modificare l’aspetto del widget, inserendo una stringa con tutti i parametri, come fosse CSS.

Esempio:

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


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("QButton 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)
        button = QPushButton("OK", objectName="custombutton", minimumHeight=48)
        button.setStyleSheet(StyleSheet)
        layout.addWidget(button, 0)


StyleSheet = """
#custombutton {
    border: 2px solid #666;
    max-height: 48px;
    border-top-right-radius:   20px;   
    border-bottom-left-radius: 20px;   
    background-color: #ff9800;
}
#custombutton:hover {
    background-color: #ffb74d;
}
#custombutton:pressed {
    background-color: #ffe0b2;
}
"""


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

Torna all’indice degli appunti

Annunci
Categorie:PyQt5, python Tag:,

PyQt5: QBrush

giugno 19, 2019 Lascia un commento

Torna all’indice degli appunti

QBrush

La classe QBrush definisce il pattern di riempimento delle figure disegnate con un oggetto QPainter.
I 4 parametri che caratterizzano un oggetto QBrush sono lo stile, il colore, il gradiente e la texture.
Il costruttore QBrush quindi, prende come argomenti lo stile, il colore, il gradiente e la texture:
E’ possibile però costruire l’oggetto senza parametri e poi settarli in un secondo momento.
Un oggetto QBrush ha quindi 4 metodi fondamentali che lo identificano:

style(): indica il pattern di riempimento della figura disegnata;
color(): indica il colore del pattern di riempimento;
gradient(): indica il tipo di gradiente del pattern di riempimento;
texture(): indica il pixmap utilizzato come riempimento;

I corrispondenti setter, che modificano le caratteristiche dell’oggetto QBrush, sono ovviamente setStyle(Qt.BrushStyle), setColor(QColor), setTexture(pixmap). Non è disponibile un setter per il gradiente, ma bisogna passare l’enum corrispondente in fase di costruzione dell’oggetto.

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

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


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("QBrush 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)
        brush = QBrush()
        self.painter.setBrush(brush)
        self.parent().pos()
        self.painter.drawRect(10, 10, 180, 80)
        self.painter.end()


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

BRUSH STYLE

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

Costante valore descrizione
Qt.NoBrush 0 Nessuna pattern
Qt.SolidPattern 1 Pattern con colore uniforme
Qt.Dense1Pattern -> Qt.Dense7Pattern 2-8 Pattern con densità crescente da 1 a 7
Qt.HorPattern 9 Pattern con linee orizzontali
Qt.VerPattern 10 Pattern con linee verticali
Qt.CrossPattern 11 Pattern con linee orizzontali e verticali
Qt.BDiagPattern 12 Pattern con linee diagonali
Qt.FDiagPattern 13 Pattern con linee diagonali opposte
Qt.DiagCrossPattern 14 Pattern con linee diagonali incrociate
Qt.LinearGradientPattern 15 Pattern con gradient Lineare
Qt.RadialGradientPattern 16 Pattern con gradient Radial
Qt.ConicalGradientPattern 17 Pattern con gradient Conical
Qt.TexturePattern 24 Pattern personalizzato

In caso di creazione dell’oggetto QBrush senza parametri, come nel codice di esempio, di default lo stile sarà Qt.NoBrush:

>>> from PyQt5.QtGui import QBrush
>>> brush = QBrush()
>>> brush.style()
0

Nota:

Come nel codice, per vedere personalizzare QBrush è necessario reimplementare il metodo paintEvent().
Per modificare lo stile dopo la creazione dell’oggetto QBrush, si utilizza il metodo setStyle(Qt.BrushStyle):

es.:

...
    def paintEvent(self, event):
        self.painter = QPainter()
        self.painter.begin(self)
        brush = QBrush()
        brush.setStyle(Qt.DiagCrossPattern)
        self.painter.setBrush(brush)
        self.parent().pos()
        self.painter.drawRect(10, 10, 180, 80)
        self.painter.end()
...

BRUSH COLOR

Il colore di default di un oggetto QBrush è il nero.
Per ottenere informazioni sul colore, si utilizza il metodo color():

>>> brush.color()
<PyQt5.QtGui.QColor object at 0x0339F6F0>
>>> brush.color().getRgb()
(0, 0, 0, 255)
>>> brush.color().name()
'#000000'

Per modificare il colore dopo la creazione dell’oggetto QBrush, si utilizza il metodo setColor(QColor):

>>> from PyQt5.QtGui import QColor
>>> new_color = QColor("#FF0000") 
>>> brush.setColor(new_color)
>>> brush.color().name()
'#FF0000'

Nel codice, basterà aggiungere una linea, sempre prima di settare l’oggetto QBrush nel QPainter:

...
    def paintEvent(self, event):
        self.painter = QPainter()
        self.painter.begin(self)
        brush = QBrush()
        brush.setStyle(Qt.DiagCrossPattern)
        new_color = QColor("#FF0000")
        brush.setColor(new_color)
        self.painter.setBrush(brush)
        self.parent().pos()
        self.painter.drawRect(10, 10, 180, 80)
        self.painter.end()
...

Nota:

Come mai il bordo del rettangolo è nero nonostante il colore del brush, sia stato modificato?
QBrush è responsabile del pattern di riempimento della forma/figura disegnata dall’oggetto QPainter, mentre per il contorno di tale forma, è necessario fare riferimento all’oggetto QPen. Modificando il colore del QPen associato al painter, cambierà pertanto il colore della cornice:

...
    def paintEvent(self, event):
        self.painter = QPainter()
        self.painter.begin(self)
        brush = QBrush()
        brush.setStyle(Qt.DiagCrossPattern)
        new_color = QColor("#FF0000")
        brush.setColor(new_color)
        self.painter.setBrush(brush)
        pen = QPen(QColor("Red"))
        pen.setWidth(2)
        self.painter.setPen(pen)
        self.parent().pos()
        self.painter.drawRect(10, 10, 180, 80)
        self.painter.end()
...

BRUSH GRADIENT

Come accennato precedentemente, è possibile sapere che tipo di gradiente viene utilizzato nell’oggetto QBrush, ma non è possibile modificarlo dopo la costruzione dell’oggetto.
Esistono 3 tipi di gradienti: QLinearGradient, QConicalGradient, and QRadialGradient. Si tratta quindi di ereditare da una di queste 3 classi e creare l’ggetto QBrush, con il gradiente personalizzato, passato come argomento.

QLinearGradient

Il gradiente di tipo Linear esegue l’interpolazione dei colori tra due punti, uno iniziale e uno finale. Fuori da questi punti, invece, i colori assegnati sono pieni.
il gradiente può assumere 3 differenti tipi di Spread: Padded, Reflected e Repeated.
Lo spread è il tipo di riempimento utilizzato fuori dall’area del gradiente.
Per conoscere il tipo di spread, si utilizza il metodo spread(), mentre per modificarlo si utilizza il metodo setSpread(spread), dove spread può assumere il valore di uno dei seguenti enum di tipo QGradiant.Spread:

Costante valore descrizione
QGradient.PadSpread 0 Valore di default. L’area è riempita con il colore di “stop” più vicino
QGradient.RepeatSpread 2 Fuori dall’area il gradiente viene ripetuto
QGradient.ReflectSpread 1 Fuori dall’area il gradiente viene riflesso

Per capire come funziona l’interpolazione, useremo i colori bianco e nero.
In sostanza si crea un oggetto gradient ereditando dalla classe prederita delle 3 sopra citate, passando come argomenti, le coordinate dello start point e quelle dell’end point dell’area del gradiente.
Poi si settano il colore di start (Nero) ed il colore di stop (bianco) e si passa l’oggetto gradient al costruttore del QBrush:

from PyQt5.QtGui import QGradient, QLinearGradient

...
    def paintEvent(self, event):
        self.painter = QPainter()
        self.painter.begin(self)
        gradient = QLinearGradient(10, 80, 180, 80)
        gradient.setColorAt(0, QColor("#000000"))
        gradient.setColorAt(1, QColor("#FFFFFF"))
        brush = QBrush(gradient)
        self.painter.setBrush(brush)
        self.parent().pos()
        self.painter.drawRect(10, 10, 180, 80)
        self.painter.end()
...

Le coordinate di start ed end in fase di costruzione dell’oggetto gradient, decidono anche la direzione della linea di interpolazione e la larghezza dell’area che la riguarda.
Se avviciniamo i punti delle coordinate, restringiamo l’area dell’interpolazione:

...
    def paintEvent(self, event):
	...
        gradient = QLinearGradient(80, 80, 100, 80)
...

Se invece di avere la stessa y, le coordinate avranno la stessa x, il gradiente sarà orizzontale invece che verticale:

...
    def paintEvent(self, event):
	...
        gradient = QLinearGradient(50, 20, 50, 80)
...

Giocando sulle coordinate si crea anche un gradiente diagonale:

...
    def paintEvent(self, event):
	...
        gradient = QLinearGradient(50, 20, 50, 80)
...

Queste possibilità, riguardano lo Spread di default, ovvero Qt.PadSpread.
Per cambiare il tipo, si utilizza il setter setSpread(spread), prima di passare l’oggetto gradient al costruttore di QBrush:


...
    def paintEvent(self, event):
        self.painter = QPainter()
        self.painter.begin(self)
        gradient = QLinearGradient(80, 80, 120, 80)
        gradient.setColorAt(0, QColor("#000000"))
        gradient.setColorAt(1, QColor("#FFFFFF"))
        gradient.setSpread(QGradient.RepeatSpread)
        brush = QBrush(gradient)
        self.painter.setBrush(brush)
        self.parent().pos()
        self.painter.drawRect(10, 10, 180, 80)
        self.painter.end()
...

startpoint e endpoint

per conoscere gli start point e gli end point di un oggetto gradient, si utilizzano i metodi start() e finalStop():

>>> from PyQt5.QtGui import QLinearGradient
>>> gradient = QLinearGradient(10, 80, 190, 80)
>>> gradient.start()
PyQt5.QtCore.QPointF(10.0, 80.0)
>>> gradient.finalStop()
PyQt5.QtCore.QPointF(190.0, 80.0)

QRadialGradient

Il gradiente di tipo Radial esegue l’interpolazione dei colori in maniera circolare tra un punto focale ed un end point.
Come per il LinearGradient, i tipi di spread sono sempre Padded, Reflected e Repeated.
Il costruttore del gradient radial, prende come argomenti: le coordinate del centro del cerchio che farà da interpolazione dei colori, il raggio del cerchio e le coordinate del focus.
Per il resto non cambia nulla:

from PyQt5.QtGui import QGradient, QRadialGradient

...
    def paintEvent(self, event):
        self.painter = QPainter()
        self.painter.begin(self)
        gradient = QRadialGradient(50, 50, 50, 80, 80)
        gradient.setColorAt(0, QColor("#000000"))
        gradient.setColorAt(1, QColor("#FFFFFF"))
        brush = QBrush(gradient)
        self.painter.setBrush(brush)
        self.parent().pos()
        self.painter.drawRect(10, 10, 180, 80)
        self.painter.end()
...

Se voglio rimpicciolire il cerchio, agisco sul terzo argomento del costruttore:

from PyQt5.QtGui import QGradient, QRadialGradient

...
    def paintEvent(self, event):
	...
        gradient = QRadialGradient(50, 50, 25, 80, 80)
...

Per spostare il centro del cerchio e le coordinate del focus, ovviamente si dovrà agire sugli altri 4 argomenti.
Con un raggio molto grande, si avrà l’effetto di coprire per intero il rettangolo:

...
    def paintEvent(self, event):
	...
        gradient = QRadialGradient(100, 50, 130, 20, 30)
...

QConicalGradient

Il gradiente di tipo Conical esegue l’interpolazione dei colori con una rotazione oraria attorno ad un punto.
Oltre alle coordinate del punto centrale, è possibile passare al costruttore del gradiente, l’angolo dal quale cominceremo a ruotare.
Come per il LinearGradient, i tipi di spread sono sempre Padded, Reflected e Repeated.

from PyQt5.QtGui import QGradient, QConicalGradient

...
    def paintEvent(self, event):
        self.painter = QPainter()
        self.painter.begin(self)
        gradient = QConicalGradient(50, 50, 45)  # 45 sono i gradi dell'angolo
        gradient.setColorAt(0, QColor("#000000"))
        gradient.setColorAt(1, QColor("#FFFFFF"))
        brush = QBrush(gradient)
        self.painter.setBrush(brush)
        self.parent().pos()
        self.painter.drawRect(10, 10, 180, 80)
        self.painter.end()
...

I parametri del gradiente di tipo Conical, sono recuperabili con gli appositi getter: angle() e center()

>>> from PyQt5.QtGui import QGradient, QConicalGradient
>>> gradient = QConicalGradient(50, 50, 45)
>>> gradient.angle()
45.0
>>> gradient.center()
PyQt5.QtCore.QPointF(50.0, 50.0)

Per modificare tali parametri invece, ci sono gli appositi setter: setAngle(angle) e setCenter(x, y):

>>> gradient.setAngle(55)
>>> gradient.angle()
55.0
>>> gradient.setCenter(60, 60)
>>> gradient.center()
PyQt5.QtCore.QPointF(60.0, 60.0)

BRUSH TEXTURE

Per utilizzare una texture nell’oggetto QBrush, per prima cosa si crea un oggetto QPixmap con l’immagine desiderata, poi lo si passa al costruttore dell’oggetto QBrush.
Una volta creato l’oggetto QBrush, si imposta lo style dello stesso a Qt.TexturePattern.
Oppure si crea l’oggetto QBrush senza argomenti, poi si associa il pixmap con l’apposito setter setTexture(pixmap).
Per sapere quale texture viene utilizzata sul brush, si utilizza il metodo texture():

from PyQt5.QtGui import QPixmap

...

    def paintEvent(self, event):
        self.painter = QPainter()
        self.painter.begin(self)
        pixmap = QPixmap("Qtsmall.png")
        brush = QBrush()
        brush.setStyle(Qt.TexturePattern)
        brush.setTexture(pixmap)
        self.painter.setBrush(brush)
        self.parent().pos()
        self.painter.drawRect(10, 10, 180, 80)
        self.painter.end()
...

Torna all’indice degli appunti

Categorie:PyQt5, python Tag:,

PyQt5: QPen

giugno 14, 2019 Lascia un commento

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:,

PyQt5: QColor

giugno 5, 2019 Lascia un commento

Torna all’indice degli appunti

QColor

Un colore viene normalmente specificato secondo una delle seguenti 3 specifiche:
RGB (Red, Green and Blue)

HSV (Hue, Saturation and Value)

CMYK (Cyan, Magenta, Yellow and Black)

RGB Color

QColor è la classe che gestisce i colori. Il costruttore crea sempre un oggetto QColor in termini RGB.
Per ottenere il colore secondo le altre due specifiche, si utilizzano i metodi toHsv() e toCmyb(), che ritornano una copia dell’oggetto, nel formato desiderato:

L’oggetto QColor può essere settato passando i valori Red, Green, Blue al costruttore; per gli esempi a seguire considereremo il colore “Verde”.
Consultando lo spazio di colori in figura, per un verde scuro prendiamo il valore a metà tra black (0) e light-green (255), quindi 128:

>>> from PyQt5.QtGui import QColor
>>> color = QColor(0, 128, 0)
>>> color.getRgb()
(0, 128, 0, 255)
>>> color.toCmyk()
<PyQt5.QtGui.QColor object at 0x033F15F0>
>>> color.toHsv()
<PyQt5.QtGui.QColor object at 0x033F1770>

E’ possibile passare al costruttore la stringa-colore esadecimale corrispondente:

>>> color_s = QColor("#008000")
>>> color_s.getRgb()
(0, 128, 0, 255)

Per recuperare i valori rgb si utilizza il metodo getRgb(), che ritorna una tupla con i valori: Red, Green, Blue e alpha-channel (trasparenza).
Per ottenere invece i singoli valori dei quattro precedenti parametri, si utilizzano gli omonimi metodi red(), green(), blue() ed alpha():

>>> color.red()
0
>>> color.green()
128
>>> color.blue()
0
>>> color.alpha()
255

Ovviamente i valori sono settabili in un colpo solo con il metodo setRgb(int, int, int, int), passando rispettivamente i valori red, green, blue e alpha, oppure singolarmente con i metodi setRed(int), setGreen(int), setBlue(int) e setAlpha(int):

>>> color_s.setBlue(20)
>>> color_s.blue()
20

Il costruttore QColor accetta anche stringhe predefinite contenenti i “nomi” dei colori esistenti:

>>> hex_color = QColor("#008000")
>>> n_color = QColor("Green")
>>> hex_color.getRgb() == n_color.getRgb()
True

Nota:
La stringa corrispondente al “green” (0, 128, 0) è rappresentata dai tre numeri in esadecimale, red “00” (0), green “80” (128) e blue “00” (0), che di seguito formano #008000″

se controlliamo su un qualsiasi color picker, noteremo la corrispondenza.

I nomi dei colori accettati dal costruttori sono reperibili con il metodo colorNames()

HSV Color

Come detto in precedenza, si costruisce un Qcolor in RGB e lo si converte in HSV con il metodo toHsv()
HSV è un sinonimo di HSL (Hue, Saturation, Lightness). Per ottenere i valori del colore secondo la specificha HSV, si utilizza il metodo getHsv(), che ritorna una tupla con i valori: Hue, Saturation, Value e alpha-channel (trasparenza).
Come nel caso della specifica RGB, è possibile ottenere i singoli valori con i metodi hue(), saturation(), value() e alpha(), e, in ugual misura, è possibile modificare tali valori con i setter corrispondenti: setHue(int), setSaturation(int), setValue(int) e
setAlpha(int):

>>> hsv = color.toHsv()
>>> hsv.getHsv()
(120, 255, 128, 255)
>>> hsv.value()
128

HSL Color

Variante della specifica HSV (o viceversa), dove “L” sta per Lightness.
Stesso principio: si crea una copia del colore RGB con il metodo toHsl() e si ottengono i valori con il metodo getHsl(), o singolarmente con i metodi hue(), saturation(), lightness(). Per la modifica degli singoli valori, si utilizzano i metodi setHue(int), setSaturation(int), setLightness(int):

>>> hsl.getHsl()
(120, 255, 64, 255)
>>> hsl.lightness()
64

CMYK Color

Ovviamente non fa differenze il colore secondo questa specifica. Si crea la copia di oggetto QColor RGB con il metodo toCmyk() e si ottiene la tupla dei valori, con il metodo getCmyk(), oppure i singoli valori con i metodi: cyan(), magenta(), yellow(), black() e alpha().
Il settaggio dei valori quindi avrà i propri setter: setCyan(), setMagenta(), setYellow(), setBlack() e setAlpha():

>>> cmyk = color.toCmyk()
>>> cmyk.getCmyk()
(255, 0, 255, 127, 255)
>>> cmyk.cyan()
255

Nota:

255 è il massimo valore che può raggiungere il byte che rappresenta il valore.
Quindi attenzione ai color picker che ragionano in percentuali inquanto 255 indica 100%.
Nell’ultimo caso il verde in CMYK ha un cyan = 100% (255), magenta = 0%, yellow = 100% (255) e black = 50% (127).

CREARE Color RGB da altri formati

E’ possibile fare anche il percorso inverso, ovvero ottenere un colore secondo le specifiche RGB, da un colore HSV, HSL o CMYK, utilizzando i metodi fromHsv(hue, saturation, value, alpha),
fromHsl(hue, saturation, value, lightness) e fromCmyk(cyan, magenta, yellow, black, alpha):

>>> rgb_color = QColor.fromCmyk(255, 0, 255, 127, 255)
>>> rgb_color.getRgb() == color.getRgb()
True

VALIDATION

Quando costruiamo un colore passando una stringa, è possibile verificare che tale stringa esista tra i colori preconfezionati, utilizzando il metodo isValid(), o direttamente con il metodo isValidColor(color):

>>> QColor.isValidColor("Red")
True
>>> QColor.isValidColor("Foo")
False
>>> new_color = QColor("Foo")
>>> new_color.isValid()
False

Ma anche quando passiamo al costruttore valori errati:

>>> new_color = QColor(255, 255, 300, 300)
>>> new_color.isValid()
False
>>> new_color = QColor(255, 255, 255, 255)
>>> new_color.isValid()
True

Torna all’indice degli appunti

Categorie:PyQt5, python Tag:,

PyQt5: QPainter

maggio 31, 2019 Lascia un commento

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:,

PyQt5: QApplication

maggio 29, 2019 Lascia un commento

Torna all’indice degli appunti

QApplication

QApplication è la classe che gestisce i settaggi ed il flusso della UI della nostra applicazione.
La cosa fondamentale è che QApplication, contiene l’event_loop, dove vengono gestiti tutti gli eventi relativi alla main window e gestisce l’inizializzazione dell’applicazione. Ad ogni Applicazione con UI, corrisponde un oggetto QApplication.

Le responsabilità principali dell’oggetto QApplication sono:

– inizializzare l’applicazione con settaggi utente quali, palette, font e doubleClickInterval;
– esegue l’event-handling, ovvero riceve segnali dal window-system e li distribuisce ai widget interessati;
– accetta parametri da riga di comando e si regola in funzione di essi;
– può definire il look and feel dell’applicazione;
– fornisce la localizzazione delle stringhe visibili all’utente (traslate);
– mette a disposizione oggetti come desktop e clipboard;
– sa tutto delle finestre dell’applicazione: quale widget è in una determinata posizione, chi sono i TopLevel Widgets, può chiudere tutte le finestre, ecc;

SETTINGS

Come detto al punto 1, è possibile cambiare alcuni settaggi fondamentali dell’applicazione, come ad esempio palette, istanza della classe QPalette che contiene i gruppi di colori che indicano lo stato di ogni widget (Active, Inactive, Disabled).
Per ottenere l’oggetto palette dell’applicazione, si utilizza il metodo palette:

>>> from PyQt5.QtWidgets import QApplication
>>> from PyQt5.QtGui import QPalette, QColor
>>> app = QApplication([])
>>> palette = app.palette()

Con il metodo color(color_role), dove color_role è un QPalette ColorRole-enum, si ottiene l’oggetto QColor utilizzato dalla applicazione:

>>> palette.color(QPalette.Window).name()
'#f0f0f0'

per modificare il colore del role, si utilizza il metodo setColor(color_role, color), dove color_role è il ColorRole-enum, mentre color, l’oggetto QColor
che vogliamo impostare:

>>> palette.setColor(QPalette.Window, QColor("aquamarine"))
>>> palette.color(QPalette.Window).name()
'#7fffd4'
>>> app.setPalette(palette)

In effetti, consideriamo il semplice codice:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtGui import QColor, QPalette
import sys


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


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)


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

modificando il QPalette.Window:

...

if __name__ == '__main__':
    app = QApplication(sys.argv)
    palette = app.palette()
    palette.setColor(QPalette.Window, QColor("aquamarine"))
    app.setPalette(palette)
    main_window = MainWindow(parent=None)
    main_window.show()
    sys.exit(app.exec_())

Lo stesso discorso vale per il settaggio di altri parametri, come ad esempio in font utilizzato dalla applicazione.

ISTANZA DI QApplication

Per ottenere l’oggetto QApplication ovunque all’interno del codice, è possibile chiamare il metodo instance() della classe QApplication:

>>> app.name_ = "bancaldo"
>>> app_ = QApplication.instance()
>>> app_ == app
True
>>> app_.name_
'bancaldo'

EVENT HANDLING

Per entrare nell’event loop si utilizza il metodo exec_(), da questo momento l’applicazione riceve gli eventi dal window-system e li distribuisce ai vari widgets. exec_() rimane nel main loop fino a chè non viene chiamato il metodo exit().

argv

Tutti i programmi Qt supportano i seguenti parametri da riga di comando:

-style= setta lo stile della UI dell’applicazione. I tipi di stile dipendono dalla configurazione di sistema;
-stylesheet= setta lo stylesheet della applicazione;
-reverse setta il layout dell’applicazione a Qt.RightToLeft;

per sapere quali argomenti sono settati per la app corrente, si utilizza il metodo arguments():

Lista Top Level Widgets

E’ possibile conoscere quali sono i Top Level Widgets, con il metodo topLevelWidgets() di QApplication:

>>> from PyQt5.QtWidgets import QApplication, QMainWindow
>>> app = QApplication([])
>>> mw = QMainWindow()
>>> app.topLevelWidgets()
[<PyQt5.QtWidgets.QMainWindow object at 0x0360E170>]

DESKTOP

il metodo desktop() ritorna un oggetto QDesktopWidget che rappresenta la root-window, ovvero il nostro desktop.
Può essere comodo conoscere le dimensioni del nostro desktop nel momento in cui dobbiamo dimensionare le finestre della nostra applicazione, sopratutto quando si ha a che fare con un doppio schermo e desktop esteso.
L’oggetto QDesktopWidget mette a disposizione due comodi metodi:

screenGeometry(): ritorna le dimensioni dello schermo;
availableGeometry(): ritorna le dimensioni diponibili (desktop);

>>> desktop = app.desktop()
>>> desktop.screenGeometry()
PyQt5.QtCore.QRect(0, 0, 1680, 1050)
>>> desktop.availableGeometry()
PyQt5.QtCore.QRect(0, 0, 1680, 1010)

CLIPBOARD

Con il metodo clipboard() otteniamo un oggetto QClipboard che ci dà l’accesso al clipboard di sistema.
E’ possibile ad esempio effettuare un semplice copia/incolla di dati, tra diverse applicazioni.
Testi, immagini, mimedata, pixmap, si portano sul clipboard con i metodi setText(text), setImage(image), setMimeData(data), setPixmap(pixmap).
Per recuperarli invece si utilizzano i metodi text(), image(), mimeData()e pixmap().

ABOUT QT

Chiamando il metodo aboutQt(), viene visualizzato un message box che indica la versione di Qt in uso.
Utile da inserire nel menu Help dell’applicazione.

ALL WIDGETS

Con il metodo allWidgets(), avremo una lista di tutti i widgets impiegati nella stessa:

>>> from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget
>>> app = QApplication([])
>>> mw = QMainWindow()
>>> for n in range(5):
...     w = QWidget(parent=mw)
...     
>>> app.allWidgets()
[<PyQt5.QtWidgets.QWidget object at 0x0372E170>, ...
>>> len(app.allWidgets()
6

BEEP

Con il metodo beep() viene appunto emesso un beep con suono e volume di default.

CLOSE ALL WINDOWS

Per chiudere tutte le top level windows, si utilizza il metodo closeAllWindows()

WHEEL SCROLL LINES

Di default il numero di scroll lines è 3 e lo si può vedere con il metodo wheelScrollLines():

>>> app.wheelScrollLines()
3

Per modificare tale numero è sufficiente chiamare il metodo setWheelScrollLines(num):

>>> app.setWheelScrollLines(5)
>>> app.wheelScrollLines()
5

ICONA

E’ possibile modificare l’icona della finestra dell’applicazione, invocando il metodo setWindowIcon(icon), dove icon è un oggetto QIcon contenete l’immagine desiderata. Per ottenere invece l’icona dell’applicazione si utilizza il metodo windowIcon():

>>> from PyQt5.QtGui import QIcon
>>> icon = QIcon("qt.png")
>>> app.setWindowIcon(icon)
>>> app.windowIcon()
<PyQt5.QtGui.QIcon object at 0x0372E3A0>

Torna all’indice degli appunti

Categorie:PyQt5, python Tag:,

PyQt5: QMenuBar, QMenu

maggio 28, 2019 Lascia un commento

Torna all’indice degli appunti

QMenuBar

La classe QMenuBar crea una barra orizzontale dove è possibile sistemare elementi detti pull-down Menu.
Il costruttore della menubar è QMenuBar:

>>> from PyQt5.QtWidgets import QApplication, QMenuBar
>>> app = QApplication([])
>>> menubar = QMenuBar()

Per aggiungere i menu sulla menubar appena creata, si utilizza il metodo addMenu(menu), dove menu è un oggetto QMenu, ovvero un widget che contiene una lista di elementi detti actions.

Vediamo nello specifico l’oggetto QMenu.

QMENU

Questa classe QMenu crea il widget che viene comunemente utilizzato nelle menubar, menu contestuali (es. bottone destro del mouse) e popup menu.
Come già anticipato, un oggetto QMenu, consiste in una lista di elementi “Action”.
Un elemento Action può avere una label, una icona ed una shortcut (scorciatoia da tastiera).
Il costruttore di un oggetto QMenu è appunto QMenu(text), dove text una stringa che rappresenta il nome del menu.
Per settare un’icona di menu, si utilizza il metodo setIcon(icon), dove icon è ovviamente un oggetto QIcon.
Aggiungiamo quindi alla menubar un menu “File” con tanto di Icona:

>>> from PyQt5.QtWidgets import QMenu
>>> from PyQt5.QtGui import QIcon
>>> menufile = QMenu("File")
>>> menufile.setIcon(QIcon("file.png"))
>>> menubar.addMenu(menufile)
<PyQt5.QtWidgets.QAction object at 0x0373D3A0>

Il metodo addMenu, ritorna l’oggetto QAction relativo al menu appena inserito.
L’oggetto qaction contiene tutte le informazioni relative al menu e può contenere appunto una icona, un testo, un tip.
E’ possibile ottenere le actions collegate ad una menubar, con il metodo actions()

>>> menubar.actions()
[<PyQt5.QtWidgets.QAction object at 0x0373D210>]
>>> fileaction = menubar.actions()[0]
>>> fileaction.icon()
<PyQt5.QtGui.QIcon object at 0x0373D3F0>
>>> fileaction.text()
'File' 

Ora che abbiamo creato un menu, dobbiamo creare le voci di menu.
Per fare questo, utilizziamo il metodo addAction(text), dove text è il nome della voce di menu.
E’ possibile anche inserire un’icona in fase di aggiunta della action, passandola al metodo precedente come primo parametro, ma è anche possibile farlo successivamente, utilizzando il metodo setIcon(icon).
In realtà, dal momento che l’utilizzo del metodo addAction(text), ritorna l’oggetto QAction, è possibile aggiungere tutti gli elementi in seguito, con i metodi:
setIcon(icon), setToolTip(tooltip):

>>> newtxt_action = menufile.addAction("New &Txt file")
>>> newtxt_action
<PyQt5.QtWidgets.QAction object at 0x0373D530>
>>> newtxt_action.setIcon(QIcon("txt.png"))
>>> menufile.addAction(QIcon("pdf.png"), "New &Pdf file")
<PyQt5.QtWidgets.QAction object at 0x0373D5D0>
>>> menufile.addAction(QIcon("doc.png"), "New &Doc file")
<PyQt5.QtWidgets.QAction object at 0x0373D670>
>>> menufile.actions()
[<PyQt5.QtWidgets.QAction object at 0x0373D530>, <PyQt5.QtWidgets.QAction object at 0x0373D5D0>, <PyQt5.QtWidgets.QAction object at 0x0373D670>]
>>> for action in menufile.actions():
...     print(action.text(), action.icon().isNull())
...     
New &Txt file False
New &Pdf file False
New &Doc file False

SEGNALI

I due segnali da tenere in considerazione sono

hovered: segnale emesso quando la voce di menu viene evidenziata al passaggio del mouse;
triggered: segnale emesso quando clicchiamo sulla voce di menu;

Entrambi i segnali rendono disponibile action, ovvero l’oggetto QAction che ha causato l’emissione del segnale.

Vediamo un esempio pratico:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QApplication, QMenuBar, QMenu, QAction, QWidget
from PyQt5.QtGui import QIcon


import sys


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("QMenu Example")
        menubar = QMenuBar(parent=self)
        menufile = QMenu("File", parent=menubar)
        for text, ext, shortcut in [("New &Txt file", "txt", "CTRL+T"),
                                     ("New &Pdf file", "pdf", "CTRL+P"),
                                     ("New &Doc file", "doc", "CTRL+D")]:
            action = QAction(text, parent=menufile)
            action.setShortcut(shortcut)
            action.setToolTip("Create new %s file" % ext)
            action.setIcon(QIcon("%s.png" % ext))
            menufile.addAction(action)
        menufile.setToolTipsVisible(True)
        menubar.addMenu(menufile)
        self.setMenuBar(menubar)
        self.resize(250, 300)
        self.central_widget = FormWidget(self) 
        self.setCentralWidget(self.central_widget) 

        menufile.triggered.connect(self.on_menu)

    def on_menu(self, action):
        print("[SIG] %s menu clicked!" % action.text())


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)


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

Nota:

Per far sì che i tooltips siano visibili, è fondamentale chiamare il metodo dell’oggetto QMenu, setToolTipsVisible(True).

TEAR-OFF

Quando un menu viene usato intensivamente, è possibile abilitarne la modalità TEAROFF, ovvero è possibile “staccare” una copia del menù e appoggiarla in una parte delle schermo a noi congeniale. Questa copia, rimane fissa e non si disattiva ad ogni click, rendendo più comode le operazioni ripetitive.
Per abilitare tale funzione si deve utilizzare il metodo setTearOffEnabled(True) dell’oggetto QMenu.
Basterà cliccare sul menu e sulla linea tratteggiata dello stesso, apparirà così un menu, in una finestra propria, da muovere a proprio piacimento.

ICON SIZE

L’oggetto QMenu non ha un metodo che consenta di modificare la dimensione delle icone nelle voci di menu.
Per avere delle icone maggiorate, bisogna appoggiarsi alla classe QProxyStyle.
La classe suddetta, permette il wrapping della classe QStyle, che gestisce lo stile di default del sistema, semplificando di fatto l’override degli elementi della classe Qstyle.
Il trucco sta nel creare una classe che erediti da QStyle ed eseguire l’override dell’elemento che ci interessa.

class CustomProxyStyle(QProxyStyle):
    def pixelMetric(self, QStyle_PixelMetric, option=None, widget=None):
        if QStyle_PixelMetric == QStyle.PM_SmallIconSize and \
                isinstance(widget, QMenu):
            return 36
        else:
            return QProxyStyle.pixelMetric(self, QStyle_PixelMetric, option,
                                           widget)

L’override avviene proprio sul metodo pixelMetric di QStyle, che restituisce il pixel Metric relativo al QStyle enum passato come argomento e al widget.
Se il valore del QStyle enum equivale a PM_SmallIconSize e il widget di riferimento è un QMenu, allora il metodo ritorna il valore di pixel personalizzato, altrimenti ritorna quello di default.
Non resta che associare il nostro stile custom, alla nostra applicazione, con il metodo setStyle(style):
Per verificare che l’impostazione sia giusta, mettiamo nello slot dedicato al click su menu, due print che controllino i valori ritornati dal metodo pixeMetric del
nostro proxy style:

...
    def on_menu(self, action):
        menufile = self.sender()
        style = self.style()
        print("[SIG] %s menu clicked!" % action.text())
        print(style)
        print("QMenu: %s" % style.pixelMetric(QStyle.PM_SmallIconSize,
                                              widget=menufile))
        print("widgets: %s" % style.pixelMetric(QStyle.PM_SmallIconSize))


if __name__ == '__main__':
    app = QApplication(sys.argv)
    custom_style = CustomProxyStyle('bancaldo')
    main_window = MainWindow(parent=None)
    main_window.show()
    app.setStyle(custom_style)
    sys.exit(app.exec_())

[SIG] New &Txt file menu clicked!
<__main__.CustomProxyStyle object at 0x010F3850>
QMenu: 36
widgets: 16

Torna all’indice degli appunti

Categorie:PyQt5, python Tag:,