In questo articolo parleremo della libreria grafica PyQt versione 6, interfaccia del toolkit Qt per il linguaggio di programmazione Python, usata per la creazione di GUI (Graphical User Interface che tradotto significa semplicemente interfaccia grafica utente). Grazie ad oltre 1000 librerie sempre in continua evoluzione messe a disposizione dallo staff di sviluppo, si ha la possibilità di creare applicazioni di vario tipo e vari usi, dalle più semplici alle più complesse, dando la possibilità di assolvere alla grande moltitudine dei compiti richiesti dalle moderne tecnologie esistenti oggi. E' in grado di girare su molte se non tutte le piattaforme in uso, dal desktop al mobile fino ad arrivare ai sistemi embedded mantenendo sempre un alto grado di stabilità. La semplicità nell'implementare il gran numero di funzionalità, la completezza delle librerie, la grande versatilità di cui dispone, il largo campo d'impiego, il tutto accompagnato da un'ottima guida utente la rendono una delle librerie multipiattaforma più usata al mondo.
Introduzione
A 7 anni di distanza dall'uscita della versione 5, le richieste delle nuove tecnologie hanno fatto sì che ne venisse sviluppata una nuova, fatta per soddisfare le esigenze dei nuovi supporti che il mercato ci propone oggi e che ci proporrà in futuro in questo continuo evolversi di sistemi, mantenendo comunque un alto livello di compatibilità con la precedente ma dando un più ampio grado di libertà per poter implementare nuove caratteristiche. Tra i punti di forza troviamo compatibilità, manutenibilità, stabilità, scalabilità, un alto grado di astrazione e un supporto guida ampio ed estremamente dettagliato, caratteristiche che oltre ad una estrema semplicità di sviluppo hanno portato ad appassionare oltre un milione di sviluppatori in tutto il mondo.
Caratteristiche tecniche
Compatibilità: La libreria PyQt6 permette di creare applicazioni multipiattaforma grazie al suo alto livello di astrazione, venendo supportata da un vasto numero di sistemi operativi tipo Windows, Linux, Unix, Mac, Darwin, Android, iOS e sistemi embedded, in poche parole il codice lo si scrive una volta e lo si porta dove si vuole.
Manutenibilità e stabilità: La semplicità d'uso permette di manutenere grandi quantità di codice con un minimo sforzo, avendo comunque un'ottima stabilità nel funzionamento.
Scalabilità: Con la libreria PyQt6 possiamo spaziare dalla creazione della più semplice applicazione alla realizzazione del più complesso sistema grazie ad una vasta libreria che ci consente di sfruttare funzionalità come strumenti per interfacce grafiche, reti, servizi di posizionamento e geolocalizzazione, database sql, SVG, XML, supporto 2D e 3D, NFC, bluetooth, grafici, espressioni regolari, thread, una vasta gamma di widget e molto altro ancora. Esiste anche un browser web su base Chromium molto funzionale.
Semplicità: Il binomio Python-Qt permette di sfruttare l'estrema semplicità e leggerezza del linguaggio di programmazione con l'estrema potenza del framework, dando la possibilità di scrivere codice anche di elevata complessità con pochi e semplici passaggi. Un esempio può essere la realizzazione di un'interfaccia grafica con PyQt6 in un programma scritto in C++, ciò permette di non appesantire ulteriormente il software già gravoso del complesso C++.
Documentazione: La documentazione che viene fornita dal supporto tecnico è chiara e ben dettagliata, è possibile comunque consultare anche la guida di Qt dato che l'implementazione è molto simile, soltanto che quest'ultima risulta essere ancora più ampia della prima.
Astrazione: La grande complessità della libreria nella sua struttura e la facilità nell'essere utilizzata, con la caratteristica di essere multipiattaforma, la compatibilità con la versione precedente è data da un alto livello di astrazione, il programmatore scrive applicazioni modulari con estrema semplicità, senza preoccuparsi delle differenze tra supporti e sistemi operativi. Un esempio può essere l'implementazione del modulo 3D che nella versione 5 era basato sul tool grafico openGL in tutte le versioni desktop, con la versione 6 le cose cambiano, Windows alla versione 6 si basa sulle Directx, Linux su Vulkan e Mac su Metal, con il risultato di tanta differenza nella parte strutturale della libreria ma tutto messo completamente in ombra al programmatore in fase d'implementazione.
Struttura
La libreria PyQt non è altro che un collegamento al framework Qt incorporandone tutte le funzionalità, compatibile sia per Python2 che per Python3. Ogni versione di PyQt è abbinata ad una versione di Qt, esempio PyQt4 è abbinata a Qt4 (ormai deprecata), PyQt5 è abbinata a Qt5, PyQt6 è abbinata a Qt6, non è possibile usare una versione di PyQt con una di Qt non corrispondente. Quando si installa una versione di PyQt si installa anche direttamente la sua versione abbinata di Qt e da quel momento si potrà accedere a tutti i moduli del framework. Per facilitare la vita dei programmatori la versione 6 è rimasta almeno per il momento compatibile con la versione 5, per essere più precisi alla versione 5.125 che è quella con minori differenze con la 6.
Per scrivere una GUI (Graphical User Interface) ci sono due modi:
- Da zero, utilizzando un normalissimo editor di testo (Atom, Pycharm, Visual Studio, Komodo, Sublime e tanti altri).
- Usare un editor grafico chiamato Qt designer che permette di creare applicazioni tramite il sistema drag and drop, viene fornito direttamente con Qt, mette a disposizione vari widget dando la possibilità di personalizzarli e crearne anche dei nuovi. Esiste anche la possibilità di vedere un'anteprima in real time del progetto in creazione.
Un'applicazione GUI può essere costituita da una finestra principale con una o più finestre di dialogo. Una piccola applicazione GUI di solito è costituita da almeno una finestra di dialogo. Un'applicazione di dialogo contiene pulsanti, ma non contiene una barra dei menu, una barra degli strumenti, una barra di stato o un widget centrale, mentre un'applicazione della finestra principale normalmente ha tutti questi elementi. Le finestre di dialogo possono essere di due tipi, modale e non modale. Nel tipo modale la finestra di dialogo impedisce all'utente di interagire con altre parti dell'applicazione, la finestra di dialogo è l'unica parte dell'applicazione con cui l'utente può interagire, finché la finestra di dialogo non viene chiusa, non è possibile accedere ad altre parti dell'applicazione. Il tipo non modale invece è l'opposto di una finestra di dialogo modale, quando una finestra di dialogo non modale è attiva, l'utente è libero di interagire con la finestra di dialogo e con il resto dell'applicazione.
Accenno al toolkit Qt
Qt è un toolkit widget gratuito e open source per la realizzazione di applicazioni GUI multipiattaforma, creato nel 1991 dall'azienda norvegese Trolltech (al tempo Qt software) che fornisce strumenti e librerie per lo sviluppo software. Viene usato per la creazione di interfacce grafiche multipiattaforma tramite l'uso di widget, scritto in C++ con un estensivo uso del preprocessore C per arricchirne il linguaggio. Esistono anche varie interfacce per essere implementato con altri linguaggi tipo Python, php, java ed altri ancora. Il toolkit Qt è uno dei framework più utilizzati al mondo.
Accenno a Python
Python è un linguaggio di alto livello, di scripting, dichiarativo con un forte supporto per la programmazione orientata ad oggetti, scritto in C, è molto popolare per la sua semplicità, la sua versatilità e il suo vasto campo di impieghi, dal web ai giochi, dall'embedded ai big data, fino alla riparazione e all'estensione di software scritti in C e Java.
Installazione
L'installazione della libreria PyQt6 è composta da pochi semplici passaggi, ricordiamo che una volta installata verrà anche installata nel sistema la versione di Qt corrispondente e vi si potrà quindi accedere a tutte le funzionalità disponibili usando codice Python. L'esempio esposto verrà fatto su un ambiente Mac, c'è comunque poca differenza con l'installazione fatta su altri sistemi operativi.
- Per prima cosa bisogna aprire una shell
- Si crea una cartella di progetto con il comando mkdir mioProgettoPyQt
- Si accede alla cartella appena creata cd mioProgettoPyQt
- Si crea un ambiente virtuale digitando il comando python -m venv venv
- Adesso bisogna attivare l'ambiente virtuale source venv/bin/activate
- Ed ora si può installare PyQt6 con l'installatore di pacchetti pip digitando pip install PyQt6
Ed ecco installata la libreria PyQt6 in un ambiente virtuale Python su un sistema Mac, adesso è pronta per essere usata in tutta la sua potenza. Piccola nota, è stato creato un ambiente virtuale per non sporcare l'installazione originale di Python, in questo caso avremmo accesso alla libreria soltanto da questo ambiente, nulla vieta però di poterla installare nell'ambiente principale una volta per tutte, e averla sempre a disposizione, per fare ciò basta saltare i punti 2,3,4,5 ed eseguire solamente il punto 6 sopracitato.
Esempi pratici
1. Creazione di una semplice finestra: Adesso inizieremo a mettere mano al codice creando una semplice finestra. Dopo aver installato tutti gli strumenti necessari elencati in precedenza quali Python, PyQt6, un editor di testo (per gli esempi descritti in questo articolo è stato usato pycharm), si può procedere con il creare un nuovo file che verrà chiamato per esempio main.py dove vi inseriremo il seguente codice:
from PyQt6.QtWidgets import QApplication, QWidget import sys app = QApplication(sys.argv) finestra = QWidget() finestra.setWindowTitle("La mia prima finestra") finestra.show() sys.exit(app.exec())
Prima di spiegare il codice facciamo un breve accenno ad alcune delle principali librerie di PyQt6 che sono QtWidgets, QtCore e QtGui.
- QtWidgets contiene una vasta gamma di oggetti, adatti per la creazione delle interfacce grafiche tipo bottoni, label, dial, form, checkbox, etc.
- QtCore contiene le classi base di PyQt, quali le astrazioni per le animazioni, macchine a stati, thread, files, memoria condivisa, espressioni regolari.
- QtGui fornisce un insieme di classi per la gestione delle finestre, immagini, font e testi da usare congiuntamente al modulo “QtWidgets”.
Nella libreria QtWidgets è presente la classe QApplication. Ogni applicazione ha bisogno di uno e solo un oggetto QApplication per funzionare. Questo oggetto contiene il ciclo degli eventi dell'applicazione, il ciclo principale che governa tutte le interazioni dell'utente con la GUI.
Vediamo ora il codice per istanziare le classi necessarie.
app = QApplication(sys.argv) #gestione eventi
e
finestra = QWidget() #creo la finestra
Poi con i metodi setWindowTitle e show viene rispettivamente dato un titolo e creata la finestra, che lanciando poi sys.exit(app.exec()) viene mandata in esecuzione. La Figura 1 mostra come viene rappresentata la stessa finestra nei tre principali sistemi operativi Windows, Mac_OS e Linux. Da notare la semplicità usata per creare gli oggetti e la capacità che si ha ad adattarsi alle diverse piattaforme, tenendo sempre lo stile degli ambienti su cui girano.
2. Layout verticale: Per organizzare insieme i widget in Qt si usano i layout. Ci sono 4 layout di base disponibili, essi sono QVBoxLayout che dispone gli elementi in maniera verticale, QHBoxLayout che dispone gli elementi in maniera orizzontale, QGridLayout che dispone gli elementi tramite una griglia e QStackedLayout che ti permette di posizionare i widget uno sopra l'altro all'interno dello stesso spazio, ma mostrando solo un layout alla volta.
Vediamo ora QVBoxLayout, possiamo partire prendendo l'esempio scritto in precedenza e aggiungervi un bottone e una label. Però, prima di fare ciò dobbiamo rifattorizzare il codice, creando una classe che contenga le proprietà e i metodi della GUI per mantenere la stesura del programma più ordinata.
from PyQt6.QtWidgets import QApplication, QWidget import sys #creo una classe dove posiziono gli elementi class Window(QWidget): def __init__(self): super().__init__() #creo la finestra principale self.setWindowTitle("esempio pratico PyQt6") #creo un' istanza della classe QApplication app = QApplication(sys.argv) #creo un' istanza della classe Window sopra creata window = Window() #avvio la GUI window.show() sys.exit(app.exec())
Adesso possiamo procedere con la costruzione del layout:
from PyQt6.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout from PyQt6.QtCore import Qt, QSize import sys #creo una classe dove posiziono gli elementi class Window(QWidget): def __init__(self): super().__init__() #creo la finestra principale self.setWindowTitle("esempio pratico PyQt6") #imposto posizione e dimensioni della finestra self.setFixedSize(QSize(300, 150)) #creo gli oggetti btn1 = QPushButton("Click me!") self.label = QLabel("Hello!") #creo un contenitore verticale layout = QVBoxLayout() #inserisco il bottone e la label nel contenitore layout.addWidget(btn1) layout.addWidget(self.label) #allineo al centro la label self.label.setAlignment(Qt.AlignmentFlag.AlignCenter) #lo inserisco nel layout self.setLayout(layout) #creo un' istanza della classe QApplication app = QApplication(sys.argv) #creo un' istanza della classe Window sopra creata window = Window() #avvio la GUI window.show() sys.exit(app.exec())
Il risultato finale sarà quello mostrato in Figura 2.
Nel blocco di codice scritto in precedenza si nota l'aggiunta delle classi Qt e QSize che servono per gestire la posizione e le dimensioni dei vari componenti. Visto che nell'esempio abbiamo usato un bottone, approfittiamo ora per introdurre la gestione degli eventi.
Segnali e slot
Per segnali è inteso come il richiamo all'evento e per slot viene inteso il metodo richiamato dal segnale che genera l'azione. Il metodo che richiama lo slot è clicked.connect(), dove all'interno delle parentesi viene richiamato il metodo che rappresenta lo slot. Per esempio la dicitura sarà la seguente:
btn1.clicked.connect(self.azione)
Applicando il comando setCheckable(True) si può trattare il bottone come se fosse uno switch (interruttore nella vita reale) restituendo True o False a seconda che sia nello stato di premuto o no.
btn1.setCheckable(True) btn1.clicked.connect(self.azione) def azione(self, cliccato): print(cliccato)
Per passare uno o più parametri si possono usare le lambda.
btn1.clicked.connect(lambda:self.azione("Hello World!"))
Adesso vediamo un esempio pratico, prendendo e modificando il codice fatto in precedenza verrà incrementato il dato valore di uno ad ogni pressione del bottone.
from PyQt6.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout from PyQt6.QtCore import Qt, QSize import sys #creo una classe dove posiziono gli elementi class Window(QWidget): def __init__(self): super().__init__() self.valore = 0 #creo la finestra principale self.setWindowTitle("esempio pratico PyQt6") #imposto posizione e dimensioni della finestra self.setFixedSize(QSize(300, 150)) #creo gli oggetti btn1 = QPushButton("Click me!") self.label = QLabel(f"incremento il valore di 1: {str(self.valore)}") #creo un contenitore verticale layout = QVBoxLayout() #inserisco il bottone e la label nel contenitore layout.addWidget(btn1) layout.addWidget(self.label) #allineo al centro la label self.label.setAlignment(Qt.AlignmentFlag.AlignCenter) #lo inserisco nel layout self.setLayout(layout) btn1.clicked.connect(self.azione) def azione(self): self.valore += 1 self.label.setText(f"incremento il valore di 1: {str(self.valore)}") #creo un' istanza della classe QApplication app = QApplication(sys.argv) #creo un' istanza della classe Window sopra creata window = Window() #avvio la GUI window.show() sys.exit(app.exec())
3. Layout orizzontale: Per organizzare il layout in maniera orizzontale bisogna usare la classe QHBoxLayout, per rappresentare questo esempio verrà usato un widget personalizzato, che creeremo nel seguente modo:
rom PyQt6.QtWidgets import QApplication, QWidget, QLabel, QHBoxLayout from PyQt6.QtCore import Qt, QSize from PyQt6.QtGui import QColor, QPalette import sys class myObj(QWidget): def __init__(self,colore): super().__init__() self.setAutoFillBackground(True) self.setFixedSize(QSize(40, 20)) self.palette = self.palette() self.palette.setColor(QPalette.ColorRole.Window, QColor(colore)) self.setPalette(self.palette)
La classe myObj ci restituisce un semplice rettangolo colorato, il cui colore verrà deciso in fase di inizializzazione dell'oggetto come mostrato nel seguente listato.
rettangoloVerde = myObj("green")
Adesso andiamo a completare il codice mettendo in fila orizzontale tre rettangoli colorati in maniera diversa, che avranno come modello la classe myObj.
from PyQt6.QtWidgets import QApplication, QWidget, QLabel, QHBoxLayout from PyQt6.QtCore import Qt, QSize from PyQt6.QtGui import QColor, QPalette import sys class myObj(QWidget): def __init__(self,colore): super().__init__() self.setAutoFillBackground(True) self.setFixedSize(QSize(40, 20)) self.palette = self.palette() self.palette.setColor(QPalette.ColorRole.Window, QColor(colore)) self.setPalette(self.palette) #creo una classe dove posiziono gli elementi class Window(QWidget): def __init__(self): super().__init__() self.valore = 0 #creo la finestra principale self.setWindowTitle("esempio pratico PyQt6") #imposto posizione e dimensioni della finestra self.setFixedSize(QSize(300, 150)) #istanzio widget personale rosso = myObj("red") giallo = myObj("yellow") verde = myObj("green") layout = QHBoxLayout() #creo un contenitore orizzontale #inserisco nel layout layout.addWidget(rosso) layout.addWidget(giallo) layout.addWidget(verde) #inserisco il layout nella finestra self.setLayout(layout) #creo un' istanza della classe QApplication app = QApplication(sys.argv) #creo un' istanza della classe Window sopra creata window = Window() #avvio la GUI window.show() sys.exit(app.exec())
In Figura 3 si può vedere il risultato del codice scritto sopra.
4. Layout nidificati: Per interfacce grafiche più complesse è possibile nidificare i layout l'uno nell'altro usando il metodo addLayout() che a breve vedremo come utilizzare. Nell'esempio seguente andremo a gestire con una GUI una schedina Arduino UNO tramite seriale, accendendo e spegnendo dei semplici diodi LED, andremo inoltre a variare il numero di giri di un toy Motor DC. Questo verrà fatto utilizzando tutto ciò che è stato esposto negli esempi precedenti, con l'aggiunta dell'utilizzo dei layout nidificati. La parte pratica è mostrata in Figura 3.1. [...]
ATTENZIONE: quello che hai appena letto è solo un estratto, l'Articolo Tecnico completo è composto da ben 4223 parole ed è riservato agli ABBONATI. Con l'Abbonamento avrai anche accesso a tutti gli altri Articoli Tecnici che potrai leggere in formato PDF per un anno. ABBONATI ORA, è semplice e sicuro.