I. L'article original

Qt Quarterly est une revue trimestrielle électronique proposée par Nokia à destination des développeurs et utilisateurs de Qt. Vous pouvez trouver les versions originales.

Nokia, Qt, Qt Quarterly et leurs logos sont des marques déposées de Nokia Corporation en Finlande et/ou dans les autres pays. Les autres marques déposées sont détenues par leurs propriétaires respectifs.

Cet article est la traduction de l'article Custom Looks using Qt 4.2 Style Sheets paru dans la Qt Quarterly Issue 20.

Cet article est une traduction d'un des tutoriels écrits par Nokia Corporation and/or its subsidiary(-ies) inclus dans la documentation de Qt, en anglais. Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia.

II. Que sont les feuilles de style de Qt ?

Les feuilles de style de Qt sont fortement inspirées par les feuilles de styles HML (CSS) mais adaptées pour les widgets. Vous pouvez définir une feuille de style sur un widget enfant individuel, sur une fenêtre entière, ou même sur une application complète en appelant QWidget::setStyleSheet() ou bien QApplication::setStyleSheet().

Derrière cela, les feuilles de style sont implémentées par une sous-classe particulière de QStyle nommée QStyleSheetStyle qui agit en tant que wrapper pour le style spécifique de la plateforme. Ce modèle spécial applique à ce dernier toutes les personnalisations spécifiées par l'utilisation des feuilles de style.

Qt 4.2 inclut un exemple de feuilles de style qui vous laisse vous expérimenter avec les feuilles de style. Il vient avec deux styles : Coffee et Pagefold.

Image non disponible
Style Coffee

Le style Coffee montré ci-dessus personnalise le style des boutons, des cadres, des libellés et des bulles d'aide mais laisse le style à la base (par exemple, celui de Windows XP) dessiner les checkboxes, les comboboxes et les boutons radio.

Image non disponible
Style Pagefold

En revanche, Pagefold redéfinit entièrement l'apparence de tous les contrôles utilisés dans la boîte de dialogue, ayant pour résultat distinctif une apparence indépendante à la plateforme.

Cet article se veut représenter une introduction rapide aux feuilles de style. Pour une description complète de la syntaxe des feuilles de style de Qt, veuillez vous référer à la documentation en ligne ou explorez l'exemple stylesheet, situé dans le dossier examples/widgets de Qt 4.2.

III. Syntaxe basique des feuilles de style

Les règles de syntaxe des feuilles de style de Qt sont la plupart du temps les mêmes que celle de CSS. Si vous connaissez déjà CSS, vous pouvez probablement passer à la partie suivante.

Une feuille de style consiste en une séquence de règles de style. Chaque règle à la forme suivante :

 
Sélectionnez
selector { attribute: value }

La partie selector est typiquement un nom de classe (par exemple, QComboBox), mais d'autres syntaxes sont possibles. La partie attribute est le nom de l'attribut d'une feuille de style, et value est la valeur assignée à l'attribut.

Par commodité, nous pouvons utiliser la notation sténographique :

 
Sélectionnez
selector1, selector2, ..., selectorM {
	attribute1: value1;
	attribute2: value2;
	...
	attributeN: valueN;
}

Qui définit simultanément N attributs sur tout widget capturant n'importe lequel des M sélecteurs. Par exemple,

 
Sélectionnez
QCheckBox, QComboBox, QSpinBox {
	color: red;
	background-color: white;
	font: bold;
}

Définit les couleurs de premier plan et d'arrière-plan ainsi que les polices de toutes les QCheckBox, QComboBox et QSpinBox.

La documentation en ligne liste les attributs supportés pour chaque type de widget. Dans cet article, nous allons nous concentrer sur les plus communs.

IV. Le modèle de boîte

Lors de l'utilisation des feuilles de style, tout widget est traité en tant que boîte avec quatre rectangles concentriques : le rectangle de marge (extérieure), le rectangle de bordure, le rectangle de marge intérieure et le rectangle de contenu. Pour un simple widget à tiret ; c'est-à-dire un widget avec 0 pixel de marge, d'épaisseur de bordure et de marge intérieure, ces quatre rectangles coïncident exactement.

Image non disponible
Modèle de boîte

La zone de marge se situe à l'extérieur de la bordure et est toujours transparente. La bordure fournit un cadre au widget. De multiples modèles intégrés de style, incluant inset, outset et ridge, sont disponibles par la définition de l'attribut border-style. La zone de marge intérieure fournit un espace entre la bordure et la zone de contenu.

V. Premiers plans et arrière-plans

La couleur de premier plan (la couleur utilisée pour dessiner le texte) du widget est spécifiée en utilisant l'attribut color. La couleur d'arrière-plan, spécifiée par l'utilisation de background-color, remplit le rectangle de marge intérieure du widget.

L'image d'arrière-plan, spécifiée par background-image, est dessinée dans la zone rectangulaire (rectangle de marge, de bordure, de contenu ou de marge intérieure) spécifiée par background-origin. L'alignement et la mosaïque de l'image d'arrière-plan à l'intérieur de la zone rectangulaire peuvent être modifiés respectivement à l'aide des attributs background-position et background-repeat.

Si nous spécifions une image avec chanel alpha (pour une semi-transparence), la couleur spécifiée en utilisant l'attribut background-color brillera à travers. Cela peut être utilisé pour réutiliser la même image d'arrière-plan dans des contextes différents.

Image non disponible
Cadre stylisé

L'exemple qui suit illustre l'usage des attributs que nous avons vus jusqu'ici :

 
Sélectionnez
QFrame {
	margin: 10px;
	border: 2px solid green;
	padding: 20px;
 
	background-color: gray;
	background-image: url(qt.png);
	background-position: top right;
	background-origin: content;
	background-repeat: none;
}

Dans cet exemple, les valeurs spécifiées pour la bordure, la marge et la marge intérieure s'appliquent uniformément à chacun des quatre côtés de tout QFrame présent dans l'application. L'attribut margin peut si vous le souhaitez être utilisé dans le but de spécifier des valeurs différentes pour le haut, la droite, le bas et la gauche.

 
Sélectionnez
QFrame {
	margin: 14px 18px 20px 18px;
}

Alternativement, nous pouvons utiliser les attributs margin-top, margin-right, margin-bottom et margin-left :

 
Sélectionnez
QFrame {
	margin-top: 14px;
	margin-right: 18px;
	margin-bottom: 20px;
	margin-left: 18px;
}

Bien que nous ayons utilisé QFrame dans les exemples ci-dessus, nous pouvons avoir spécifié tout widget de Qt supportant le model de boîte, incluant QCheckBox, QLabel, QLineEdit, QListView, QMenu, QPushButton, QTextEdit, et QToolTip.

VI. Créer des styles évolutifs

Par défaut, les images de fond indiquées en utilisant background-image sont répétées autant de fois que nécessaire, autant horizontalement que verticalement, afin de couvrir la zone de marges intérieures du widget (soit la zone située à l'intérieure de la bordure). Si nous voulons créer des arrière-plans qui seraient joliment mis à l'échelle des dimensions du widget, nous aurons besoin de spécifier une soi-disant "image de bordure".

Les images de bordures sont spécifiées en utilisant l'attribut border-image. Elles fournissent les bordures ainsi que l'arrière-plan du widget. Une image de bordure est découpée en neuf cellules, un petit peu comme un panneau de morpion :

Image non disponible
Image de bordure

Lors du remplissage de la bordure d'un widget, les quatre cellules de coin sont plus ou moins prises telles qu'elles, tandis que les cinq autres cellules sont soit étirées, soit répétées pour remplir l'espace disponible.

Lors de l'indication d'une image de bordure, nous devons spécifier les quatre "coupures" définissant les neuf cellules, en addition avec l'image de bordure elle-même. Nous devons aussi indiquer si nous voulons que les cellules n'étant pas des cellules de coin soient étirées ou répétées, et nous devons définir border-width de manière à ce que cela corresponde aux coupures (pour permettre l'évolutivité des coins).

Image non disponible
Boutons

En tant qu'exemple, voici la feuille de style nécessaire à reproduire le bouton ci-dessus (affiché sous quatre tailles différentes) :

 
Sélectionnez
QPushButton {
	border-width: 4px;
	border-image: url(button.png) 4 4 4 4 stretch stretch;
}

Comme dans le cas d'une image de fond, l'image de bordure peut détenir un chanel alpha qui se joigne avec la couleur d'arrière-plan.

VII. Contrôle des dimensions

Les attributs min-width et min-height peuvent être utilisés pour spécifier la longueur et la largeur minimale de la zone de contenu d'un widget. Ces valeurs affectent le minimumSizeHint() du widget en question qui sera honoré par le layout. Par exemple :

 
Sélectionnez
QPushButton {
	min-width: 68px;
	min-height: 28px;
}

Si l'attribut n'est pas spécifié, la largeur minimale est dérivée à partir du contenu du widget ainsi que du style actuel.

VIII. Gérer les pseudo-états

L'apparence des widgets peut être personnalisée pour des états d'interface utilisateur spécifiques en passant par des soi-disant pseudo-états dans les feuilles de style. Par exemple, nous pouvons préférer donner à un bouton un aspect creux lorsque celui-ci serait pressé par l'utilisateur en passant par une spécification du pseudo-état :pressed :

 
Sélectionnez
QPushButton {
	border: 2px outset green;
	background: gray;
}
 
QPushButton:pressed {
	border-style: inset;
}

Voici une liste des pseudo-états disponibles :

Pseudo-état Description
:checked Le widget est coché.
:disabled Le bouton n'est pas activé.
:enabled Le bouton est activé.
:focus Le widget détient le fucus d'entrée.
:hover La souris est en train de survoler le widget.
:indeterminate La case à cocher ou le bouton radio est partiellement coché.
:off Pour les widgets pouvant être basculés, cela s'applique au widgets à l'état "off".
:on Pour les widgets pouvant être basculés, cela s'applique au widgets à l'état "on". Cela s'applique aussi aux comboboxes ayant leur liste ouverte, et aux barres de menus ayant l'un de leurs menus ouvert.
:pressed Le widget est en train d'être pressé par l'utilisation de la souris.
:unchecked Le bouton n'est pas coché.

IX. Micro-personnalisation avec les sous-contrôles

Beaucoup de widgets contiennent des éléments embarqués, parfois appelés "sous-contrôles". Les flèches haut et bas d'une QSpinBox sont des exemples de sous-contrôle.

Les sous-contrôles peuvent être indiqués en utilisant :: (par exemple, QDateTimeEdit:: up-button). Les styliser est relativement similaire à styliser un widget. Ils suivent le model de boîte décrit plus haut (c'est-à-dire qu'ils peuvent avoir une border, un arrière-plan, etc…) et peuvent être combinés avec les pseudo-états (par exemple, QSpinBox::up-button:hover).

Le tableau ci-dessous liste les sous-contrôles disponibles aux auteurs de feuilles de style :

Sous-contrôle Description
::down-arrow La flèche vers le bas d'un QComboBox ou d'une QSpinBox.
::down-button Le bouton vers le bas d'une QSpinBox.
::drop-down Le bouton de déroulement vers le bas d'un QComboBox.
::indicator L'indicateur d'une QCheckBox, d'un QRadioButton ou d'une QGroupBox.
::item Un menu, une barre de menu ou un élément de barre d'état.
::menu-indicator L'indicateur de menu d'un QPushButton.
::title Le titre d'une QGroupBox.
::up-arrow La flèche vers le haut d'une QSpinBox.
::up-button Le bouton vers le bas d'une QSpinBox.

Les sous-contrôles peuvent être placés n'importe où dans la boîte du widget en utilisant subcontrol-position et subcontrol-origin. Le positionnement du sous-contrôle peut être relativement bien accordé par l'utilisation du positionnement relatif ou absolu. Le choix de la technique de positionnement dépend de si le sous-contrôle détient des dimensions fixées ou détient des dimensions dépendantes de la boîte du widget.

IX-A. Positionnement relatif

Le positionnement relatif est approprié pour les sous-contrôles ayant une taille fixée (spécifiée en utilisant les attributs width et height). Utilisant cette technique, le sous-contrôle peut être déplacé par l'utilisation d'un décalage depuis la position qui a été définie par l'utilisation de subcontrol-position et de subcontrol-origin. L'attribut left peut être utilisé dans le but de déplacer le sous-contrôle vers la droite et l'attribut top peut lui aussi être utilisé pour déplacer le sous-contrôle vers le bas. Par exemple :

 
Sélectionnez
QPushButton::menu-indicator {
	image: url(menu_indicator.png);
	width: 13px;
	height: 13px;
 
	subcontrol-origin: padding;
	subcontrol-position: bottom right;
}
Image non disponible
Sous-contrôle 1
Image non disponible
Sous-contrôle 2

Quand l'utilisateur presse un bouton, nous pouvons déplacer l'indicateur de menu depuis sa position originale de quelques pixels afin de simuler un appui sur le bouton :

 
Sélectionnez
QPushButton::menu-indicator:pressed {
	position: relative;
	top: 2px;
	left: 2px;
}

IX-B. Positionnement absolu

Le positionnement absolu est approprié pour les sous-contrôles ayant des dimensions dépendant des dimensions de la boîte du widget. Comme précédemment, subcontrol-origin est utilisé pour définir un rectangle de référence dans la boîte du widget. La zone rectangulaire pour les sous-contrôles est alors définie en fournissant des décalages à partir des bords situés en haut, à droite, en bas et à gauche de ce rectangle de référence :

 
Sélectionnez
QPushButton::menu-indicator {
	border: 2px solid red;
 
	subcontrol-origin: padding;
	position: absolute;
	top: 2px;
	right: 2px;
	bottom: 2px;
	left: 40px;
}

Pour les sous-contrôles ayant une longueur fixée ou bien une hauteur fixée, subcontrol-position peut être utilisé pour spécifier un alignement à l'intérieur du rectangle de subcontrol-origin :

 
Sélectionnez
QPushButton::menu-indicator {
	image: url(menu_indicator.png);
	width: 13px;
 
	subcontrol-origin: padding;
	subcontrol-position: bottom right;
	position: relative;
	top: 2px;
	bottom: 2px;
	right: 2px;
}

X. Divers

Note : Cet article concerne la version 4.2 de Qt, mais les informations présentes à l'intérieur s'avèrent être toujours d'actualité.

Fidèle à ma nouvelle habitude, j'adresse ici de chaleureux remerciements à dourouc05 pour sa patience et son aide, ainsi qu'a RideKick pour sa relecture !

Au nom de toute l'équipe Qt, j'aimerais adresser le plus grand remerciement à Nokia pour nous avoir autorisé la traduction de cet article !