Il modulo Qt Widget integra un sistema di styling mediante un'API che mima il comportamento dei fogli di stile per HTML.
QSS (Qt StyleSheet) è un linguaggio ispirato a CSS introdotto a partire dalla versione 4.2 che si applica alla formattazione di widgets. Poiché consente di ridefinire il look&feel dell'interfaccia, esso è di grande utilità in ambito embedded, ma si applica allo stesso modo alle applicazioni sviluppate per gli ambienti desktop supportati da Qt.
La sintassi per un elemento di style in QSS è la seguente:
selettore1,
selettore2,
...
selettoreN {
attributo1: valore;
attributo2: valore;
...
attributoN: valore;
}
I selettori hanno un ruolo equivalente a quello delle classi CSS, consentendo di identificare il set di elementi cui devono essere applicati uno o più attributi.
I selettori sono generalmente tipi di widget, ad esempio QLabel
o QPushButton
, mentre gli attributi si riferiscono a proprietà specifiche come il colore o l'immagine di sfondo, il bordo, il colore del testo. Ad esempio, il listato QSS seguente ridefinisce molteplici caratteristiche di una widget di tipo QLabel
:
QLabel {
background-color: yellow; /* colore dello sfondo */
color: magenta; /* colore del testo */
border: 2px solid cyan; /* colore e spessore del bordo in pixel */
border-radius: 8px; /* raggio di curvatura in pixel per gli angoli */
}
Il foglio di stile può essere applicato modificando direttamente la proprietà stylesheet
in Qt Designer per ogni widget, oppure via codice invocando lo slot pubblico setStyleSheet(const QString &styleSheet)
della classe base QWidget.
Inoltre, il foglio di stile applicato a widget che fungono da elementi contenitore per altre widget come finestre, dialog o groupbox, viene applicato anche alle widget ivi contenute. È quindi possibile generalizzare l'aspetto di una categoria di widget nel contesto del suo elemento contenitore, di modo che tutte le widget dello stesso tipo abbiamo esattamente la stessa resa grafica senza dovere applicare esplicitamente il medesimo foglio di stile ad ogni singolo elemento dell'interfaccia.
Ad esempio, applicare il foglio di stile riportato sopra ad una QMainWindow
avrà come effetto quello di propagare lo stile a tutte le widget di tipo QLabel
in essa contenute direttamente o annidate in altre widget contenitore come QGroupBox
e QFrame
.
Selettori complessi
Il meccanismo di propagazione assicura consistenza e semplicità per la definizione di proprietà grafiche che interessano molteplici elementi dell'interfaccia. Tuttavia, a volte è necessario restringere l'applicazione di uno stile ad alcuni specifici elementi dell'interfaccia, o specificare stili diversi per elementi dello stesso tipo. In questi casi è possibile applicare regole di pattern matching ai selettori sul modello della sintassi introdotta con CSS:
- Selettore universale
*
: assegnale le proprietà a tutte le widget. - Selettore di tipo
NomeClasse
: assegna gli attributi solo a widget che corrispondono ad un determinato tipo base e ai tipi derivati. Ad esempio, usando come tipoQWidget
otteniamo lo stesso comportamento del selettore universale, mentre conQAbstractButton
restringiamo l'applicazione di un insieme di attributi ai soli tipi derivatiQCheckBox
,QPushButton
,QRadioButton
eQToolButton
. - Selettore di proprietà
NomeClasse[NomeProprietà="valore"]
: consente di applicare un set di attributi soltanto alle istanze del tipo NomeClasse la cui proprietà NomeProprietà ha il valore desiderato. Affinché questo selettore funzioni la proprietà in questione deve essere convertibile al tipoQString
. Ad esempio, il selettoreQPushButton[flat="false"]
restringe l'applicazione di un insieme di attributi alle sole istanze diQPushButton
che la cui proprietà booleanaflat
ha valorefalse
. - Selettore di classe
.NomeClasse
: assegna gli attributi solo a widget che corrispondono ad un determinato tipo base ma non ai tipi derivati, a differenza del selettore di tipo. - Selettore basato su identificativo
NomeClasse#nomeIstanza
: assegna un insieme di attributi alle sole istanze della classe NomeClasse il cui nome è esattamente uguale a nomeIstanza. Occorre notare che quando si creano le interfacce mediante Qt Designer, ad ogni widget viene assegnato un nome univoco per evitare conflitti nella connessione automatica tra eventi e slot realizzata da moc. Pertanto in questo caso, tale selettore può essere usato per definire comportamenti specifici per singoli elementi dell'interfaccia. Quando l'interfaccia, o parte di essa viene generata invocando direttamente le API di Qt per la costruzione di layout e widget, per applicare questo selettore è necessario assegnare un nome alle widget invocando la funzione membrosetObjectName(QString)
. - Selettore di discendenza generica
NomeClasseContenitore NomeClasse
: applica un set di attributi a tutte le istanze di tipo NomeClasse che sono contenute direttamente o indirettamente in istanze di tipo NomeClasseContenitore. Si noti che la relazione di appartenenza tra widget in Qt si esplicita mediante l'uso dell'attributoparent
che viene passato come argomento al costruttore o mediante l'apposito metodosetParent(QWidget*)
. - Selettore di discendenza diretta
NomeClasseContenitore > NomeClasse
: applica un set di attributi a tutte le istanze di tipo NomeClasse che sono contenute direttamente in istanze di tipo NomeClasseContenitore, ma non a widget di tipo NomeClasse annidate in altri elementi contenuti in istanze di tipo NomeClasseContenitore.
Tipologie di attributi
L'insieme di attributi applicabili dipende dal tipo di widget. Alcune proprietà generiche come il colore del testo e dello sfondo, margini e spaziature sono universalmente applicabili a tutte le widget. Alcune widget hanno proprietà peculiari con attributi specifici, ad esempio le viste ad albero o in forma tabulare hanno attributi specifici per definire l'alternanza dei colori delle righe. Per una lista completa ed esaustiva si rimanda alla apposita sezione della documentazione ufficiale.
Pseudo-stati e sub-controls
Lo stile di alcune widget può essere determinato anche dallo stato. Ad esempio, un pulsante può adottare stili differenti per evidenziarsi al passaggio del mouse o quando l'utente vi clicca sopra. Qt definisce una serie di pseudo-stati che consentono di definire lo stile di molte widget senza ricorrere all'uso del selettore di proprietà. La sintassi di QSS per la definizione di pseudo-stati è la seguente:
selettore:pseudostato {
attributo1: valore;
attributo2: valore;
...
attributoN: valore;
}
Ad esempio, il listato QSS seguente restringe lo stile applicabile a QLineEdit
alle sole istanze il cui valore della proprietà read-only
è false
:
QLineEdit:read-only {
background-color: yellow;
}
In alcuni casi gli pseudo-stati possono essere concatenati mediante l'uso degli operatori logici AND e OR. Ad esempio, nel listato seguente si impone che il testo delle istanze di tipo QCheckBox
si colori di verde al passaggio del mouse oppure quando compare il segno di spunta.
/*
* "," equivale all'operatore OR
*/
QCheckBox:hover,
QCheckBox:checked {
color: green
}
In questo altro esempio, invece, si impone che il testo sia di colore verde solo quando entrambe le condizioni sono soddisfatte, cioè in presenza del segno di spunta e al passaggio del mouse.
/*
* ":" equivale all'operatore AND
*/
QCheckBox:hover:checked {
color: green
}
Priorità e risoluzione di conflitti
La definizione di fogli di stile prevede la possibilità che sorgano conflitti quando più regole di stile ridefiniscono il valore delle stesse proprietà con valori differenti. In questi casi vigono i seguenti criteri per la risoluzione dei conflitti:
-
Il livello di specializzazione: i selettori complessi hanno generalmente la priorità su quelli semplici. Ad esempio un selettore basato su identificativo è più specifico di uno generico basato sul tipo, quindi in caso di conflitto esso prevale sul primo come nel caso seguente:
/* Tutte le istanze di QLabel con nome greenLabel saranno verdi, * anche se la regola generale prevede il colore bianco */ QLabel#greenLabel { background-color: green } QLabel { background-color: white }
-
L'ordine: le regole di stile vengono applicate seguendo l'ordine di definizione, pertanto, a parità di livello di specializzazione, la regola più recente sovrascrive le precedenti. Ad esempio, nel caso seguente si vuole che il colore di sfondo dei pulsanti in genere sia rosso, con eccezione dei soli elementi di tipo
QCheckBox
che devono essere gialli:QAbstractButton { background-color: red } QCheckBox { background-color: yellow }
Invertire l'ordine delle due regole in questo caso comporterebbe la perdita della caratterizzazione riservata alle sole istanze di tipoQCheckBox
. Poiché esso è un tipo derivato daQAbstractButton
, la sua caratterizzazione sarebbe sovrascritta dalla regola più generica.
Definizione di colori nei fogli di stile
L'uso dei colori non è limitato ai soli colori di base definiti dalla enum Qt::GlobalColor
. I colori possono essere valori arbitrari ed esistono anche attributi particolari per la definizione di gradienti. Ad esempio, nel listato seguente si assegna una valore RGB con trasparenza:
QLabel { background-color: rgba(255, 0, 0, 127) }
Inoltre, ogni tema predefinito definisce una palette di colori secondo il modello dalla classe QPalette
. È possibile quindi usare valori della palette del tema come ad esempio riportato nel listato seguente:
QLabel { background-color: palette(dark); }
I valori ammissibili per la funzione palette sono riportati nella documentazione ufficiale.
Relazioni tra fogli di stile, classe QStyle e temi predefiniti
La classe QStyle
è una classe astratta responsabile dell'applicazione delle regole definite nel foglio di stile. Il framework predispone una serie di stili predefiniti che si integrano con gli vari ambienti desktop dei sistemi supportati. Inoltre, fornisce un'alternativa generica rappresentata dallo stile Fusion per garantire il medesimo look&feel dell'applicazione a prescindere dal sistema ospite.
In linea teorica è possibile definire un proprio stile, usando la classe QStyle
come classe base e ridefinendo opportunamente i metodi membro. Tuttavia, allo stato attuale il supporto ai fogli di stile non è esteso alle sottoclassi derivate da QStyle
che non facciano parte del framework.
Inoltre, l'implementazione della specifica QSS varia a seconda dello stile predefinito del sistema. Ad oggi, lo stile che implementa la specifica nel modo più completo è Fusion. Pertanto, qualora si voglia realizzare una personalizzazione completa dell'interfaccia è bene specificare l'utilizzo dello stile Fusion come tema base al fine di garantire una maggiore consistenza per le regole di stile su tutti gli ambienti desktop target della nostra applicazione.
Il listato seguente mostra come applicare il tema base Fusion in fase di inizializzazione dell'applicazione:
#include "mainwindow.h"
#include <QApplication>
#include <QStyleFactory>
int main(int argc, char *argv[])
{
QApplication::setStyle(QStyleFactory::create("Fusion")); // <-- tema Fusion
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
Nelle lezioni successive vedremo anche che è possibile definire un foglio di stile come risorsa binaria dell'applicazione di modo da rendere più semplice il deployment dell'applicazione e il supporto a temi multipli.