Home > PyQt5, python > PyQt5: QDialog

PyQt5: QDialog

29 Aprile 2019

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: ,
I commenti sono chiusi.