FAQ Qt

FAQ QtConsultez toutes les FAQ
Nombre d'auteurs : 26, nombre de questions : 298, dernière mise à jour : 15 juin 2021
Sommaire→Modules→Qt Quick 1- 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 :
somethingSi 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
stackItemSans, on obtient ceux-ci :
item
itemCela 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> listNameUn 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 += networkUne 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_S60Ensuite, 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++
}
}





