Archivio

Posts Tagged ‘PyQt5’

PyQt5: QDoubleSpinBox

21 Maggio 2019 Commenti chiusi

Torna all’indice degli appunti

QDoubleSpinBox

La classe QDoubleSpinBox crea un widget per la regolazione di un numero decimale, o mediante le freccette regolatrici, o scrivendo direttamente nella entry dedicata.
E’ praticamente identico al widget QSpinBox, che però gestisce i numeri interi.
Le funzionalità dell’oggetto QDoubleSpinBox, sono ereditate dalla classe QAbstractSpinBox.
Per creare l’oggetto double_spinbox, si utilizza il costruttore è QDoubleSpinBox:

>>> from PyQt5.QtWidgets import QApplication, QDoubleSpinBox
>>> app = QApplication([])
>>> double_spinbox = QDoubleSpinBox()

Di default il valore del range minimo e massimo dello double_spinbox è 0.0-99.99.
Questi valori sono reperibili con i due metodi minimum e maximum:

>>> double_spinbox.minimum()
0.0
>>> double_spinbox.maximum()
99.99

Per settare un range differente, si utilizzano i metodi setMinimum(minimum), setMaximum(maximum), o in un colpo solo setRange(minimum, maximum):

>>> double_spinbox.setMinimum(5.0)
>>> double_spinbox.setMaximum(300.0)
>>> double_spinbox.minimum()
5.0
>>> double_spinbox.maximum()
300.0

Il valore sul quale si posizionerà il double_spinbox, è settabile con il metodo setValue(value) e ottenibile con value:

>>> double_spinbox.setValue(50)
>>> double_spinbox.value()
50.0

REGOLAZIONE double_spinbox DA TASTIERA

I tasti utilizzabili da tastiera per regolare lo double_spinbox sono:

frecce su/giù: aggiustano il valore dello double_spinbox dello step definito con il metodo setSingleStep(step);
Pag Down/Pag Up: aggiustano il valore del double_spinbox dello step definito con il metodo setPageStep(step);
Home: porta il double_spinbox al valore minimo, definito con il metodo setMinimum(minimum);
End: porta il double_spinbox al valore massimo, definito con il metodo setMaximum(maximum);

REGOLAZIONE DIAL DA MOUSE

Se usiamo la rotella del mouse per regolare lo double_spinbox, il valore incrementato sarà quello determinato con il metodo setSingleStep(step).
Il valore di default dello scroll, è reperibile dall’istanza della classe QStyleHints, classe che permette di accedere a specifici settaggi relativi alla piattaforma corrente. L’istanza di tale classe è ottenibile con il metodo styeHints dell’oggetto QApplication.

>>> from PyQt5.QtWidgets import QApplication
>>> app = QApplication([])
>>> hints = app.styleHints()
>>> hints.wheelScrollLines()
3

DECIMALI

Di default, il double_spinbox gestisce 2 decimali. Per modificare questo numero, si utilizza il metod setDecimals(num) e per ottenere il numero di decimali utilizzati, si utilizza il metodo
decimals:

>>> double_spinbox.setValue(50.55555555)
>>> double_spinbox.value()
50.56
>>> double_spinbox.decimals()
2
>>> double_spinbox.setDecimals(5)
>>> double_spinbox.setValue(50.55555555)
>>> double_spinbox.value()
50.55556

Di seguito il codice che crea il widget:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import (QWidget, QApplication, QVBoxLayout, QDoubleSpinBox)


import sys


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

        self.resize(200, 50)


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QVBoxLayout()
        self.setLayout(layout)
        double_spinbox = QDoubleSpinBox()
        double_spinbox.setRange(0, 300)
        double_spinbox.setValue(50)
        double_spinbox.setSingleStep(0.5)
        layout.addWidget(double_spinbox)


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

PREFISSO E SUFFISSO

Ovviamente è possibile settare un prefisso ed un suffisso per il valore visualizzato all’interno dello double_spinbox, con i metodi:
setPrefix(prefix): setta il prefisso indicato dalla stringa prefix;
setSuffix(suffix): setta il suffisso indicato dalla stringa suffix;

Per ottenere gli eventuali prefissi e suffissi dello double_spinbox, si utilizzano invece i metodi: prefix e suffix.

...

class FormWidget(QWidget):
    def __init__(self, parent):
        ...
        double_spinbox.setPrefix("Price:")
        double_spinbox.setSuffix("$")
        double_spinbox.setSingleStep(2)
        layout.addWidget(double_spinbox)
...

SEGNALI

Come per lo spinbox, l’unico segnale da tenere in considerazione nelle double_spinbox, è:

valueChanged: Emesso qualdo il valore dello double_spinbox viene modificato;

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QSpinBox

21 Maggio 2019 Commenti chiusi

Torna all’indice degli appunti

QSpinBox

La classe QSpinBox crea un widget per la regolazione di un numero, o mediante le freccette regolatrici, o scrivendo direttamente nella entry dedicata.
Le funzionalità dell’oggetto QSpinBox, sono ereditate dalla classe QAbstractSpinBox.
Per creare l’oggetto spinbox, si utilizza il costruttore è QSpinBox:

>>> from PyQt5.QtWidgets import QApplication, QSpinBox
>>> app = QApplication([])
>>> spinbox = QSpinBox()

Di default il valore del range minimo e massimo dello spinbox è 0-99.
Questi valori sono reperibili con i due metodi minimum e maximum:

>>> spinbox.minimum()
0
>>> spinbox.maximum()
99

Per settare un range differente, si utilizzano i metodi setMinimum(minimum), setMaximum(maximum), o in un colpo solo setRange(minimum, maximum):

>>> spinbox.setMinimum(5)
>>> spinbox.setMaximum(300)
>>> spinbox.minimum()
5
>>> spinbox.maximum()
300

Il valore sul quale si posizionerà il spinbox, è settabile con il metodo setValue(value) e ottenibile con value:

>>> spinbox.setValue(50)
>>> spinbox.value()
50

REGOLAZIONE spinbox DA TASTIERA

I tasti utilizzabili da tastiera per regolare lo spinbox sono:

frecce su/giù: aggiustano il valore dello spinbox dello step definito con il metodo setSingleStep(step);
Pag Down/Pag Up: aggiustano il valore del spinbox dello step definito con il metodo setPageStep(step);
Home: porta il spinbox al valore minimo, definito con il metodo setMinimum(minimum);
End: porta il spinbox al valore massimo, definito con il metodo setMaximum(maximum);

REGOLAZIONE DIAL DA MOUSE

Se usiamo la rotella del mouse per regolare lo spinbox, il valore incrementato sarà quello determinato con il metodo setSingleStep(step).
Il valore di default dello scroll, è reperibile dall’istanza della classe QStyleHints, classe che permette di accedere a specifici settaggi relativi alla piattaforma corrente. L’istanza di tale classe è ottenibile con il metodo styeHints dell’oggetto QApplication.

>>> from PyQt5.QtWidgets import QApplication
>>> app = QApplication([])
>>> hints = app.styleHints()
>>> hints.wheelScrollLines()
3

Di seguito il codice che crea il widget:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import (QWidget, QApplication, QVBoxLayout, QSpinBox)


import sys


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

        self.resize(200, 50)


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QVBoxLayout()
        self.setLayout(layout)
        spinbox = QSpinBox()
        spinbox.setRange(0, 300)
        spinbox.setValue(50)
        spinbox.setSingleStep(2)
        layout.addWidget(spinbox)


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

PREFISSO E SUFFISSO

Ovviamente è possibile settare un prefisso ed un suffisso per il valore visualizzato all’interno dello spinbox, con i metodi:
setPrefix(prefix): setta il prefisso indicato dalla stringa prefix;
setSuffix(suffix): setta il suffisso indicato dalla stringa suffix;

Per ottenere gli eventuali prefissi e suffissi dello spinbox, si utilizzano invece i metodi: prefix e suffix.

...

class FormWidget(QWidget):
    def __init__(self, parent):
        ...
        spinbox.setPrefix("Price:")
        spinbox.setSuffix("$")
        spinbox.setSingleStep(2)
        layout.addWidget(spinbox)
...

SEGNALI

L’unico segnale da tenere in considerazione nelle spinbox, è:

valueChanged: Emesso qualdo il valore dello spinbox viene modificato;

>>> spinbox.valueChanged.connect(lambda value: print("[SIG] value changed to: %s" % value))
<PyQt5.QtCore.QMetaObject.Connection object at 0x0358CDF0>
>>> spinbox.setValue(50)
[SIG] value changed to: 50

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QDial

16 Maggio 2019 Commenti chiusi

Torna all’indice degli appunti

QDial

La classe QDial crea un widget rappresentante un potenziometro di forma circolare.
Ereditando da QAbstractSlider, utilizza i metodi ereditati da suddetta classe.
Il costruttore è ovviamente QDial:

>>> app = QApplication([])
>>> dial = QDial()

Di default il valore del range minimo e massimo del dial è 0-99.
Questi valori sono reperibili con i due metodi minimum e maximum:

>>> dial.minimum()
0
>>> dial.maximum()
99

Per settare un range differente, si utilizzano i metodi setMinimum(minimum) e setMaximum(maximum):

>>> dial.setMinimum(5)
>>> dial.setMaximum(300)
>>> dial.minimum()
5
>>> dial.maximum()
300

Il valore sul quale si posizionerà il dial, è settabile con il metodo setValue(value) e ottenibile con value:

>>> dial.setValue(150)
>>> dial.value()
150

REGOLAZIONE DIAL DA TASTIERA

I tasti utilizzabili da tastiera per regolare il dial sono:

frecce sx/su e rx/giù: aggiustano il valore del dial dello step definito con il metodo setSingleStep(step);
Pag Down/Pag Up: aggiustano il valore del dial dello step definito con il metodo setPageStep(step);
Home: porta il dial al valore minimo, definito con il metodo setMinimum(minimum);
End: porta il dial al valore massimo, definito con il metodo setMaximum(maximum);

REGOLAZIONE DIAL DA MOUSE

Se usiamo la rotella del mouse per regolare il dial, il valore incrementato sarà quello determinato con il metodo setSingleStep(step).
Il valore di default dello scroll, è reperibile dall’istanza della classe QStyleHints, classe che permette di accedere a specifici settaggi relativi alla piattaforma corrente. L’istanza di tale classe è ottenibile con il metodo styeHints dell’oggetto QApplication.

>>> from PyQt5.QtWidgets import QApplication
>>> app = QApplication([])
>>> hints = app.styleHints()
>>> hints.wheelScrollLines()
3

Ecco un codice di esempio per vedere l’aspetto ed il comportamento del dial:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QWidget, QApplication, QBoxLayout, QDial


import sys


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("QDial Example")
        self.central_widget = FormWidget(parent=self)
        self.setCentralWidget(self.central_widget)
        self.resize(300, 250)


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.setLayout(layout)
        dial = QDial()
        dial.setMinimum(5)
        dial.setMaximum(200)
        dial.setValue(100)  # setto l'indicatore a metà scala
        layout.addWidget(dial)


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

Premendo le frecce direzionali, PagUp/PagDown, Home/End, il dial si muove lungo gli step predefiniti.
Per modificare gli step, utilizziamo i metodi visti in precedenza:

>>> dial.setSingleStep(10)
>>> dial.singleStep()
10
>>> dial.setPageStep(50)
>>> dial.pageStep()
50

WRAPPING

Settando il flag wrapping a True, facciamo in modo che la rotella una volta raggiunto il valore massimo, riparta dal valore minimo, senza avere un gradino (ottenibile invece riportando a False, il flag.
Questa modalità si ottiene utilizzando il metodo setWrapping(bool). Per conoscere lo stato del flag, si utilizza il metodo wrapping:

>>> dial.setWrapping(True)
>>> dial.wrapping()
True

INDICATORI DI SCALA (notches)

Come si nota dalla figura precedente, il dial è privo di scala.
Per abilitare la scala del dial, si utilizza il metodo setNotchesVisible(bool).

Se vogliamo che i notches siano più fini, ovviamente bisognerà modificare il valore del single step, con il metodo setSingleStep(step).

...
class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.setLayout(layout)
        dial = QDial()
        dial.setMinimum(5)
        dial.setMaximum(200)
        dial.setValue(100)
        dial.setSingleStep(5)
        dial.setPageStep(50)
        layout.addWidget(dial)
        dial.setNotchesVisible(True)
...

SEGNALI

Il più importante è decisamente valueChanged, ereditato dalla classe QAbstractSlider.
Il segnale viene emesso quando il valore del dial viene modificato per via del metodo setValue(value).
Il valore è accessibile grazie al parametro value.

>>> dial.valueChanged.connect(lambda value: print("[SIG] value changed to %s" % value))
<PyQt5.QtCore.QMetaObject.Connection object at 0x0372CD70>
>>> dial.setValue(50)
[SIG] value changed to 50

Ecco un altro esempio:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import (QWidget, QApplication, QBoxLayout, QDial, QLabel,
                             QProgressBar)
from PyQt5.QtCore import Qt


import sys


class CustomProgressBar(QProgressBar):
    def __init__(self, minimum, maximum):
        super(CustomProgressBar, self).__init__()
        self.setRange(minimum, maximum)
        self.setAlignment(Qt.AlignCenter)
        self._text = "0/0 (0%)"
        self.setFormat("%v/%m (%p%)")

    def set_text(self, text):
        self._text = text

    def text(self):
        return self._text

    def update(self, step):
        maximum = self.maximum()
        rate = step * 100 / maximum
        string = "{}/{} ({}%)".format(step, maximum, rate)
        self.set_text(string)
        self.setValue(step)


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


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.setLayout(layout)
        minimum, maximum = 0, 200
        dial = QDial()
        dial.setMinimum(minimum)
        dial.setMaximum(maximum)
        dial.setSingleStep(5)
        dial.setPageStep(50)
        dial.setNotchesVisible(True)
        dial.setFixedSize(100, 100)
        val = dial.value()
        self.label = QLabel("Volume: %s" % val)
        self.bar = CustomProgressBar(minimum, maximum)

        layout.addWidget(dial)
        layout.addWidget(self.label)
        layout.addWidget(self.bar)

        dial.valueChanged.connect(self.on_value_change)

    def on_value_change(self, value):
        text = "Volume: %s" % value
        self.label.setText(text)
        self.bar.update(value)


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

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QSlider

16 Maggio 2019 Commenti chiusi

Torna all’indice degli appunti

QSlider

La classe QSlider crea un widget rappresentante un cursore, che, spostato traduce la sua posizione nel numero corrispondente.
Le funzionalità dell’oggetto QSlider, sono ereditate dalla classe QAbstractSlider.
Per creare l’oggetto cursore, si utilizza il costruttore è QSlider:

>>> from PyQt5.QtWidgets import QApplication, QSlider
>>> app = QApplication([])
>>> slider = QSlider()

Di default il valore del range minimo e massimo dello Slider è 0-99.
Questi valori sono reperibili con i due metodi minimum e maximum:

>>> Slider.minimum()
0
>>> Slider.maximum()
99

Per settare un range differente, si utilizzano i metodi setMinimum(minimum), setMaximum(maximum), o in un colpo solo setRange(minimum, maximum):

>>> slider.setMinimum(5)
>>> slider.setMaximum(300)
>>> slider.minimum()
5
>>> slider.maximum()
300

Il valore sul quale si posizionerà il Slider, è settabile con il metodo setValue(value) e ottenibile con value:

>>> slider.setValue(50)
>>> slider.value()
50

REGOLAZIONE Slider DA TASTIERA

I tasti utilizzabili da tastiera per regolare lo Slider sono:

frecce sx/su e rx/giù: aggiustano il valore dello Slider dello step definito con il metodo setSingleStep(step);
Pag Down/Pag Up: aggiustano il valore del Slider dello step definito con il metodo setPageStep(step);
Home: porta il Slider al valore minimo, definito con il metodo setMinimum(minimum);
End: porta il Slider al valore massimo, definito con il metodo setMaximum(maximum);

REGOLAZIONE DIAL DA MOUSE

Se usiamo la rotella del mouse per regolare lo slider, il valore incrementato sarà quello determinato con il metodo setSingleStep(step).
Il valore di default dello scroll, è reperibile dall’istanza della classe QStyleHints, classe che permette di accedere a specifici settaggi relativi alla piattaforma corrente. L’istanza di tale classe è ottenibile con il metodo styeHints dell’oggetto QApplication.

>>> from PyQt5.QtWidgets import QApplication
>>> app = QApplication([])
>>> hints = app.styleHints()
>>> hints.wheelScrollLines()
3

ORIENTAMENTO

Di default, l’orientamento dello slider è verticale. Per cambiarlo si utilizza il metodo setOrientation(orientation) dove orientation può essere uno tra gli enum: Qt.Horizontal di valore 1, o Qt.Vertical, di valore 2.

>>> from PyQt5.QtCore import Qt
>>> slider.orientation()
2
>>> slider.setOrientation(Qt.Horizontal)
>>> slider.orientation()
1

TICK MARKS

I tick marks rappresentano la linea graduata dello slider. Di default la linea graduata non viene visualizzata.
Per definire in quale posizione visualizzare i tick marks, si utilizza il metodo setTickPosition(position), dove position può essere uno tra i seguenti enum di QSlider:

costante valore descrizione
QSlider.NoTicks 0 Default: nessuna scala graduata
QSlider.TicksBothSides 3 Disegna la scala graduata da entrambi i lati del cursore
QSlider.TicksAbove 1 Disegna la scala graduata sopra al cursore con orientamento verticale
QSlider.TicksBelow 2 Disegna la scala graduata sotto al cursore con orientamento orizzontale
QSlider.TicksLeft 1 Disegna la scala graduata a sinistra del cursore con orientamento verticale
QSlider.TicksRight 2 Disegna la scala graduata a destra del cursore con orientamento verticale
>>> slider.tickPosition()
0
>>> slider.setTickPosition(QSlider.TicksBothSides)
>>> slider.tickPosition()
3

E’ possibile anche modificare il tick Interval, ovvero ogni quanti step disegnare una tacca, con il metodo setTickInterval(step).
Per sapere invece il tick interval dello slider, si utilizza il metodo tickInterval:

>>> slider.setTickInterval(5)
>>> slider.tickInterval()
5

Di seguito il codice che crea il widget:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import (QWidget, QApplication, QVBoxLayout, QSlider,
                             QProgressBar)
from PyQt5.QtCore import Qt


import sys


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


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QVBoxLayout()
        self.setLayout(layout)
        slider = QSlider(orientation=Qt.Horizontal)
        slider.setRange(0, 200)
        slider.setTickPosition(QSlider.TicksBothSides)
        slider.setSingleStep(5)
        slider.setPageStep(50)
        slider.setTickInterval(5)
        slider.setOrientation(Qt.Horizontal)
        layout.addWidget(slider)


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

SEGNALI

I principali segnali da tenere in considerazione nelle slider, sono:

valueChanged: Emesso qualdo il valore dello slider viene modificato;

>>> slider.valueChanged.connect(lambda value: print("[SIG] value changed to: %s" % value))
<PyQt5.QtCore.QMetaObject.Connection object at 0x035BCD30>
>>> slider.setValue(60)
[SIG] value changed to: 60

sliderPressed: Emesso quando si clicca sul cursore dello slider per trascinarlo;
sliderMoved: Emesso quando lo slider si muove;
sliderReleased: Emesso quando il cursore dello slider viene rilasciato;

Ovviamente in una applicazione pratica, il segnale più frquentemente utilizzato è il primo, ad esempio, per aggiornare un widget legato al valore dello slider:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import (QWidget, QApplication, QGridLayout, QSlider,
                             QLabel, QProgressBar)
from PyQt5.QtCore import Qt


import sys


class CustomProgressBar(QProgressBar):
    def __init__(self, minimum, maximum):
        super(CustomProgressBar, self).__init__()
        self.setRange(minimum, maximum)
        self.setAlignment(Qt.AlignCenter)
        self._text = "0/0 (0%)"
        self.setFormat("%v/%m (%p%)")

    def set_text(self, text):
        self._text = text

    def text(self):
        return self._text

    def update(self, step):
        maximum = self.maximum()
        rate = step * 100 / maximum
        string = "{}/{} ({}%)".format(step, maximum, rate)
        self.set_text(string)
        self.setValue(step)


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


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QGridLayout()
        self.setLayout(layout)
        minimum, maximum = 0, 200
        l_slider = QSlider()
        l_slider.setObjectName("left")
        r_slider = QSlider()
        r_slider.setObjectName("right")
        for row, slider in enumerate((l_slider, r_slider)):
            slider.setRange(0, 200)
            slider.setTickPosition(QSlider.TicksBelow)
            slider.setOrientation(Qt.Horizontal)
            slider.setSingleStep(5)
            slider.setPageStep(25)
            layout.addWidget(slider, row, 1)
            slider.valueChanged.connect(self.on_slider)
        self.l_bar = CustomProgressBar(minimum, maximum)
        self.r_bar = CustomProgressBar(minimum, maximum)
        layout.addWidget(QLabel("left"), 0, 0)
        layout.addWidget(QLabel("right"), 1, 0)
        layout.addWidget(self.l_bar, 0, 2)
        layout.addWidget(self.r_bar, 1, 2)

    def on_slider(self, value):
        slider = self.sender()
        if slider.objectName().lower() == "left":
            self.l_bar.update(value)
        else:
            self.r_bar.update(value)


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

Come si nota dal codice, è possibile assegnare un nome all’oggetto creato, con il metodo setObjectName(name) ed è possibile reperirlo con il metodo objectName. Ovviamente non ci sarebbero questi problemi collegando gli eventi dei singoli widget, a slot non condivisi.

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QColumnView

16 Maggio 2019 Commenti chiusi

Torna all’indice degli appunti

QColumnView

Il ColumnView widget è simile ad un ListWidget, solo che solitamente contiene elementi
che contengono a loro volta altri elementi (child).
Ogni sottoelemento viene piazzato orizzontalmente in un altro List Widget.
Questo tipo di visualizzazione è chiamata anche Cascade List.
La classe QColumnView è una delle classi Model/View, stuttura portante del framework model/view di Qt.
Ne deriva che QColumnView implementa l’interfaccia QAbstractItemView, necessaria alla visualizzazione corretta di modelli di tipo QAbstractItemModel.

Facciamo riferimento ad un tree-model, per meglio capire il funzionamento del QColumnView.
Creaiamo un struttura gerarchica con 3 squadre di calcio e 3 giocatori per ognuna di essa

>>> from PyQt5.QtGui import QStandardItem, QStandardItemModel
>>> d_team = {"Inter": ["Handanovic", "D'Ambrosio", "De Vrij"],
...           "Milan": ["Donnarumma", "Calabria", "Romagnoli"],
...           "Juev": ["Szczezny", "De Sciglio", "Chiellini"]}
... 
>>> model = QStandardItemModel()
>>> for row, team in enumerate(d_team.keys()):
...     model.setItem(row, 0, QStandardItem(team))
...     item = model.item(row, 0)
...     for rrow, player in enumerate(d_team.get(item.text())):
...         item.setChild(rrow, 0, QStandardItem(player))

Poi creiamo un oggetto QColumnView al quale passiamo il nostro model con il metodo setModel(QStandardItemModel):

>>> from PyQt5.QtWidgets import QApplication, QColumnView
>>> app = QApplication([])
>>> cw = QColumnView()
>>> cw.setModel(model)

LARGHEZZA DELLE COLONNE

La larghezza delle colonne è settabile con il metodo setColumnWidth(QList) dove QList è una lista di int, uno per ogni colonna, partendo dalla prima. Se la lista non contiene numeri sufficienti, verranno modificate solo le colonne corrispondenti. Se i numeri sono troppi, verranno memorizzati per le future colonne aggiunte. Per ottenere invece la lista delle larghezze delle colonne, si utilizza il metodo columnWidths:

>>> cw.columnWidths()
[256]
>>> cw.setColumnWidths([120, 120])
>>> cw.columnWidths()
[120]

Nota:
Perchè appare solo una colonna?
Perchè al momento è attiva solo la prima, la seconda, quella che contiene i giocatori, non essendo stata attivata con un click, è nascosta.
Quando la colonna verrà visualizzata, allora assumerà la larghezza assegnata in precedenza.

E’ possibile anche togliere dalle colonne i >Grip Resize, ovvero le freccette che permettono di modificare la larghezza della colonna.
Il metodo per abilitarle o disabilitarle, è setGripResize(bool), di default sono presenti.

Ecco un codice minimale d’esempio:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QWidget, QApplication, QBoxLayout, QColumnView
from PyQt5.QtGui import QStandardItem, QStandardItemModel
import sys


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


class FormWidget(QWidget):
    def __init__(self, parent, model=None):
        super(FormWidget, self).__init__(parent)
        layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.setLayout(layout)
        self.column_view = QColumnView()
        self.column_view.setModel(model)
        self.column_view.setColumnWidths([120, 120, 120])
        self.column_view.clicked.connect(self.on_data)
        self.column_view.setResizeGripsVisible(False)
        layout.addWidget(self.column_view)

    def on_data(self):
        print("[SIG] Column widths: %s" % self.column_view.columnWidths())


class CustomModel(QStandardItemModel):
    def __init__(self):
        super(CustomModel, self).__init__()
        self.setVerticalHeaderItem(0, QStandardItem("Team"))
        self.setVerticalHeaderItem(1, QStandardItem("Player"))

    def generate_tree(self):

        d_team = {"Inter": ["Handanovic", "D'Ambrosio", "De Vrij"],
                  "Milan": ["Donnarumma", "Calabria", "Romagnoli"],
                  "Juve": ["Szczezny", "De Sciglio", "Chiellini"]}
        for row, team in enumerate(d_team.keys()):
            self.setItem(row, 0, QStandardItem(team))
            item = self.item(row, 0)
            for rrow, player in enumerate(d_team.get(item.text())):
                item.setChild(rrow, 0, QStandardItem(player))


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

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QTableWidget

14 Maggio 2019 Commenti chiusi

Torna all’indice degli appunti

QTableWidget

Il TableWidget è widget complesso che permette di visualizzare i dati in forma di griglia, con righe, colonne e le rispettive intestazioni.
Il costruttore è ovviamente QTableWidget. Dopo aver istanziato l’oggetto, si definiscono le dimensioni della tabella, cno gli appositi metodi setRowCount(row) e setColumnCount(column):

>>> from PyQt5.QtWidgets import QTableWidget, QApplication 
>>> app = QApplication([])
>>> table = QTableWidget(5, 2, None)
>>> table.setRowCount(5)
>>> table.setColumnCount(2)

Le dimensioni della tabella possono essere recuperate con i soliti metodi rowCount e columnCount:

>>> table.rowCount()
5
>>> table.columnCount()
2

ITEMS

Gli elementi della tabella sono tutti oggetti QTableWidgetItem e vengono creati fuori dalla tabella ed inseriti, in seguito, con il metodo
setItem(tablewidgetitem).
I Top-level item di questo tipo vanno costruiti senza parent e possono tutti contenere: testo, icone e checboxes.

HEADER

E’ possibile settare le intestazioni della tabella, sia orizzontali (intestazioni di colonna), sia verticali (intestazioni di riga) con i metodi:

setHorizontalHeader(column, tablewidgetitem): setta un oggetto QTableWidgetItem nell’intestazione della colonna column;
setVerticalHeader(row, tablewidgetitem): setta un oggetto QTableWidgetItem nell’intestazione della riga row;

>>> from PyQt5.QtWidgets import QTableWidgetItem
>>> for position in range(1, 6):
...     table.setVerticalHeaderItem(position, QTableWidgetItem("pos %s" % position))

E’ anche possibile settare multiple intestazioni con una lista di labels, con i metodi:

setHorizontalHeaderLabels(labels): setta le intestazioni presenti nella lista labels, una per colonna;
setVerticalHeaderLabels(labels): setta le intestazioni presenti nella lista labels, una per riga;

>>> table.setHorizontalHeaderLabels(["team", "pts"])

I dati delle intestazioni possono essere recuperati con i metodi:

horizontalHeader(column): ritorna l’oggetto QTableWidgetItem della colonna column;
verticalHeader(row): ritorna l’oggetto QTableWidgetItem della riga row;

>>> table.horizontalHeaderItem(0)
<PyQt5.QtWidgets.QTableWidgetItem object at 0x0379D6C0>
>>> table.horizontalHeaderItem(0).text()
'team'
>>> table.verticalHeaderItem(0).text()
'pos 1'

Come detto, l’inserimento degli elementi in tabella, avviene per mezzo del metodo setItem(qtablewidgetitem):

>>> for row, data in enumerate([("Inter", "66"), ("Milan", "62"), ("Juventus", "89"),
...                             ("Atalanta", "65"), ("Napoli", "76")]):
...     team, pts = data
...     table.setItem(row, 0, QTableWidgetItem(team))
...     table.setItem(row, 1, QTableWidgetItem(pts)) 

MODEL

Il model di riferimento del QTableWidget è il QAbstractTableModel che eredita da QAbstractItemModel.

>>> model = table.model()
>>> index_0_0 = model.index(0, 0)
>>> model.itemData(index_0_0)
{0: 'Inter'}
>>> table.itemFromIndex(index_0_0)
<PyQt5.QtWidgets.QTableWidgetItem object at 0x0379D9E0>
>>> table.itemFromIndex(index_0_0).text()
'Inter'

Ovviamente esiste una via diretta per ottenere un elemento dalla tabella, utilizzando il metodo item(row, column):

>>> table.item(0, 0)
<PyQt5.QtWidgets.QTableWidgetItem object at 0x0379D9E0>
>>> table.item(0, 0).text()
'Inter'

METODI

I metodi più significativi del QTableWidget sono:

setCurrentItem(tablewidgetitem): setta tablewidgetitem come elemento corrente:

>>> table.setCurrentItem(table.item(0, 0))

currentItem: ritorna l’oggetto tablewidgetitem corrente:

>>> table.currentItem()
<PyQt5.QtWidgets.QTableWidgetItem object at 0x0379D9E0>
>>> table.currentItem().text()
'Inter'

setCurrentCell(row, column): setta la cella corrente con coordinate row e column:

table.setCurrentCell(1, 1)

Non c’è un metodo currentCell, ma è possibile utilizzare i già noti currentRow e currentColumn.

sortItems(column, ordering): setta il tipo di ordinamento per la colonna column. il tipo di ordinamento ordering, può essere uno dei due valori Qt.AscendingOrder, o Qt.DescendingOrder:

>>> for row in range(table.rowCount()):
...     print(table.item(row, 0).text(), table.item(row, 1).text())
...     
Inter 66
Milan 62
Juventus 89
Atalanta 65
Napoli 76
>>> table.sortItems(0, Qt.AscendingOrder)
>>> for row in range(table.rowCount()):
...     print(table.item(row, 0).text(), table.item(row, 1).text())
...     
Atalanta 65
Inter 66
Juventus 89
Milan 62
Napoli 76

Come si nota i dati della prima colonna sono stati ordinati.

>>> table.sortItems(1, Qt.DescendingOrder)
>>> for row in range(table.rowCount()):
...     print(table.item(row, 0).text(), table.item(row, 1).text())
...     
Juventus 89
Napoli 76
Inter 66
Atalanta 65
Milan 62

Metodi di eliminazione

Per eliminare tutti gli elementi della view, comprese le selezioni e le intestazioni, si utilizza il metodo clear.
Per eliminare solo i contenuti della tabella, escluse selezioni e intestazioni, si utilizza il metodo clearContents.

Metodi di selezione

Per selezionare tutti gli elementi in tabella, utilizzare il metodo selectAll;
Per per visualizzare gli elementi selezionati, si utilizza il metodo selectedItems:

>>> table.selectedItems()
[<PyQt5.QtWidgets.QTableWidgetItem object at 0x0379D3A0>]
>>> table.selectAll()
>>> table.selectedItems()
[<PyQt5.QtWidgets.QTableWidgetItem object at 0x0379D260>, <PyQt5.QtWidgets.QTabl...

Per selezionare tutti gli elementi di una riga, si utilizza il metodo selectRow(row)
Per selezionare tutti gli elementi di una colonna, si utilizza il metodo selectColumn(column)

>>> table.selectAll()
>>> sel = table.selectedItems()
>>> len(sel)
10
>>> table.selectRow(2)
>>> len(table.selectedItems())
2
>>> table.selectColumn(0)
>>> len(table.selectedItems())
5

Se vogliamo settare una selezione parziale, che non sia appunto, una colonna o una riga intere, oppure un selectAll, si utilizza il metodo setRangeSelected(range, bool), dove range è un oggetto QTableWidgetSelectionRange e bool è
il flag che rende visibile o meno la selezione nel widget.
QTableWidgetSelectionRange altro non è che una classe che immagazzina i riferimenti rispettivamente di: top_row, left_column, bottom_row, right_column.
Supponento di voler selezionare le due righe centrali dovremo fare come segue:

>>> from PyQt5.QtWidgets import QTableWidgetSelectionRange
>>> top_row = 1
>>> left_column = 0
>>> bottom_row = 2
>>> right_column = 1
>>> range_sel = QTableWidgetSelectionRange(top_row, left_column, bottom_row, right_column)
>>> table.selectAll()
>>> for item in table.selectedItems():
...     item.setSelected(False)
...     
>>> sel = QTableWidgetSelectionRange(1, 0, 2, 1)
>>> table.setRangeSelected(sel, True)
>>> len(table.selectedItems())
4

SEGNALI

I più significativi sono:

currentItemChanged: segnale emesso quando cambia l’elemento corrente

>>> table.currentItemChanged.connect(lambda: print("[SIG] Current item changed!"))
<PyQt5.QtCore.QMetaObject.Connection object at 0x0377BE30>
>>> table.setCurrentItem(table.item(0, 0))
[SIG] Current item changed!
>>> table.setCurrentItem(table.item(0, 0))

currentCellChanged: segnale emesso quando cambia la cella corrente. Questo segnale causerà anche l’emissione del segnale currentItemChanged, qualora
cambi ovviamente il current Item

>>> table.currentCellChanged.connect(lambda: print("[SIG] Current cell changed!"))
<PyQt5.QtCore.QMetaObject.Connection object at 0x0535D170>
>>> table.setCurrentCell(2, 0)
[SIG] Current item changed!
[SIG] Current cell changed!

itemClicked: segnale emesso quando clicchiamo su un elemento. Viene passato allo slot connesso, anche l’item sul quale abbiamo cliccato.

cellClicked: segnale emesso quando clicchiamo su una cella

itemSelectionChanged: segnale emesso quando cambia la selezione

>>> table.itemSelectionChanged.connect(lambda: print("[SIG] selection changed!"))
<PyQt5.QtCore.QMetaObject.Connection object at 0x0535D230>
>>> table.setRangeSelected(QTableWidgetSelectionRange(1, 0, 3, 1), True)
[SIG] selection changed!

Di seguito un codice di esempio:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QWidget, QApplication, QBoxLayout
from PyQt5.QtWidgets import (QTableWidget, QTableWidgetItem,
                             QTableWidgetSelectionRange)
from PyQt5.QtGui import QIcon


import sys


class MainWindow(QMainWindow):
    def __init__(self, parent=None, data=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("QTableWidget Example")
        self.central_widget = FormWidget(parent=self, data=data)
        self.setCentralWidget(self.central_widget)
        self.resize(300, 250)


class FormWidget(QWidget):
    def __init__(self, parent, data=None):
        super(FormWidget, self).__init__(parent)
        layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.setLayout(layout)
        self.table = QTableWidget()
        self.table.setRowCount(5)
        self.table.setColumnCount(2)
        layout.addWidget(self.table)
        self.table.setHorizontalHeaderLabels(["Team", "pts"])
        if data:
            for row, tup in enumerate(data):
                team, pts, iconfile = tup
                icon = QIcon(iconfile)
                self.table.setItem(row, 0, QTableWidgetItem(icon, team))
                self.table.setItem(row, 1, QTableWidgetItem(pts))

        self.table.itemClicked.connect(self.on_item_click)
        self.table.itemSelectionChanged.connect(self.on_selection_change)

    def on_item_click(self, item):
        print("[SIG] clicked on %s" % item.text())

    def select_by(self, string):
        rows = self.table.rowCount()
        columns = self.table.columnCount()
        for row in range(rows):
            for column in range(columns):
                item = self.table.item(row, column)
                if item.text().lower() == string.lower():
                    item.setSelected(True)
                else:
                    item.setSelected(False)

    def on_selection_change(self):
        print("[SIG] Selected Items chaged:")
        self.table = self.sender()
        items = self.table.selectedItems()
        for item in items:
            print(item.text())


if __name__ == '__main__':
    app = QApplication(sys.argv)
    teams = [("Inter", "66", "interlogo.png"),
             ("Milan", "62", "milanlogo.png"),
             ("Juventus", "89", "juvelogo.png"),
             ("Napoli", "76", "napolilogo.png"),
             ("Atalanta", "65", "atalantalogo.png")]
    main_window = MainWindow(parent=None, data=teams)
    main_window.show()
    sys.exit(app.exec_())

e con selezione preimpostata:

...

class FormWidget(QWidget):
    def __init__(self, parent, data=None):
        ...
        self.table.setRangeSelected(QTableWidgetSelectionRange(1, 0, 2, 1), 
                                    True)
        ...

E’ ovviamente possibile selezionare per criterio.
Mettiamo ad esempio di voler evidenziare le celle che contengono un determinato valore, possiamo agire in due modi: o selezionando quelle che sposano un certo criterio, o spegnendo quelle che non lo sposano.
Notare che le vecchie selezioni rimangono attive, quindi è sempre bene deselezionare tutto, prima di procedere con una nuova selezione.

caso 1:

...

class FormWidget(QWidget):
    def __init__(self, parent, data=None):
        ...

        self.select_by("Inter")
        
    def select_all(self, select):
        self.table.selectAll()
        for item in self.table.selectedItems():
            item.setSelected(select)

    def select_by(self, string):
        self.select_all(False)  # deseleziono tutto
        rows = self.table.rowCount()
        for row in range(rows):
            item = self.table.item(row, 0)
            if item.text().lower() == string.lower():
                item.setSelected(True)
            else:
                item.setSelected(False)
        ...

caso 2:

...

class FormWidget(QWidget):
    def __init__(self, parent, data=None):
        ...
        self.select_by("Inter")
        
    def select_by(self, string):
        self.select_all(True)  # seleziono tutto
        for item in self.table.selectedItems():
            if item.text().lower() != string.lower():
                item.setSelected(False)
        ...

ORDINAMENTO

E’ possibile attivare l’ordinamento delle colonne della tabella con il metodo setSortingEnabled(bool), in questo modo sarà possibile ordniare i dati, cliccando sull’intestazione della colonna. Gli accoppiamenti sulle altre colonne, verranno mantenuti:

...
        self.table.setSortingEnabled(True)
        self.table.itemClicked.connect(self.on_item_click)
        self.select_by("Inter")
...

Quando si clicca sull’intestazione della colonna, si ottengono alternativamente, un ordinamento crescente e poi decrescente.

Inoltre non si perdono le selezioni precedentemente fatte.

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QStandardItemModel

9 Maggio 2019 Commenti chiusi

Torna all’indice degli appunti

QStandardItemModel

Il QStandardItemModel è una classe che fornisce un modello generico per l’immagazzinamento di dati personalizzati.
Come il QStandardItem, anche il QStandardItemModel fa parte delle classi Model/View, classi cardine del framework Model/View.
Viene comunemente utilizzato come repository per i tipi di dato standard di Qt.
Gli elementi di un QStandardItemModel sono ovviamente oggetti QStandardItem.
Il QStandardItemModel implementa un’interfaccia QAbstractItemModel, ovvero il model può essere usato per fornire i dati in ogni view che supporti tale interfaccia, come ad esempio QListView, QTableView e QTreeView.

Se la nostra esigenza è di avere una lista o un albero, per prima cosa dobbiamo creare un oggetto QStandardItemModel ed usare i metodi appendRow(QStandardItem) e appendColumn(QStandardItem), per aggiungere elementi al model.
Se invece abbiamo necessità di una tabella, la possiamo dimensionare con i metodi setRowCount e setColumnCount.
Per aggiungere un elemento alla tabella useremo il metodo setItem(row, column, QStandarItem.
Per inserire elementi possiamo usare i metodi insertRow(row, [QStanrdItems]) e insertColumn(column, [QStandardItems]), mentre per eliminare elementi sono disponibili i metodi removeRow(row) e removeColumn(column).
Tutti questi metodi sono già stati affrontati negli appunti inerenti il QStandardItem.
Per rimuovere tutti gli elementi dal model si utilizza il metodo clear

Per accedere ad un elemento si utilizza il metodo item(row, column) e per cercarne più di uno, il metodo findItems(string, Qt.MatchFlags)

Vediamo ad esempio come creare una tabella:

>>> from PyQt5.QtGui import QStandardItemModel, QStandardItem
>>> model = QStandardItemModel()
>>> model.setRowCount(4)
>>> model.setColumnCount(4)
>>> for row in range(model.rowCount()):
...     for column in range(model.columnCount()):
...         model.setItem(row, column, QStandardItem("row {}, column {}".format(row, column))) 

Per recuperare gli elementi possiamo, come già accennato, utilizzare il metodo item(row, column):

>>> model.item(0, 0)
<PyQt5.QtGui.QStandardItem object at 0x03667F80>
>>> model.item(0, 0).text()
'row 0, column 0'
>>> model.item(2, 2).text()
'row 2, column 2'
>>> for row in range(4):
...     for column in range(4):
...         print(model.item(row, column).text())
...         
row 0, column 0
row 0, column 1
row 0, column 2
row 0, column 3
row 1, column 0
row 1, column 1
row 1, column 2
row 1, column 3
row 2, column 0
row 2, column 1
row 2, column 2
row 2, column 3
row 3, column 0
row 3, column 1
row 3, column 2
row 3, column 3

o findItems(text, Qt.MatchFlag, col=0) utilizzando i Qt.MatchFlags, che sono qui riassunti:

Costante Valore Descrizione
Qt.MatchExactly 0 Esegue un controllo di corrispondenza QVariant-based
Qt.MatchFixedString 8 Esegue un controllo di corrispondenza sulla stringa (string-based). Per avere una ricerca case-sensitive bisogna specificare il flag MatchCaseSensitive.
Qt.MatchContains 1 Il termine di ricerca è contenuto nell’elemento.
Qt.MatchStartsWith 2 Il termine di ricerca corrisponde all’inizio dell’elemento.
Qt.MatchEndsWith 3 Il termine di ricerca corrisponde con la fine dell’elemento.
Qt.MatchCaseSensitive 16 La ricerca è case sensitive.
Qt.MatchRegExp 4 Esegue una ricerca string-based usando una espressione regolare cometermine di ricerca.
Qt.MatchWildcard 5 Esegue una ricerca string-based usando una stringa con wildcards come termine di ricerca.
Qt.MatchWrap 32 Esegue un wrap around, ovvero quando la ricerca raggiunge l’ultimo elemento del model, ricomincia dal primo finchè non raggiunge nuovamente la fine.
Qt.MatchRecursive 64 Cerca sull’intera gerarchia.

Di default la ricerca viene effettuata sulla colonna 0, per cercare su un’altra colonna, bisogna specificarla:

>>> from PyQt5.QtCore import Qt
>>> model.findItems("row 2", Qt.MatchStartsWith)
[<PyQt5.QtGui.QStandardItem object at 0x03A35120>]
>>> model.findItems("row", Qt.MatchStartsWith)
[<PyQt5.QtGui.QStandardItem object at 0x035ABDF0>, <PyQt5.QtGui.QStandardItem object at 0x035ABEE0>, <PyQt5.QtGui.QStandardItem object at 0x0347A080>, <PyQt5.QtGui.QStandardItem object at 0x0347A1C0>]
>>> items = model.findItems("row", Qt.MatchStartsWith)
>>> for item in model.findItems("row", Qt.MatchStartsWith): print(item.text())
... 
row 0, column 0
row 1, column 0
row 2, column 0
row 3, column 0
>>> for item in model.findItems("row", Qt.MatchStartsWith, 1): print(item.text())
... 
row 0, column 1
row 1, column 1
row 2, column 1
row 3, column 1

INVISIBLE ROOT ITEM

Con il metodo invisibleRootItem di model, otteniamo il root item che ci carantisce un accesso agli elementi di tipo TOP-LEVEL. Questo metodo viene usato quando si ha a che fare con i tree-models.

>>> root = model.invisibleRootItem()
>>> root.hasChildren()
True
>>> root.child(1, 2)
<PyQt5.QtGui.QStandardItem object at 0x035ABF80>

INDEX from ITEM

Il metodo indexFromItem(QStandardItem) ritorna l’oggetto QModelIndex relativo a quell’item, nel model di riferimento.

>>> item = root.child(1, 1)
>>> modelindex = model.indexFromItem(item)
>>> modelindex
<PyQt5.QtCore.QModelIndex object at 0x033F5B70>
>>> item = root.child(1, 1)
>>> modelindex = model.indexFromItem(item)
>>> modelindex
<PyQt5.QtCore.QModelIndex object at 0x033F5B70>
>>> modelindex.internalId()
9929232

internalId è l’indice dell’item passato come argomento, all’interno del model

ITEM from INDEX

Il passaggio inverso a quello precedente, si ottiene con il metodo itemFromIndex(QModelIndex), che ritorna l’oggetto QStandardItem corrispondente all’oggetto QModelIndex passato come argomento:

>>> model.itemFromIndex(modelindex)
<PyQt5.QtGui.QStandardItem object at 0x035ABF30>
>>> item = model.itemFromIndex(modelindex)
>>> item.text()
'row 1, column 1'
>>> item.font()
<PyQt5.QtGui.QFont object at 0x033F5B30>

HEADERS

E’ possibile settare gli headers orizzontali e verticali della tabella intrinseca al model, con i metodi setHorizontalHeaderLabels([labels]) e setVerticalHeaderLabels([labels]).
Come si nota,l’argomento passato ai metodi, è una lista di labels e qualora dovessero essere più labels, delle righe o colonne disponibili, questo comporterà l’aumento delle stesse.
Non comporterà però una sostituzione della posizione degli item, nelle rispettive righe/colonne.

>>> model.setHorizontalHeaderLabels(["column 1", "column 2", "column 3", "column 4"])
>>> model.item(0, 0).text()
'row 0, column 0'
>>> model.horizontalHeaderItem(0)
<PyQt5.QtGui.QStandardItem object at 0x0347A6C0>
>>> model.horizontalHeaderItem(0).text()
'column 1'
>>> model.item(0, 0).text()
'row 0, column 0'

Come si nota l’item di riga 0 e colonna 0 è rimasto al suo posto.
L’item header può venire impostato anche con il metodo setHorizontalHeaderItem(column, QStandardItem) per la colonna

>>> model.setHorizontalHeaderItem(0, QStandardItem("New Column 1"))
>>> model.horizontalHeaderItem(0).text()
'New Column 1'

e setVerticalHeaderItem(row, QStandardItem), per la riga:

>>> model.setVerticalHeaderItem(0, QStandardItem("Row 1"))
>>> model.verticalHeaderItem(0).text()
'Row 1'

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QStandardItem

9 Maggio 2019 Commenti chiusi

Torna all’indice degli appunti

QStandardItem

La classe QStandardItem mette a disposizione l’elemento cardine da utilizzare con la classe QStandardItemModel, classe generica utilizzata per immagazzinare dati personalizzati da visualizzare in widgets come QlistView, QTableView e QTreeView.
Tutti questi oggetti fanno parte della famiglia di classi Model/View, necessarie per l’utilizzo del framework model/view di Qt.

Vediamo pertanto l’elemento base di questa filiera: QStandardItem

Ogni elemento può avere il proprio text, font, icon, background e foreground, settabili con i rispettivi metodi setText(text), setIcon(QIcon), setFont(QFont), setBackground(QBrush) e setForeground(QBrush).

>>> from PyQt5.QtGui import QStandardItem
>>> item = QStandardItem()
>>> item.setText("Italy")
>>> from PyQt5.QtGui import QIcon
>>> item.setIcon(QIcon("italy.png"))
>>> from PyQt5.QtGui import QFont
>>> item.setFont(QFont("Lucida", 12, QFont.StyleItalic))

è possibile ovviamente recuperare queste informazioni con i rispettivi metodi text, font, icon ecc.

>>> item.text()
'Italy'
>>> item.icon()
<PyQt5.QtGui.QIcon object at 0x03525D50>
>>> item.font()
<PyQt5.QtGui.QFont object at 0x032E5530>

Di default un QStandardItem è editabile (editable), selezionabile (selctable), non spuntabile (checkable):

>>> item.isEnabled()
True
>>> item.isSelectable()
True
>>> item.isCheckable()
False

Questi flags possono essere modificati tramite il metodo setFlags(Qt.ItemFlags).
Qt.ItemFlags può assumere i seguenti valori:

costante

valore

descrizione
Qt.NoItemFlags 0 nessuna proprietà settata
Qt.ItemIsSelectable 1 può essere selezionato
Qt.ItemIsEditable 2 può essere editato
Qt.ItemIsDragEnabled 4 può essere trascinato (drag and drop)
Qt.ItemIsDropEnabled 8 può essere usato come drop-target (drag and drop)
Qt.ItemIsUserCheckable 16 può essere spuntato dall’utente (chackable)
Qt.ItemIsEnabled 32 l’utente può interagire con l’elemento (attivo)
Qt.ItemIsAutoTristate 64 lo stato dell’elemento dipende dallo stato dei suoi figli
Qt.ItemNeverHasChildren 128 l’elemento non avrà mai elementi figlio
Qt.ItemIsUserTristate 256 l’utente può ciclare su 3 stati separati

Per recuperare i flags di un elemento, si utilizza il metodo flags che ritornerà un oggetto QItemFlags.
Nel caso volessi rendere l’elemento chackable e non editable:

>>> from PyQt5.QtCore import Qt
>>> item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
>>> item.isEditable()
False
>>> item.isEnabled()
True
>>> item.isCheckable()
True
>>> item.isSelectable()
True

In caso di Checkable element, è possibile settare lo stato con il metodo setCheckState(Qt.CheckState) ed è recuperabile con il metodo checkState.
Qt.CheckState può assumere i seguenti valori: Unchecked (0), PartiallyChecked (1), Checked (2).

>>> item.checkState()
0
>>> item.setCheckState(Qt.Checked)
>>> item.checkState()
2

Oltre a setFlags, è possibile settare i flag dell’elemento con i singoli metodi:

setSelectable(bool), setEditable(bool), setCheckable(bool)…

oppure dati come font, backgrouund, foreground con:

setFont(QFont), setBackground(QBrush), setForeground(QBrush)…

DATI

La peculiarità della classe QStandardItem è quella di poter settare dati al proprio interno, rispettivamente al role desiderato.
Per fare questa operazione di utilizza il metodo setData(data, ItemDataRole), dove data è il valore che vogliamo immagazzinare all’interno dell’elemento e ItemDataRole, è il tipo di dato al quale vogliamo associare il dato stesso.
I Roles sono valori utilizzati dalla view, per dire al model di che dati ha bisogno e si distinguono in:

costante

valore

descrizione
Qt.DisplayRole 0 Utilizzato quando i dati sono in formato testo (QString)
Qt.DecorationRole 1 Utilizzato quando i dati riguardano una decorazione (QColor, QIcon, QPixMap)
Qt.EditRole 2 Utilizzato quando i dati devono essere editati in un editor (QString)
Qt.ToolTipRole 3 Utilizzato quando i dati riguardano il tooltip di un elemento (QString)
Qt.StatusTipRole 4 Utilizzato per i dati da visualizzare sulla status bar (QString)
Qt.WhatsThisRole 5 Utilizzato per i dati di un elemento in modalità “What’s This?” (QString)
Qt.SizeHintRole 13 Utilizzato per il size hint dell’elemento che sarà visualizzato nella view (QSize)

Poi ci sono i Roles relativi all’aspetto e i meta-data:

costante

valore

descrizione
Qt.FontRole 6 Utilizzato per il font (QFont)
Qt.TextAlignmentRole 7 Utilizzato per il tipo di allineamento del testo (Qt.Alignment)
Qt.BackgroundRole 8 Utilizzato per il background brush dell’elemento (QBrush)
Qt.ForegroundRole 9 Utilizzato per il foreground brush (text color) dell’elemento (QBrush)
Qt.CheckStateRole 10 Utilizzato per ottenere il checked state dell’elemento (Qt.CheckState)
Qt.InitialSortOrderRole 14 Utilizzato per ottenere l’ordinamento iniziale di una sezione “header view” (Qt.SortOrder)

…i roles di accessibilità:

costante

valore

descrizione
Qt.AccessibleTextRole 11 Il testo utilizzato da accessibility extensions e plugins come gli screen readers (QString)
Qt.AccessibleDescriptionRole 12 Un’ altra descrizione dell’elemento per scopi di accessibilità ulteriori(QString)

ed infine gli User roles:

costante

valore

descrizione
Qt.UserRole 0x0100 Il primo role da utilizzare in caso di scopi specifici

Per settare i dati per un tipo di role, si procede quindi come segue:

>>> from PyQt5.QtGui import QFont
>>> from PyQt5.QtCore import Qt
>>> parent.setData(QFont("Red"), Qt.FontRole)
>>> parent.setData("Parent Item", Qt.ToolTipRole)

Per ottenere i dati impostati per un elemento si utilizza il metodo data(ItemDataRole):

>>> parent.data(Qt.ToolTipRole)
'Parent Item'
>>> parent.data(Qt.FontRole)
<PyQt5.QtGui.QFont object at 0x03507A70>

TABELLA

Altra caratteristica fondamentale è che ogni elemento può avere una tabella bidimensionale di elementi figlo (child).
Questo rende possibile costruire gerarchie di elementi.
Le dimensioni della tabella figlio (child-table) possono essere settate con i metodi setRowCount(rows) e setColumnCount(columns).
Gli elementi figlio possono essere inseriti nella child-table con il metodo setChild(row, column, child), dove child è ovviamente l’oggetto QStandardItem figlio.
Il riferimento all’elemento figlio (child-item) si ottiene con il metodo child(row, column).
Per sapere se un oggetto QStandardItem ha figli, si usa il metodo hasChildren
Da un oggetto figlio è invece possibile risalire al padre con il metodo parent:

>>> from PyQt5.QtGui import QStandardItem
>>> parent = QStandardItem("parent")
>>> child_1 = QStandardItem("child 1")
>>> parent.setRowCount(3)
>>> parent.setChild(0, 0, child_1)
>>> parent.hasChildren()
True
>>> parent.child(0, 0)
<PyQt5.QtGui.QStandardItem object at 0x03527E40>
>>> parent.child(0, 0).text()
'child 1'
>>> child_2 = QStandardItem("child 2")
>>> parent.setChild(1, 0, child_2)
>>> parent.child(1, 0).text()
'child 2'
>>> child_2.parent()
<PyQt5.QtGui.QStandardItem object at 0x03527DA0>
>>> child_2.parent().text()
'parent'

E’ possibile inserire righe e colonne alla tabella in posizioni definite, con i metodi insertRow(row, items) e insertColumn(column, items), oppure semplicemente effettuare un append in fondo alla tabella con i metodi appendRow(items) e appendColumn(items), dove items è una lista di QstandardItem.
L’utilizzo di questi metodi comporta l’accrescimento della tabella a seconda delle esigenze.

>>> parent.columnCount()
1
>>> parent.rowCount()
3
>>> parent.insertRow(1, [QStandardItem("extrachild 3"), QStandardItem("extrachild 4")])
>>> parent.rowCount()
4
>>> parent.columnCount()
2
>>> parent.child(1, 0).text()
'extrachild 3'
>>> parent.child(1, 1).text()
'extrachild 4'

Inserendo la colonna, l’inserimento degli elementi avverrà dall’alto:

parent.insertColumn(1, [QStandardItem("extrachild 5"), QStandardItem("extrachild 6")])
parent.rowCount()
4
parent.columnCount()
3
parent.child(0, 1).text()
'extrachild 5'
parent.child(1, 1).text()
'extrachild 6'

Un riga di elementi figli può essere eliminata con i metodi removeRow(row) e takeRow(row), allo stesso modo è possibile eliminare una colonna con i rispettivi metodi removeColumn(column) e takeColumn(column).
Per eliminare un gruppo di righe o di colonne si utilizzano invece i metodi:
removeRows(row, rows), dove row è l’indice della riga da eliminare e rows, quante righe eliminare da row in avanti;
removeColumns(column, columns), dove column è l’indice della colonna da eliminare e columns, quante colonne eliminare da column in avanti.

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: model-view

8 Maggio 2019 Commenti chiusi

Torna all’indice degli appunti

Programmazione Model/View

Qt contiene un set di classi che usano un’architettura model/view per la gestione della relazione che intercorre tra i dati dell’applicazione (model), e il modo di rappresentarli all’utente (view).
L’utilizzo di questa architettura introduce la separazione tra le varie funzionalità che permettono una maggiore flessibilità nella presentazione degli elementi.
Il pattern model/view è simile al più noto MVC (Model-View-Controller), la differenza è che in questa architettura gli oggetti view e controller sono combinati insieme, introducendo il concetto di Delegate.


Nel pattern model/view, il model comunica con la sorgente di dati (Data) e fornisce un’interfaccia per gli altri componenti di questa architettura.
La view, ottiene dal model degli indici (model-indexes), che sono i riferimenti agli elementi della sorgente dei dati.
Fornendo questi indici al model, la view può ottenere gli elementi dei dati dalla sorgente (Data) e rappresentarli.
Il Delegate fa da collegamento tra model e view.
Quando un elemento viene editato da view, il delegate comunica direttamente con il model attraverso il model-index di tale elemento.
In generale quindi, le classi model/view possono essere suddivise in tre gruppi: models, views e delegates.
Ognuno di questi componenti è definito da una abstract class, che fornisce un’interfaccia comune che può essere subclassata, in modo da poter reimplementare i metodi necessari al nostro scopo.

Ovviamente Models, Views e Delegates comunicano tra loro usando segnali e slots.

I segnali emessi dal model informano la view che i dati in possesso di Data, sono cambiati;
I segnali emessi dalla view informano gli altri componenti se sono avvenute modifiche da parte dell’utente;
I segnali emessi dal delegate durante l’editing di un elemento, informano model e view, dello stato dell’editor;

MODELS

Il componente model si basa sulla classe QAbstractItemModel, che fornisce un’interfaccia comune utilizzata da view e delegate per avere accesso ai dati.
Tale interfaccia è flessibile a tal punto da poter gestire views che rappresentano ad esempio:


Come anticipato, il model ha anche il compito di notificare, a tutte le views ad esso collegato, quando un dato cambia e questo viene effettuato tramite il meccanismo segnali/slot.

Qt mette a disposizione alcuni models preconfezionati, come ad esempio:

QStringListModel: usato per immagazzinare una semplice lista di stringhe;
QStandardItemModel: gestisce strutture di elementi ad albero, dove ogni elemento può contenere diversi tipi di dato;
QFileSystemModel: usato per gestire informazioni su files e directories;

model index

Per far sì che la rappresentazione di un dato (view) rimanga separata dal modo di accedere al dato stesso, viene introdotto il concetto di model-index.
Ogni singolo pezzo di informazione di un dato che può essere ottenuto dal model, viene rappresentato da un model-index. Tale indice viene utilizzato dalla view e dal delegate per rappresentare il dato richiesto.
I model-indexes forniscono riferimenti temporanei a pezzi di informazione e possono quindi essere usati per ottenere, o modificare i dati,attraverso il modello.
Dal momento che i models possono riorganizzare la propria struttura interna di volta in volta, i model-indexes possono diventare non validi e non dovrebbero essere immagazzinati.
Se invece si dovesse avere la necessità di un long term model-index, sarebbe bene utilizzare esplicitamente un persisten model-index:

QModelIndex: utilizzato come riferimento temporaneo all’informazione;
QPersistentModelIndex: utilizzato come riferimento persistente all’informazione.

Per ottenere un model-index che corrisponda ad un dato, devono essere specificate 3 proprietà al model: row, column e parent model-index.

Row e Column

Nella sua forma più semplice, un model può essere visto come una tabella, dove, ogni suo elemento, può essere localizzato con i numeri di riga (row) e di colonna (column).

>>> model = QStandardItemModel()
>>> model.setItem(0, 0, QStandardItem("A"))
>>> model.setItem(1, 1, QStandardItem("B"))
>>> model.setItem(2, 1, QStandardItem("C"))
>>> from PyQt5.QtCore import QModelIndex
>>> index_a = model.index(0, 0, QModelIndex())
>>> index_b = model.index(1, 1, QModelIndex())
>>> model.itemFromIndex(index_a).text()
'A'
>>> model.itemFromIndex(index_b).text()
'B'

In tutti i casi dove gli elementi sono di tipo top-level, il parent index è sempre QModelIndex().
Diverso è il caso del tree model dove ci sono elementi (child) che avranno bisogno di un parent index.

>>> model = QStandardItemModel()
>>> model.setItem(0, 0, QStandardItem("A"))
>>> item_a = model.item(0, 0)
>>> item_a.setChild(1, 0, QStandardItem("B"))
>>> item_a.hasChildren()
True
>>> index_a = model.index(0, 0, QModelIndex())
>>> index_b = model.index(1, 0, index_a)
>>> model.itemFromIndex(index_a).text()
'A'
>>> model.itemFromIndex(index_b).text()
'B'
>>> model.itemFromIndex(index_b).parent().text() # il parent di B
'A'

Item Roles

Gli elementi in un model sono costituiti da diversi roles (ruoli), nel senso che un certo tipo di dato ha una funzione ben precisa.
Ad esempio Qt.DisplayRole è usato per accedere alla stringa che viene visualizzata come testo, in una view,
mentre Qt.ToolTipRole è il dato che apparirà nel tooltip relativo ad un elemento e così via.
Quindi Un elemento (item), contiene diversi dati, uno per ogni Qt.ItemDataRole.
L’accesso a questi dati del model è garantito dal metodo data(index, role), dove index è l’indice dell’item e role il Qt.Role assegnato al dato:

...
>>> item_a.setData("Tool tip of A item", Qt.ToolTipRole)
>>> model.data(index_a, Qt.ToolTipRole)
'Tool tip of A item'

VIEWS

Come detto, nell’architettura model/view, una view ottiene i dati dal model e li mostra all’utente.
La separazione tra contenuti e loro rappresentazione, è ottenuta dall’utilizzo di una standard model interface (QAbstractItemModel), di una standard view interface (QAbstractItemView) e di model indexes.
Una view può essere costruita senza model, ma bisognerà fornirle un model non appena si vorranno visualizzare le informazioni richieste.
Il model viene fornito alla view con il metodo setModel(model) dove model è un oggetto QStandardItemModel.

All’occorrenza, sono già disponibili implementazioni complete di views, come ad esempio:

QListView: per visualizzare i dati come lista;
QTableView: per visualizzare i dati in forma tabellare;
QTreeView: per visualizzare i dati in forma di lista gerarchica;

Vediamo un’esempio con QListView:

import sys
from PyQt5.QtWidgets import (QApplication, QListView, QMainWindow, QVBoxLayout,
                             QWidget)
from PyQt5.QtGui import QStandardItemModel, QStandardItem


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


class FormWidget(QWidget):
    def __init__(self, parent, model=None):
        super(FormWidget, self).__init__(parent)
        layout = QVBoxLayout()
        self.setLayout(layout)
        self.view = QListView()
        self.view.setModel(model)
        layout.addWidget(self.view)


if __name__ == "__main__":
    app = QApplication([sys.argv])
    iterable = ["italy", "france", "spain"]
    model = QStandardItemModel()
    for row, name in enumerate(iterable):
        item = QStandardItem(name)
        item.setCheckable(True)
        model.setItem(row, 0, item)
    main_window = MainWindow(parent=None, model=model)
    main_window.show()
    sys.exit(app.exec_())

DELEGATES

Il componente delegate si basa sulla classe QAbstractItemDelegate.
L’implementazione standard di tale classe è QStyledItemDelegate e viene usata come delegate di default nelle views standard.
A differenza del pattern MVC, delegate non è un componente separato come lo sarebbe il Controller.
Le standard views di Qt usano un’istanza di QItemDelegate. Questa implementazione di delegate, permette di rappresentare tutti gli elementi, in uno stile riconoscibile da tutte le standard views (QListView, QTable, QTreeView, ecc). Il delegate di default gestisce anche tutti i roles utilizzati dalle suddette views.
E’ possibile ottenere l’oggetto delegate utilizzato da una view, con il metodo itemDelegate:

>>> import sys
... from PyQt5.QtWidgets import (QApplication, QListView, QMainWindow, QVBoxLayout,
...                              QWidget)
>>> app = QApplication([])
>>> iterable = ["italy", "france", "spain"]
>>> from PyQt5.QtGui import QStandardItemModel, QStandardItem
>>> model = QStandardItemModel()
>>> for row, name in enumerate(iterable):
...     model.setItem(row, 0, QStandardItem(name))
>>> view = QListView()
>>> view.setModel(model)
>>> view.itemDelegate()
<PyQt5.QtWidgets.QStyledItemDelegate object at 0x0361E300>

Nel caso si utilizzi una view personalizzata, è bene utilizzare un delegate personalizzato e per settare tale delegate si utilizzerà il metodo setItemDelegate(custom_delegate).
Stesso discorso vale per il widget editor che permette l’editing dell’elemento, sarà necessario l’editor con il metodo createEditor.
Quando l’utente, tramite l’editor, avrà modificato un elemento, il delegate avrà il compito di comunicare tale “nuovo” dato al model. Questa operazione viene effettuata con il metodo setModelData(editor, model, modelindex).

Viediamo un esempio completo.

VIEW MODEL E DELEGATE personalizzati

Vediamo come gestire, con un custom-model ed un custom-delegate, un custom widget come ad esempio una list-view.
Quando si crea una list-view personalizzata, è necessario subclassare QAbstractListModel.
Come si nota dal sito ufficiale, quando si subclassa QAbstractListModel, bisogna implementare i metodi:

rowCount
data

Facoltativamente anche headerData.

rowCount(parent_index): ritorna il numero di righe (row) sotto il parent_index passato come argomento;
data(index, role): ritorna il dato immagazzinato per l’elemento di indice index, per il role specificato;

Iniziamo dal custom list-model:

class CustomListModel(QAbstractListModel):
    def __init__(self, parent=None, iterable=[]):
        super(CustomListModel, self).__init__(parent)
        self._data = iterable

    def rowCount(self, parent=QModelIndex()):
        return len(self._data)

    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid() or not 0 <= index.row() < self.rowCount():
            return QVariant()
        row = index.row()
        if role == Qt.DisplayRole:
            return str(self._data[row])
        return QVariant()

Come da manuale, abbiamo implementato i metodi rowCount e data.

>>> from PyQt5.QtCore import QAbstractListModel, QModelIndex, Qt, QVariant
>>> custom_model = CustomListModel(parent=None, iterable=[10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
>>> custom_model.rowCount()
10
>>> id_row4 = custom_model.index(3)
>>> custom_model.data(id_row4)
'40'
>>> custom_model.data(id_row3, Qt.BackgroundRole)
<PyQt5.QtCore.QVariant object at 0x0355D670>

Come si nota, nel caso non vengano trovati dati per il role specificato, verrà restituito un generico QVariant.
QVariant, in parole povere, è una classe che può rappresentare tutti i tipi di dati Qt.

Implementiamo il model personalizzato nel codice di esempio precedente:

import sys
from PyQt5.QtWidgets import (QApplication, QListView, QMainWindow, QVBoxLayout,
                             QWidget)
from PyQt5.QtCore import QAbstractListModel, QModelIndex, Qt, QVariant


class MainWindow(QMainWindow):
    def __init__(self, parent=None, model=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("Custom ListView Example")
        self.central_widget = FormWidget(self, model)
        self.setCentralWidget(self.central_widget)
        self.resize(300, 200)


class FormWidget(QWidget):
    def __init__(self, parent, model=None):
        super(FormWidget, self).__init__(parent)
        layout = QVBoxLayout()
        self.setLayout(layout)
        self.view = QListView()
        self.view.setModel(model)
        layout.addWidget(self.view)


class CustomListModel(QAbstractListModel):
    def __init__(self, parent=None, iterable=[]):
        super(CustomListModel, self).__init__(parent)
        self._data = iterable

    def rowCount(self, parent=QModelIndex()):
        return len(self._data)

    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid() or not 0 <= index.row() < self.rowCount():
            return QVariant()
        row = index.row()
        if role == Qt.DisplayRole:
            return str(self._data[row])
        return QVariant()


if __name__ == "__main__":
    app = QApplication([sys.argv])
    iterable = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
    model = CustomListModel(parent=None, iterable=iterable)
    main_window = MainWindow(parent=None, model=model)
    main_window.show()
    sys.exit(app.exec_())

Vediamo ora il custom-Delegate.

Quando in una view visualizziamo un dato dal model, il delegate è il componente che si occupa di disegnare l’elemento rappresentante tale dato.
Si occupa anche di fornire un editor nel momento in cui tale elemento viene editato.
Il dato verrà disegnato dal delegate in modo diverso a seconda del role corrispondente.
Il delegate di default è QStyledItemDelegate.
Il metodo che ci interessa implementare è paint(painter, option, index, dove painter è un oggetto QPainter, necessario per il disegno di linee e forme e option, un oggetto QStyleOptionViewItem, fondamentale per descrivere i parametri necessari al disegno di un elemento in una view.
Sarà molto importante utilizzare le variabili rect e state di option, per sapere rispettivamente, dove disegnare e se l’elemento è attivo o selezionato.

Per il nostro esempio vogliamo disegnare una progressbar in ogni elemento della list-view, quindi:

class CustomDelegate(QStyledItemDelegate):
    def paint(self, painter, option, index):
        item_var = index.data(Qt.DisplayRole)  # QVariant corrispondente
        item_str = item_var.toPyObject()
        opts = QStyleOptionProgressBar()
        opts.rect = option.rect
        opts.minimum = 0
        opts.maximum = 100
        opts.text = item_str
        opts.textAlignment = Qt.AlignCenter
        opts.textVisible = True
        opts.progress = int(item_str)
        QApplication.style().drawControl(QStyle.CE_ProgressBar, opts, painter)

per implementare il nostro custom-delegate, basterà utilizzare il metodo della view: setDelegate(delegate):

import sys
from PyQt5.QtWidgets import (QApplication, QListView, QMainWindow, QVBoxLayout,
                             QWidget, QStyledItemDelegate, QStyle,
                             QStyleOptionProgressBar)
from PyQt5.QtCore import QAbstractListModel, QModelIndex, Qt, QVariant


class MainWindow(QMainWindow):
    ...


class FormWidget(QWidget):
    ...
        self.view = QListView()
        self.view.setModel(model)
        delegate = CustomDelegate()
        self.view.setItemDelegate(delegate)
        layout.addWidget(self.view)


class CustomListModel(QAbstractListModel):
    ...


class CustomDelegate(QStyledItemDelegate):
    def paint(self, painter, option, index):
        item_data = index.data(Qt.DisplayRole)  # QVariant corrispondente
        opts = QStyleOptionProgressBar()
        opts.rect = option.rect
        opts.minimum = 0    # limite minimo progress bar
        opts.maximum = 100  # limite massimo progress bar
        opts.text = "{}/{} [{}%]".format(item_data, 100, int(item_data))
        opts.textAlignment = Qt.AlignCenter
        opts.textVisible = True
        opts.progress = int(item_data)
        QApplication.style().drawControl(QStyle.CE_ProgressBar, opts, painter)


if __name__ == "__main__":
    app = QApplication([sys.argv])
    iterable = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
    model = CustomListModel(parent=None, iterable=iterable)
    main_window = MainWindow(parent=None, model=model)
    main_window.show()
    sys.exit(app.exec_())

Manca ora l’attivazione dell’editor per la modifica del dato dell’elemento.

Prima di tutto bisogna ricordarsi che l’editor è attivabile se il custom model prevede tra i suoi flag, Qt.ItemIsEditable, quindi reimplementiamo il metodo
flags ereditato da QAbstractItemModel:

class CustomListModel(QAbstractListModel):
    ...
    def flags(self, index):
        flags_ = super(CustomListModel, self).flags(index)
        return flags_ | Qt.ItemIsEditable
    ...

In questo modo, tra i flag di ogni elemento di indice index, ci sarà sempre il flag ItemIsEditable.

Ora reimplementiamo i 3 metodi fondamentali del delegate:

createEditor: ritorna il widget utilizzato per modificare il dato proveniente dal model;
setEditorData: fornisce il widget con il dato da modificare;
setModelData: ritorna il dato modificato al model;

Già che ci siamo utilizziamo un widget QSpinBox per modificare il dato:

class CustomDelegate(QStyledItemDelegate):
    ...

    def createEditor(self, parent, option, index):
        editor = QSpinBox(parent)
        editor.setRange(0, 100)
        return editor

    def setEditorData(self, editor, index):
        value = index.data(Qt.DisplayRole)
        editor.setValue(int(value))

    def setModelData(self, editor, model, index):
        value = editor.value()
        variant = QVariant(value)
        model.setData(index, variant)

Come si nota, in createEditor creiamo il widget SpinBox che utilizzeremo per la modifica del dato, con setEditorData, facciamo in modo che al doppio click, quando si attiva il widget editor per l’elemento editato, il dato di tale elemento sia visibile, infine, setModelData, metodo con il quale avvisiamo il model della modifica effettuata.
Dei 3 metodi di QItemDelegate, setModelData è il puù importante poichè prende il dato dall’editor widget e lo immagazzina nel model, ad un determinato indice index.

Non ci resta che tornare nuovamente al custom model e reimplementare il metodo setData:

class CustomListModel(QAbstractListModel):
    ...
    def setData(self, index, variant, role=Qt.EditRole):
        if role == Qt.EditRole:
            self._data[index.row()] = variant.value()
            self.dataChanged.emit(index, index)  # emetto il segnale
            return True
        return False
    ...

Come da specifica, il metodo setData di QAbstractItemModel da cui deriva il nostro QAbstractListModel, prende il dato di un determinato role e lo setta nell’elemento di indice index.
Se il dato viene settato, il metodo deve emettere il segnale dataChanged e poi ritornare True, altrimenti ritornare False.
Il segnale dataChanged viene emesso ogni volta che il dato di un elemento (item) viene modificato e serve ad aggiornare la view, negli elementi coinvolti dalla modifica, quelli compresi tra topleft_index e bottomright_index.
Nota: Essendo in una lista, topleft_index e bottomright_index coincidono.
con index.row recuperiamo l’indice di riga del list-model e modifichiamo in tale indice, il dato prelevato dall’oggetto variant in arrivo dal delegate tramite il metodo setModelData.

il codice definitivo si traduce quindi in:

import sys
from PyQt5.QtWidgets import (QApplication, QListView, QMainWindow, QVBoxLayout,
                             QWidget, QStyledItemDelegate, QStyle, QSpinBox,
                             QStyleOptionProgressBar)
from PyQt5.QtCore import QAbstractListModel, QModelIndex, Qt, QVariant


class MainWindow(QMainWindow):
    def __init__(self, parent=None, model=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("Custom ListView Example")
        self.central_widget = FormWidget(self, model)
        self.setCentralWidget(self.central_widget)
        self.resize(300, 200)


class FormWidget(QWidget):
    def __init__(self, parent, model=None):
        super(FormWidget, self).__init__(parent)
        layout = QVBoxLayout()
        self.setLayout(layout)
        delegate = CustomDelegate()
        self.view = QListView()
        self.view.setModel(model)
        self.view.setItemDelegate(delegate)
        layout.addWidget(self.view)


class CustomListModel(QAbstractListModel):
    def __init__(self, parent=None, iterable=[]):
        super(CustomListModel, self).__init__(parent)
        self._data = iterable

    def rowCount(self, parent=QModelIndex()):
        return len(self._data)

    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid() or not 0 <= index.row() < self.rowCount():
            return QVariant()
        row = index.row()
        if role == Qt.DisplayRole:
            return str(self._data[row])
        return QVariant()

    def flags(self, index):
        flags_ = super(CustomListModel, self).flags(index)
        return flags_ | Qt.ItemIsEditable

    def setData(self, index, variant, role=Qt.EditRole):
        print("[MODEL] Setting data...")
        if role == Qt.EditRole:
            self._data[index.row()] = variant.value()
            print("[MODEL] Emitting signal for indexes %s, %s..."
                  % (index.row(), index.row()))
            self.dataChanged.emit(index, index)  # emetto il segnale
            return True
        return False


class CustomDelegate(QStyledItemDelegate):
    def paint(self, painter, option, index):
        item_data = index.data(Qt.DisplayRole)  # QVariant corrispondente
        opts = QStyleOptionProgressBar()
        opts.rect = option.rect
        opts.minimum = 0    # limite minimo progress bar
        opts.maximum = 100  # limite massimo progress bar
        opts.text = "{}/{} [{}%]".format(item_data, 100, int(item_data))
        opts.textAlignment = Qt.AlignCenter
        opts.textVisible = True
        opts.progress = int(item_data)
        QApplication.style().drawControl(QStyle.CE_ProgressBar, opts, painter)

    def createEditor(self, parent, option, index):
        print("[DELEGATE] Creating editor for index %s..." % index.row())
        editor = QSpinBox(parent)
        editor.setRange(0, 100)
        return editor

    def setEditorData(self, editor, index):
        print("[DELEGATE] Updating view for index %s..." % index.row())
        value = index.data(Qt.DisplayRole)
        editor.setValue(int(value))

    def setModelData(self, editor, model, index):
        print("[DELEGATE] Setting data to model at index %s..." % index.row())
        value = editor.value()
        variant = QVariant(value)
        model.setData(index, variant)


if __name__ == "__main__":
    app = QApplication([sys.argv])
    iterable = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
    model = CustomListModel(parent=None, iterable=iterable)
    main_window = MainWindow(parent=None, model=model)
    main_window.show()
    sys.exit(app.exec_())


[DELEGATE] Creating editor for index 4...
[DELEGATE] Updating view for index 4...
[DELEGATE] Setting data to model at index 4...
[MODEL] Setting data...
[MODEL] Emitting signal for indexes 4, 4...
[DELEGATE] Updating view for index 4...

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QSplashScreen

2 Maggio 2019 Commenti chiusi

Torna all’indice degli appunti

QSplashScreen

Il widget QSplashScreen fornisce appunto uno “splashscreen” da mostrare durante l’avvio dell’applicazione, specialmente quando questo richiede parecchio tempo.
Il costruttore è QSplashScreen(pixmap), dove pixmap è un oggetto QPixmap contenente l’immagine da utilizzare come splash.
Lo splashscreen apparirà al centro dello schermo e per far sì che sia sempre in primo piano, sarà necessario passare al costruttore il flag window Qt.WindowStaysOnTopHint:

>>> from PyQt5.QtWidgets import QApplication, QSplashScreen
>>> from PyQt5.QtGui import QPixmap
>>> app = QApplication([])
>>> pixmap = QPixmap("pyqt5logo.png")
>>> from PyQt5.QtCore import Qt
>>> splash = QSplashScreen(pixmap, Qt.WindowStaysOnTopHint)

Se si crea un oggetto QSplashScreen senza passare alcun argomento in fase di costruzione, è possibile farlo in seguito con i metodi forniti. Ad esempio si può passare l’oggetto QPixmap, con il metodo setPixmap(pixmap):

>>> newsplash = QSplashScreen()
>>> newsplash.setPixmap(pixmap)

Per risalire all’oggetto QPixmap utilizzato nello splash, si utilizza il metodo pixmap:

>>> newsplash.pixmap()
<PyQt5.QtGui.QPixmap object at 0x0363F230>

Per visualizzare lo splash si utilizza il metodo show.

Particolare menzione spetta al metodo repaint:
questo metodo sovrascrive il metodo repaint della classe ereditata QWidget. La differenza è che, in questo caso, viene chiamato il metodo processEvents di QApplication, per assicurare che vengano mostrati gli update anche in caso di assenza di event-loop.

Fondamentale è infine il metodo finish(mainwindow), che prende come argomento il widget che farà da Main Window dell’applicazione.
In pratica il metodo suddetto, dice allo splash screen di aspettare fino a chè non appare mainwindow, dopodichè viene chiamato il metodo close sullo splash screen stesso, per passare poi la palla all’applicazione vera e propria.
Ovviamente questa è la fase di avvio della applicazione, in cui vengono caricate librerie, effettuate connessioni ecc; quella fase di attesa per la quale si è deciso di utilizzare uno splash screen.

MESSAGGI

Una peculiarità dello splash screen è quella di poter visualizzare dei messaggi, durante l’attesa.
I messaggi vengono aggiunti con il metodo setMessage(message, alignment, color), dove message è la il messaggio che vogliamo visualizzare, alignment è il solito Qt flag e color, un oggetto QColor:

>>> from PyQt5.QtGui import QColor
>>> newsplash.showMessage("Loading libraries...", Qt.AlignLeft, QColor("Red"))

i messaggi passati all’oggetto QSplashScreen possono essere recuperati con il metodo message:

>>> newsplash.message()
'Loading libraries...'

Per rimuovere un messaggio dallo splash screen si utilizza il metodo clearMessage:

>>> newsplash.clearMessage()
>>> newsplash.message()
''

L’utilizzo del metodo showMessage, comporta la perdita del messaggio precedentemente inviato allo splash.

>>> newsplash.showMessage("first message", Qt.AlignLeft, QColor("Red"))
>>> newsplash.showMessage("second message", Qt.AlignLeft, QColor("Red"))
>>> newsplash.message()
'second message'

Qualora si volesse mostrare una sorta di log, procedere come segue:

>> message = "first message"
>>> newsplash.showMessage(message, Qt.AlignLeft, QColor("Red"))
>>> message += "\nsecond message"
>>> newsplash.showMessage(message, Qt.AlignLeft, QColor("Red"))
>>> newsplash.message()
'first message\nsecond message'

SEGNALI

L’unico segnale utile di questa classe è messageChanged, che ovviamente viene emesso ogni volta che il messaggio dello splash, cambia:

>>> newsplash.messageChanged.connect(lambda: print("[SIG] message has changed"))
<PyQt5.QtCore.QMetaObject.Connection object at 0x03644330>
>>> newsplash.showMessage("New message")
[SIG] message has changed
>>> newsplash.showMessage("Another message")
[SIG] message has changed

Ecco un esempio di utilizzo di splash screen:

from PyQt5.QtWidgets import (QWidget, QApplication, QVBoxLayout,
                             QMainWindow, QPushButton, QSplashScreen)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap, QFont, QColor
import sys
import time


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("A simple App")
        self.central_widget = FormWidget(self)
        self.setCentralWidget(self.central_widget)
        self.resize(200, 50)


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QVBoxLayout()
        self.setLayout(layout)
        btn_quit = QPushButton("QUIT")
        layout.addWidget(btn_quit, 0)
        btn_quit.clicked.connect(self.parent().close)


def main():
    """time.sleep simulata i tempi di caricamento dell'applicazione"""
    app = QApplication(sys.argv)
    pixmap = QPixmap("pyqt5logo.png")
    splash = QSplashScreen(pixmap, Qt.WindowStaysOnTopHint)
    splash.show()
    message = "\n INFO: Loading players..."
    splash.setFont(QFont("Lucida", 12, QFont.StyleItalic))
    splash.showMessage(message, Qt.AlignLeft, QColor("Red"))
    splash.repaint()
    time.sleep(2)
    message += "\n INFO: Loading evaluations..."
    splash.showMessage(message)
    splash.repaint()
    time.sleep(2)
    window = MainWindow()
    window.show()
    splash.finish(window)
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

Come si nota, time.sleep(sec) simula il tempo di caricamento dell’applicazione, prima che venga mostrata la UI.
Il metodo repaint viene chiamato periodicamente per far sì che vengano effettuati ciclici aggiornamenti dello splash, mentre finish avvisa lo splash, che dovrà chiudersi non appena la main window sarà visualizzata.

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,