Archivio

Posts Tagged ‘PyQt5’

PyQt5: QDialog

29 Aprile 2019 Commenti chiusi

Torna all’indice degli appunti

QDialog

La classe QDialog è la classe base per le finestre di Dialogo.
Le finestre di dialogo servono per comunicare con l’utente e/o svolgere determinati compiti.
Possono ritornare un valore e possono contenere dei bottoni.
Le finestre di dialogo sono sempre di tipo Top level window.
Se hanno un top-level parent, una volta create si centreranno su di esso.
Possono essere di due tipologie: modal e modeless.

MODAL DIALOG

Il modal dialog viene utilizzato quando si vuole evitare all’utente di interagire con il resto dell’applicazione, mentre il dialog è attivo.
Il Modal dialog può essere suddiviso in due categorie:
application modal, dove l’utente deve chiudere il dialog prima di poter interagire con ogni altra finestra dell’applicazione;
window modal, dove viene bloccata solo la finestra associata al dialog, mentre il resto dell’applicazione è utilizzabile dall’utente.

Per far sì che una finestra di dialog sia di tipo modal, si utilizza il metodo setModal(bool).
Per sapere invece di che tipo è un dialog, si utilizza il metodo isModal:

>>> from PyQt5.QtWidgets import QApplication, QDialog
>>> app = QApplication([])
>>> dialog = QDialog()
>>> dialog.isModal()
False
>>> dialog.setModal(True)
>>> dialog.isModal()
True

Il metodo che permette di mostrare un dialog è exec.
Quando l’utente chiuderà la finestra di dialogo, exec ritornerà un valore.

il codice per gli esempi successivi sarà…

import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton,
                             QMainWindow, QVBoxLayout, QDialog)


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


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QVBoxLayout()
        button = QPushButton("SHOW DIALOG")
        layout.addWidget(button, 0)
        self.setLayout(layout)

        button.clicked.connect(self.on_button)

    def on_button(self): # slot
        dialog = QDialog()
        dialog.setWindowTitle("Dialog")
        dialog.resize(200, 150)
        dialog.setModal(True)
        dialog.exec()


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

…dove modificheremo man mano il codice all’interno dello slot legato al bottone, per vedere le varianti di dialog disponibili.

come si nota, se proviamo a cliccare nuovamente sul bottone nella main windows, la finestra stessa non sarà attiva.
Per questo motivo esistono i dialog modeless.

MODELESS DIALOG

Il dialog modeless, come già anticipato, funziona indipendentemente dal resto dell’applicazione.
Per mostrare un modeless dialog, si utilizza il metodo show

...
class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        self.dialog = None
        layout = QVBoxLayout()
        self.button1 = QPushButton("SHOW DIALOG")
        self.button2 = QPushButton("CONTINUE")
        layout.addWidget(self.button1, 0)
        layout.addWidget(self.button2, 0)
        self.setLayout(layout)

        self.button1.clicked.connect(self.on_button1)
        self.button2.clicked.connect(self.on_button2)

    def on_button1(self):
        self.dialog = QDialog()
        self.dialog.setModal(False)
        self.dialog.resize(200, 150)
        self.dialog.show()

    def on_button2(self):
        print("Main window is still active!")
...

Come si nota, viene creato il dialog senza bloccare la main window.

Nota:
Nel caso modeless, se creassimo il dialog all’interno dello slot come variabile locale allo slot stesso, al termine dell’esecuzione della callback, il dialog verrebbe distrutto dal garbage collector. Per tanto è necessario rendere il dialog persistente, come attributo cioè dell’oggetto FormWidget.
Ecco il perchè di self.dialog = QDialog() e non dialog = QDialog().
Questo non accade chiaramente per il dialog modal, che è una top-level window bloccante.

Ovviamente è possibile personalizzare il dialog ereditando dalla classe principale QDialog e aggiungendo ad esempio bottoni ed altri widgets. Creiamo ad esempio un dialog personalizzato con una label, un line-edit e due bottoni di conferma e di uscita:

class CustomInputDialog(QDialog):
    def __init__(self, parent, title):
        super(CustomInputDialog, self).__init__(parent)
        self.resize(200, 100)
        self.setWindowTitle(title)
        self.setModal(True)
        layout = QGridLayout()
        self.setLayout(layout)
        label = QLabel("insert name")
        self.inputtext = QLineEdit()
        layout.addWidget(label, 0, 0)
        layout.addWidget(self.inputtext, 0, 1)
        button_ok = QPushButton("OK")
        button_cancel = QPushButton("CANCEL")
        layout.addWidget(button_ok, 1, 0)
        layout.addWidget(button_cancel, 1, 1)

        button_ok.clicked.connect(self.accept)
        button_cancel.clicked.connect(self.reject)

I due bottoni li colleghiamo rispettivamente ai due metodi di QDialog accept e reject.
accept: nasconde il dialog e setta il result-code del dialog ad Accepted;
rejected: nasconde il dialog e setta il result-code del dialog a Rejected;

Questi due return_code, sono i due valori ritornati dal modal-dialog per mezzo del metodo exec.
Se il metodo chiamato in precedenza è accept, il result-code equivale ad Accepted e quindi 1,
se invece il metodo chiamato è reject, il result-code equivale a Rejected, quindi 0.

Per mezzo di questi valori, possiamo condizionare il comportamento della nostra applicazione.

import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QGridLayout,
                             QMainWindow, QVBoxLayout, QDialog, QLabel,
                             QLineEdit)

...

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


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        self.dialog = None
        layout = QVBoxLayout()
        button = QPushButton("SHOW DIALOG")
        layout.addWidget(button, 0)
        self.setLayout(layout)
        button.clicked.connect(self.on_button)

    def on_button(self):
        self.dialog = CustomInputDialog(None, "custom dialog")
        result = self.dialog.exec()
        if result:
            print("You have clicked <OK>: text is <%s>" %
                  self.dialog.inputtext.text())
        else:
            print("You have clicked <CANCEL>")

...

Oltre ai metodi accept() e reject(), abbiamo done(result_code). Questo metodo chiude il dialog e setta il result-code al valore result_code passato come argomento. E’ possibile quindi decidere quale comportamento avrà il dialog senza dover ad esempio cliccare fisicamente su un bottone.

>>> dialog.accepted.connect(lambda: print("[SIG] dialog accepted"))
<PyQt5.QtCore.QMetaObject.Connection object at 0x0398CDF0>
>>> dialog.rejected.connect(lambda: print("[SIG] dialog rejected"))
<PyQt5.QtCore.QMetaObject.Connection object at 0x0398CE70>
>>> dialog.accept()
you have clicked on OK
[SIG] dialog accepted
>>> dialog.reject()
you have clicked on CANCEL
[SIG] dialog rejected
>>> dialog.done(QDialog.Accepted)
you have clicked on OK
[SIG] dialog accepted
>>> dialog.done(QDialog.Rejected)
you have clicked on CANCEL
[SIG] dialog rejected

SEGNALI

Come anticipato nell’esempio precedente, i segnali significativi di questa classe sono:

accepted: segnale emesso quando il dialog è stato accettato dall’utente, o quando sono stati chiamati i metodi accept o done con argomento QDialog.Accepted (1).
rejected: segnale emesso quando il dialog è stato rigettato dall’utente, o quando sono stati chiamati i metodi reject o done con argomento QDialog.Rejected (0).
finished: segnale emesso ogni volta che viene settato il result_code, o dall’utente, o con i metodi done(result_code), accept e reject.

>>> dialog.finished.connect(lambda: print("[SIG] result code set"))
<PyQt5.QtCore.QMetaObject.Connection object at 0x0398CD70>
>>> dialog.accept()
[SIG] dialog accepted
[SIG] result code set
>>> dialog.done(QDialog.Rejected)
[SIG] dialog rejected
[SIG] result code set

Il tipo di result-code si ottiene con il metodo result:

>>> dialog.disconnect() # elimino le connessioni dei segnali precedenti
>>> dialog.finished.connect(lambda: print("[SIG] result code set to <%s>" % dialog.result()))
<PyQt5.QtCore.QMetaObject.Connection object at 0x0398CD70>
>>> dialog.accept()
[SIG] result code set to <1>
>>> dialog.reject()
[SIG] result code set to <0>

DIALOGS

Fortunatamente non dobbiamo reinventare la ruota inquanto sono disponibili molti tipi di dialog preconfezionati.

QInputDialog

QInputDialog è una classe che fornisce una finestra di dialogo composta da una label, un lineedit widget e due bottoni “Ok, “Cancel”.
Non è necessario utilizzare il costruttore e poi il metodo exec o show per mostrare il dialog, ma è sufficiente chiamare il metodo getText(parent, title, label) della classe QInputDialog.
Se l’utente preme “ok”, tale metodo ritornerà una tupla con il testo inserito dall’utente e True;
se l’utente preme “cancel”, verrà ritornata una tupla con una stringa vuota e False.

from PyQt5.QtWidgets import QInputDialog
text, select = QInputDialog.getText(None, "Input Dialog", "Insert your name")

>>> text
'bancaldo'
>>> select
True

QColorDialog

QColorDialog è la classe che mette a disposizione una finestra per la scelta di un colore.
Il metodo che mostra tale finestra è getColor, che, una volta premuto “ok”, ritornerà l’oggetto QColor rappresentante la scelta effettuata.

from PyQt5.QtWidgets import QColorDialog
color = QColorDialog.getColor()

Ovviamente l’oggetto QColor metterà a disposizione tutti i dati relativi al colore scelto:

>>> color.name()
'#8eff74'
>>> color.red()
142
>>> color.getRgb()
(142, 255, 116, 255)
>>> color.saturation()
139

QFontDialog

QFontDialog è la classe che mette a disposizione una finestra per la scelta del font.
Come per i precedenti, il metodo che mostra tale finestra è getFont:
Se l’utente preme “ok”, tale metodo ritornerà una tupla con l’oggetto QFont corrispondente alla scelta fatta e True;
se l’utente preme “cancel”, verrà ritornata una tupla con l’oggetto QFont di default e False.

>>> from PyQt5.QtWidgets import QFontDialog
>>> font, select = QFontDialog.getFont()

Ovviamente l’oggetto QFont metterà a disposizione tutti i dati relativi al font scelto:

>>> from PyQt5.QtWidgets import QFontDialog
>>> font, select = QFontDialog.getFont()
>>> font.family()
'Arial'
>>> font.pointSize()
10
>>> font.styleName()
'Bold'
>>> font.underline()
True
>>> font.toString()
'Arial,10,-1,5,75,0,1,0,0,0,Bold'

QFileDialog

Merita un discorso a parte il dialog che permette di scegliere un file e molto altro ancora.

QProgressDialog

Ecco un codice per una progress dialog volante:

import sys
from PyQt5.QtWidgets import (QApplication, QProgressDialog)
from PyQt5.QtCore import Qt
import time


def show_progress(max=100):
    progress = QProgressDialog("Doing stuff...[0/0]", "Abort", 0, max, None)
    progress.setWindowModality(Qt.WindowModal)
    progress.setModal(True)
    progress.show()
    for i in range(max + 1):
        progress.setValue(i)
        progress.setLabelText("Doing stuff...[{}/{}]".format(
            i, progress.maximum()))
        if progress.wasCanceled():
            break
        time.sleep(0.05)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    show_progress(max=210)

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QToolBar

24 Aprile 2019 Commenti chiusi

Torna all’indice degli appunti

QToolBar

QToolBar è una classe che mette e disposizione un pannello che può contenere diversi controlli.
Per aggiungere un controllo alla toolbar, si possono utilizzare i seguenti metodi:

addAction(QAction): aggiunge un toolbutton alla fine della toolbar;
insertAction(beforeaction, newaction): inserisce un toolbutton newaction prima del toolbutton beforeaction;
addSeparator: aggiunge un separatore alla fine della toolbar;
insertSeparator(beforeaction): inserisce un separatore prima del toolbutton beforeaction
addWidget(widget): aggiunge un widget alla fine della toolbar;
insertWidget(beforewidget, newwidget): inserisce un widget newwidget prima del widget beforewidget;

Per elencare le action presenti nella toolbar si utilizza il metodo actions:

>>> from PyQt5.QtWidgets import QApplication, QToolBar, QAction
>>> app = QApplication([])
>>> tb = QToolBar()
>>> act1 = QAction("open")
>>> act2 = QAction("new")
>>> tb.addAction(act2)
>>> tb.insertAction(act2, act1)
>>> tb.actions()
[<PyQt5.QtWidgets.QAction object at 0x03995210>, <PyQt5.QtWidgets.QAction object at 0x03995300>]
>>> tb.addSeparator()
<PyQt5.QtWidgets.QAction object at 0x039953F0>
>>> tb.actions()
[<PyQt5.QtWidgets.QAction object at 0x03995210>, <PyQt5.QtWidgets.QAction object at 0x03995300>, <PyQt5.QtWidgets.QAction object at 0x039953F0>]

Per rimuovere i controlli inseriti in una toolbar, si utilizzano i metodi:

removeAction(action): rimuove dalla toolbar la action passata come argomento;
clear: rimuove dalla toolbar tutti i controlli;

>>> tb.removeAction(act2)
>>> tb.actions()
[<PyQt5.QtWidgets.QAction object at 0x03995210>, <PyQt5.QtWidgets.QAction object at 0x039953F0>]

ICON SIZE

Il Size di default delle icone dei controlli della toolbar è 24×24.
E’ possibile modificare tali dimensioni con il metodo setIconSize(QSize).
Per sapere invece le dimensioni correnti delle icone dei controlli, si utilizza il metodo iconSize:

>>> from PyQt5.QtGui import QIcon
>>> open_icon = QIcon('open24.png')
>>> open_action = QAction(open_icon, 'Open')
>>> tb.addAction(open_action)
>>> tb.iconSize()
PyQt5.QtCore.QSize(24, 24)
>>> from PyQt5.QtCore import QSize
>>> tb.setIconSize(QSize(48, 48))
>>> tb.iconSize()
PyQt5.QtCore.QSize(48, 48)

Ecco un codice basico per vedere la toolbar in azione.

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import (QWidget, QApplication, QBoxLayout,
                             QToolBar, QAction)
from PyQt5.QtCore import QSize
from PyQt5.QtGui import QIcon
import sys


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


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.setLayout(layout)
        self.toolbar = QToolBar()
        open_icon = QIcon('open.png')
        open_action = QAction(open_icon, 'Open', self)
        self.toolbar.addAction(open_action)
        tool_icon = QIcon('tools.png')
        tool_action = QAction(tool_icon, 'Tools', self)
        self.toolbar.addAction(tool_action)
        exit_icon = QIcon('exit.png')
        exit_action = QAction(exit_icon, 'Exit', self)
        self.toolbar.addAction(exit_action)
        layout.addWidget(self.toolbar, 0)
        self.toolbar.setIconSize(QSize(48, 48))


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

visivamente il risultato è:

24×24

48×48

...
        self.toolbar.setIconSize(QSize(48, 48))
...

ORIENTAMENTO

L’orientamento della toolbar può essere sia orizzontale che verticale.
Il metodo che permette di settare questa preferenza è setOrientation(orientation). Di default l’orientamento è orizzontale ma è possibile appunto modificarlo, grazie alla costante Qt.Vertical.
Per settare un orientamento verticale, ad esempio:

...
from PyQt5.QtCore import Qt
...

        self.toolbar.setIconSize(QSize(48, 48))
        self.toolbar.setOrientation(Qt.Vertical)
...

SEGNALI

Il segnale più importante relativo alla toolbar, è actionTriggered.
E’ possibile legare il segnale allo slot in maniera semplice:

...

        self.toolbar.actionTriggered.connect(self.on_tool_bar)

    def on_tool_bar(self):
            print("[SIG] clicked on a tool button...")

Oppure modificando la connessione segnale-slot, con actionTriggered[QAction], modificando la callback (slot), in modo che si aspetti la action relativa al controllo cliccato, passata come argomento:

...

        self.toolbar.actionTriggered[QAction].connect(self.on_tool_bar)

    def on_tool_bar(self, action):
            print("[SIG] clicked on tool button <%s>" % action.text())
...

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QProgressBar

23 Aprile 2019 Commenti chiusi

Torna all’indice degli appunti

QProgressBar


Il QProgressBar è un widget che mette a disposizione dell’utente, una barra di avanzamento orizzontale, o verticale.
La progress bar viene utilizzata per indicare all’utente che l’applicazione sta ancora svolgendo una determinata operazione.

Il costruttore è ovviamente QProgressBar e i metodi principali sono:

setMinimum(int): setta il valore minimo della progress bar;
setMaximum(int): setta il valore massimo della progress bar;
setRange(min, max): setta il valore minimo e massimo della progress bar in un unico metodo;
setValue(int): setta il valore di avanzamento della progress bar;
reset: riporta il valore dell’avanzamento della progress bar a zero;

per ottenere i valori impostati in una progress bar, si utilizzano i metodi:

minimum: ritorna il valore minimo impostato per la progress bar;
maximum: ritorna il valore massimo per la progress bar;
value: ritorna il valore di avanzamento della progress bar;

>>> from PyQt5.QtWidgets import QApplication, QProgressBar
>>> app = QApplication([])
>>> pb = QProgressBar()
>>> pb.setMinimum(0)
>>> pb.setMaximum(100)
>>> pb.setValue(50)
>>> pb.minimum()
0
>>> pb.maximum()
100
>>> pb.value()
50
>>> pb.reset()
>>> pb.value()
-1

ORIENTAMENTO

L’orientamento della progress bar è impostabile tramite il metodo setOrientation(Orientation) dove orientation può essere una delle segeunti costanti:

Qt.Horizontal
Qt.Vertical

Il tipo di orientamento pùo essere ottenuto con il metodo orientation.

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

TESTO PROGRESS BAR

Il formato del testo che appare a fianco della barra di avanzamento, può essere settato con il metodo setFormat(format), dove format può utilizzare i seguenti parametri:

%p: viene sostituito dalla percentuale di completamento;
%v: viene sostituito dal valore di avanzamento della progress bar
%m: viene sostituito dal numero totale di step previsti settati col metodo setMaximum(int);

Infine, per ottenere il formato utilizzato da una progress bar, si utilizza il metodo format.

>>> format = "%v/%m (%p%)"
>>> pb.setFormat(format)
>>> pb.format()
'%v/%m (%p%)'

il testo dell’avanzamento della progress bar si ottiene con il metodo text

>>> pb.text()
'50/100 (50%)'

Se non vogliamo che il testo dell’avanzamento della progress bar sia visibile, possiamo utilizzare il metodo setTextVisible(bool).

SEGNALI

L’unico segnale degno di nota è valueChanged, emesso ad ogni cambio di step:

>>> pb.valueChanged.connect(lambda: print("progress bar value changed to: %s" % pb.value()))
<PyQt5.QtCore.QMetaObject.Connection object at 0x037261B0>
>>> pb.setValue(51)
progress bar value changed to: 51

Vediamo un esempio visivo di una semplice progress bar con un pulsante che avvii l’avanzamento della stessa;

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import (QWidget, QApplication, QBoxLayout, QPushButton,
                             QProgressBar)
import sys
import time


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


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.setLayout(layout)
        self.progressbar = QProgressBar()
        self.progressbar.setMaximum(0)
        self.progressbar.setMaximum(200)
        self.progressbar.setFormat("%v/%m (%p%)")
        btn_start = QPushButton("START")
        layout.addWidget(self.progressbar, 0)
        layout.addWidget(btn_start, 0)
        btn_start.clicked.connect(self.on_start)

    def on_start(self):
        maximum = self.progressbar.maximum()
        for n in range(maximum + 1):
            time.sleep(0.02)
            self.progressbar.setValue(n)


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

Può capitare ci sia la necessità di passare un argomento allo slot richiamato quando viene emesso un segnale.
Ad esempio, se non avessimo a disposizione il metodo maximum avremmo il problema di passare il valore massimo della progress bar, allo slot dove ci occupiamo dell’avanzamento della progress bar stessa.
Per questo genere di situazioni ci viene incontro la funzione lambda, come nell’esempio di seguito:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import (QWidget, QApplication, QBoxLayout, QPushButton,
                             QProgressBar)
import sys
import time


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


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        maximum = 150
        layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.setLayout(layout)
        self.progressbar = QProgressBar()
        self.progressbar.setMaximum(0)
        self.progressbar.setMaximum(maximum)
        self.progressbar.setFormat("%v/%m (%p%)")
        btn_start = QPushButton("START")
        layout.addWidget(self.progressbar, 0)
        layout.addWidget(btn_start, 0)
        btn_start.clicked.connect(lambda: self.on_start(maximum))  # lambda

    def on_start(self, length):
        for n in range(length + 1):
            time.sleep(0.05)
            self.progressbar.setValue(n)


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

PERSONALIZZAZIONE PROGRESS BAR

Mettiamo di voler eliminare il testo a fianco della barra, ma volerlo visualizzare al centro della stessa.
Per fare questo tipo di personalizzazioni, sarebbe sempre meglio sottoclassare la classe QProgressBar.
Creiamo per tanto la nostra sottoclasse come di seguito:

...

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%) Click start..."
        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)
        time.sleep(0.02)
        self.set_text(string)
        self.setValue(step)
...

Oltre al fatto di inserire i valori minimi a massimi in fase di inizializzazione, accediamo al testo interno della progress bar,
l’argomento _text per modificarlo, creando quindi un metodo ad-hoc set_text(text), ma soprattutto, creiamo un metodo update(step), che aggiorna il valore della progress bar, nonchè il testo al suo interno.

Il codice totale sarà quindi:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import (QWidget, QApplication, QBoxLayout, QPushButton,
                             QProgressBar)
from PyQt5.QtCore import Qt
import sys
import time


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%) Click start..."
        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)
        time.sleep(0.02)
        self.set_text(string)
        self.setValue(step)


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


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.setLayout(layout)
        self.progressbar = CustomProgressBar(0, 200)
        btn_start = QPushButton("START")
        layout.addWidget(self.progressbar, 0)
        layout.addWidget(btn_start, 0)
        btn_start.clicked.connect(self.on_start)

    def on_start(self):
        max = self.progressbar.maximum()
        for n in range(max + 1):
            self.progressbar.update(n)


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

23 Aprile 2019 Commenti chiusi

Torna all’indice degli appunti

QPlainTextEdit

Il widget QMessageBox è una sottoclasse dell’oggetto QDialog e permette di dare informazioni all’utente (es. errori), o fare delle domande (“Sicuro sì/no” ecc).
Un message box è costituito da:

primary text: un testo principale, che avvisa l’utente su una determinata situazione;
informative text: testo informativo per spiegare il tipo di alert, o per porre una domanda all’utente;
detailed text: un testo dettagliato per fornire ulteriori informazioni, se richieste dall’utente.
icona: l’icona mostrata nel message box;
standard button: bottoni per permettere all’utente di dare una risposta.

Per creare un message box è molto semplice, si utilizza il costruttore QMessageBox e, una volta creato l’oggetto, si utilizza il metodo
setText(text)
Per dare un titolo alla finestra del messaggio, si utilizza il metodo setWindowTitle(text).
Per visualizzare il message box si utilizza il metodo exec:

>>> from PyQt5.QtWidgets import QApplication, QMessageBox
>>> app = QApplication([])
>>> mbox = QMessageBox()
>>> mbox.setWindowTitle("Message Box")
>>> mbox.setText("Message text")
>>> mbox.exec()

Nota:

Il metodo exec ritorna il valore del pulsante che viene premuto.
I valori del pulsanti standard sono reperibili sul sito ufficiale.
Nell’esempio precedente, premendo sul pulsante OK, oltre alla distruzione del message box, verrà ritornato il valore 1024.
Il valore dello standard button QMessageBox.Ok è infatti 0x00000400 ovvero 4*16^2.

Vediamo come si aggiungono l’informative text, il detailed text e i pulsanti condizionali.
l’Informative text si aggiunge con il metodo l’setInformativeText(text), il l’detailed text, con il metodo setDetailedText(text) e i pulsanti con il metodo setStandardButtons(buttons), dove buttons può essere un singolo pulsante o una union di più pulsanti.

Gli standard buttons disponibili sono:

QMessageBox.Ok
QMessageBox.Open
QMessageBox.Save
QMessageBox.Cancel
QMessageBox.Close
QMessageBox.Discard
QMessageBox.Apply
QMessageBox.Reset
QMessageBox.RestoreDefaults
QMessageBox.Help
QMessageBox.SaveAll
QMessageBox.Yes
QMessageBox.YesToAll
QMessageBox.No
QMessageBox.NoToAll
QMessageBox.Abort
QMessageBox.Retry
QMessageBox.Ignore

>>> mbox.setWindowTitle("Save Dialog")
>>> mbox.setText("Document has been modified!")
>>> mbox.setInformativeText("Do you want to save your changes?")
>>> mbox.setDetailedText("Click on Save button to save changes.")
>>> mbox.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
>>> mbox.exec()

Cliccando sul pulsante Show details otterremo il messaggio supplementare come in figura:

E’ possibile anche aggiungere gli standard buttons manualmente con il metodo addButton(standardButton) che ritorna
lo standard button appena aggiunto. Questo riferimento è utile nel momento in cui lo si vorrà eliminare con il metodo removeButton(standardButton)
Qualora non si avesse un riferimento al bottone, è possibile ottenere la lista dei bottoni presenti nel message box, con il metodo buttons.

>>> mbox.addButton(QMessageBox.Save)
<PyQt5.QtWidgets.QPushButton object at 0x035FE6C0>
>>> mbox.buttons()
[<PyQt5.QtWidgets.QPushButton object at 0x035FE800>, <PyQt5.QtWidgets.QPushButton object at 0x035FE6C0>]
>>> save_button = mbox.buttons()[1]
>>> save_button.text()
'Save'
>>> mbox.removeButton(save_button)
>>> mbox.buttons()
[<PyQt5.QtWidgets.QPushButton object at 0x035FE530>]

ICONA

E’ possibile assegnare al message box un’icona che indichi il livello di importanza del messaggio.
Il tipi di messaggio che solitamente si usano sono 4: Question, Information, Warning, Critical.
Di default il message box è impostato per non mostrare alcuna icona, per agire diversamente si utilizzano i metodi:

setIcon(standardIcon): fornisce una delle icon standard al message box;
setIconPixmap(customIcon): fornisce una icona personalizzata al message box;

Le standard icon sono le seguenti:

QMessageBox.Question
QMessageBox.Information
QMessageBox.Warning
QMessageBox.Critical

>>> mbox.setIcon(QMessageBox.Warning)
>>> mbox.exec()

STATIC FUNCTIONS

La costruzione dell’oggetto message box può avvenire in un colpo solo, grazie all’utilizzo dell static functions della classe
QMessageBox. Sono esattamente 4, come il tipo di messaggio che si vuole creare:

question, information, warning, critical.
In un colpo solo è possibile passare a tali metodi il parent, la il window title, il text e la union dei pulsanti:

>>> QMessageBox.warning(None, "Save Dialog", "The document has been modified.\nDo You want to save your changes",
...                     QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)

SEGNALI

Come detto in una nota precedente è possibile risalire al pulsante premuto, facendo riferimento al valore ritornato dal metodo exec.
Oppure connettiamo il segnale buttonClicked, segnale emesso quando clicchiamo su un bottone del message box.
E’ molto importante che la connessione del segnale allo slot di competenza, avvenga PRIMA della chiamata al metodo exec, che distruggerebbe altrimenti l’oggetto message box.

Nota:
Come indicato sul sito ufficiale il segnale buttonClicked viene emesso ogni volta che un bottone viene cliccato all’interno del message box e il bottone cliccato viene ritornato come parametro button, accessibile pertanto dallo slot collegato al segnale stesso:

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


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


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.setLayout(layout)
        button = QPushButton("SAVE")
        layout.addWidget(button, 0)

        button.clicked.connect(self.on_button)

    def on_button(self):
        box = QMessageBox()
        box.buttonClicked.connect(self.on_message_button)
        box.setWindowTitle("Save Dialog")
        box.setText("The document has been modified!")
        box.setInformativeText("Do you want to save changes?")
        box.setIcon(QMessageBox.Warning)
        box.setStandardButtons(QMessageBox.Save | QMessageBox.Discard |
                               QMessageBox.Cancel)
        box.exec()

    def on_message_button(self, button):
        print("[SIG] button <%s> clicked!" % button.text())


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

9 Aprile 2019 Commenti chiusi

Torna all’indice degli appunti

QFileDialog

La classe QFileDialog mette a disposizione un dialog dove è possibile selezionare uno o più file o directories, spostarsi nel filesystem, o salvare un file in una determinata posizione. Il classico FileBrowser per intenderci.

OPEN FILE DIALOG

Il metodo più semplice per creare un file-dialog è l’utilizzo del metodo getOpenFileName(parent, caption, directory, filter, options:
Gli argomenti da passare al metodo sono appunto:

parent: il parent del file-dialog;
caption: il titolo del file-dialog;
directory: la directory dove si posizionerà il file-dialog;
filter: i tipi di file (estensioni) che verranno mostrati, filtri myltipli vanno separati dal doppio ;
options: sono le flags che modificano il comportamento del file-dialog;

options

Le options più importanti sono le seguenti:

ShowDirsOnly: mostra solo le directories e non i files;
DontResolveSymlinks: non risolve i link simbolici nel file-dialog, di default vengono invece risolti;
DontConfirmOverwrite: se un file è esistente, non chiede conferma di sovrascrittura;
DontUseNativeDialog: Non utilizza il fila-dialog nativo;

vediamo qualche esempio:

>>> from PyQt5.QtWidgets import QApplication, QFileDialog
>>> app = QApplication([])
fd = QFileDialog()
fd.getOpenFileName(parent=None, caption="Open File", directory="", filter="All files (*.*);; Python files (*.py)")
('F:/bancaldo/Qt5/example.py', 'All files (*.*)')

Come si nota, il metodo getOpenFileName ritorna un tupla composta da due stringhe:
una indicante il percorso del file selezionato e l’altra contenente il filtro selezionato nel file-dialog.

Come modificare le options? Mettiamo di non voler utilizzare il file-dialog nativo:
Per impostare le options possiamo utilizzare il metodo setOption(option, bool):

>>> filter = "All files (*.*);; Python files (*.py)"
>>> fd.setOption(QFileDialog.DontUseNativeDialog, True)
>>> options = fd.options()
>>> fd.getOpenFileName(parent=None, caption="Open File", directory="", filter=filter, options=options)

Se vogliamo invece visualizzare solo le directories:

>>> fd.setOption(QFileDialog.ShowDirsOnly, True)
>>> options = fd.options()
>>> fd.getOpenFileName(parent=None, caption="Open File", directory="", filter=filter, options=options)

Qualora si vollessero modificare più cose, prima schegliere se utilizzare o meno il file-dialog nativo, poi
modificarne il comportamento:

>>> fd.setOption(QFileDialog.DontUseNativeDialog, True)
>>> options = fd.options()
>>> fd.setOption(QFileDialog.ShowDirsOnly, True)
>>> options = fd.options()
>>> fd.getOpenFileName(parent=None, caption="Open File", directory="", filter=filter, options=options)

E’ possibile modificare la directory di un file-dialog mediante il metodo setDirectory(path):

>>> fd.setDirectory(r"new_path")

OPEN FILES DIALOG

Le altre modalità di file-dialog, sono ottenute tramite i due metodi getOpenFileNames(parent, caption, directory, filter, options) che, invece di ritornare la stringa del path del file selezionato, ritorna una lista di stringhe di più file selezionati.
Infatti, a differenza del simile getOpenFileName, questo dialog permette la selezione multipla di file.
I parametri da passare al metodo sono gli stessi.

SAVE FILE DIALOG

Altra modalità fondamentale è il Dialog che ci permette di salvare un file.
Si ottiene con il metodo getSaveFileName(parent, caption, directory, filter, options).
Anche in questo caso i parametri del metodo sono sempre quelli, ma se nell’argomento directory aggiungiamo anche
il nome di un file, questo apparirà direttamente nel dialog:

>>> fd.getSaveFileName(parent=None, caption="Save File", directory="example.doc", filter=filter, options=options)

DIRECTORY DIALOG

Permettere di selezionare una directory e non un file, tramite il metodo getExistingDirectory(parent, caption, directory, options), che ritorna la stringa della directory selezionata. In sostanza è come il getOpenFileName con settata la option ShowDirsOnly.

URLs

E’ possibile ottenere dai dialog anche le url complete delle selezioni, utilizzando i corrispettivi metodi:
getOpenFileNameUrl:
ritorna una tupla con un oggetto QUrl con l’url completo del file selezionato ed una stringa del filtro utilizzato

>>> fd.getOpenFileUrl(parent=None, caption="", directory="", options=options)
(PyQt5.QtCore.QUrl('file:///C:/bancaldo/Qt5/example.txt'), 'All Files (*)')

>>> url = fd.getOpenFileUrl(parent=None, caption=””, directory=””, options=options)[0]
>>> url.url()
‘file:///C:/bancaldo/Qt5/example.txt’
[/code]

getOpenFileUrls:

Come sopra ma otterremo una lista di oggetti QUrl rappresentanti i file selezionati nel dialog:

>>> urls = fd.getOpenFileUrls(parent=None, caption="", directory="", options=options)[0]
>>> for url in urls:
...     print(url.url())
...     
file:///C:/bancaldo/Qt5/example.txt
file:///C:/bancaldo/Qt5/example2.txt

getSaveFileNameUrl:

Ritorna l’url del file da salvare:

>>> url = fd.getSaveFileUrl(parent=None, caption="", directory="", options=options)[0]
>>> url.url()
'file:///F:/bancaldo/Qt5/example3.txt'

getExistingDirectoryUrl:

Ritorna l’url della directory selezionata, attenzione all’oggetto QUrl richiesto nell’argomento directory!

>>> url = fd.getExistingDirectoryUrl(parent=None, caption="", directory=QUrl(), options=options)
>>> url.url()
'file:///F:/bancaldo/Qt5'

[/code]

Ecco un esempio pratico di utilizzo di un file-dialog:

import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QTextEdit, QPushButton,
                             QFileDialog, QMainWindow, QVBoxLayout)


class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setWindowTitle("QFileDialog example")
        self.central_widget = FileDialogWidget(self)
        self.setCentralWidget(self.central_widget)
        self.resize(400, 150)


class FileDialogWidget(QWidget):
    def __init__(self, parent):
        super(FileDialogWidget, self).__init__(parent)
        self.layout = QVBoxLayout(self)
        self.file_label = QTextEdit()
        button_open_file = QPushButton("Browse single file")
        button_open_files = QPushButton("Browse multiple file")
        button_save = QPushButton("Save")
        self.setLayout(self.layout)
        self.layout.addWidget(self.file_label)
        self.layout.addWidget(button_open_file)
        self.layout.addWidget(button_open_files)
        self.layout.addWidget(button_save)

        button_open_file.clicked.connect(self.on_open_single_file)
        button_open_files.clicked.connect(self.on_open_multi_files)
        button_save.clicked.connect(self.on_save_file)

    def on_open_single_file(self):
        file_name = QFileDialog.getOpenFileName(
            parent=self, caption="Open file dialog", directory="",
            filter="All Files (*);;Python Files (*.py)")[0]
        if file_name:
            self.file_label.setText("Open file:\n%s" % file_name)

    def on_open_multi_files(self):
        text = "Open files:\n"
        files = QFileDialog.getOpenFileNames(
            parent=self, caption="Open files dialog", directory="",
            filter="All Files (*);;Python Files (*.py)")[0]
        if files:  # files è ina lista di stringhe!
            for file_path in files:
                text += "%s\n" % file_path
            self.file_label.setText(text)

    def on_save_file(self):
        file_name = QFileDialog.getSaveFileName(
            parent=self, caption="Save file dialog", directory="",
            filter="All Files (*);;Python Files (*.py)")[0]
        if file_name:
            self.file_label.setText("Save file:\n%s" % file_name)


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

8 Aprile 2019 Commenti chiusi

Torna all’indice degli appunti

QTime

QTime è una classe che mette a disposizione un set di metodi per la gestione dell’orario.
Oltre alla creazione di oggetti QTime è possibile effettuare comparazioni tra essi.
La creazione di un oggetto QTime può avvenire, sia passando esplicitamente al costruttore, ore, minuti, secondi e millisecondi, sia utilizzando lo static-method currenTime, che ritorna l’ora corrente di sistema:

>>> from PyQt5.QtCore import QTime
>>> QTime.currentTime()
PyQt5.QtCore.QTime(9, 7, 38, 84)
>>> QTime(9, 8, 0, 0)
PyQt5.QtCore.QTime(9, 8)

I metodi per ottenere i valori dell’oggetto QTime sono hour, minute, second, msec:

>>> ct = QTime.currentTime()
>>> ct.hour()
9
>>> ct.minute()
12
>>> ct.second()
11
>>> ct.msec()
592

Per modificare manualmente l’orario, dopo la creazione dell’oggetto QTime, è possibile utilizzare il metodo setHMS(hours, minutes, seconds, milliseconds):

ct.setHMS(1, 1, 50, 50)
True
>>> ct.hour()
1

Con i due metodi addSecs(int), addMSecs(int) è possibile incrementare l’orario della quantità desiderata di secondi e/o msec:

>>> ct.addSecs(20)
PyQt5.QtCore.QTime(1, 2, 10, 50)

Come per l’ggetto QDate, l’utilizzo di questi metodi, non modifica l’oggetto corrente, ma ne ritorna un altro. Per la modifica si utilizza il metodo setHMS(hours, minutes, seconds, milliseconds).

>>> new_time = ct.addMSecs(4500)
>>> new_time
PyQt5.QtCore.QTime(1, 1, 54, 550)

Quando si hanno 2 o più oggetti QTime, è possibile sapere quanti secondi o msec di differenza ci sono tra i due orari, con i metodi secsTo(time) e mSecsTo(time):

>>> ct.secsTo(new_time)
4
>>> ct.msecsTo(new_time)
4500

CONFRONTI

I confronti tra date vengono effettuati con i comuni operatori:

>>> ct < new_time
True

CRONOMETRO

E’ possibile utilizzare l’oggetto QTime anche per cronometrare i millisecondi passati da un determinato evento.
I metodi utilizzabili sono:
start: avvia il cronometro;
restart: resetta il cronometro, ritornando i msec passati dal precedente avvio (start);
elapsed: ritorna i msec passati dall’ avvio (start);

>>> start_time = QTime.currentTime()
>>> start_time.start()
>>> start_time.elapsed()
14383
>>> start_time.restart()
23415
>>> start_time.elapsed()
8955

CONVERSIONE DA TIME A STRINGA

E’ possibile convertire un oggetto QTime in formato stringa con il metodo toString(format) dove format rappresenta il tipo di formato utilizzato per la conversione.
format può assumere i seguenti patterns:

h l’ora rappresentata senza il leading zero (0-23 o 1-12 con am/pm attivo);
hh l’ora rappresentata con il leading zero (01-23 o 01-12 con am/pm attivo);
H l’ora rappresentata senza il leading zero (0-23 anche con am/pm attivo);
HH l’ora rappresentata con il leading zero (01-23 anche con am/pm attivo);
m il minuto rappresentato senza il leading zero (0-59);
mm il minuto rappresentato con il leading zero (00-59);
s i secondi rappresentati senza il leading zero (0-59);
ss i secondi rappresentati con il leading zero (00-59);
z i millisecondi rappresentati senza il trailing zero(0-999);
zzz i millisecondi rappresentati con il trailing zero(000-999);
AP visualizza in modalità AM/PM;
ap visualizza in modalità AM/PM, ma in minuscolo;
t visualizza il time-zone (es ‘CEST’);

>>> ct = QTime.currentTime()
>>> ct.toString("h")
'10'
>>> format = "hh:mm:ss.zzz A t"
>>> ct.toString(format)
'10:27:30.689  ora legale Europa occidentale'

CONVERSIONE DA STRINGA A TIME

Inversamente della situazione precedente, è possibile convertire una stringa in oggetto QTime, con il metodo fromString(string, format), dove string è la stringa dell’orario e format il tipo di formato utilizzato per la conversione.

>>> s_time = '11:28:31.100'
>>> format = "hh:mm:ss.zzz"
>>> new_time = QTime.fromString(s_time, format)
>>> new_time
PyQt5.QtCore.QTime(11, 28, 31, 100)
TIME WIDGET QTIMEEDIT

Il widget utilizzato per gestire l’orario è QTimeEdit.
I metodi di QTimeEdit sono i seguenti:

time(): ottiene l’orario mostrato nel widget;
setMinimumTime(QTime): definisce il valore minimo dell’orario definibile dall’utente;
setMaximumTime(QTime): definisce il valore massimo dell’orario definibile dall’utente;
minimumTime: ottiene il valore minimo impostato per il widget;
maximumTime: ottiene il valore massimo impostato per il widget;
setDisplayFormat(format): definisce il formato dell’orario utilizzato dal widget;
displayFormat: ottiene il formato dell’orario utilizzato dal widget;

from PyQt5.QtWidgets import QWidget, QApplication, QBoxLayout, QTimeEdit
from PyQt5.QtCore import QTime
from PyQt5.QtWidgets import QMainWindow
import sys


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


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QBoxLayout(QBoxLayout.TopToBottom)
        time_edit = QTimeEdit(QTime.currentTime())
        layout.addWidget(time_edit, 0)
        self.setLayout(layout)


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

Con i metodi visti precedentemente possiamo affinare la visualizzazione ed il campo di utilizzo.
Usiamo il format degli esempi precedenti e limitiamo la modifica dalle 8 alle 17:

...
        time_edit = QTimeEdit(QTime.currentTime())
        time_edit.setMinimumTime(QTime(8, 00, 00))
        time_edit.setMaximumTime(QTime(17, 0, 0))
        time_edit.setDisplayFormat("hh:mm:ss.zzz")
...

SEGNALI

Il segnale degno di nota, oltre a quelli soliti ereditati, è timeChanged, emesso ad ogni modifica dell’orario, da parte dell’utente, che sia un o un click sulle freccette del widget:

...
        time_edit.timeChanged.connect(self.on_time_changed)

    def on_time_changed(self):
        time_editor = self.sender()
        format = time_editor.displayFormat()
        newtime = time_editor.time().toString(format)
        print("[SIG] Time has changed to %s" % newtime)
        print("      Minimum time set: %s" % time_editor.minimumTime())
        print("      Maximum time set: %s" % time_editor.maximumTime())
        print("      time format is  : %s" % format)
...

Nota:
l’utilizzo di sender() è solo didattico per capire da chi arriva il segnale,
rendendo time_edit attributo dell’istanza di QWidget (self.time_edit) in fase di inizializzazione,
evita l’utilizzo del metodo sender di QWidget.

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QDate

8 Aprile 2019 Commenti chiusi

Torna all’indice degli appunti

QDate

QDate è una classe che mette a disposizione un set di metodi per la gestione delle date.
E’ disponibile anche un widget Calendar per la rappresentazione delle stesse.

La costruzione dell’oggetto date si ottiene passando al costruttore gli argomenti year, month, day:

>>> date = QDate(2019, 3, 30)

Con il metodo getDate si ottiene la data impostata

>>> date.getDate()
(2019, 3, 30)

Per modificare un oggetto data già esistente si utilizza il metodo setDate(year, month, day):

>>> date.setDate(2019,3,31)
True
>>> date.getDate()
(2019, 3, 31)

Anno, mese e giorno sono ottenuti grazie ai metodi year, month, day dell’oggetto date:

>>> date.year()
2019
>>> date.month()
3
>>> date.day()
31

E’ possibile anche aggiungere anni, mesi e giorni all’oggetto date, con i metodi addYears(int), addMonths(int), addDays(int).

Nota:

Questi metodi però non modificano l’oggetto date esistente, ma ritornano un nuovo oggetto date con i valori modificati.

>>> date.addYears(1)
PyQt5.QtCore.QDate(2020, 3, 31)
>>> date.addMonths(2)
PyQt5.QtCore.QDate(2019, 5, 31)
>>> date.addDays(1)
PyQt5.QtCore.QDate(2019, 4, 1)
>>> date.getDate()
(2019, 3, 31)

Per scalare nella data, ovviamente si utilizzeranno numeri negativi:

>>> date.addDays(-3)
PyQt5.QtCore.QDate(2019, 3, 28)

Vediamo alcuni metodi utili della classe QDate:

dayOfWeek:
ritorna il numero del giorno della settimana:

>> date.getDate()
(2019, 3, 31)
>>> date.dayOfWeek()
7

dayOfYear
ritorna il numero del giorno dell’anno:

>>> date.dayOfYear()
90

daysInMonth e daysInYear:
ritorna il numero di giorni presenti nel mese e nell’anno dell’oggetto date corrente

>>> date.daysInMonth()
31
>>> date.daysInYear()
365

weekInYear
ritorna il numero della settimana nell’anno:

>>> date.weekNumber()
(13, 2019)

Se vogliamo conoscere il nome del giorno e del mese, possiamo utilzzare i seguenti metodi:

longDayName(dayofweek)
ritorna il nome del giorno della settimana in forma estesa

>>> date.longDayName(date.dayOfWeek())
'domenica'

shortDayName(dayofweek)
ritorna il nome del giorno della settimana in forma abbreviata

>>> date.shortDayName(date.dayOfWeek())
'dom'

longMonthName(month)
ritorna il nome del mese in forma estesa

>>> date.longMonthName(date.month())
'marzo'

shortMonthName(month)
ritorna il nome del mese in forma abbreviata

>>> date.shortMonthName(date.month())
'mar'

E’ possibile sapere anche se un anno è bisestile, con il metodo isLeapYear(year):

>>> QDate.isLeapYear(date.year())
False
>>> QDate.isLeapYear(2020)
True

Per calcolare quanti giorni mancano ad una determinata data si utilizza il metodo daysTo(date):

>>> nextdate = date.addMonths(2)
>>> nextdate
PyQt5.QtCore.QDate(2019, 5, 31)
>>> date.daysTo(nextdate)
61

Per ottenere la data in forma di stringa, si utilizza il metodo toString

>>> date.toString()
'dom mar 31 2019'

CONFRONTI

I confronti tra date vengono effettuati con i comuni operatori:

>>> date > nextdate
False
>>> date < nextdate
True
>>> samedate = nextdate.addMonths(-2)
>>> date == samedate
True

DATA CORRENTE

Si ottiene con il metodo currentDate

>>> QDate.currentDate()
PyQt5.QtCore.QDate(2019, 3, 31)

E’ possibile controllare che una data sia esistente, con il metodo isValid(year, month, day)

>>> QDate.isValid(2019, 2, 30)
False
>>> QDate.isValid(2019, 2, 28)
True

CONVERSIONE DA DATA A STRINGA

E’ possibile convertire un oggetto QDate in formato stringa con il metodo toString(format) dove format rappresenta
il tipo di formato utilizzato per la conversione.
format può assumere i seguenti patterns:

d il giorno rappresentato senza il leading zero (1-31);
dd il giorno rappresentato con il leading zero(01-31);
ddd nome del giorno abbreviato (‘Lun’-‘Dom’);
dddd nome del giorno esteso (‘Lunedì’-‘Domenica’);
M il mese rappresentato senza il leading zero (1-12);
MM il mese rappresentato con il leading zero (01-12);
MMM nome del mese abbreviato (‘Gen’-‘Dic’);
MMMM nome del mese esteso (‘Gennaio’-‘Dicembre’);
yy anno espresso con due cifre (00-99)
yyyy anno espresso per intero

>>> date.toString("d")
'31'
>>> date.toString("d M yy")
'31 3 19'
>>> date.toString("d/M/yy")
'31/3/19'
>>> date.toString("d/MM/yy")
'31/03/19'
>>> date.toString("dddd d MMMM yyyy")
'domenica 31 marzo 2019'

CONVERSIONE DA STRINGA A DATA

Inversamente della situazione precedente, è possibile convertire una stringa in oggetto QDate, con il metodo fromString(string, format),
dove string è ovviamente la data in formato stringa e format il tipo di formato utilizzato per la conversione.

>>> format = "dddd d MMMM yyyy"
>>> sdate = date.toString(format)
>>> sdate
'domenica 31 marzo 2019'
>>> newdate = QDate.fromString(sdate, format)
>>> newdate.getDate()
(2019, 3, 31)
>>> date == newdate
True
DATE WIDGET QDATEWIDGET

La classe QDateEdit ci mette a disposizione il primo widget per gestire gli oggetti QDate.
I metodi disponibili sono:

date(): ottiene la data mostrata nel widget;
setMinimumDate(QDate): definisce la data minima definibile dall’utente;
setMaximumDate(QDate): definisce la data massima definibile dall’utente;
minimumDate: ottiene il valore minimo impostato per il widget;
maximumDate: ottiene il valore massimo impostato per il widget;
setDisplayFormat(format): definisce il formato della data utilizzato dal widget;
displayFormat: ottiene il formato della data utilizzato dal widget;

from PyQt5.QtWidgets import QWidget, QApplication, QBoxLayout, QDateEdit
from PyQt5.QtCore import QDate
from PyQt5.QtWidgets import QMainWindow
import sys


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


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QBoxLayout(QBoxLayout.TopToBottom)
        date_edit = QDateEdit(QDate.currentDate())
        layout.addWidget(date_edit, 0)
        self.setLayout(layout)


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

Con i metodi visti precedentemente possiamo affinare la visualizzazione ed il campo di utilizzo.
Usiamo il format degli esempi precedenti e limitiamo la modifica della data:

...
        date_edit = QDateEdit(QDate.currentDate())
        date_edit.setMinimumDate(QDate(2019, 4, 5))
        date_edit.setMaximumDate(QDate(2019, 4, 15))
        date_edit.setDisplayFormat("dddd dd/MM/yyyy")
...

SEGNALI

Il segnale degno di nota, oltre a quelli soliti ereditati, è dateChanged, emesso ad ogni modifica della data, da parte dell’utente, che sia un o un click sulle freccette del widget:

...
        date_edit.dateChanged.connect(self.on_date_changed)

    def on_date_changed(self):
        date_editor = self.sender()
        format = date_editor.displayFormat()
        newdate = date_editor.date().toString(format)
        print("[SIG] Date has changed to %s" % newdate)
        print("      Minimum time set: %s" % date_editor.minimumDate())
        print("      Maximum time set: %s" % date_editor.maximumDate())
        print("      date format is  : %s" % format)
...
CALENDARIO

Il widget utilizzato per gestire le date è QCalendarWidget.
I metodi di QCalendarWidget sono i seguenti:

showToday(): mostra la data corrente sul calendario (metodo chiamato di default);
showSelectedDate(): mostra la data selezionata;
showNextMonth(): mostra sul calendario il mese successivo a quello corrente;
showNextYear(): mostra sul calendario la stessa data sull’anno successivo;
showPreviousMonth(): mostra il mese precedente a quello corrente;
showPreviousYear(): mostra sul calendario la stessa data nell’anno precedente;

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


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


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QBoxLayout(QBoxLayout.TopToBottom)

        calendar = QCalendarWidget()

        layout.addWidget(calendar, 0)
        self.setLayout(layout)


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

Sulla barra della data sono selezionabili sia il mese (oltre alle freccette), sia l’anno.

Se vogliamo limitare il range di date entro cui il calendario può spostarsi, possiamo utilizzare i metodi:
setMinimumDate(QDate): per settare la minima data oltre la quale il calendario non può scendere;
setMaximumDate(QDate): per settare la massima data oltre la quale il calendario non può andare;

from PyQt5.QtCore import QDate
...
        calendar = QCalendarWidget()
        layout.addWidget(calendar, 0)
        self.setLayout(layout)
        calendar.setMinimumDate(QDate(2019, 3, 20))
        calendar.setMaximumDate(QDate(2019, 3, 25))
...

per conoscere quali date minima e massima sono state impostate, si utilizzano i metodi minimumDate e maximumDate.

>>> from PyQt5.QtCore import QDate
>>> calendar.setMinimumDate(QDate(2019, 3, 20))
>>> calendar.setMaximumDate(QDate(2019, 3, 25))
>>> calendar.minimumDate()
PyQt5.QtCore.QDate(2019, 3, 20)
>>> calendar.maximumDate()
PyQt5.QtCore.QDate(2019, 3, 25)

Per disattivare la barra di navigazione del calendario, si utilizza il metodo setNavigationBarVisible(bool).
Per rendere visibile la griglia invece si usa il metodo setGridVisible(bool)

...
        calendar = QCalendarWidget()
        calendar.setNavigationBarVisible(False)
        calendar.setGridVisible(True)
...

SEGNALI

I più importanti sono due, clicked e selectionChanged; la differenza tra i due è che,
il primo segnale viene emesso ad ogni click su una data, mentre il secondo solo se la data selezionata cambia rispetto a quella precedente.

...

        calendar.clicked.connect(self.on_click)
        calendar.selectionChanged.connect(self.on_selection_changed)

    def on_click(self):
        calendar = self.sender()
        date = calendar.selectedDate()
        s_date = date.toString("dddd d MMMM yyyy")
        print("[SIG] click on calendar on date: '%s'" % s_date)

    def on_selection_changed(self):
        calendar = self.sender()
        date = calendar.selectedDate()
        s_date = date.toString("dddd d MMMM yyyy")
        print("[SIG] selection changed: now date is: '%s'" % s_date)
...

Nota:
l’utilizzo di sender() è solo didattico per capire da chi arriva il segnale,
rendendo calendar attributo dell’istanza di QWidget (self.calendar) in fase di inizializzazione,
permetterebbe di risparmiare sulle linee di codice.

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QDockWidget

3 Aprile 2019 Commenti chiusi

Torna all’indice degli appunti

QDockWidget

La classe QDockWidget mette a disposizione una window secondaria che può essere posizionata in un’area dedicata della QMainWindow, detta Dock Widget Area attorno al central Widget, oppure lasciata floating come Top Level Window, quindi esterna alla QMainWindow stessa.


Il DockWidget consiste di una title bar e di una content area.

Vediamo un sempio pratico così composto:
il CentralWidget della QMainWindow sarà un QTextEdit su quale verranno inseriti testi;
il DockWidget sarà un semplice QListWidget composto da qualche semplice item;

Per aggiungere il widget desiderato all’interno del QDockWidget, si usa il metodo setWidget(widget):

from PyQt5.QtWidgets import (QMainWindow, QApplication, QPushButton, QWidget,
                             QVBoxLayout, QHBoxLayout, QTextEdit, QDockWidget,
                             QListWidget)
from PyQt5.QtCore import Qt
import sys


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        layout = QHBoxLayout()

        self.setWindowTitle("DockWidget example")
        self.dock_widget = QDockWidget("Dockable", self)
        self.list_widget = QListWidget()
        self.list_widget.addItem("item 1")
        self.list_widget.addItem("item 2")
        self.list_widget.addItem("item 3")

        self.dock_widget.setWidget(self.list_widget) # aggiungo il list-widget!
        self.dock_widget.setFloating(False)
        self.frame_widget = FrameWidget(self)
        self.setCentralWidget(self.frame_widget)
        self.addDockWidget(Qt.RightDockWidgetArea, self.dock_widget)
        self.setLayout(layout)

        self.list_widget.clicked.connect(self.on_item)

    def on_item(self):
        row = self.list_widget.currentRow()
        item = self.list_widget.item(row)
        self.frame_widget.text_string += "%s\n" % item.text()
        self.frame_widget.text_edit.setText(self.frame_widget.text_string)


class FrameWidget(QWidget):
    def __init__(self, parent):
        self.text_string = ""
        super(FrameWidget, self).__init__(parent)
        layout = QVBoxLayout(self)
        self.text_edit = QTextEdit()
        button = QPushButton("CLEAR")
        layout.addWidget(self.text_edit)
        layout.addWidget(button)
        self.setLayout(layout)
        button.clicked.connect(self.on_clear)

    def on_clear(self):
        self.text_string = ""
        self.text_edit.clear()


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

L’ancoraggio iniziale del dock-widget:

        self.addDockWidget(Qt.RightDockWidgetArea, self.dock_widget)

è modificabile alternando nel metodo addDockWidget(side, dockwidget) di QMainWindow i seguenti valori:

Qt.RightDockWidgetArea: ancoraggio destro (di default nell’esempio);
Qt.LeftDockWidgetArea: ancoraggio sinistro;

Qt.TopDockWidgetArea: ancoraggio alto;

Qt.BottomDockWidgetArea: ancoraggio basso;

Ovviamente il dock-widget è trascinabile sulle varie zone!
Trascinandolo esternamente alla main window, diventerà una secondary window.

Floatable widget

Volendo settare il dock-widget floating di default, non utilizzeremo il metodo addDockWidget di QMainWindow,
ma il metodo setFloatable(bool) del QDockWidget stesso:

...
        self.setCentralWidget(self.frame_widget)
        # self.addDockWidget(Qt.RightDockWidgetArea, self.dock_widget)
        self.dock_widget.setFloating(True)
...

DockWidget Allowed Areas

Possiamo decidere su quali aree (left, right, top, bottom) poter ancorare il dock-widget.
Basta passare al metodo di QDockWidget, setAllowedAreas(const), la costante o l’unione delle costanti rappresentanti
le aree permesse. Mettiamo di voler escludere le Dock widget area Top e Bottom, basterà scrivere:

...
        self.dock_widget.setAllowedAreas(Qt.LeftDockWidgetArea |
                                         Qt.RightDockWidgetArea)
...

Di default la costante utilizzata è: Qt.AllDockWidgetAreas

DockWidget Features

Anche le Features sono costanti che passiamo al metodo setFeatures(const) del dock-widget.
Di default sono l’unione delle seguenti:

QDockWidget.DockWidgetClosable: permette di chiudere il dock-widget;
QDockWidget.DockWidgetMovable: permette di muovere il dock-widget;
QDockWidget.DockWidgetFloatable: permette di renderlo floatable;

supponiamo di volere un dock-widget che non sia chiudibile, basterà scrivere:

...
        self.dock_widget.setFeatures(self.dock_widget.DockWidgetMovable |
                                     self.dock_widget.DockWidgetFloatable)
...

SEGNALI

I principali segnali sono:

allowedAreasChanged(allowedAreas):

il segnale viene emesso quando cambiano le regole della allowed areas:

>> from PyQt5.QtWidgets import (QMainWindow, QApplication, QPushButton, QWidget,
...                              QVBoxLayout, QHBoxLayout, QTextEdit, QDockWidget,
...                              QListWidget)
>>> from PyQt5.QtCore import Qt
>>> app = QApplication([])
>>> dw = QDockWidget("Dockable", None)
>>> def allowed_area_changed_signal():
...     print("[SIG] Allowed area changed!")
...     
>>> dw.setAllowedAreas(Qt.RightDockWidgetArea | Qt.LeftDockWidgetArea) # Aree di partenza
>>> dw.allowedAreasChanged.connect(allowed_area_changed_signal)
<PyQt5.QtCore.QMetaObject.Connection object at 0x0393DFB0>
>>> dw.setAllowedAreas(Qt.RightDockWidgetArea | Qt.LeftDockWidgetArea) # Le aree non cambiano
>>> dw.setAllowedAreas(Qt.RightDockWidgetArea) # Le Aree cambiano
[SIG] Allowed area changed!

dockLocationChanged(area):

Questo segnale viene emesso ovviamente quando la posizione del dock-widget cambia, in seguito ad uno
spostamento effettuato dall’utente.

featuresChanged(features):

Questo segnale viene emesso quando le features del dock-widget vengono modificate:

>>> def features_changed_signal():
...     print("[SIG] Dock Widget features changed!")
...     
>>> dw.featuresChanged.connect(features_changed_signal)
<PyQt5.QtCore.QMetaObject.Connection object at 0x0398F1F0>
>>> dw.setFeatures(dw.DockWidgetMovable | dw.DockWidgetFloatable)
[SIG] Dock Widget features changed!

topLevelChanged(bool):

Il segnale viene emesso quando il dock-widget diventa una top level window, ad esempio quando viene portato
all’esterno della Main Window, diventando di fatto una secondary window.
Aggiungendo il seguente codice, sarà sufficiente premere sul tastino che rende la dock-widget floatable ed il segnale
sarà emesso:

...
        self.dock_widget.topLevelChanged.connect(self.top_level_win_signal)

    def top_level_win_signal(self):
        print("[SIG] The DockWidget is now a Top Level one")
...

visibilityChanged(bool):

Il segnale viene emesso quando il widget modifica la propria visibilità:

>>> def visibility_changed_signal():
...     if dw.isVisible():
...         print("[SIG] The DockWidget is visible!")
...     else:
...         print("[SIG] The DockWidget is not visible!")
...         
>>> dw.visibilityChanged.connect(visibility_changed_signal)
<PyQt5.QtCore.QMetaObject.Connection object at 0x0393DFB0>
>>> dw.show()
[SIG] The DockWidget is visible!
>>> dw.hide()
[SIG] The DockWidget is not visible!

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QListWidget

3 Aprile 2019 Commenti chiusi

Torna all’indice degli appunti

QListWidget

Il ListWidget permette di visualizzare una lista di elementi.
Questi elementi possono essere semplici stringhe di testo o oggetti QListWidgetItem, che permettono ad esempio di avere associata un’icona.

I metodi principali sono:

addItem(item):
aggiunge l’elemento al listwidget. Questo elemento può essere anche un QListWidgetItem!

>>> from PyQt5.QtWidgets import (QMainWindow, QApplication, QGridLayout, QWidget,
...                              QListWidget)
>>> from PyQt5.QtWidgets import QListWidgetItem
>>> from PyQt5.QtGui import QIcon
>>> app = QApplication([])
>>> lw = QListWidget()
>>> lw.addItem("Simple string 1")
>>> item = QListWidgetItem(QIcon("Italy.png"), "Italy")
>>> lw.addItem(item)
>>> lw.count()
2

insertItem(row, item):
aggiunge l’elemento alla riga specificata row.

>>> lw.insertItem(1, "Simple string 2")
>>> for index in range(lw.count()):
...     print("[%s]" % index, lw.item(index).text())
...     
[0] Simple string 1
[1] Simple string 2
[2] Italy

count:
conta gli elementi del list-widget

current Item:
per settare il current item, si utilizza il metodo setCurrentItem(item) dove item è l’elemento
sul quale vogliamo spostare il focus.
Per sapere quale è attualmente il current item, si utilizza il metodo currentItem:

>>> item
<PyQt5.QtWidgets.QListWidgetItem object at 0x0398D260>
>>> lw.setCurrentItem(item)
>>> lw.currentItem()
<PyQt5.QtWidgets.QListWidgetItem object at 0x0398D260>

current Row:

Stessi concetti del metodo precedente, ma si sposta il focus sull’elemento desiderato, tramite row.
Il metodo è appunto setCurrentRow(row). Per ottenere invece la current row, si utilizza il metodo currentRow:

>>> lw.currentRow()
2
>>> lw.setCurrentRow(1)
>>> lw.currentRow()
1

Sorting:

E’ possibile impostare l’ordinamento degli elementi tramite il metodo sortItems(order).
Il parametro order può avere i seguenti due valori:
Qt.AscendingOrder: ordine ascendente;
Qt.DescendingOrder: ordine discendente;

>>> from PyQt5.QtCore import Qt
>>> for index in range(lw.count()):
...     print("[%s]" % index, lw.item(index).text())
...     
[0] Simple string 1
[1] Simple string 2
[2] Italy
>>> lw.sortItems(Qt.DescendingOrder)
>>> for index in range(lw.count()):
...     print("[%s]" % index, lw.item(index).text())
...     
[0] Simple string 2
[1] Simple string 1
[2] Italy

L’ordinamento degli elementi del list-widget può essere attivato/disattivato con il metodo setSortingEnabled(bool)
e per conoscere lo stato di questo flag, si utilizza il metodo isSortingEnabled(bool).

remove item:
Per Rimuovere l’item dalla list-widget si usa il metodo removeItemWidget(item). Questo metodo rimuove l’item dal list-widget, ma non lo cancella di fatto. Per rimuoverlo definitivamente bisogna usare takeItem(row) che ritorna l’elemento eliminato.

>>> item_to_remove = lw.item(2)
>>> lw.removeItemWidget(item_to_remove)
>>> for index in range(lw.count()):
...     print("[%s]" % index, lw.item(index).text())
...     
[0] Simple string 2
[1] Simple string 1
[2] Italy
>>> lw.takeItem(2)
<PyQt5.QtWidgets.QListWidgetItem object at 0x0398D260>
>>> for index in range(lw.count()):
...     print("[%s]" % index, lw.item(index).text())
...     
[0] Simple string 2
[1] Simple string 1

SEGNALI:

i segnali del list-widget sono i seguenti:

currentItemChanged: emesso quando cambia il current item;
currentRowChanged(int): emesso quando cambia la current row;
currentTextChanged: currentText è il text-data del current item, di conseguenza questo segnale viene emesso
ogni qualvolta cambia il current item;
itemActivated: segnale emesso quando un item viene attivato tramite doppio-click o premendo ;
itemChanged: segnale emesso quando l’item cambia i propri dati, più avanti faremo un accenno all’editing dell’item;
itemClicked: segnale emesso al click sull’item;
itemDoubleClicked: segnale emesso al doppio-click su un item;
itemEntered: segnale emesso quando il mouse passa sull’item, è necessario attivare il mouseTracking
per il list-widget con il metodo setMouseTracking;
itemPressed: segnale emesso quando sull’item viene premuto un botton del mouse;
itemSelectionChanged: segnale emesso quando cambia la selezione;

Ecco un esempio con tutti i segnali; decommentare il segnale da monitorare:

import sys
from PyQt5.QtWidgets import (QMainWindow, QApplication, QVBoxLayout, QWidget,
                             QListWidget, QListWidgetItem, QPushButton, QLabel)
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt


class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setWindowTitle("QListWidget Example")
        self.central_widget = FrameWidget(self)
        self.setCentralWidget(self.central_widget)
        self.resize(200, 200)


class FrameWidget(QWidget):
    def __init__(self, parent=None):
        super(FrameWidget, self).__init__(parent)
        layout = QVBoxLayout()
        self.setLayout(layout)
        self.text_label = QLabel()
        self.list_widget = QListWidget()
        self.list_widget.setMouseTracking(True)
        for index, name in enumerate(["Italy", "France", "Spain"]):
            item = QListWidgetItem(QIcon("%s.png" % name), name)
            self.list_widget.insertItem(index, item)
        button_asc = QPushButton("Ascend")
        button_desc = QPushButton("Descend")
        layout.addWidget(self.text_label)
        layout.addWidget(self.list_widget)
        layout.addWidget(button_asc)
        layout.addWidget(button_desc)

        button_asc.clicked.connect(self.on_sorted)
        button_desc.clicked.connect(self.on_sorted)
        # self.list_widget.clicked.connect(self.on_list_widget)
        # self.list_widget.currentItemChanged.connect(self.on_current_i_changed)
        # self.list_widget.currentRowChanged[int].connect(self.on_row_changed)
        # self.list_widget.currentTextChanged.connect(self.on_text_changed)
        # self.list_widget.itemActivated.connect(self.on_item_activated)
        # self.list_widget.itemChanged.connect(self.on_item_changed)
        # self.list_widget.itemClicked.connect(self.on_item_clicked)
        # self.list_widget.itemDoubleClicked.connect(self.on_item_double_clicked)
        # self.list_widget.itemEntered.connect(self.on_item_entered)
        # self.list_widget.itemPressed.connect(self.on_item_pressed)
        # self.list_widget.itemSelectionChanged.connect(self.on_sel_changed)

    def on_current_i_changed(self):
        item = self.list_widget.currentItem()
        print("[SIG] Current item changed to <%s>" % item.text())

    def on_row_changed(self, row):
        item = self.list_widget.item(row)
        print("[SIG] Current item changed to <{}[{}]>".format(item.text(), row))

    def on_text_changed(self):
        item = self.list_widget.currentItem()
        print("[SIG] Current text-data changed to <%s>" % item.text())

    def on_item_activated(self):
        item = self.list_widget.currentItem()
        print("[SIG] Activated item <%s>" % item.text())

    def on_item_changed(self):
        item = self.list_widget.currentItem()
        print("[SIG] item changed to <%s>" % item.text())

    def on_item_clicked(self):
        item = self.list_widget.currentItem()
        print("[SIG] item <%s> clicked" % item.text())

    def on_item_double_clicked(self):
        item = self.list_widget.currentItem()
        print("[SIG] item <%s> double-clicked" % item.text())

    def on_item_entered(self):
        item = self.list_widget.currentItem()
        print("[SIG] Mouse on ListWidget: current item is <%s>" % item.text())

    def on_item_pressed(self):
        item = self.list_widget.currentItem()
        print("[SIG] item <%s> pressed" % item.text())

    def on_sel_changed(self):
        item = self.list_widget.currentItem()
        print("[SIG] item <%s> selected" % item.text())

    def on_sorted(self):
        button = self.sender()
        if button.text().lower() == "ascend":
            self.list_widget.sortItems(Qt.AscendingOrder)
        else:
            self.list_widget.sortItems(Qt.DescendingOrder)

    def on_list_widget(self):
        item = self.list_widget.currentItem()
        row = self.list_widget.currentRow()
        self.text_label.setText("{} selected (row {})".format(item.text(), row))


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

Editing Item

Merita un approfondimento l’editing di un item.
Per poter editare i dati di un item e modificarli (causando l’emissione del segnale itemChanged, dobbiamo
utilizzare i metodi openPersistentEditor(item) e closePersistentEditor(item), del list-widget.
Ad esempio possiamo fare in modo che al doppio click su un item, si apra l’editor; per questo possiamo utilizzare il
segnale itamActivated, mentre a dati modificati, possiamo chiudere l’editor sfruttando il segnale
itemChanged:

...

class FrameWidget(QWidget):
    def __init__(self, parent=None):
        ...
        self.list_widget.itemActivated.connect(self.on_item_activated)
        self.list_widget.itemChanged.connect(self.on_item_changed)
        ...

    def on_item_activated(self):
        item = self.list_widget.currentItem()
        print("[SIG] Activated item <%s>" % item.text())
        self.list_widget.openPersistentEditor(item) # qui apriamo l'editor

    def on_item_changed(self):
        item = self.list_widget.currentItem()
        print("[SIG] item changed to <%s>" % item.text())
        self.list_widget.closePersistentEditor(item) # qui chiudiamo l'editor
    ...

Nota:
Cosa succede se edito un item ma premo invio senza modificarne i dati?
L’editor rimane attivo sull’item, poichè lo chiudiamo solo in caso di modifica dei dati.

Per chiuderlo anche in caso di NON-modifica, possiamo ad esempio intercettare l’evento KeyPress e sovrascriverlo in modo che,
alla pressione del tasto , l’editor venga chiuso:

...

class FrameWidget(QWidget):
    def __init__(self, parent=None):
        ...
        self.list_widget.itemActivated.connect(self.on_item_activated)
        self.list_widget.itemChanged.connect(self.on_item_changed)
        ...

    def on_item_activated(self):
        item = self.list_widget.currentItem()
        print("[SIG] Activated item <%s>" % item.text())
        self.list_widget.openPersistentEditor(item) # qui apriamo l'editor

    def on_item_changed(self):
        item = self.list_widget.currentItem()
        print("[SIG] item changed to <%s>" % item.text())
        self.list_widget.closePersistentEditor(item) # qui chiudiamo l'editor

    def keyPressEvent(self, event):  # sovrascrivo l'evento KeyPressEvent
        print("[EVENT] Key Return pressed")
        if event.key() == Qt.Key_Return:
            item = self.list_widget.currentItem()
            self.list_widget.closePersistentEditor(item)
    ...

Ora, anche non modificando i deti dell’item, il persistent editor si chiuderà.

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QStackedWidget

2 Aprile 2019 Commenti chiusi

Torna all’indice degli appunti
QStackedWidget

Lo StackedWidget mette a disposizione una pila di widget visibili solo uno alla volta.
Immaginiamolo come un mazzo di carte una sopra l’altra.
Quando si crea lo StackedWidget con il costruttore QStackedWidget, inizialmente è vuoto e va di conseguenza popolato.
Quando vengono aggiunti i widgets, questi finiscono in una lista interna all’oggetto StackedWidget.

I metodi principali sono:

addWidget(widget):
aggiunge il widget in fondo alla lista del QStackedWidget, ritornando l’indice della posizione di inserimento.
Se al momento dell’inserimento, la lista del QStackedWidget è vuota, il widget aggiunto diventa il
current widget

>>> from PyQt5.QtWidgets import QApplication, QStackedWidget, QLabel
>>> app = QApplication([])
>>> sw = QStackedWidget()
>>> sw.addWidget(QLabel("Label 1"))
0

addWidget esegue un append sulla lista interna, infatti:

>>> sw.addWidget(QLabel("Label 2"))
1

Per ottenere il current widget quindi si utilizza il metodo currentwidget:

>>> current = sw.currentWidget()
>>> current.text()
'Label 1

Notare che il current widget rimane il primo inserito fino a che non lo si modifica tramite il metodo setCurrentWidget(widget).
Per ottenere un widget dalla lista interna del QStackedWidget si utilizza il metodo widget(index):

>> label2 = sw.widget(1)
>>> label2.text()
'Label 2'
>>> sw.setCurrentWidget(label2)
>>> sw.currentWidget().text()
'Label 2'

insertWidget(index, widget)
inserisce il widget all’indice dato. Se l’indice dato è “out of range”, viene effettuato l’append.
Come per addWidget, se la lista è vuota, il widget diventa il current widget.

>>> for index in range(sw.count()):
...     print("[{}] {}".format(index, sw.widget(index).text()))
...     
[0] Label 1
[1] Label 2
>>> sw.insertWidget(1, QLabel("Label 3"))
1
>>> for index in range(sw.count()):
...     print("[{}] {}".format(index, sw.widget(index).text()))
...     
[0] Label 1
[1] Label 3
[2] Label 2

removeWidget(widget)

Rimuove il widget passato come argomento. Il widget non viene cancellato, ma solo rimosso dallo stacked widget.
In caso si volesse riutilizzarlo ricordarsi di riassegnargli il parent!

>>> label3 = sw.widget(1)
>>> sw.removeWidget(label3)
>>> for index in range(sw.count()):
...     print("[{}] {}".format(index, sw.widget(index).text()))
...     
[0] Label 1
[1] Label 2

currentIndex:
simile a currentWidget ma invece che ritornare il widget corrente, ne ritorna l’indice:

>>> sw.currentWidget()
<PyQt5.QtWidgets.QLabel object at 0x0363D3A0>
>>> sw.currentIndex()
1

setCurrentIndex(index):
simile a setCurrentWidget ma invece che settare il widget corrente, via widget, lo setta via indice:

>>> sw.currentIndex()
1
>>> sw.setCurrentIndex(0)
>>> sw.currentIndex()
0
>>> sw.currentWidget().text()
'Label 1'

count:
ritorna il numero di widget della lista interna al QStackedWidget.

indexOf(widget):
ritorna l’indice del widget passato come argomento o -1 se non lo trova in lista:

>>> sw.indexOf(label1)
0
>>> sw.indexOf(label3)
-1

SEGNALI:

currentChanged(index): emesso quando il current widget cambia.
widgetRemoved(index): emesso quando un widget viene rimosso.

esempio:

import sys
from PyQt5.QtWidgets import (QMainWindow, QApplication, QPushButton, QWidget,
                             QLabel, QVBoxLayout, QStackedWidget, QComboBox)
from PyQt5.QtCore import Qt


class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setWindowTitle("QStackedWidget Example")
        self.central_widget = FrameWidget(self)
        self.setCentralWidget(self.central_widget)
        self.resize(200, 200)


class FrameWidget(QWidget):
    def __init__(self, parent=None):
        super(FrameWidget, self).__init__(parent)
        self.count = 0

        self.stacked_w = QStackedWidget()
        self.stacked_w.setFixedSize(200, 50)

        self.combobox = QComboBox()
        self.combobox.activated.connect(self.stacked_w.setCurrentIndex)

        add_button = QPushButton('Add widget')
        add_button.clicked.connect(self.add_widget)

        insert_button = QPushButton('Insert widget')
        insert_button.clicked.connect(self.insert_widget)

        remove_button = QPushButton('Remove widget')
        remove_button.clicked.connect(self.remove_widget)

        self.stacked_w.currentChanged[int].connect(self.on_current_changed)
        self.stacked_w.widgetRemoved[int].connect(self.on_widget_removed)

        self.num_widgets_label = QLabel('{} widget(s)'.format(self.count))
        self.num_widgets_label.setAlignment(Qt.AlignCenter)

        layout = QVBoxLayout()
        layout.addWidget(self.combobox)
        layout.addWidget(self.stacked_w)
        layout.addStretch()
        layout.addWidget(add_button)
        layout.addWidget(insert_button)
        layout.addWidget(remove_button)
        layout.addWidget(self.num_widgets_label)
        self.setLayout(layout)

    def on_widget_removed(self, index):
        print("[SIG] Widget <%s> Removed!" % index)

    def on_current_changed(self, index):
        print("[SIG] Current Widget changed to <%s>" % index)

    def update_num_widgets(self, number):
        self.num_widgets_label.setText('{} widget(s)'.format(number))

    def add_widget(self):
        pag = self.count + 1
        index = self.stacked_w.addWidget(QLabel('Content {}'.format(pag)))
        self.combobox.addItem('Page {}'.format(pag))
        self.combobox.setCurrentIndex(index)
        self.stacked_w.setCurrentIndex(index)
        self.count += 1
        self.update_num_widgets(self.stacked_w.count())

    def insert_widget(self):
        pag = self.count + 1
        index = self.stacked_w.insertWidget(0, QLabel('Content {}'.format(pag)))
        self.combobox.insertItem(0, 'Page {}'.format(pag))
        self.combobox.setCurrentIndex(index)
        self.stacked_w.setCurrentIndex(index)
        self.count += 1
        self.update_num_widgets(self.stacked_w.count())

    def remove_widget(self):
        widget = self.stacked_w.currentWidget()
        if widget:
            self.combobox.removeItem(self.stacked_w.indexOf(widget))
            self.stacked_w.removeWidget(widget)
            self.update_num_widgets(self.stacked_w.count())


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