QML est un langage déclaratif qui permet de définir les IHM.
Plus d’infos et d’exemples sur mon GitHub
Introduction - Divers
- Visualiser un fichier qml sans besoin de compiler
qmlscene <fichier.qml> - Exécuter un fichier qml comme si c’était un exécutable: ajouter un shebang
#! env qml - Il s’agit d’un langage déclaratif: l’ordre d’écriture n’a pas d’importance. On peut tout à fait lier un élément avec une propriété qui n’apparait que plus bas dans le fichier.
- Ne pas confondre property binding (avec le symbole ‘
:’) et assignation (avec le symbole ‘=’)- Dans la mesure du possible, préférer les properties bindings
- Si on affecte une valeur à une propriété qui était liée, le lien (property binding) disparait et est remplacé par la valeur affectée
- Référer à l’élément parent: mot clé ‘
parent’ - Il existe 2 syntaxes équivalentes pour plusieurs propriétés d’un même groupe
| anchors {
anchors.Top: ... | Top: ...
anchors.Left: ... | Left: ...
| }
Basic Elements
Composants graphiques
| Item name | Rôle | Propriétés notables | Evènements notables |
|---|---|---|---|
| Item | Element vide. Sert uniquement de container | x, y, width, height | |
| Rectangle | Rectange | x, y, width, height, color | |
| Text | |||
| TextInput | |||
| Image | Affiche une image issue d’un fichier | source, sourceSize | |
| BorderImage | Comme image mais qui a la propriété particulière de s’étirer sans déformer les coins et les côtés | border {left, top, right, bottom}: définit les zones côtés et coins pour ne pas les déformer, horizontalTileMode/verticalTileMode: BorderImage.Stretch/Repeat/Round - politique d’étirement: étirer/répéter/répéter et étirer pour tomber sur un multiple pile | |
| AnimatedImage | Image animée (gif) | frameCount, currentFrame | |
| Gradient | Pour créer un dégradé de couleurs. Contient des balises GradientStop et une propriété orientation. | ||
| MouseArea | zone pour intéraction avec la souris. Comporte les évènement Ne pas oublier de définir une taille, voir mieux d’utiliser anchors.fill: parent | On peut aussi récupérer les propriétés de la MouseArea depuis les autres éléments en utilisant <mouseAreaId>.pressed/released/clicked |
onClicked, onPressed, onReleased, onPressAndHold, onHovered, … |
Gestures
| Composant | Description |
|---|---|
| Flickable | zone dans laquelle on peut placer un élément qu’on peut déplacer |
| PinchArea | Zone pour zommer/dézoomer en pincant les doigts |
| MultiPointTouchArea | Permet de définir ses propres gestures |
Properties
Basic properties
| Propriété | Description |
|---|---|
| x, y | position à l’intérieur du parent |
| z | ordre d’apparition (plus z est grand, plus l’élément est au 1er plan) |
| width, height | dimensions |
| implicitWidth, implicitHeight | Dimensions par défaut du widget si les dimensions ne sont pas explicitement définies avec les propriétés width et height. |
| color | couleur (doit être indiqué entre quotes): “green”, “blue”, “steelblue”, “lightblue”, “#00000000” (alpha, red, gree, blue), “#000000” (red, gree, blue) |
| text | Texte à afficher |
| clip | empêche les éléments enfants de dépasser du parent (false par défaut) |
| opacity | Opacité - de 0.0 (transparent) à 1.0 (opaque) |
| scale | Facteur de mise à l’echelle. Attention: Les dimensions de l’item (width & height) restent inchangées, la mise à l’échelle est uniquement pour l’affichage. |
| rotation | Angle de rotation en degré dans le sens horaire. Le centre de rotation par défaut est le centre de l’objet |
| transformOrigin | Permet de redéfinir l’origine utilisée pour les transformations: Item.TopLeft / Item.BottomRight / Item.Center / Item.Top / Item.Left / etc |
| gradient | Un élément de type Gradient |
Interractions avec le clavier
| Propriété | Description |
|---|---|
| focus | true/false: active le focus sur l’élément concerné quand l’élément parent prend le focus. La propriété activeFocus permet de savoir si l’élément a le focus (pour changer d’autres propriétés - couleur, etc). A combiner avec un item ‘FocusScope’ pour que le comportement soit correct lorsqu’on crée des composants réutilisables. |
| activeFocusOnTab | true/false: Accepte de prendre le focus lors d’un appui sur TAB |
| KeyNavigation.right: <widgetID> | Indique quel élément doit prendre le focus si la touche ‘droite’ est appuyée |
| Keys.onLeftPressed | Signal lorsque la touche ‘gauche’ est appuyée. N’existe que pour une sélection de touches. Pour toutes les touches, utiliser Keys.onPressed. Note: ce n’est pas une propriété d’un Item, mais une ‘attached property’ |
| Keys.onPressed(KeyEvent event) | Signal lorsqu’une touche (n’importe laquelle) est appuyée. le signal fournit un objet event dans lequel l’id de la touche est donné |
Property binding
Il est possible de définir la valeur d’une propriété en fonction d’une autre propriété (soit directement, soit avec des calculs)
width = height // la largeur est égale à la hauteur
width = height * 2 // La largeur est égale au double de la hauteur
On peut également faire référence à d’autres éléments
width = parent.width / 2 // Largeur est égale à la moitié de la argeur du parent
width = <otherItemId>.width // Largeur identique à celle d'un autre élément
Positionnement
Plusieurs façons de positionner les widgets
- Manuellement avec les positions x et y - déconseillé
- anchors layout
- Row, Column & Grid
- RowLayout, ColumnLayout & GridLayout
Anchors layout
On peut ancrer chacun des 4 côtés d’un objet par rapport à d’autres (parent inclus)
anchors.left: <widgetId>.right // Le bord gauche de l'objet est aligné avec le bord gauche d'un autre objet (fonctionne également avec 'parent')
Note:
- les ancrages existant sont : left, right, top, bottom
- l’ancrage est prioritaire par rapport aux positions x et y (et également les dimensions width et height si l’ancrage à un rôle sur la taille)
On peut spécifier des marges en plus de l’ancrage
anchors.left: <widgetId>.right
anchors.leftMargin: 10 // Un ancrage est présent à gauche mais avec une marge de 10 pixels
On peut aussi définir un ancrage pour centrer un objet
anchors.verticalCenter: <itemId>.verticalCenter // notre item est centré verticalement sur un autre
anchors.horizontalCenter: 20 // centrage horizontale sur une valeur particulière
anchors.baseline: <value> // la baseline est utile pour le text. il correspond à la ligne de base de caractères (sous le 'a'. Le 'g' dépasse sous la baseline)
On peut centrer un objet dans un autre
anchors.centerIn: <itemId>
On peut définir un objet qui doit remplir un autre
anchors.fill: <itemId>
Note:
- peut être combiné avec les margins
- la propriété anchors.margins est équivalent à spécifier les 4 marges avec la même valeur en une seule instruction
Row, Column, Grid
- Les éléments sont disposés en ligne, colonne ou grille
- Ces éléments doivent également être ancrés à leur parent
- la propriété margin permet de définir l’espace entre les éléments
Layouts
- RowLayout, ColumnLayout, GridLayout
- Généralement, le layout est positionné pour occuper tout le parent (
anchors.fill: parent) - Les layout possèdent quelques propriétés (telle que margin)
- Les items sont arrangés selon le layout choisi. c’est le layout qui redimensionne les items
- chaque item peut positionner des propriétés liées au layout
Layout.fillWidth / Layout.fillHeight: l’item utilise toute la largeur/hauteur du layoutLayout.minimumWidth / Layout.minimumHeight / Layout.maximumWidth, Layout.maximumHeight: spécifie les dimensions min/max de l’itemLayout.preferredWidth / Layout.preferredHeight: Dimensions préféréeLayout.alignment: Qt.AlignTop | Qt.AlignRight(etc.): positionne l’item dans le layoutGridLayoutpossède quelques propriétés supplémentaires- rowSpan et columnSpan: pour étendre un item sur plusieurs lignes/colonnes
- Les items sont placés selon l’ordre de déclaration. Mais on peut forcer un item à une place particulière avec les propriétés Layout.row et Layout.column
Transformations
- On peut soit utiliser les propriétés rotation, scale, opacity pour les transformations simple
- Ou on peut utiliser la propriété tranform pour une ou plusieurs transformations plus complexes
transform : [
Rotation {
origin.x: <val>; origin.y: <val>
angle: <angle>
},
Scale {
},
Translate {
},
Matrix4x4 { // Permet des transformations plus poussées
}
]
Animations
- Plusieurs syntaxes existantes pour les animations
- Animation on
<property> - Animation en spécifiant la cible
- Utilisation des Behavior
- Animation on
- Il existe plusieurs types d’animations
- NumberAnimation
- ColorAnimation
- SpringAnimation
- RotationAnimation
- PauseAnimation
- PathAnimation: pour qu’un élément se déplace selon un parcours prédéfini
- Flippable: Pas réellement une animation, mais un objet qui a 2 faces différentes et qu’on peut retourner comme une carte (Animation rotation selon l’axe z)
- SequentialAnimation: contient plusieurs animations et les exécute l’une après l’autre)
- ParallelAnimation: contient plusieurs animations et le exécute en même temps
- …
Syntaxes
Animation on <property>
NumberAnimation on width {
from: 10; to: 100
duration: 1000
}
Animation
RotationAnimation {
target: myItem
property: rotation // variante: ''properties'' suivi d'une liste de plusieurs propriétés
from: 0; to: 180
duration: 2000
running: true // propriété accessible par les autres widgets pour démarrer/arrêter l'animation à la demande
}
Behavior Le comportement à adopter dès que la propriété x change: La transition de la valeur se fait selon une SpringAnimation
Behavior on x { SpringAnimation {} }
States & Transitions
- Les states permettent de définir une sortie de machine d’état. Dans chaque état, on peut spécifier des propriétés particulière pour chaque élément.
- Les transitions (optionnelles) permettent de définir les animations à appliquer lors d’un changement d’état.
States
- On peut définir autant d’état que l’on souhaite
- dans chaque état, on peut appliquer des propriétés spécifiques. Si une propriété n’est pas spécifié, c’est la valeur par défaut qui s’applique (valeur spécifiée dans l’élément hors des states
- Si aucun state actif, les propriétés par défaut s’appliquent
- 2 façons d’activer les états
- avec la propriété
state. Il faut affecter le nom de l’état souhaité. Cela impose d’avoir une mécanique qui positionne la propriétéstateavec la valeur souhaitée - avec la propriété
whende chaque state. Lorsque la condition devient vraie, le state est activé. Il ne doit y avoir qu’un seul state actif à la fois
- avec la propriété
state: "initialState" // définit le state actif au démarrage. Si omis, aucun state actif. Les propriétés par défaut sont appliquées
states: [ // Liste de tous les state
State {
name: "stateA" // Aucune condition d'activation. L'état doit être activé en gérant manuellement la propriété state="StateA"
PropertyChanges { target: <elementId>; <property>: <value> } //
PropertyChanges { ... }
},
State {
name: "stateB"
when: <condition to activate> // l'état devient actif lorsque la condition est vérifiée
PropertyChanges { ... }
}
]
Transitions
- On peut ajouter des transitions entre les states
- les transitions servent à ajouter des animations pour que la transition d’un état à l’autre soit moins abrupte
- On peut utiliser n’importe quelle animation
- Particularités sur les animations
- le plus logique est que l’animation porte sur une propriété qui change d’un state à l’autre (même si ce n’est pas obligatoire, on est libre de faire ce qu’on veut)
- on peut spéficier une target ou plusieurs avec la propriété
targets: [...] - si la propriété from est omise, on utilise les valeurs actuelles de l’élément (ex: un déplacement depuis la position où se trouve actuellement l’élément)
- la propriété to peut être omise si la valeur finale est définie dans l’état d’arrivée
transitions: [
Transition {
from: "stateA"; to: "stateB"
reversible: true // transition de stateA vers stateB, et inversement
PropertyAnimation { ... } // N'importe quel animation avec les propriétés sur lequelles appliquer l'anim
},
Transition {
from: "*"; to: "*" // transition de n'importe quel état, vers n'importe quel état. Cette ligne peut être omise
PropertyAnimation { ... }
}
]
Composants spéciaux
Loader
- Charge dynamiquement un composant lorsque c’est utile (sur propriété
active: true). Lorsque active devient faux, le composant est déchargé de la mémoire. - Charge soit un composant avec sourceComponent ou un fichier qml avec source
- A utiliser avec Binding et Connections, surtout dans le cas où on charge des fichiers qml.
Repeater
- Instancie le delegate selon un model
- Plutôt utile si le Repeater est placé dans un Layout
- Use case le plus simple: model est défini par une valeur numérique -> le délégate est répété autant de fois qu’indiqué
- numéro d’instance du delegate accessible via
model.indexRepeater { model: <n> delegate : <Element> { <propriété>: model.index } } - Alternative use case: model est une liste d’éléments -> le délégate est répété autant de fois qu’il y a d’éléments dans la liste
Repeater { model: ["red", "blue", "green", "yellow"] delegate: Rectangle { color: model.modelData // la valeur de chaque élément de la liste } } - utilisation avec une liste
ListModel {
id: nameModel
ListElement { name: "foo"; age: 42} // Chaque élément de la liste est une sorte de dictionnaire
ListElement { name: "bar"; age: 33}
}
Component {
id: nameDelegate
Text: model.name + "/" + model.age // on accède aux données du model. Selon les clé du dictionnaire qu'on a choisi
}
Column {
Repeater {
model: nameModel
delegate: nameDelegate
}
}
ListView
- Le Repeater ne fait qu’instancier plusieurs éléments. Certains éléments peuvent être en dehors de la fenêtre. La ListView est un élément qui affiche une liste.
- Le principe de fonctionnement est similaire au Repeater: un model et un delegate
- La ListView peut afficher soit un model défini directement dans le qml (ListModel) ou un model défini dans le code C++ (QAbstractItemModel or QAbstractListModel et fonctionne également avec les proxyModel)
- La ListView peut être scrollée pour afficher la suite ou le début de la liste
- Les éléments sont instanciés/détruits s’ils doivent être affichés ou non (uniquement si clip: true)
- Pour créer un delegate qui soit aussi réutilisable que possible, il faut référer à la listView (pour accéder au currentIndex par ex) en utilisant la propriété attachée
ListView.view. Créer une propriété au niveau top level qui réfère ListView.viewListView { model: <model> delegate: <delegate> clip: true // n'instancie que les éléments devant être affichés cacheBuffer: <n> // (optionnel) taille en pixel de la zone non visible avant et après qui doit être chargée en cache même si non affichée onCurrentIndexChanged: ... // signal émis lorsque l'index courant ''currentIndex'' est modifié. Le currentIndex peut être modifié par le delegate, lors d'un clic souris par ex orientation: Qt.Horizontal layoutDirection: Qt.LeftToRight / verticalLayoutDirection: Qt.TopToBottom highlight: <Element> // permet de crééer un élément affiché sur l'élément courant (position z=0 par défaut -> en arrière plan) }
Créer des composants personnalisés
- Soit avec le type ‘Component’
- Soit en créant un fichier par composant
- le nom du fichier est le nom du composant
- le fichier doit commencer par une majuscule
- Le composant doit contenir un seul élément racine (autant d’éléments enfants qu’on souhaite)
- On ne peut accéder qu’aux propriétés du composant racine. On ne peut pas accéder aux propriétés des éléments enfants
- Pour que le composant soit standard et puisse être réutiliser, ne pas oublier de spécifier les propriétés ‘‘implicitWidth’’ et ‘‘implicitHeight’’ pour éviter que le composant ait une taille nulle si les dimensions ne sont pas explicitement spécifiées.
- On peut définir des propriétés pour notre composant
- Ces propriétés peuvent également servir de variables locales
- l’attribut ‘readonly’ permet d’éviter d’écraser malencontreusement la propriété par une assignation
[readonly] property <type> <nom> [: <value>]
- Si on veut rendre accésssible une propriété d’un élément enfant à l’extérieur du composant, il faut créer un alias
property alias <nom>: <childItemId>.<property> - Ajouter une fonction au composant
- pas forcément la meilleure conception d’utiliser des fonction
- s’ajoute dans l’élément racine du composant
- se code en javascript
function <doSomething>() {
code
...
}
// Appel de la fonction sur le composant
<myItem>.<doSomething>()
- Ajouter des signaux au composant
signal <nomDuSignal>- l’action à réaliser sur réception du signal dans le handler
onNomDuSignal - on peut passer une ou plusieurs variables
signal <name> [(<type> <name>), ...]
signal checked(bool checkValue)
// handler
onChecked:
Interaction C++/QML
Affichage dans le QML des données C++
- Ajouter l’objet au contexte
Instanciation d’un objet C++ dans le QML
- la classe doit dériver de QObjet ou QQuickItem
- macro Q_OBJECT
- enregistrement de la classe avec qmlRegisterType
qmlRegisterType<NomClasse>("NomComposant", 1, 0, "NomObjet")- NomComposant est utilisé pour l’import dans QML avec la version indiquée (ici 1.0)
- Dans QML, on instancie un objet avec le nom indiqué NomObjet. Généralement NomComposant = NomOBjet (mais pas obligé)
- Import dans QML
- import NomComposant 1.0
- Utilisation
- Utilisation de NomObjet comme n’importe quel élément QML
- On accède aux propriétés: attributs de la classe C++ déclarés avec Q_PROPERTY
- On peut accéder/utiliser les signaux et slots de la classe C++
- On peut appeler des méthodes sur la classe: méthodes déclarées avec Q_INVOKABLE
Objet C++ capable de s’afficher à l’écran
- Même principe que ci-dessus
- La classe doit dériver de QQuickPaintedItem
- La classe peut dériver de QQuickItem: plus performant mais bcp plus compliqué à implémenter (peut prendre plusieurs semaines vs. 1-2 jours)
- La classe doit ré-implémenter la méthode paint pour spécifier comment s’afficher à l’écran
- Gestion des évènements via la méthode
event(QEvent* ev)ou par une des méthodes spécialisée pour chaque type d’event (mousePressEvent, mouseReleaseEvent, touchEvent, scrollEvent, etc.)