I. Quelques piqûres de rappel▲
I-A. L'architecture MVC▲
Originaire du langage Smalltalk, ce modèle permet de diviser une interface utilisateur en trois entités :
- le modèle contient les données. Il s'occupe du traitement et de l'interaction des données avec, par exemple, une base de données ;
- la vue est la représentation graphique. Elle correspond à la partie affichage des données, de ce que l'utilisateur peut voir ;
- le contrôleur prend en charge l'interaction avec l'utilisateur, recevant tous les événements déclenchés par l'utilisateur (clic, sélection...) et mettant par la suite à jour les données.
Cette architecture permet donc de séparer les différentes entités d'une application, aboutissant à une architecture flexible, claire et maintenable.
I-B. Mise en œuvre avec Qt▲
Dans sa version 4, Qt a introduit un ensemble de nouvelles vues mettant en œuvre une architecture de type modèle/vue.
I-B-1. Les modèles▲
Tous les modèles fournis par Qt sont basés sur la classe QAbstractItemModel. Cette classe est la plus abstraite et la plus haute dans la hiérarchie des classes des différents modèles. Cette classe fournit une interface que tous les modèles doivent respecter, afin d'être utilisés correctement avec une vue.
- QStringListModel : stockage d'une liste de QString ;
- QFileSystemModel : un ensemble d'informations sur un fichier ou un répertoire du système de fichier local (anciennement QDirModel) ;
- QStandardItemModel : gestion de tout type de structure, complexe ou non ;
- QSqlTableModel, QSqlRelationalTableModel : accès à une base de données (SQLite, MySQL...).
Cependant, il arrive parfois (voire souvent) que ces modèles ne conviennent pas à l'utilisation et que l'on souhaite créer ses propres modèles. Pour cela, rien de réellement complexe. Il suffit en effet de créer une classe dérivant d'une des classes suivantes :
- QAbstractItemModel : comme évoqué plus haut, il s'agit de la classe la plus abstraite ;
- QAbstractListModel : classe abstraite fournissant une interface pour un modèle de type liste (associé à une QListView) ;
- QAbstractTableModel : classe abstraite fournissant une interface pour un modèle de type tableau (associé à une QTableView).
À noter qu'il n'existe pas de classe séparée pour un modèle de type hiérarchique (QTreeView). Il s'agit simplement de QAbstractItemModel.
Vous remarquerez que toutes ces classes utilisent la même racine, à savoir QAbstractXModel.
Une fois que l'on a choisi la classe de base convenant le mieux à notre utilisation, il ne reste plus qu'à redéfinir certaines méthodes virtuelles comme :
- int QAbstractItemModel::rowCount(const QModelIndex & parent = QModelIndex()) const : fonction retournant le nombre de lignes du modèle ;
- QVariant QAbstractItemModel::data(const QModelIndex & index, int role = Qt::DisplayRole) const : fonction renvoyant la donnée associée au rôle role pour l'index index.
Pour des modèles modifiables, il est aussi nécessaire de redéfinir la fonction bool QAbstractItemModel::setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole).
La création de modèles personnalisés n'est cependant pas l'objet de ce tutoriel. Bien que nous l'aborderons brièvement au travers d'un exemple, je vous renvoie à la documentation traitant le sujet en profondeur.
I-B-2. Les vues▲
- QListView : liste d'éléments ;
- QTableView : tableau d'éléments ;
- QTreeView : représentation d'éléments sous forme hiérarchique (arbre d'éléments).
Une fois la vue choisie, le modèle y est affecté en utilisant la fonction void QAbstractItemView::setModel(QAbstractItemModel * model).
Pour davantage de précisions, libre à vous de vous reporter à la documentation.
II. Parlons du contrôleur▲
Comme vous l'aurez sans doute remarqué, nous n'avons jusqu'à présent pas évoqué le dernier aspect, à savoir le contrôleur. Voici la vérité : Qt n'utilise pas tout à fait une architecture MVC. Ce n'est plus un C, mais bien un D. Pour ceux qui n'auraient pas fait le rapprochement, il s'agit d'un D pour délégué (delegate). Eh oui, maintenant, vous savez tout : Qt utilise en réalité une architecture modèle-vue entourée d'un délégué.
II-A. Présentation du concept▲
Mais à quoi diantre ce fameux délégué sert-il ?
Au contraire d'une architecture MVC classique, l'architecture modèle-vue instaurée par Qt ne fournit pas de réel composant permettant de gérer les interactions avec l'utilisateur. De ce fait, ceci est géré par la vue elle-même.
- personnaliser l'édition des éléments au moyen d'un éditeur ;
- personnaliser le rendu des éléments à l'intérieur d'une vue.
Ainsi, grâce à un délégué, il vous est possible de personnaliser la manière dont les entrées utilisateurs seront gérées, ainsi que le rendu des éléments. Par exemple, lorsqu'on utilise une QTableView avec un modèle éditable ou clique sur une cellule de votre table, il est possible de modifier la valeur de la cellule grâce à un QLineEdit. Cela est possible grâce au délégué qui a fourni à la vue un moyen d'éditer les données au travers d'un composant de type QLineEdit.
II-B. Utiliser un délégué existant▲
Vous aurez sans doute remarqué que, par défaut, même si vous n'avez paramétré aucun délégué, votre vue se charge toute seule de fournir un moyen d'éditer vos données. Cela est dû au fait que les vues sont déjà dotées d'un délégué par défaut : QStyledItemDelegate. En effet, de base, Qt fournit deux délégués :
- QItemDelegate ;
- QStyledItemDelegate.
La seule différence résidant entre ces deux délégués est que QStyledItemDelegate utilise le style courant pour le rendu des données. Ces deux délégués héritent cependant de la même classe, à savoir QAbstractItemDelegate, fournissant une interface de base générique à tous les délégués. Qt fournit en outre QSqlRelationalDelegate, permettant d'éditer et afficher des données d'un QSqlRelationalTableModel.
Cependant, même si les vues sont dotées d'un délégué par défaut, il est bien entendu possible de modifier et paramétrer celui-ci grâce à la fonction void QAbstractItemView::setItemDelegate(QAbstractItemDelegate * delegate). Il est aussi possible de récupérer le délégué courant grâce à la fonction QAbstractItemDelegate * QAbstractItemView::itemDelegate() const.
II-C. Jetons un coup d'œil à la mécanique interne▲
Nous avons dit que le délégué était en partie chargé de fournir à l'utilisateur un moyen d'éditer les données au sein d'une vue, en fournissant un éditeur, tout simplement un composant de type QWidget. Même si, précédemment, on a évoqué l'exemple d'une QLineEdit, le délégué par défaut ne crée pas que des QLineEdit. En effet, celui-ci crée le composant adapté au type de données de la cellule. Par exemple, si la cellule contient un int, le délégué par défaut créera un composant de type QSpinBox. Pour un composant de type double, il s'agira d'un QDoubleSpinBox.
Regardons le code de %QTDIR%/src/gui/itemviews/qstyleditemdelegate.cpp, notamment la méthode createEditor() :
QVariant
::
Type t =
static_cast
<
QVariant
::
Type>
(index.data(Qt
::
EditRole).userType());
return
d->
editorFactory()->
createEditor(t, parent);
Que font ces deux lignes ? Tout d'abord, le type de la donnée contenue dans la cellule est stocké dans la variable t. Souvenez-vous, la fonction data() de QAbstractItemModel renvoyant une variable de type QVariant, celle-ci est capable de gérer de nombreux types de données. La fonction userType() renvoie ce type sous forme d'un int (ce qui justifie l'emploi de static_cast). Si la variable est de type QString, t serait donc égal à QVariant::String,QVariant::Date pour une variable de type QDate, etc.
La deuxième ligne fait appel à la méthode createEditor() de QItemEditorFactory, editorFactory() étant définie comme :
const
QItemEditorFactory
*
editorFactory() const
{
return
factory ? factory : QItemEditorFactory
::
defaultFactory();
}
On y comprend donc que cette méthode retourne la fabrique par défaut si aucune n'a été définie. Mais qu'est-ce donc que cette fameuse fabrique ? QItemEditorFactory est une classe dite de fabrique (factory), permettant entre autres de créer l'éditeur correspondant au type de données que l'on a évoqué plus haut.
Par défaut, voici les composants créés en fonction du type :
Type | Widget |
---|---|
bool | QComboBox |
int / unsigned | QSpinBox |
double | QDoubleSpinBox |
QDate | QDateEdit |
QDateTime | QDateTimeEdit |
QPixmap | QLabel |
QString | QLineEdit |
QTime | QTimeEdit |
Le délégué par défaut est donc capable de gérer une grande partie des types de données. Cependant, il se peut que nous ayons besoin d'un comportement autre que celui par défaut. Pour cela, il suffit de créer son propre délégué, c'est ce que nous allons voir dans le chapitre suivant.
III. Créer son propre délégué▲
Comme vu dans la partie précédente, on dispose de trois classes de base déjà fournies par Qt :
- QAbstractItemDelegate ;
- QItemDelegate ;
- QStyledItemDelegate.
Depuis Qt 4.4, il est recommandé d'utiliser QStyledItemDelegate. On l'utilisera donc comme classe de base de nos délégués.
Lorsque l'on souhaite uniquement personnaliser l'édition des éléments dans notre vue et non le rendu, on doit redéfinir quatre méthodes.
III-A. createEditor()▲
QWidget
*
QStyledItemDelegate
::
createEditor ( QWidget
*
parent, const
QStyleOptionViewItem
&
option,
const
QModelIndex
&
index ) const
Cette fonction retourne le widget (éditeur) pour éditer l'item se trouvant à l'index index. Le paramètre option permet de contrôler comment le widget apparaît.
Cette fonction sera par exemple appelée lorsque l'utilisateur effectuera un double-clic sur la cellule d'une QTableView. L'éditeur retourné par la fonction sera alors présenté à l'utilisateur.
III-B. setEditorData()▲
void
QStyledItemDelegate
::
setEditorData ( QWidget
*
editor, const
QModelIndex
&
index ) const
Cette fonction permet de transmettre à l'éditeur editor les données à afficher à partir du modèle se trouvant à l'index index.
Une fois l'éditeur créé et ouvert, cette fonction sera appelée afin de le remplir avec la donnée concernée à partir du modèle. La donnée sera alors extraite du modèle puis affichée.
III-C. setModelData()▲
void
QStyledItemDelegate
::
setModelData ( QWidget
*
editor, QAbstractItemModel
*
model, const
QModelIndex
&
index ) const
Cette fonction est, en quelque sorte, l'inverse de la précédente. Son rôle est de récupérer les données de l'éditeur et de les stocker à l'intérieur du modèle, à l'index identifié par le paramètre index.
III-D. updateEditorGeometry()▲
void
QStyledItemDelegate
::
updateEditorGeometry ( QWidget
*
editor, const
QStyleOptionViewItem
&
option,
const
QModelIndex
&
index ) const
Lorsque la taille de la vue change, cette fonction permet de redimensionner l'éditeur à la bonne taille.
De manière générale, l'implémentation contiendra la ligne de code suivante :
editor->
setGeometry(option.rect);
option.rect délimite la zone de l'éditeur.
Si l'on souhaite par ailleurs personnaliser le rendu des éléments, il faudra redéfinir une cinquième méthode.
III-E. paint()▲
void
QStyledItemDelegate
::
paint ( QPainter
*
painter, const
QStyleOptionViewItem
&
option,
const
QModelIndex
&
index ) const
Comme son nom l'indique, cette fonction effectue le rendu d'un certain élément en utilisant painter. Les éléments sont rendus en utilisant le style courant de la vue.
IV. Passons à la pratique▲
Maintenant que l'on sait comment créer son propre délégué, on va mettre ce qu'on vient d'apprendre en œuvre au travers d'un exemple. Pour éviter de s'épancher dans un long discours, voici une capture d'écran de l'application à réaliser :
Il s'agit donc d'un tableau (QTableView) comportant deux colonnes, présentant à gauche un élément et à droite une valeur associée (un pourcentage). Lorsque l'on n'est pas en mode édition, on dessine une barre, représentant le pourcentage. En édition, on utilise un QSlider afin de faire varier le pourcentage comme bon nous semble.
IV-A. Le modèle▲
La première étape va être de créer son propre modèle. On n'utilisera pas ici de QStandardItemModel. La première chose à faire est de réfléchir à quelle classe de base on va hériter. Le modèle devant être présenté en tant que tableau, il devient alors évident que l'on va hériter de la classe QAbstractTableModel.
IV-A-1. La classe▲
class
TableModel : public
QAbstractTableModel
{
Q_OBJECT
public
:
enum
Columns {
Element =
0
, Value, Count =
Value +
1
}
;
explicit
TableModel(QObject
*
parent =
0
);
Qt
::
ItemFlags flags(const
QModelIndex
&
index) const
;
int
rowCount(const
QModelIndex
&
parent =
QModelIndex
()) const
;
int
columnCount(const
QModelIndex
&
parent =
QModelIndex
()) const
;
QVariant
data(const
QModelIndex
&
index, int
role =
Qt
::
DisplayRole) const
;
QVariant
headerData(int
section, Qt
::
Orientation orientation, int
role =
Qt
::
DisplayRole) const
;
bool
setData(const
QModelIndex
&
index, const
QVariant
&
value, int
role =
Qt
::
DisplayRole);
void
addElement(const
QString
&
element, int
value);
private
:
QList
<
QPair
<
QString
, int
>
>
mElements;
}
;
Pour des raisons de lisibilité et flexibilité, on définit une énumération Columns permettant de lister les différentes colonnes. Count est une petite astuce pour obtenir le nombre d'éléments et servira pour la méthode columnCount().
La méthode Qt::ItemFlags flags(const QModelIndex & index) const; sera utile pour décider quelles parties de notre modèle seront éditables ou non (la colonne des éléments sera en lecture seule).
Les éléments seront stockés à l'intérieur d'une liste de QPair. Pour rappel, une paire est un ensemble composé de deux éléments. Le premier élément sera le texte QString et le deuxième la valeur associée (int). La méthode void addElement(const QString & element, int value); permettra d'ajouter un élément à l'intérieur du modèle.
IV-A-2. flags()▲
Qt
::
ItemFlags TableModel::
flags(const
QModelIndex
&
index) const
{
if
(index.column() ==
Element)
{
return
Qt
::
ItemIsEnabled |
Qt
::
ItemIsSelectable;
}
else
if
(index.column() ==
Value)
{
return
Qt
::
ItemIsEnabled |
Qt
::
ItemIsSelectable |
Qt
::
ItemIsEditable;
}
return
QAbstractTableModel
::
flags(index);
}
À l'intérieur de la fonction, on vérifie la colonne visée grâce à la méthode column() de index. Si la colonne est la colonne des éléments (Element), on retourne les drapeaux Qt::ItemIsEnabled | Qt::ItemIsEnabled. Ceci signifie que notre élément sera activé et sélectionnable mais pas éditable. Si la colonne est la colonne des valeurs (Value), on y ajoute le drapeau Qt::ItemIsEditable, permettant à l'utilisateur d'éditer l'élément.
Si on ne se trouve dans aucune de ces configurations (ce qui ne devrait pas être le cas ici), on laisse la responsabilité à la classe de base de retourner les drapeaux nécessaires.
IV-A-3. rowCount() et columnCount()▲
int
TableModel::
columnCount(const
QModelIndex
&
parent) const
{
return
parent.isValid() ? 0
: Count;
}
Si l'index parent est valide (ce modèle n'est pas hiérarchique), on renvoie 0, sinon Count (énumération précédemment définie).
int
TableModel::
rowCount(const
QModelIndex
&
parent) const
{
return
parent.isValid() ? 0
: mElements.count();
}
Comme précédemment, on renvoie 0 si parent est valide. Sinon, le nombre de lignes du modèle correspond au nombre d'éléments de mElements : mElements.count().
IV-A-4. data(), headerData() et setData()▲
QVariant
TableModel::
data(const
QModelIndex
&
index, int
role) const
{
if
(!
index.isValid() ||
index.row() <
0
||
index.row() >=
mElements.count())
{
return
QVariant
();
}
switch
(role)
{
case
Qt
::
DisplayRole:
case
Qt
::
EditRole:
if
(index.column() ==
Element)
{
return
mElements[index.row()].first;
}
else
if
(index.column() ==
Value)
{
return
mElements[index.row()].second;
}
break
;
}
return
QVariant
();
}
On commence par vérifier la validité de l'index. Si tel n'est pas le cas, on renvoie une donnée invalide : QVariant().
On renvoie ensuite, selon le rôle role, la donnée correspondante. Dans ce modèle, on prend en compte les rôles Qt::DisplayRole et Qt::EditRole. S'il s'agit d'un de ces deux rôles, on renvoie le premier élément de la paire correspondant à l'index index si la colonne est Element ou bien le second pour la colonne Value.
Pour tous les rôles restants, on retourne une donnée invalide.
QVariant
TableModel::
headerData(int
section, Qt
::
Orientation orientation, int
role) const
{
if
(orientation ==
Qt
::
Horizontal &&
role ==
Qt
::
DisplayRole)
{
switch
(section)
{
case
Element:
return
trUtf8("Elément"
);
break
;
case
Value:
return
trUtf8("Valeur"
);
break
;
}
}
return
QAbstractTableModel
::
headerData(section, orientation, role);
}
Ce code ne devrait pas avoir besoin de descriptions supplémentaires. Si on est dans l'orientation Horizontal avec le rôle DisplayRole, on renvoie le texte correspondant à la section (colonne).
bool
TableModel::
setData(const
QModelIndex
&
index, const
QVariant
&
value, int
role)
{
if
(!
index.isValid() ||
index.row() <
0
||
index.row() >=
mElements.count())
{
return
false
;
}
switch
(role)
{
case
Qt
::
DisplayRole:
case
Qt
::
EditRole:
if
(index.column() ==
Element)
{
mElements[index.row()].first =
value.toString();
}
else
if
(index.column() ==
Value)
{
mElements[index.row()].second =
value.toInt();
}
emit
dataChanged(index, index);
return
true
;
break
;
}
return
false
;
}
Voir la fonction data(). Il est par ailleurs important de prendre soin d'émettre le signal void QAbstractItemModel::dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight), afin de notifier à la vue que les données ont changé.
IV-A-5. addElement()▲
void
TableModel::
addElement(const
QString
&
element, int
value)
{
const
int
count =
mElements.count();
if
(value <
0
)
{
value =
0
;
}
else
if
(value >
100
)
{
value =
100
;
}
beginInsertRows(QModelIndex
(), count, count);
mElements <<
qMakePair
(element, value);
endInsertRows();
}
On commence par récupérer le nombre d'éléments courant puis on s'assure que value est comprise entre 0 et 100. La fonction void QAbstractItemModel::beginInsertRows(const QModelIndex & parent, int first, int last) permet de commencer une opération d'insertion de ligne dans un modèle (il est nécessaire de l'appeler). Après avoir ajouté l'élément à l'intérieur de la liste en utilisant la fonction utilitaire QPair<T1, T2> qMakePair(const T1 & value1, const T2 & value2), on termine l'opération d'insertion avec endInsertRows().
IV-A-6. Exercice▲
En vous aidant de la documentation, modifiez le modèle afin que les éléments de la première colonne soient alignés de manière centrée.
Premier indice : la fonction à modifier est data().
Deuxième indice : le rôle à gérer est Qt::TextAligmentRole.
case Qt::TextAlignmentRole:
if (index.column() == Element)
{
return Qt::AlignCenter;
}
break;
IV-B. Le délégué▲
Le modèle étant désormais terminé et prêt à l'emploi, on va s'atteler au délégué, qui nous permettra d'éditer les valeurs grâce à un curseur et de dessiner une barre en dehors du mode édition.
IV-B-1. La classe▲
class
TableDelegate : public
QStyledItemDelegate
{
Q_OBJECT
public
:
explicit
TableDelegate(QObject
*
parent =
0
);
QWidget
*
createEditor(QWidget
*
parent, const
QStyleOptionViewItem
&
option, const
QModelIndex
&
index) const
;
void
setEditorData(QWidget
*
editor, const
QModelIndex
&
index) const
;
void
setModelData(QWidget
*
editor, QAbstractItemModel
*
model, const
QModelIndex
&
index) const
;
void
paint(QPainter
*
painter, const
QStyleOptionViewItem
&
option, const
QModelIndex
&
index) const
;
void
updateEditorGeometry(QWidget
*
editor, const
QStyleOptionViewItem
&
option, const
QModelIndex
&
index) const
;
}
;
Aucune surprise ici. On crée une classe TableDelegate héritant de QStyledItemDelegate et on redéfinit les méthodes nécessaires (voir précédemment).
IV-B-2. createEditor()▲
QWidget
*
TableDelegate::
createEditor(QWidget
*
parent, const
QStyleOptionViewItem
&
option,
const
QModelIndex
&
index) const
{
if
(index.column() ==
TableModel::
Value)
{
QSlider
*
editor =
new
QSlider
(Qt
::
Horizontal, parent);
editor->
setRange(0
, 100
);
editor->
setAutoFillBackground(true
);
return
editor;
}
return
QStyledItemDelegate
::
createEditor(parent, option, index);
}
Comme à l'habitude, on agit en fonction de la colonne (column()). Dans le cas de la colonne Value, on crée un QSlider horizontal. On lui affecte un intervalle de valeurs de 0 à 100 et on place la propriété autoFillBackground à true afin d'obtenir un fond opaque. Sinon, on laisse la responsabilité à la classe parente de créer l'éditeur.
IV-B-3. setEditorData() et setModelData()▲
void
TableDelegate::
setEditorData(QWidget
*
editor, const
QModelIndex
&
index) const
{
if
(index.column() ==
TableModel::
Value)
{
QSlider
*
slider =
qobject_cast
<
QSlider
*>
(editor);
if
(slider)
{
const
int
value =
index.model()->
data(index).toInt();
slider->
setValue(value);
}
}
else
{
QStyledItemDelegate
::
setEditorData(editor, index);
}
}
Afin d'initialiser correctement le curseur, on récupère la valeur correspondante dans le modèle à l'index index (que l'on n'oublie pas de convertir en int, la fonction data() renvoyant un QVariant) puis on l'affecte au curseur en appelant la fonction setValue(). Le paramètre editor étant de type QWidget, on n'oublie pas de le convertir en QSlider à l'aide de qobject_cast.
void
TableDelegate::
setModelData(QWidget
*
editor, QAbstractItemModel
*
model, const
QModelIndex
&
index) const
{
if
(index.column() ==
TableModel::
Value)
{
QSlider
*
slider =
qobject_cast
<
QSlider
*>
(editor);
if
(editor)
{
model->
setData(index, slider->
value());
}
}
else
{
QStyledItemDelegate
::
setModelData(editor, model, index);
}
}
On réalise ici l'opération inverse. Lors de la fin de l'édition, on récupère la valeur du curseur puis on l'affecte à notre modèle à l'index index. Ceci permet donc de mettre à jour le modèle une fois l'édition terminée et l'éditeur fermé.
IV-B-4. updateEditorGeometry()▲
void
TableDelegate::
updateEditorGeometry(QWidget
*
editor, const
QStyleOptionViewItem
&
option,
const
QModelIndex
&
index) const
{
Q_UNUSED
(index);
editor->
setGeometry(option.rect);
}
Comme convenu, on met à jour la géométrie (position et taille) de l'éditeur via les informations du paramètre option. La macro Q_UNUSED permet de marquer le paramètre index comme non utilisé, afin de ne pas obtenir d'avertissement lors de la compilation (unused parameter).
IV-B-5. paint()▲
void
TableDelegate::
paint(QPainter
*
painter, const
QStyleOptionViewItem
&
option, const
QModelIndex
&
index) const
{
if
(index.column() ==
TableModel::
Value)
{
painter->
save();
const
int
value =
index.model()->
data(index).toInt();
QRect
rect(option.rect);
const
int
width =
(value *
rect.width()) /
100
;
rect.setWidth(width);
QColor
c;
if
(value <=
20
)
{
c =
Qt
::
red;
}
else
if
(value <=
50
)
{
c =
QColor
(240
, 96
, 0
);
}
else
{
c =
Qt
::
green;
}
painter->
fillRect(rect, c);
QTextOption
o;
o.setAlignment(Qt
::
AlignCenter);
painter->
drawText(option.rect, QString
("%1 %"
).arg(value), o);
painter->
restore();
}
else
{
QStyledItemDelegate
::
paint(painter, option, index);
}
}
Pour finir, la partie rendu. Souhaitant uniquement agir sur la colonne des valeurs, on commence par vérifier si on est dans ce cas. Sinon, on laisse la responsabilité à la classe parente d'effectuer le rendu.
On commence par enregistrer (sauvegarder) l'état courant du painter. On récupère la valeur située à l'index correspondant afin de calculer la largeur effective de la barre grâce à un simple produit en croix. On obtient donc un rectangle QRect. On en profite pour affecter une couleur différente selon la valeur correspondante :
- rouge si la valeur est inférieure ou égale à 20 ;
- orange si la valeur est inférieure ou égale à 50 ;
- vert sinon.
Disposant du rectangle et de sa couleur, on utilise la fonction fillRect() pour dessiner et remplir le rectangle de la couleur correspondante. Pour finir, on dessine le texte correspondant à la valeur suivie du pourcentage, de manière centrée, avant de restaurer l'état du painter préalablement sauvegardé.
Ceci conclut donc le programme. Il est laissé au soin du lecteur de le compléter en créant la vue, le modèle puis le délégué avant de remplir le modèle des valeurs souhaitées, afin d'avoir un programme complet et opérationnel.
V. Conclusion▲
Au terme de ce tutoriel, vous aurez appris :
- ce que sont les délégués : des composants permettant de personnaliser l'édition et le rendu des éléments à l'intérieur d'une vue ;
- comment créer son propre délégué.
D'autre part, on a vu, au travers d'un exemple simple, comment créer de toutes pièces son propre modèle. Pour un autre exemple d'utilisation, un peu plus concret, la documentation fournit par ailleurs un Star Delegate Example.
VI. Remerciements▲
Merci à Thibaut Cuvelier, Claude Leloup et Maxime Gault pour leur relecture !