FAQ Qt
FAQ QtConsultez toutes les FAQ
Nombre d'auteurs : 26, nombre de questions : 298, dernière mise à jour : 15 juin 2021
- Comment définir une propriété par défaut depuis le C++ ?
- Qu'est-ce qu'une default property ?
- Comment créer une liste d'éléments ?
- Comment définir plusieurs polices à un élément textuel ?
- Comment mettre en place un cache ?
- Comment recevoir des événements du bouton de volume sous Symbian ?
- Comment retarder une animation ?
- Comment être notifié quand une propriété est modifiée ?
Définir une propriété par défaut permet de déterminer la source de la parenté des éléments enfants. En C++, il est possible de définir une propriété par défaut grâce à la macro Q_CLASSINFO(Name, Value) avec pour Name "DefaultProperty" et Value une propriété de type QDeclarativeListProperty.
Voici un exemple commenté illustrant cela :
class
MyClass : public
QObject
{
Q_OBJECT
Q_PROPERTY
(QDeclarativeListProperty
<
QObject
>
objects READ
objects)
Q_CLASSINFO
("DefaultProperty"
, "objects"
)
public
:
MyClass() {
}
QDeclarativeListProperty
<
QObject
>
objects() {
// On utilise le constructeur QDeclarativeListProperty::QDeclarativeListProperty
// (QObject *object, void *data, AppendFunction append, CountFunction count = 0,
// AtFunction at = 0, ClearFunction clear = 0) dans le but de spécifier des
// fonctions permettant d'effectuer des opérations :
return
QDeclarativeListProperty
<
QObject
>
(this
, 0
, &
MyClass::
appendObjects,
&
MyClass::
getObjectsCount, &
MyClass::
objectAt, &
MyClass::
clearObjects);
}
private
:
static
void
appendObjects(QDeclarativeListProperty
<
QObject
>
*
list, QObject
*
obj) {
// Du fait que l'on a affaire à une fonction statique, on ne peut pas tout
// simplement faire _objects.append(obj). Les opérations suivantes sont donc
// nécessaires.
// On récupère l'objet de MyClass depuis list avec un qobject_cast :
MyClass *
objects =
qobject_cast
<
MyClass *>
(list->
object);
// Si le pointeur objects n'est pas NULL :
if
(objects) {
// On définit le parent du QObject* à ajouter à la liste :
obj->
setParent(objects);
// Et on l'ajoute à la liste :
objects->
_objects.append(obj);
}
}
static
int
getObjectsCount(QDeclarativeListProperty
<
QObject
>
*
list) {
// Le processus est identique à celui de la fonction appendObjects :
// On récupère l'objet de MyClass depuis list avec un qobject_cast :
MyClass *
objects =
qobject_cast
<
MyClass *>
(list->
object);
// La valeur de retour, 0 si objects est NULL, sinon le nombre
// d'objets dans la liste :
return
(objects) ? objects->
_objects.count() : 0
;
}
static
QObject
*
objectAt(QDeclarativeListProperty
<
QObject
>
*
list, int
index) {
// Le processus est identique à celui de la fonction appendObjects :
// On récupère l'objet de MyClass depuis list avec un qobject_cast :
MyClass *
objects =
qobject_cast
<
MyClass *>
(list->
object);
// La valeur de retour, NULL si objects est NULL, sinon l'objet à
// la position indiquée par index :
return
(objects) ? objects->
_objects.at(index) : NULL
;
}
static
void
clearObjects(QDeclarativeListProperty
<
QObject
>
*
list) {
// Le processus est identique à celui de la fonction appendObjects :
// On récupère l'objet de MyClass depuis list avec un qobject_cast :
MyClass *
objects =
qobject_cast
<
MyClass *>
(list->
object);
// Si objects n'est pas NULL :
if
(objects)
// On vide la liste :
objects->
_objects.clear();
}
QList
<
QObject
*>
_objects;
}
;
Dans QDeclarativeListProperty<QObject> objects(), on a utilisé :
QDeclarativeListProperty
::
QDeclarativeListProperty
(QObject
*
object, void
*
data, AppendFunction append, CountFunction count =
0
, AtFunction at =
0
, ClearFunction clear =
0
)
Par le biais de ce constructeur, on a défini des pointeurs sur fonctions permettant d'effectuer les opérations append, count, at et clear sur la liste objects.
Dans le main.cpp, on déclare le type MyObject avec qmlRegisterType() :
qmlRegisterType<
MyClass>
("MyClass"
, 1
, 0
, "MyClass"
);
Et enfin, on utilise la classe dans un fichier QML :
import
QtQuick 1.0
import
MyClass 1.0
Item
{
id
:
main
width
:
400
height
:
200
Item
{
id
:
it }
MyClass {
Item
{
objectName
:
"something"
}
Item
{
objectName
:
"anotherthing"
}
Component.onCompleted
: {
console.log
(
objects[
0
].
objectName)
}
}
}
Le log indique ceci :
something
Si l'on n'avait pas défini la propriété objects comme la propriété par défaut de la classe MyClass, on aurait dû écrire le code précédent comme ceci pour instaurer la parenté de manière identique :
import
QtQuick 1.0
import
MyClass 1.0
Item
{
id
:
main
width
:
400
height
:
200
Item
{
id
:
it }
MyClass {
objects
:
[
Item
{
objectName
:
"something"
}
,
Item
{
objectName
:
"anotherthing"
}
]
Component.onCompleted
: {
console.log
(
objects[
0
].
objectName)
}
}
}
Le fait d'utiliser l'attribut "default" dans la déclaration d'une propriété marque celle-ci comme étant la propriété par défaut de son type. C'est un facteur déterminant la source de la parenté des éléments enfants.
Par exemple :
// Fichier Compo.qml
import
QtQuick 1.0
Item
{
id
:
item
objectName
:
"item"
default property
alias
content
:
stackItem.children
Item
{
id
:
stackItem
objectName
:
"stackItem"
anchors.fill
:
parent
}
}
Dans le fichier Compo.qml, on a spécifié une propriété par défaut sur un élément Item à l'Id stackItem. Voici un fichier main.qml qui utilise ce composant en lui insérant d'autres éléments :
// main.qml
import
QtQuick 1.0
Item
{
id
:
main
width
:
400
height
:
200
Compo {
anchors.fill
:
parent
Rectangle
{
id
:
rect
}
Item
{
id
:
it }
Component.onCompleted
: {
console.log
(
rect.
parent
.
objectName)
console.log
(
it.
parent
.
objectName)
}
}
}
La parenté est ici déterminée par la propriété par défaut. Avec celle-ci, on obtient les logs suivants :
stackItem
stackItem
Sans, on obtient ceux-ci :
item
item
Cela signifie que, dans le main.qml, les éléments définis à l'intérieur de l'élément Compo (c'est-à-dire, le Rectangle et l'Item) ont été ajoutés dans la liste "children" de l'élément stackItem, d'où la parenté.
Il est assez fréquent que l'on ait besoin d'écrire des listes d'éléments pour pouvoir les manipuler par la suite. Une première approche envisageable est l'utilisation d'un ListModel dans le but d'y stocker les valeurs permettant de recréer par la suite les éléments :
ListModel
{
id
:
mymodel
ListElement
{
contentText
:
"Quelque chose"
; bold
:
true
}
ListElement
{
contentText
:
"Autre chose"
; bold
:
false
}
}
ListView
{
model
:
mymodel
delegate
:
Text
{
text
:
contentText; font.bold
:
bold }
anchors.fill
:
parent
}
Component.onCompleted
: {
console.log
(
mymodel.get
(
0
).
contentText);
}
Toutefois, cette solution peut souvent s'avérer lourde. Une deuxième solution est celle fournie par VisualItemModel :
Item
{
id
:
main
width
:
400
height
:
200
VisualItemModel
{
id
:
mymodel
Text
{
text
:
"Quelque chose"
; font.bold
:
true
}
Text
{
text
:
"Autre chose"
; font.bold
:
false
}
}
ListView
{
anchors.fill
:
parent
model
:
mymodel
}
}
Une troisième solution est d'utiliser le type list. La syntaxe de déclaration d'une propriété de type list est la suivante :
property
list
<
Type>
listName
Un exemple d'utilisation :
Item
{
id
:
main
width
:
400
height
:
200
property
list
<
Text
>
mylist
mylist
:
[
Text
{
text
:
"Quelque chose"
; font.bold
:
true
}
,
Text
{
text
:
"Autre chose"
; font.bold
:
false
}
]
Repeater
{
// ...
model
:
mylist
delegate
:
// ce que l'on veut en utilisant mylist[index]
}
Component.onCompleted
: {
console.log
(
mylist[
0
].
text
);
}
}
La solution de list<T> est assez pratique pour les manipulations ne mettant pas en jeu des éléments graphiques. L'utiliser pour afficher des éléments à l'écran n'est pas une bonne solution. Ainsi, pour définir une liste de chaines de caractères, utiliser list<string> sera très pratique. Pour définir une liste d'éléments Text, on préfèrera la plupart du temps les VisualItemModels.
Pour définir une police à un élément textuel possédant la propriété font, il existe deux moyens : définir directement le nom d'une police installée sur le système avec font.family ou bien utiliser un élément FontLoader pour utiliser une police située à un emplacement donné, que ce soit en local ou sur Internet :
Item
{
id
:
main
width
:
400
; height
:
200
Text
{
id
:
text
font.family
:
"Times"
text
:
"Texte"
}
FontLoader
{
id
:
symbolLoader
name
:
"Symbol"
}
FontLoader
{
id
:
externalLoader
source
:
"qrc:/fonts/font.ttf"
}
MouseArea
{
anchors.fill
:
parent
onClicked
: {
switch (
text
.
font.
family) {
case "Times"
:
text
.
font.
family =
symbolLoader.
name
;
break;
case symbolLoader.
name
:
text
.
font.
family =
externalLoader.
name
;
break;
case externalLoader.
name
:
text
.
font.
family =
"Times"
;
break;
}
}
}
}
Il est également fréquent que l'on ait besoin de spécifier une police de remplacement, dans le cas où la police ne serait pas disponible. Pour cela, il suffit de spécifier son nom dans la propriété font.family de l'élément textuel :
Text
{
id
:
text
font.family
:
"Times, Symbol, "
+
externalLoader.name
text
:
"Police "
+
font.family
}
FontLoader
{
id
:
externalLoader
source
:
"qrc:/fonts/font.ttf"
}
Dans l'exemple ci-dessus, la police Times sera utilisée. Si elle n'est pas disponible, ce sera la police Symbol. Si cette dernière n'est également pas disponible, ce sera la police chargée avec l'élément FontLoader.
Lorsque l'on souhaite afficher une ressource volumineuse n'étant pas située en local (par exemple, une image), on aime assez pouvoir la mettre en cache pour éviter qu'il y ait lieu de multiples téléchargements ralentissant l'application.
Lorsque QML tente d'accéder à une ressource en provenance de l'extérieur, il utilise la classe QNetworkAccessManager. Par le biais de la fonction setNetworkAccessManagerFactory() de QDeclarativeEngine, qui prend pour argument une classe dérivée de QDeclarativeNetworkAccessManagerFactory, il est possible d'implémenter une "usine" pour créer des QNetworkAccessManager personnalisés afin de profiter d'un système de cache orchestré par une instance de QNetworkDiskCache, définie au QNetworkAccessManager grâce à sa méthode setCache().
Pour mettre en place un cache, il s'agit donc de créer une classe dérivée de QDeclarativeNetworkAccessManagerFactory et d'implémenter sa fonction create(), comme ceci :
class
NetworkAccessManagerFactory : public
QDeclarativeNetworkAccessManagerFactory
{
public
:
virtual
QNetworkAccessManager
*
create(QObject
*
parent)
{
// Création d'une instance de QNetworkAccessManager :
QNetworkAccessManager
*
manager =
new
QNetworkAccessManager
(parent);
// Création d'une instance de QNetworkDiskCache :
QNetworkDiskCache
*
cache =
new
QNetworkDiskCache
(manager);
// L'emplacement où les fichiers de cache seront écrits :
cache->
setCacheDirectory(QDesktopServices
::
storageLocation(QDesktopServices
::
CacheLocation));
// Le cache accepte jusqu'à 10Mo :
cache->
setMaximumCacheSize(10
*
1024
*
1024
);
// On définit le cache du manager à notre objet cache :
manager->
setCache(cache);
// On retourne le manager prêt à l'usage :
return
manager;
}
}
;
Ce code nécessite la présence de la ligne suivante dans le fichier de projet :
QT += network
Une fois l'usine préparée, il s'agit de faire en sorte que le QDeclarativeEngine l'utilise :
QDeclarativeView
view;
view.engine()->
setNetworkAccessManagerFactory(new
NetworkAccessManagerFactory);
view.setSource(QUrl
("qml/main.qml"
));
view.show();
Et dans le fichier QML, la façon de coder reste inchangée :
import
QtQuick 1.0
Item
{
id
:
main
width
:
400
height
:
200
Image
{
anchors.centerIn
:
parent
source
:
"http://www.example.com/image.png"
}
}
Lors de la première exécution de l'application, l'image est chargée depuis l'extérieur. Lors des lancements suivants, elle ne charge plus l'image depuis l'url spécifiée mais depuis le cache.
Dans QML, sur les périphériques Symbian, les événements des touches de volume ne sont pas délivrés par Keys.onVolumeDownPressed, Keys.onVolumeUpPressed ou Keys.onPressed. Une solution est d'utiliser la S60 Remote Control API. Voici un exemple de code pour le faire avec QML et C++.
On crée un nouvel élément QML pour délivrer l'événement voulu. L'API peut en réalité recevoir d'autres événements de touches multimédias. Voir le lien ci-dessus pour plus de détails.
#ifndef MediakeyCaptureItem_H
#define MediakeyCaptureItem_H
#include
<QDeclarativeItem>
#ifdef Q_WS_S60
#include
<remconcoreapitargetobserver.h>
#include
<remconcoreapitarget.h>
#include
<remconinterfaceselector.h>
class
MediakeyCaptureItemPrivate;
class
MediakeyCaptureItem : public
QDeclarativeItem
{
Q_OBJECT
public
:
MediakeyCaptureItem(QDeclarativeItem
*
parent =
0
);
void
paint(QPainter
*
painter, const
QStyleOptionGraphicsItem
*
option, QWidget
*
widget);
signals
:
void
volumeDownPressed();
void
volumeUpPressed();
private
:
MediakeyCaptureItemPrivate *
d_ptr;
private
:
// Friend class definitions
friend
class
MediakeyCaptureItemPrivate;
}
;
#endif
// Q_WS_S60
#endif
// MediakeyCaptureItem_H
#include
"MediaKeyCaptureItem.h"
#ifdef Q_WS_S60
// A private class to access Symbian RemCon API
class
MediakeyCaptureItemPrivate : public
QObject
, public
MRemConCoreApiTargetObserver
{
public
:
MediakeyCaptureItemPrivate(MediakeyCaptureItem *
parent);
~
MediakeyCaptureItemPrivate();
virtual
void
MrccatoCommand(TRemConCoreApiOperationId aOperationId,
TRemConCoreApiButtonAction aButtonAct);
private
:
CRemConInterfaceSelector*
iInterfaceSelector;
CRemConCoreApiTarget*
iCoreTarget;
MediakeyCaptureItem *
d_ptr;
}
;
// Consructor
MediakeyCaptureItem::
MediakeyCaptureItem(QDeclarativeItem
*
parent): QDeclarativeItem
(parent)
{
d_ptr =
new
MediakeyCaptureItemPrivate(this
);
}
// The paint method
void
MediakeyCaptureItem::
paint(QPainter
*
painter, const
QStyleOptionGraphicsItem
*
option, QWidget
*
widget)
{
// This item has no visual
}
// Constructor
MediakeyCaptureItemPrivate::
MediakeyCaptureItemPrivate(MediakeyCaptureItem *
parent): d_ptr(parent)
{
QT_TRAP_THROWING
(iInterfaceSelector =
CRemConInterfaceSelector::
NewL());
QT_TRAP_THROWING
(iCoreTarget =
CRemConCoreApiTarget::
NewL(*
iInterfaceSelector, *
this
));
iInterfaceSelector->
OpenTargetL();
}
// Destructor
MediakeyCaptureItemPrivate::
~
MediakeyCaptureItemPrivate(){
delete
iInterfaceSelector;
delete
iCoreTarget;
}
// Callback when media keys are pressed
void
MediakeyCaptureItemPrivate::
MrccatoCommand(TRemConCoreApiOperationId aOperationId,
TRemConCoreApiButtonAction aButtonAct)
{
//TRequestStatus status;
switch
( aOperationId )
{
case
ERemConCoreApiVolumeUp:
emit
d_ptr->
volumeUpPressed();
break
;
case
ERemConCoreApiVolumeDown:
emit
d_ptr->
volumeDownPressed();
break
;
default
:
break
;
}
}
#endif
// Q_WS_S60
Ensuite, il faut enregistrer dans le code C++ ce composant :
qmlRegisterType<
MediakeyCaptureItem>
("Mediakey"
, 1
, 0
, "MediakeyCapture"
);
Ensuite, il faut ajouter quelques lignes dans le .pro :
symbian {
INCLUDEPATH += MW_LAYER_SYSTEMINCLUDE
LIBS += -L\epoc32\release\armv5\lib -lremconcoreapi
LIBS += -L\epoc32\release\armv5\lib -lremconinterfacebase
}
En QML, on appelle ce composant comme suit :
import
Qt
4.7
import
Mediakey 1.0
Item
{
...
MediakeyCapture{
onVolumeDownPressed
:
console.log('VOLUME DOWN PRESSED '
)
onVolumeUpPressed
:
console.log('VOLUME UP PRESSED '
)
}
}
Voici un exemple d'utilisation de Qt Quick et des animations pour, après un clic de l'utilisateur, jouer une série d'événements retardés (comme ouvrir puis fermer une liste). On commence avec un cercle rouge. Quand l'utilisateur clique dessus, il s'anime en un rectangle et déclenche un timer qui, à terme, lance la transformation inverse, du rectangle au cercle.
import
QtQuick 1.0
Rectangle
{
property
int
time
:
800
property
int
size
:
300
width
:
size
; height
:
size
; radius
:
size
color
:
"red"
Behavior
on
radius
{
NumberAnimation
{
duration
:
time
}
}
Timer
{
id
:
reset
interval
:
time
;
onTriggered
:
parent.radius =
size
}
MouseArea
{
anchors.fill
:
parent
onClicked
: {
parent
.
radius =
0
;
reset
.start
(
)
}
}
}
Si vous vouliez simplement avoir des événements qui se suivent directement, il aurait été possible d'utiliser une SequentialAnimation. Cet exemple, quant à lui, montre des délais arbitraires.
Lorsqu'une propriété d'un item est modifiée, un signal xxxChanged est émis, où xxx est le nom de la propriété. On peut donc s' y connecter par onXxxChanged (attention, la première lettre du nom de la propriété passe en majuscule).
import
Qt
4.7
//Utilisation d'un rectangle en arrière-plan
Rectangle
{
width
:
100
; height
:
100
//Ajout d'une label
Text
{
id
:
txt; anchors.centerIn
:
parent
; font.pointSize
:
24
}
//ajout de la propriété value
property
int
value
:
0
//code exécuté quand la propriété value change
onValueChanged :
txt.text =
'<b>'
+
value
+
'</b>'
//Timer qui incrémente la valeur de value toutes les 500 ms
Timer
{
interval
:
500
; running
:
true
; repeat
:
true
onTriggered
:
value
++
}
}