Résumé de la théorie des modèles/vues Qt [Partie 2]
- 1. Gestion de la sélection dans la vue Elément
- 2. Créer un nouveau modèle
- 3. Classe de commodité d'affichage basée sur les éléments
- 4. Utilisez la vue des éléments par glisser-déposer
- 5. Modèle de procuration
- 6. Modèle de référence de sous-classement
- Seven, modèle / classe de vue
1. Gestion de la sélection dans la vue Elément
1. Conception
Le modèle de sélection utilisé dans la classe de vue d'élément fournit une description générale des sélections basée sur l'architecture modèle/vue. Alors que les classes standard pour manipuler les sélections sont suffisantes pour les vues d'éléments fournies, le modèle de sélection nous permet de créer des modèles de sélection spécialisés pour répondre aux besoins de nos propres modèles et vues d'éléments.
Les informations sur l'élément sélectionné dans la vue sont stockées dans une instance de la classe QItemSelectionModel. Il gère les index de modèle pour les éléments d'un modèle unique, indépendamment de toute vue. Puisqu'un modèle peut avoir plusieurs vues, les sélections peuvent être partagées entre les vues, ce qui permet à une application d'afficher plusieurs vues de manière cohérente.
Les sélections consistent en des plages de sélection. Ces méthodes conservent efficacement les informations sur les grandes sélections en enregistrant uniquement les indices de modèle de début et de fin de chaque plage de sélections. Créer des ensembles non contigus d'éléments de sélection en utilisant plusieurs plages de sélection pour décrire les sélections ;
La sélection est appliquée à la collection d'indices de modèle détenus par le modèle de sélection. La dernière application sélectionnée est appelée la sélection actuelle. L'effet de cette sélection peut être modifié en utilisant certains types de commandes de sélection même après son application. Ceux-ci sont discutés plus loin dans cette section.
1. Élément actuel et élément sélectionné
Dans une vue, il y a toujours un élément courant et un élément sélectionné, qui sont deux états distincts. Un élément peut être l'élément actuel ou l'élément sélectionné en même temps. La vue est chargée de s'assurer qu'il y a toujours un élément courant, par exemple la navigation au clavier nécessite un élément courant.
Le tableau ci-dessous met en évidence les différences entre l'élément actuel et l'élément sélectionné.
élément actuel | Élément sélectionné |
---|---|
Il ne peut y avoir qu'un seul élément courant. | Il peut y avoir plusieurs sélections. |
L'élément actuel changera avec la navigation au clavier ou les clics de souris. | Lorsqu'un utilisateur interagit avec un élément, le fait que l'état de sélection de l'élément soit défini ou non dépend de plusieurs modes prédéfinis, par exemple, sélection unique, sélection multiple, etc. |
Si la touche d'édition F2 est enfoncée ou si l'élément est double-cliqué, l'élément actuel sera édité (à condition que l'édition soit activée). | L'élément actuel peut être utilisé avec des ancres pour spécifier des plages de sélection ou de désélection (ou une combinaison des deux). |
L'élément actuel est représenté par un rectangle de focus. | L'élément sélectionné est représenté par un rectangle de sélection. |
Lors de la manipulation de sélections, QItemSelectionModel peut généralement être considéré comme un enregistrement des états de sélection de tous les éléments d'un modèle d'élément. Une fois le modèle de sélection établi, les éléments peuvent être sélectionnés, désélectionnés ou basculés entre leurs états sélectionnés sans savoir quels éléments sont déjà sélectionnés. Les index de tous les éléments sélectionnés peuvent être obtenus à tout moment, et d'autres composants peuvent être informés du changement du modèle de sélection via le mécanisme de signal et de créneau.
2. Utilisez le modèle de sélection
La classe de vue standard fournit un modèle de sélection par défaut qui peut être utilisé dans la plupart des applications. Le modèle de sélection appartenant à une vue peut être obtenu à l'aide de la fonction selectionModel() de la vue, et partagé entre plusieurs vues via setSelectionModel(), il n'est donc généralement pas nécessaire de construire un nouveau modèle de sélection.
Créez une sélection en spécifiant un modèle et une paire d'indices de modèle QItemSelection. Cela utilise des indices pour faire référence aux éléments du modèle donné et les interprète comme des éléments en haut à gauche et en bas à droite dans le bloc d'éléments sélectionnés. Pour appliquer une sélection à un élément du modèle, la sélection doit être soumise au modèle de sélection ; cela peut être réalisé de plusieurs manières, chacune ayant un effet différent sur les sélections déjà présentes dans le modèle de sélection.
1. Sélectionnez l'élément
Afin de démontrer certaines des principales caractéristiques de la sélection, nous créons une instance d'un modèle de table personnalisé avec 32 éléments au total, puis ouvrons une vue de table pour afficher les données qu'elle contient :
TableModel *model = new TableModel(8, 4, &app);
QTableView *table = new QTableView(0);
table->setModel(model);
QItemSelectionModel *selectionModel = table->selectionModel();
Le modèle de sélection par défaut pour la vue du tableau sera récupéré pour une utilisation ultérieure. Au lieu de modifier les éléments du modèle, nous sélectionnons certains éléments qui seront affichés dans le coin supérieur gauche du tableau. Pour cela, il faut obtenir l'index du modèle correspondant aux éléments supérieur gauche et inférieur droit de la zone à sélectionner :
QModelIndex topLeft;
QModelIndex bottomRight;
topLeft = model->index(0, 0, QModelIndex());
bottomRight = model->index(5, 2, QModelIndex());
Pour sélectionner ces éléments dans le modèle et voir les modifications correspondantes dans le tableau, un objet de sélection doit être construit puis appliqué au modèle de sélection :
QItemSelection selection(topLeft, bottomRight);
selectionModel->select(selection, QItemSelectionModel::Select);
Appliquez la sélection au modèle de sélection à l'aide de la commande définie par la combinaison des drapeaux de sélection.
Dans ce cas, le drapeau utilisé fait que les éléments enregistrés dans l'objet de sélection sont inclus dans le modèle de sélection, quel que soit leur état antérieur. La sélection résultante s'affiche dans la vue.
La sélection des éléments d'éléments peut être modifiée par diverses opérations définies par les drapeaux de sélection. Les choix produits par ces opérations peuvent avoir des structures complexes, mais le modèle de choix représente effectivement ces choix. Lorsque vous apprendrez à mettre à jour une sélection, nous verrons comment utiliser les différents indicateurs de sélection pour manipuler les éléments sélectionnés.
2. Lire l'état de sélection
L'index de modèle stocké dans le modèle de sélection peut être lu à l'aide de la fonction selectedindex(). Cette fonction renvoie une liste non triée d'indices de modèle, qui peuvent être itérés tant que nous savons à quel modèle ils correspondent :
const QModelIndexList indexes = selectionModel->selectedIndexes();
for (const QModelIndex &index : indexes) {
QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
model->setData(index, text);
}
Le code ci-dessus utilise une boucle for basée sur une plage pour itérer et modifier l'élément à l'index renvoyé par le modèle de sélection.
Le modèle de sélection émet des signaux pour indiquer les changements de sélection. Ils informent les autres composants des modifications apportées à la sélection globale et à l'élément actuellement ciblé dans le modèle d'élément. Nous pouvons connecter le signal selectionChanged() à une fonction slot et vérifier quel élément du modèle est coché ou décoché lorsque la sélection change.
La fonction slot est appelée avec deux objets QItemSelection : l'un contenant une liste d'indices correspondant aux éléments nouvellement sélectionnés ; l'autre contenant les indices correspondant aux éléments nouvellement désélectionnés.
Dans le code ci-dessous, nous fournissons un emplacement qui reçoit le signal selectionChanged(), remplit l'élément sélectionné avec une chaîne et efface le contenu de l'élément désélectionné.
void MainWindow::updateSelection(const QItemSelection &selected, const QItemSelection &deselected)
{
QModelIndexList items = selected.indexes();
for (const QModelIndex &index : qAsConst(items)) {
QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
model->setData(index, text);
}
items = deselected.indexes();
for (const QModelIndex &index : qAsConst(items)) {
model->setData(index, QString());
}
Nous pouvons garder une trace de l'élément actuellement focalisé en connectant le signal currentChanged() à une fonction slot appelée avec deux index de modèle. Ceux-ci correspondent respectivement à l'élément ciblé précédent et à l'élément actuellement ciblé.
Dans le code ci-dessous, nous fournissons un slot qui reçoit le signal currentChanged() et utilisons les informations fournies pour mettre à jour la barre d'état de QMainWindow :
void MainWindow::changeCurrent(const QModelIndex ¤t, const QModelIndex &previous)
{
statusBar()->showMessage(tr("Moved from (%1,%2) to (%3,%4)")
.arg(previous.row())
.arg(previous.column())
.arg(current.row()).arg(current.column()));
}
Les sélections effectuées par l'utilisateur peuvent être directement surveillées via ces signaux, mais nous pouvons également mettre à jour directement le modèle de sélection.
3. Mettre à jour la sélection
Les commandes de sélection sont fournies par des combinaisons de drapeaux de sélection, définis par QItemSelectionModel::SelectionFlag. Chaque indicateur de sélection indique au modèle de sélection comment mettre à jour l'enregistrement interne des éléments sélectionnés lorsque l'une des fonctions select() est appelée. L'indicateur le plus couramment utilisé est l'indicateur de sélection, qui demande au modèle de sélection d'enregistrer l'élément spécifié comme sélectionné. L'indicateur de basculement amène le modèle de sélection à inverser l'état des éléments spécifiés, c'est-à-dire à sélectionner tous les éléments non sélectionnés donnés et à désélectionner tous les éléments actuellement sélectionnés. L'indicateur de désélection est utilisé pour désélectionner tous les éléments spécifiés.
Les éléments individuels du modèle de sélection sont mis à jour en créant une sélection d'éléments et en l'appliquant au modèle de sélection. Dans le code ci-dessous, nous appliquons une deuxième sélection d'éléments au modèle de table illustré ci-dessus, en utilisant la commande Basculer pour basculer l'état de sélection d'un élément donné.
QItemSelection toggleSelection;
topLeft = model->index(2, 1, QModelIndex());
bottomRight = model->index(7, 3, QModelIndex());
toggleSelection.select(topLeft, bottomRight);
selectionModel->select(toggleSelection, QItemSelectionModel::Toggle);
Les résultats de l'opération sont affichés dans une vue tableau, ce qui offre un moyen pratique de visualiser :
par défaut, les commandes de sélection ne fonctionnent que sur un seul élément spécifié par l'index du modèle. Cependant, les drapeaux utilisés pour décrire les commandes de sélection peuvent être combinés avec d'autres drapeaux pour modifier des lignes et des colonnes entières. Par exemple, si vous appelez select() avec un seul index, mais avec une combinaison de select et Rows, la ligne entière contenant l'élément référencé sera sélectionnée. Le code suivant montre comment utiliser les indicateurs de lignes et de colonnes :
QItemSelection columnSelection;
topLeft = model->index(0, 1, QModelIndex());
bottomRight = model->index(0, 2, QModelIndex());
columnSelection.select(topLeft, bottomRight);
selectionModel->select(columnSelection,
QItemSelectionModel::Select | QItemSelectionModel::Columns);
QItemSelection rowSelection;
topLeft = model->index(0, 0, QModelIndex());
bottomRight = model->index(1, 0, QModelIndex());
rowSelection.select(topLeft, bottomRight);
selectionModel->select(rowSelection,
QItemSelectionModel::Select | QItemSelectionModel::Rows);
Bien que seuls 4 index soient fournis au modèle de sélection, l'utilisation des indicateurs de sélection de ligne et de colonne signifie que deux colonnes et deux lignes sont sélectionnées. L'image ci-dessous montre le résultat des deux sélections :
Les commandes exécutées sur le modèle d'exemple impliquent toutes l'accumulation d'éléments sélectionnés dans le modèle. Vous pouvez également effacer la sélection ou remplacer la sélection actuelle par une nouvelle.
Pour remplacer la sélection actuelle par une nouvelle, les autres marqueurs de sélection doivent être fusionnés avec celui actuel. Les commandes utilisant cet indicateur indiquent au modèle de sélection de remplacer l'ensemble actuel d'indices de modèle par les indices spécifiés lors de l'appel de select(). Pour effacer toutes les sélections avant de commencer à ajouter de nouvelles sélections, combinez l'indicateur d'autres sélections avec l'indicateur d'effacement. Cela a pour effet de réinitialiser la collection d'indices de modèle pour le modèle sélectionné.
4. Sélectionnez tous les éléments du modèle
Afin de sélectionner tous les éléments du modèle, une sélection doit être créée pour chaque couche du modèle qui couvre tous les éléments de cette couche. Pour ce faire, les index correspondant aux éléments en haut à gauche et en bas à droite peuvent être récupérés à l'aide d'un index parent donné :
QModelIndex topLeft = model->index(0, 0, parent);
QModelIndex bottomRight = model->index(model->rowCount(parent)-1,
model->columnCount(parent)-1, parent);
Utilisez ces indicateurs et modèles pour la sélection. Sélectionnez ensuite l'élément correspondant dans le modèle de sélection :
QItemSelection selection(topLeft, bottomRight);
selectionModel->select(selection, QItemSelectionModel::Select);
Cela doit être fait pour tous les niveaux du modèle. Pour les éléments de niveau supérieur, nous définirons l'index parent de la manière habituelle :
QModelIndex parent = QModelIndex();
Dans un modèle hiérarchique, la fonction hasChildren() est utilisée pour déterminer si un élément est un élément parent d'un autre élément.
2. Créer un nouveau modèle
La séparation fonctionnelle entre les composants modèle/vue permet la création de modèles qui peuvent tirer parti des vues existantes. Cette approche nous permet d'utiliser des composants d'interface graphique standard tels que QListView, QTableView et QTreeView pour présenter des données provenant de diverses sources.
La classe QAbstractItemModel fournit une interface suffisamment flexible pour prendre en charge les sources de données qui organisent les informations dans une hiérarchie, permettant aux données d'être insérées, supprimées, modifiées ou triées d'une manière ou d'une autre. Il prend également en charge le glisser-déposer.
Les classes QAbstractListModel et QAbstractTableModel fournissent un support d'interface pour des structures de données plus simples et non hiérarchiques, et sont plus faciles à utiliser comme points de départ pour des modèles de liste et de table simples.
Dans cette section, nous allons créer un modèle simple en lecture seule pour explorer les principes de base de l'architecture modèle/vue. Plus loin dans cette section, nous adapterons ce modèle simple afin que les utilisateurs puissent modifier les éléments.
1. Concevoir un modèle
Lors de la création d'un nouveau modèle pour une structure de données existante, il est important de déterminer quel type de modèle doit être utilisé pour fournir une interface aux données. Si la structure de données peut être représentée sous la forme d'une liste ou d'un tableau d'éléments, des sous-classes de QAbstractListModel ou QAbstractTableModel peuvent être créées, car ces classes fournissent des implémentations par défaut appropriées pour de nombreuses fonctions.
Cependant, si la structure de données sous-jacente ne peut être représentée que par une arborescence hiérarchique, il est nécessaire de sous-classer QAbstractItemModel. L'exemple de modèle d'arbre simple adopte cette approche.
Dans cette section, nous allons implémenter un modèle simple basé sur une liste de chaînes, donc QAbstractListModel fournit une classe de base idéale sur laquelle s'appuyer.
Quelle que soit la forme que prend la structure de données sous-jacente, il est généralement judicieux de compléter l'API standard de QAbstractItemModel par une API qui permet un accès plus naturel à la structure de données sous-jacente dans des modèles spécialisés. Cela facilite le remplissage du modèle avec des données, mais permet toujours à d'autres composants génériques de modèle/vue d'interagir avec lui à l'aide de l'API standard. Le modèle décrit ci-dessous fournit un constructeur personnalisé à cet effet.
2. Exemple de modèle en lecture seule
Le modèle implémenté ici est un modèle de données simple, non hiérarchique et en lecture seule basé sur la classe standard QStringListModel. Il a une QStringList comme source de données interne et n'implémente que ce qui est nécessaire pour créer un modèle fonctionnel. Pour faciliter l'implémentation, nous sous-classons QAbstractListModel car il définit un comportement par défaut sensible pour le modèle de liste et il expose une interface plus simple que la classe QAbstractItemModel.
Lors de l'implémentation du modèle, il est important de se rappeler que QAbstractItemModel ne stocke aucune donnée lui-même, il fournit simplement l'interface que la vue utilise pour accéder aux données. Pour un modèle minimal en lecture seule, seules quelques fonctions doivent être implémentées, car la plupart des interfaces ont des implémentations par défaut. La déclaration de classe est la suivante :
class StringListModel : public QAbstractListModel
{
Q_OBJECT
public:
StringListModel(const QStringList &strings, QObject *parent = nullptr)
: QAbstractListModel(parent), stringList(strings) {
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
private:
QStringList stringList;
};
En plus du constructeur du modèle, il suffit d'implémenter deux fonctions : rowCount() renvoie le nombre de lignes du modèle et data() renvoie une donnée correspondant à l'index de modèle spécifié.
Les modèles bien comportés implémentent également headerData() pour fournir des vues arborescentes et des vues tabulaires avec du contenu à afficher dans leurs en-têtes.
Notez qu'il s'agit d'un modèle non hiérarchique, nous n'avons donc pas à nous soucier des relations parent-enfant. Si notre modèle est hiérarchique, nous devons également implémenter les fonctions index() et parent().
La liste des chaînes est stockée en interne dans la variable membre privée stringList.
1. La taille du modèle
Nous voulons que le nombre de lignes dans le modèle soit le même que le nombre de chaînes dans la liste des chaînes. Nous implémentons la fonction rowCount() comme ceci :
int StringListModel::rowCount(const QModelIndex &parent) const
{
return stringList.count();
}
Étant donné que le modèle n'est pas hiérarchique, nous pouvons ignorer en toute sécurité l'index de modèle correspondant au parent. Par défaut, les modèles dérivés de QAbstractListModel ne contiennent qu'une seule colonne, nous n'avons donc pas besoin de réimplémenter la fonction columnCount().
2. en-tête et données du modèle
Pour les éléments en vue, nous voulons retourner les chaînes dans la liste des chaînes. La fonction data() est chargée de renvoyer la donnée correspondant au paramètre index :
QVariant StringListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() >= stringList.size())
return QVariant();
if (role == Qt::DisplayRole)
return stringList.at(index.row());
else
return QVariant();
}
Nous renvoyons un QVariant valide uniquement si l'index de modèle fourni est valide, le numéro de ligne se trouve dans la plage d'entrées de la liste de chaînes et le rôle demandé est celui que nous prenons en charge.
Certaines vues, telles que QTreeView et QTableView, peuvent afficher le titre avec les données de l'élément. Si notre modèle est affiché dans une vue avec un en-tête, nous voulons que l'en-tête affiche les numéros de ligne et de colonne. Nous pouvons fournir des informations sur les fichiers d'en-tête en sous-classant la fonction headerData() :
QVariant StringListModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
if (orientation == Qt::Horizontal)
return QStringLiteral("Column %1").arg(section);
else
return QStringLiteral("Row %1").arg(section);
}
De même, nous renvoyons un QVariant valide uniquement si le caractère est celui que nous prenons en charge. Tenez également compte de l'orientation de l'en-tête lorsque vous décidez des données exactes à renvoyer.
Toutes les vues n'affichent pas les en-têtes avec les données d'élément, et celles qui le font peuvent être configurées pour les masquer. Néanmoins, il est recommandé d'implémenter la fonction headerData() pour fournir des informations pertinentes sur les données fournies par le modèle.
Un élément peut avoir plusieurs rôles, et différentes données seront fournies en fonction des rôles spécifiés. Les éléments de notre modèle n'ont qu'un seul rôle, DisplayRole, nous renvoyons donc les données de l'élément quel que soit le rôle attribué. Cependant, nous pouvons réutiliser les données fournies pour DisplayRole dans d'autres rôles, par exemple la vue peut utiliser ToolTipRole pour afficher les informations sur l'élément dans l'info-bulle.
3. Modèle modifiable
Le modèle en lecture seule montre comment présenter des sélections simples à l'utilisateur, mais pour de nombreuses applications, le modèle de liste modifiable est plus utile. Nous pouvons modifier le modèle en lecture seule pour rendre l'élément modifiable en modifiant la fonction data() que nous avons implémentée pour la lecture seule et en implémentant deux fonctions supplémentaires : flags() et setData(). Les déclarations de fonction suivantes sont ajoutées à la définition de classe :
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole) override;
1. Rendre le modèle modifiable
Le délégué vérifie si un élément est modifiable avant de créer un éditeur. Le modèle doit faire savoir au délégué que ses éléments sont modifiables. Pour ce faire, nous renvoyons les indicateurs corrects pour chaque élément du modèle ; dans ce cas, nous activons tous les éléments et les rendons à la fois sélectionnables et modifiables :
Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::ItemIsEnabled;
return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}
Notez que nous n'avons pas besoin de savoir comment le délégué effectue le processus d'édition réel. Nous avons juste besoin de fournir au délégué un moyen de définir les données dans le modèle. Ceci est réalisé avec la fonction setData() :
bool StringListModel::setData(const QModelIndex &index,
const QVariant &value, int role)
{
if (index.isValid() && role == Qt::EditRole) {
stringList.replace(index.row(), value.toString());
emit dataChanged(index, index, {
role});
return true;
}
return false;
}
Dans ce modèle, l'entrée dans la liste des chaînes correspondant à l'index du modèle est remplacée par la valeur fournie. Cependant, avant de modifier une liste de chaînes, vous devez vous assurer que l'index est valide, que les éléments sont du type correct et que les rôles sont pris en charge. Par convention, nous nous en tenons au rôle EditRole, puisque c'est ce qu'utilise le délégué d'élément standard.
Cependant, pour les valeurs booléennes, nous pouvons utiliser Qt::CheckStateRole et définir le drapeau Qt::ItemIsUserCheckable ; puis utiliser une case à cocher pour modifier la valeur. Les données sous-jacentes de ce modèle sont les mêmes pour tous les acteurs, ce détail facilite donc l'intégration du modèle avec des composants standard.
Après avoir défini les données, le modèle doit informer la vue que certaines données ont changé. Cela se fait en émettant le signal datachchanged(). Étant donné qu'un seul élément de données a changé, la plage d'éléments spécifiés dans le signal est limitée à un index de modèle.
De plus, la fonction data() doit être modifiée pour ajouter le test Qt::EditRole :
QVariant StringListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() >= stringList.size())
return QVariant();
if (role == Qt::DisplayRole || role == Qt::EditRole)
return stringList.at(index.row());
else
return QVariant();
}
2. Insérer et supprimer des lignes
Le nombre de lignes et de colonnes du modèle peut être modifié. Dans le modèle de liste de chaînes, cela n'a de sens que de modifier le nombre de lignes, nous ne réimplémentons donc que les fonctions d'insertion et de suppression de lignes. Ceux-ci sont déclarés dans la définition de classe :
bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
Étant donné que les lignes de ce modèle correspondent aux chaînes de la liste, la fonction insertRows() insère des chaînes vides dans la liste des chaînes avant la position spécifiée. Le nombre de chaînes insérées est égal au nombre de lignes spécifié.
L'index parent est généralement utilisé pour déterminer où dans le modèle la ligne doit être ajoutée. Dans cet exemple, nous n'avons qu'une liste de chaînes de niveau supérieur, il nous suffit donc d'insérer des chaînes vides dans la liste.
bool StringListModel::insertRows(int position, int rows, const QModelIndex &parent)
{
beginInsertRows(QModelIndex(), position, position+rows-1);
for (int row = 0; row < rows; ++row) {
stringList.insert(position, "");
}
endInsertRows();
return true;
}
Le modèle appelle d'abord la fonction beginInsertRows() pour informer les autres composants du nombre de lignes qui vont changer. La fonction spécifie les numéros de ligne de la première et de la dernière ligne à insérer, ainsi que l'index de modèle de leur parent. Après avoir modifié la liste des chaînes, il appelle endInsertRows() pour terminer l'opération et notifier aux autres composants que les dimensions du modèle ont été modifiées, en retournant true pour indiquer le succès.
Les fonctions qui suppriment des lignes du modèle sont également faciles à écrire. La ligne à supprimer du modèle est spécifiée par la position et le nombre de lignes donnés. Pour simplifier l'implémentation, nous ignorons l'index parent et supprimons simplement l'élément correspondant de la liste des chaînes.
bool StringListModel::removeRows(int position, int rows, const QModelIndex &parent)
{
beginRemoveRows(QModelIndex(), position, position+rows-1);
for (int row = 0; row < rows; ++row) {
stringList.removeAt(position);
}
endRemoveRows();
return true;
}
La fonction beginRemoveRows() est toujours appelée avant la suppression de toute donnée sous-jacente et spécifie la première et la dernière ligne à supprimer. Cela permet à d'autres composants d'accéder aux données avant qu'elles ne deviennent indisponibles. Après avoir supprimé des lignes, le modèle émet endRemoveRows() pour terminer l'opération et informer les autres composants que les dimensions du modèle ont changé.
3. Prochaines étapes
Nous pouvons afficher les éléments du modèle dans une liste verticale à l'aide de la classe QListView, en affichant les données fournies par ce modèle ou tout autre modèle. Pour le modèle de liste de chaînes, la vue fournit également un éditeur par défaut afin que l'élément puisse être utilisé. Dans la classe de vue, nous vérifions les possibilités offertes par les classes de vue standard.
Le document de référence sur les sous-classes de modèles traite plus en détail des exigences des sous-classes de QAbstractItemModel et fournit des conseils sur les fonctions virtuelles qui doivent être implémentées pour activer diverses fonctionnalités dans différents types de modèles.
3. Classe de commodité d'affichage basée sur les éléments
Les contrôles basés sur des éléments ont des noms qui reflètent leur objectif : QListWidget fournit une liste d'éléments, QTreeWidget affiche une structure arborescente à plusieurs niveaux et QTableWidget fournit un tableau d'éléments de cellule. Chaque classe hérite du comportement de la classe QAbstractItemView, qui implémente le comportement général de la sélection d'éléments et de la gestion des titres.
1. QListWidget
Une liste d'éléments à un seul niveau est généralement affichée à l'aide d'un QListWidget et de certains QListWidgetItems. Les contrôles de liste sont construits de la même manière que les autres contrôles :
QListWidget *listWidget = new QListWidget(this);
Les éléments de liste peuvent être ajoutés directement au contrôle de liste après la construction :
new QListWidgetItem(tr("Sycamore"), listWidget);
new QListWidgetItem(tr("Chestnut"), listWidget);
new QListWidgetItem(tr("Mahogany"), listWidget);
Ils peuvent également être construits sans widget de liste parent et ajoutés à la liste ultérieurement :
QListWidgetItem *newItem = new QListWidgetItem;
newItem->setText(itemText);
listWidget->insertItem(row, newItem);
Chaque élément de la liste peut afficher une étiquette de texte et une icône. La couleur et la police utilisées pour rendre le texte peuvent être modifiées pour donner à l'élément un aspect personnalisé. Les info-bulles, les invites d'état et l'aide « Qu'est-ce que c'est ? » sont facilement configurables pour garantir que la liste est correctement intégrée à l'application.
newItem->setToolTip(toolTipText);
newItem->setStatusTip(toolTipText);
newItem->setWhatsThis(whatsThisText);
Par défaut, les éléments de la liste sont affichés dans l'ordre dans lequel ils ont été créés. La liste d'éléments peut être triée selon les critères donnés dans Qt::SortOrder pour produire une liste d'éléments triée par ordre alphabétique :
listWidget->sortItems(Qt::AscendingOrder);
listWidget->sortItems(Qt::DescendingOrder);
2. Contrôle QTreeWidget
Les classes QTreeWidget et QTreeWidgetItem fournissent une arborescence ou une liste hiérarchique d'éléments. Chaque élément d'un contrôle d'arborescence peut avoir ses propres éléments enfants et peut afficher des informations pour plusieurs colonnes. Créez un contrôle de fenêtre arborescente comme n'importe quel autre contrôle de fenêtre :
QTreeWidget *treeWidget = new QTreeWidget(this);
Le nombre de colonnes doit être défini avant d'ajouter des éléments au contrôle d'arborescence. Par exemple, nous pouvons définir deux colonnes et créer un en-tête pour fournir des étiquettes en haut de chaque colonne :
treeWidget->setColumnCount(2);
QStringList headers;
headers << tr("Subject") << tr("Default");
treeWidget->setHeaderLabels(headers);
Le moyen le plus simple de définir des étiquettes pour chaque section consiste à fournir une liste de chaînes. Pour les en-têtes plus complexes, nous pouvons construire un élément d'arborescence, le décorer comme nous le souhaitons et l'utiliser comme en-tête du contrôle d'arborescence.
Les éléments de niveau supérieur d'un contrôle d'arborescence sont construits avec le contrôle d'arborescence comme parent. Ils peuvent être insérés dans n'importe quel ordre, ou nous pouvons nous assurer qu'ils sont répertoriés dans un ordre spécifique en spécifiant l'élément précédent lors de la construction de chaque élément :
QTreeWidgetItem *cities = new QTreeWidgetItem(treeWidget);
cities->setText(0, tr("Cities"));
QTreeWidgetItem *osloItem = new QTreeWidgetItem(cities);
osloItem->setText(0, tr("Oslo"));
osloItem->setText(1, tr("Yes"));
QTreeWidgetItem *planets = new QTreeWidgetItem(treeWidget, cities);
Le contrôle d'arborescence gère les éléments de niveau supérieur légèrement différemment des autres éléments situés plus en profondeur dans l'arborescence. Les éléments peuvent être supprimés du niveau supérieur de l'arborescence en appelant la fonction takeTopLevelItem() du contrôle d'arborescence, mais les éléments sont supprimés des niveaux inférieurs en appelant la fonction takeChild() de son élément parent. Utilisez la fonction insertTopLevelItem() pour insérer des éléments au niveau supérieur de l'arborescence. Aux niveaux inférieurs de l'arborescence, utilisez la fonction insertChild() du parent.
Il est facile de déplacer des éléments entre les niveaux supérieur et inférieur de l'arborescence. Nous avons juste besoin de vérifier si l'élément est un élément de niveau supérieur, cette information est fournie par la fonction parent() de chaque élément. Par exemple, on peut supprimer la rubrique en cours dans un champ arbre quelle que soit sa position :
QTreeWidgetItem *parent = currentItem->parent();
int index;
if (parent) {
index = parent->indexOfChild(treeWidget->currentItem());
delete parent->takeChild(index);
} else {
index = treeWidget->indexOfTopLevelItem(treeWidget->currentItem());
delete treeWidget->takeTopLevelItem(index);
}
L'insertion d'éléments ailleurs dans le contrôle d'arborescence suit le même schéma :
QTreeWidgetItem *parent = currentItem->parent();
QTreeWidgetItem *newItem;
if (parent)
newItem = new QTreeWidgetItem(parent, treeWidget->currentItem());
else
newItem = new QTreeWidgetItem(treeWidget, treeWidget->currentItem());
3. QTable Widget
Des tableaux d'éléments similaires à ceux trouvés dans les applications de tableur sont construits à l'aide de QTableWidget et QTableWidgetItem. Ils fournissent un contrôle de tableau déroulant avec des en-têtes et des éléments à utiliser.
Les tableaux peuvent être créés avec un certain nombre de lignes et de colonnes, ou ils peuvent être ajoutés à des tableaux de taille indéterminée selon les besoins.
QTableWidget *tableWidget;
tableWidget = new QTableWidget(12, 3, this);
Les éléments sont construits en dehors de la table puis ajoutés à la table à l'emplacement souhaité :
QTableWidgetItem *newItem = new QTableWidgetItem(tr("%1").arg(
pow(row, column+1)));
tableWidget->setItem(row, column, newItem);
Des en-têtes horizontaux et verticaux peuvent être ajoutés à un tableau en construisant des éléments à l'extérieur du tableau et en les utilisant comme en-têtes :
QTableWidgetItem *valuesHeaderItem = new QTableWidgetItem(tr("Values"));
tableWidget->setHorizontalHeaderItem(0, valuesHeaderItem);
Notez que les lignes et les colonnes du tableau commencent à 0.
4. Caractéristiques communes
Chaque classe de commodité dispose d'un certain nombre de fonctionnalités publiques basées sur des éléments disponibles via la même interface dans chaque classe. Nous décrivons ces fonctionnalités dans les sections suivantes et fournissons quelques exemples des différentes commandes. Voir la liste des classes de modèle/vue pour chaque contrôle pour plus de détails sur chaque fonction utilisée.
1. Objets cachés
Parfois, il est utile de masquer des éléments dans le contrôle d'affichage des éléments au lieu de les supprimer. Les éléments de tous les contrôles ci-dessus peuvent être masqués et affichés ultérieurement. Nous pouvons déterminer si un élément est masqué en appelant la fonction isItemHidden(), et nous pouvons utiliser setItemHidden() pour masquer un élément.
Étant donné que cette opération est basée sur les articles, les mêmes fonctions sont disponibles pour les trois classes de commodité.
2. Sélectionnez
La façon dont un élément est sélectionné est contrôlée par le mode de sélection du contrôle (QAbstractItemView::SelectionMode). Cette propriété contrôle si l'utilisateur peut sélectionner un ou plusieurs éléments et, dans les sélections à plusieurs éléments, si la sélection doit être une plage contiguë d'éléments. Le mode de sélection fonctionne de la même manière pour tous les contrôles ci-dessus.
Sélection unique : le mode Sélection unique par défaut est le plus approprié lorsque l'utilisateur doit sélectionner un seul élément dans le contrôle. Dans ce mode, l'élément actuel et l'élément sélectionné sont identiques.
Sélection d'éléments multiples : dans ce mode, l'utilisateur peut basculer l'état de sélection de n'importe quel élément du contrôle sans modifier la sélection existante, de la même manière que les cases à cocher non exclusives peuvent être activées indépendamment.
Sélection étendue : les contrôles qui nécessitent généralement la sélection de nombreux éléments adjacents, tels que les contrôles dans une feuille de calcul, doivent utiliser le mode Sélection étendue. Dans ce mode, une plage contiguë d'éléments du contrôle peut être sélectionnée avec la souris et le clavier. Si des touches de modification sont utilisées, des sélections complexes peuvent également être créées, y compris de nombreux éléments qui ne sont pas adjacents à d'autres éléments sélectionnés dans le contrôle.
Si l'utilisateur sélectionne un élément sans utiliser de touche de modification, efface la sélection existante.
Utilisez la fonction selectedItems() pour lire les éléments sélectionnés dans le contrôle et fournir une liste itérable d'éléments associés. Par exemple, nous pouvons trouver la somme de toutes les valeurs de la liste d'éléments sélectionnés avec le code suivant :
const QList<QTableWidgetItem *> selected = tableWidget->selectedItems();
int number = 0;
double total = 0;
for (QTableWidgetItem *item : selected) {
bool ok;
double value = item->text().toDouble(&ok);
if (ok && !item->text().isEmpty()) {
total += value;
number++;
}
}
Notez que pour le mode de sélection unique, l'élément actuel sera dans la sélection. Dans les modes de sélection multiple et de sélection étendue, l'élément actuel peut ne pas figurer dans la sélection, selon la façon dont l'utilisateur forme la sélection.
3. Rechercher
Il est souvent utile de pouvoir rechercher des éléments dans un contrôle de vue d'élément, soit en tant que développeur, soit en tant que service fourni aux utilisateurs. Les trois classes de commodité de vue d'élément fournissent une fonction commune findItems() pour la rendre aussi cohérente et simple que possible.
Recherche le texte contenu dans les entrées selon des critères spécifiés par un ensemble de valeurs dans Qt::MatchFlags. Nous pouvons obtenir une liste des éléments correspondants en utilisant la fonction findItems() :
const QList<QTreeWidgetItem *> found = treeWidget->findItems(
itemText, Qt::MatchWildcard);
for (QTreeWidgetItem *item : found) {
item->setSelected(true);
// Show the item->text(0) for each item.
}
Le code ci-dessus provoque la sélection de l'élément dans le contrôle d'arborescence lorsqu'il contient le texte donné dans la chaîne de recherche. Ce mode est également disponible pour les champs liste et table.
4. Utilisez la vue des éléments par glisser-déposer
L'infrastructure de glisser-déposer de Qt est entièrement prise en charge par le framework modèle/vue. Les éléments des listes, des tables et des arborescences peuvent être déplacés dans la vue, et les données peuvent être importées et exportées sous forme de données encodées MIME.
Les vues standard prennent automatiquement en charge le glisser-déposer en interne, ce qui permet de déplacer des éléments pour modifier l'ordre dans lequel ils sont affichés. Par défaut, ces vues ne prennent pas en charge le glisser-déposer, car elles sont configurées pour l'utilisation la plus simple et la plus courante. Pour autoriser le glissement d'un élément, certaines propriétés de la vue doivent être activées et l'élément lui-même doit également autoriser le glissement.
Un modèle qui permet uniquement d'exporter des éléments à partir de la vue, et ne permet pas d'y insérer des données, a moins de besoins qu'un modèle de glisser-déposer entièrement activé.
1. Vue facile à utiliser
Par défaut, chaque type d'élément utilisé par QListWidget, QTableWidget et QTreeWidget est configuré pour utiliser un ensemble différent d'indicateurs. Par exemple, chaque QListWidgetItem ou QTreeWidgetItem est initialement activé, inspectable, sélectionnable et peut être utilisé comme source pour les opérations de glisser-déposer ; chaque QTableWidgetItem peut également être modifié et utilisé comme cible pour les opérations de glisser-déposer.
Bien que tous les éléments standard définissent un indicateur ou deux pour le glisser-déposer, vous devrez généralement définir diverses propriétés sur la vue elle-même pour tirer parti de la prise en charge intégrée du glisser-déposer :
- Pour activer le déplacement d'éléments, définissez la propriété dragEnabled de la vue sur true.
- Pour permettre aux utilisateurs de déposer des éléments internes ou externes dans une vue, définissez la propriété acceptDrops du viewport() de la vue sur true.
- Pour montrer à l'utilisateur où se trouvait l'élément actuellement déposé lorsqu'il a été déposé, définissez la propriété showDropIndicator de la vue. Cela fournit à l'utilisateur des informations continuellement mises à jour sur la position de l'élément dans la vue.
Par exemple, nous pouvons activer la fonctionnalité glisser-déposer dans un contrôle de liste avec la ligne de code suivante :
QListWidget *listWidget = new QListWidget(this);
listWidget->setSelectionMode(QAbstractItemView::SingleSelection);
listWidget->setDragEnabled(true);
listWidget->viewport()->setAcceptDrops(true);
listWidget->setDropIndicatorShown(true);
Le résultat est un contrôle de liste qui permet de copier des éléments dans des vues et permet même aux utilisateurs de faire glisser des éléments entre des vues contenant le même type de données. Dans les deux cas, les éléments sont copiés plutôt que déplacés.
Pour que l'utilisateur puisse déplacer des éléments dans la vue, nous devons définir le dragDropMode du contrôle de liste :
listWidget->setDragDropMode(QAbstractItemView::InternalMove);
1. Utiliser des classes de modèles/vues
Les vues configurées pour le glisser-déposer suivent le même modèle que les vues de commodité. Par exemple, une QListView peut être configurée de la même manière qu'une QListWidget :
QListView *listView = new QListView(this);
listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
listView->setDragEnabled(true);
listView->setAcceptDrops(true);
listView->setDropIndicatorShown(true);
L'accès aux données affichées par la vue étant contrôlé par le modèle, le modèle utilisé doit également prendre en charge les opérations de glisser-déposer. Les opérations supportées par le modèle peuvent être spécifiées en réimplémentant la fonction QAbstractItemModel::supporteddroptions(). Par exemple, utilisez le code suivant pour activer les opérations de copie et de déplacement :
Qt::DropActions DragDropListModel::supportedDropActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
Bien que n'importe quelle combinaison de valeurs dans Qt::droptions puisse être donnée, le modèle doit être écrit pour les prendre en charge. Par exemple, pour permettre à Qt::MoveAction d'être utilisé correctement avec un modèle de liste, le modèle doit fournir une implémentation de QAbstractItemModel::removeRows(), directement ou héritée d'une classe de base.
2. Prise en charge du glisser-déposer pour l'élément
En réimplémentant la fonction QAbstractItemModel::flags() pour fournir les drapeaux appropriés, le modèle indique à la vue quels éléments sont déplaçables et lesquels sont acceptés.
Par exemple, un modèle qui fournit une liste simple basée sur QAbstractListModel peut activer les opérations de glisser-déposer sur chaque élément en s'assurant que les drapeaux renvoyés contiennent les valeurs Qt::ItemIsDragEnabled et Qt::ItemIsDropEnabled :
Qt::ItemFlags DragDropListModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags defaultFlags = QStringListModel::flags(index);
if (index.isValid())
return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
else
return Qt::ItemIsDropEnabled | defaultFlags;
}
Notez que les éléments peuvent être glissés et déposés au-dessus du modèle, mais seuls les éléments valides peuvent être glissés.
Dans le code ci-dessus, puisque le modèle est dérivé de QStringListModel, nous obtenons un ensemble d'indicateurs par défaut en appelant l'implémentation de la fonction flags().
3. Encoder les données exportées
Lorsque des éléments de données sont exportés du modèle via des opérations de glisser-déposer, ils sont encodés dans le format approprié pour un ou plusieurs types MIME. Les modèles déclarent les types MIME qu'ils peuvent utiliser pour fournir des éléments en réimplémentant la fonction QAbstractItemModel::mimeTypes(), renvoyant une liste de types MIME standard.
Par exemple, un modèle qui ne fournit que du texte brut fournirait l'implémentation suivante :
QStringList DragDropListModel::mimeTypes() const
{
QStringList types;
types << "application/vnd.text.list";
return types;
}
Le modèle doit également fournir du code, encodant les données dans le format publicitaire. Ceci est réalisé en réimplémentant la fonction QAbstractItemModel::mimeData() pour fournir un objet QMimeData, comme dans toute autre opération de glisser-déposer.
Le code ci-dessous montre comment encoder chaque élément de données correspondant à une liste d'index donnée en texte brut et le stocker dans un objet QMimeData.
QMimeData *DragDropListModel::mimeData(const QModelIndexList &indexes) const
{
QMimeData *mimeData = new QMimeData;
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
for (const QModelIndex &index : indexes) {
if (index.isValid()) {
QString text = data(index, Qt::DisplayRole).toString();
stream << text;
}
}
mimeData->setData("application/vnd.text.list", encodedData);
return mimeData;
}
Puisqu'une liste d'indices de modèle est fournie à la fonction, cette méthode est suffisamment générale pour fonctionner avec des modèles hiérarchiques et non hiérarchiques.
Notez que les types de données personnalisés doivent être déclarés en tant que méta-objets et que les opérateurs de flux doivent être implémentés pour eux. Voir la description de la classe QMetaObject pour plus de détails.
4. Insérez les données supprimées dans le modèle
La manière dont un modèle donné gère les données manquantes dépend de son type (liste, table ou arborescence) et de la manière dont son contenu peut être présenté à l'utilisateur. En général, la méthode utilisée pour tenir compte des données manquantes doit être la méthode la plus adaptée au stockage de données sous-jacent du modèle.
Différents types de modèles ont tendance à traiter les données manquantes de différentes manières. Les modèles de liste et de table fournissent simplement une structure plate pour stocker les éléments de données. Ainsi, ils peuvent insérer de nouvelles lignes (et colonnes) lorsque des données sont supprimées sur des éléments existants dans la vue, ou ils peuvent écraser le contenu de l'élément dans le modèle avec certaines données fournies. Un modèle d'arbre est souvent capable d'ajouter des enfants contenant de nouvelles données à son magasin de données sous-jacent, de sorte que son comportement est plus prévisible en ce qui concerne l'utilisateur.
Les données supprimées sont gérées par la réimplémentation d'un modèle de QAbstractItemModel::dropMimeData(). Par exemple, un modèle qui gère de simples listes de chaînes pourrait fournir une implémentation qui gère les données placées sur les éléments existants séparément des données placées au-dessus du modèle (c'est-à-dire les données placées sur des éléments non valides).
En réimplémentant QAbstractItemModel::canDropMimeData(), le modèle peut interdire la suppression de certains éléments, ou dépendre des données supprimées.
Le modèle doit d'abord s'assurer que l'opération doit être effectuée, que les données fournies sont dans un format consommable, et que leur destination dans le modèle est valide :
bool DragDropListModel::canDropMimeData(const QMimeData *data,
Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
Q_UNUSED(action);
Q_UNUSED(row);
Q_UNUSED(parent);
if (!data->hasFormat("application/vnd.text.list"))
return false;
if (column > 0)
return false;
return true;
}
bool DragDropListModel::dropMimeData(const QMimeData *data,
Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
if (!canDropMimeData(data, action, row, column, parent))
return false;
if (action == Qt::IgnoreAction)
return true;
Un modèle simple de liste de chaînes à une seule colonne peut indiquer un échec si les données fournies ne sont pas en texte brut ou si un numéro de colonne non valide est spécifié pour suppression.
Les données à insérer dans le modèle sont traitées différemment selon qu'elles sont placées sur un élément existant. Dans cet exemple simple, nous souhaitons autoriser le placement d'éléments entre des éléments existants, avant le premier élément de la liste et après le dernier élément.
Lorsqu'une suppression se produit, l'index de modèle correspondant à l'élément parent est soit valide, indiquant que la suppression s'est produite sur un élément, soit invalide, indiquant que la suppression s'est produite quelque part dans la vue correspondant au niveau supérieur du modèle.
int beginRow;
if (row != -1)
beginRow = row;
Nous vérifions d'abord le numéro de ligne fourni pour voir s'il peut être utilisé pour insérer un élément dans le modèle, que l'index parent soit valide ou non.
else if (parent.isValid())
beginRow = parent.row();
La suppression se produit sur les éléments si l'index du modèle parent est valide. Dans ce modèle de liste simple, nous découvrons le numéro de ligne de l'élément et utilisons cette valeur pour insérer l'élément supprimé au niveau supérieur du modèle.
else
beginRow = rowCount(QModelIndex());
Lorsqu'un glisser-déposer se produit ailleurs dans la vue et que les numéros de ligne ne sont pas disponibles, nous ajoutons l'élément au niveau supérieur du modèle.
Dans un modèle hiérarchique, lorsqu'un élément est glissé-déposé, il est préférable d'insérer le nouvel élément dans le modèle en tant qu'enfant de cet élément. Dans l'exemple simple présenté ici, le modèle n'a qu'un seul niveau, cette approche n'est donc pas adaptée.
5. Décodez les données importées
Chaque implémentation de dropMimeData() doit également décoder les données et les insérer dans la structure de données sous-jacente du modèle.
Pour un modèle de liste de chaînes simple, les éléments encodés peuvent être décodés et diffusés dans une QStringList :
QByteArray encodedData = data->data("application/vnd.text.list");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
QStringList newItems;
int rows = 0;
while (!stream.atEnd()) {
QString text;
stream >> text;
newItems << text;
++rows;
}
La chaîne peut ensuite être insérée dans le magasin de données sous-jacent. Pour plus de cohérence, cela peut être fait via la propre interface du modèle :
insertRows(beginRow, rows, QModelIndex());
for (const QString &text : qAsConst(newItems)) {
QModelIndex idx = index(beginRow, 0, QModelIndex());
setData(idx, text);
beginRow++;
}
return true;
}
Notez que le modèle doit généralement fournir des implémentations des fonctions QAbstractItemModel::insertRows() et QAbstractItemModel::setData().
5. Modèle de procuration
Dans le cadre modèle/vue, les éléments de données fournis par un seul modèle peuvent être partagés par un nombre quelconque de vues, et chaque vue peut représenter les mêmes informations d'une manière complètement différente. Les vues et les délégués personnalisés sont un moyen efficace de fournir des représentations complètement différentes des mêmes données. Cependant, les applications doivent souvent fournir des vues régulières pour les versions traitées des mêmes données, telles que des vues qui trient différemment une liste d'éléments.
Bien qu'il puisse sembler approprié d'effectuer les opérations de tri et de filtrage en tant que fonctions internes de la vue, cette approche ne permet pas à plusieurs vues de partager les résultats de telles opérations potentiellement coûteuses. Une autre approche (impliquant un tri dans le modèle lui-même) conduit à des problèmes similaires, dans la mesure où chaque vue doit afficher des données organisées selon les traitements les plus récents.
Pour résoudre ce problème, le framework modèle/vue utilise un modèle proxy pour gérer les informations fournies entre chaque modèle et vue. Du point de vue de la vue, un modèle proxy est un composant qui se comporte comme un modèle normal et accède aux données du modèle source au nom de la vue. Les signaux et les slots utilisés par la structure modèle/vue garantissent que chaque vue est mise à jour de manière appropriée, quel que soit le nombre de modèles proxy placés entre elle-même et le modèle source.
1. Utiliser le modèle proxy
Un modèle proxy peut être inséré entre un modèle existant et n'importe quel nombre de vues. Qt fournit un modèle de proxy standard, QSortFilterProxyModel, qui est généralement instancié et utilisé directement, mais peut également être sous-classé pour fournir un comportement de filtrage et de tri personnalisé. La classe QSortFilterProxyModel peut être utilisée des manières suivantes :
QSortFilterProxyModel *filterModel = new QSortFilterProxyModel(parent);
filterModel->setSourceModel(stringListModel);
QListView *filteredView = new QListView;
filteredView->setModel(filterModel);
Puisque les modèles proxy héritent de QAbstractItemModel, ils peuvent être connectés à n'importe quel type de vue et peuvent être partagés entre les vues. Ils peuvent également être utilisés pour traiter les informations obtenues à partir d'autres modèles d'agents dans l'arrangement de pipeline.
La classe QSortFilterProxyModel est conçue pour être instanciée et utilisée directement dans l'application. Des modèles proxy plus spécialisés peuvent être créés en sous-classant cette classe et en implémentant les opérations de comparaison requises.
2. Modèle de proxy personnalisé
Généralement, le type de traitement utilisé dans un modèle proxy implique le mappage de chaque élément de données depuis son emplacement d'origine dans le modèle source vers un emplacement différent dans le modèle proxy. Dans certains modèles, certains éléments peuvent ne pas avoir de position correspondante dans le modèle de proxy ; ces modèles sont des modèles de proxy de filtre. La vue accède aux éléments à l'aide d'index de modèle fournis par le modèle proxy, qui ne contiennent aucune information sur le modèle source ou la position de l'élément d'origine dans ce modèle.
QSortFilterProxyModel permet de filtrer les données dans le modèle source avant de les fournir à la vue, et permet également de fournir le contenu du modèle source à la vue sous forme de données pré-triées.
1. Modèle de filtre personnalisé
La classe QSortFilterProxyModel fournit un modèle de filtrage assez général qui peut être utilisé dans une variété de situations courantes. Pour les utilisateurs avancés, QSortFilterProxyModel peut être sous-classé pour fournir un mécanisme qui permet d'implémenter des filtres personnalisés.
Les sous-classes de QSortFilterProxyModel peuvent réimplémenter deux fonctions virtuelles qui sont appelées lorsqu'elles sont demandées par le modèle de proxy ou lors de l'utilisation de l'index de modèle :
- filterAcceptsColumn() est utilisé pour filtrer des colonnes spécifiques d'une partie du modèle source.
- filterAcceptsRow() est utilisé pour filtrer des lignes spécifiques d'une partie du modèle source.
Les implémentations par défaut des fonctions ci-dessus dans QSortFilterProxyModel renvoient true pour s'assurer que tous les éléments sont passés à la vue ; les réimplémentations de ces fonctions doivent renvoyer false pour filtrer les lignes et les colonnes individuelles.
2. Modèle de tri personnalisé
Une instance de QSortFilterProxyModel utilise la fonction std::stable_sort() pour configurer un mappage entre les éléments du modèle source et les éléments du modèle proxy, permettant à une hiérarchie d'éléments triés d'être exposée à la vue sans modifier la structure du modèle source. Pour fournir un comportement de tri personnalisé, la fonction lessThan() doit être réimplémentée pour effectuer des comparaisons personnalisées.
3. Modèle de référence de sous-classement
Les sous-classes de modèle sont nécessaires pour fournir des implémentations de la plupart des fonctions virtuelles définies dans la classe de base QAbstractItemModel. Le nombre de ces fonctions qui doivent être implémentées dépend du type de modèle - qu'il fournisse une simple liste, un tableau ou une hiérarchie d'éléments complexe pour la vue. Les modèles qui héritent de QAbstractListModel et QAbstractTableModel peuvent profiter des implémentations par défaut des fonctions fournies par ces classes. Un modèle qui expose des éléments de données dans une structure arborescente doit fournir des implémentations pour de nombreuses fonctions virtuelles dans qab
6. Modèle de référence de sous-classement
Les sous-classes de modèle sont nécessaires pour fournir des implémentations de la plupart des fonctions virtuelles définies dans la classe de base QAbstractItemModel. Le nombre de ces fonctions qui doivent être implémentées dépend du type de modèle - qu'il fournisse une simple liste, un tableau ou une hiérarchie d'éléments complexe pour la vue. Les modèles qui héritent de QAbstractListModel et QAbstractTableModel peuvent profiter des implémentations par défaut des fonctions fournies par ces classes. Un modèle qui expose des éléments de données dans une structure arborescente doit fournir des implémentations pour de nombreuses fonctions virtuelles dans QAbstractItemModel.
Les fonctions qui doivent être implémentées dans les sous-classes de modèles peuvent être divisées en trois groupes :
- **Traitement des données d'éléments** : tous les modèles doivent implémenter une fonctionnalité qui permet aux vues et aux délégués d'interroger les dimensions du modèle, d'inspecter les éléments et de récupérer des données.
- Navigation et création d'index : les modèles hiérarchiques doivent fournir des fonctions que les vues peuvent appeler pour naviguer dans l'arborescence qu'elles exposent et obtenir l'index de modèle pour un élément.
- Prise en charge du glisser-déposer et gestion du type MIME : le modèle hérite de fonctions qui contrôlent la manière dont les opérations de glisser-déposer internes et externes sont effectuées. Ces fonctions permettent de décrire des éléments de données avec des types MIME que d'autres composants et applications peuvent comprendre.
1. Traitement des données des articles
Les modèles peuvent fournir différents niveaux d'accès aux données qu'ils fournissent : ils peuvent être de simples composants en lecture seule, certains modèles peuvent prendre en charge des opérations de redimensionnement, tandis que d'autres peuvent permettre la modification d'éléments.
2. Accès en lecture seule
Afin de fournir un accès en lecture seule aux données fournies par un modèle, les fonctions suivantes doivent être implémentées dans des sous-classes de modèle :
Nom de la fonction | fonction |
---|---|
drapeaux() | Utilisé par d'autres composants pour obtenir des informations sur chaque élément fourni par le modèle. Dans de nombreux modèles, la combinaison d'indicateurs doit inclure Qt::ItemIsEnabled et Qt::ItemIsSelectable. |
données() | Utilisé pour fournir des données d'élément aux vues et aux délégués. En général, le modèle n'a besoin de fournir des données que pour Qt::DisplayRole et tous les rôles d'utilisateur spécifiques à l'application, mais il est également recommandé de fournir des données pour Qt::ToolTipRole, Qt::AccessibleTextRole et Qt::AccessibleDescriptionRole. Consultez la documentation Qt::ItemDataRole enum pour plus d'informations sur les types associés à chaque rôle. |
headerData () | Fournit à la vue des informations à afficher dans son titre. Seules les vues pouvant afficher des informations d'en-tête peuvent récupérer ces informations. |
rowCount () | Fournit le nombre de lignes de données exposées par le modèle. |
Les quatre fonctions ci-dessus doivent être implémentées dans tous les types de modèles, y compris les modèles de liste (sous-classes QAbstractListModel) et les modèles de table (sous-classes QAbstractTableModel).
De plus, les fonctions suivantes doivent être implémentées dans des sous-classes directes de QAbstractTableModel et QAbstractItemModel :
Nom de la fonction | fonction |
---|---|
nombre_colonnes () | Fournit le nombre de colonnes de données exposées par le modèle. Le modèle de liste ne fournit pas cette fonction car elle est déjà implémentée dans QAbstractListModel. |
1. Éléments modifiables
Un modèle modifiable permet la modification d'éléments de données et peut également fournir des fonctions permettant l'insertion et la suppression de lignes et de colonnes. Pour activer la fonctionnalité d'édition, les fonctions suivantes doivent être correctement implémentées :
Nom de la fonction | fonction |
---|---|
drapeaux() | La combinaison appropriée d'indicateurs doit être renvoyée pour chaque article. Plus précisément, la valeur renvoyée par cette fonction doit inclure Qt::ItemIsEditable en plus de la valeur qui s'applique aux éléments du modèle en lecture seule. |
setData () | Utilisé pour modifier l'élément de données associé à l'index de modèle spécifié. Afin de pouvoir accepter les entrées utilisateur fournies par les éléments de l'interface utilisateur, cette fonction doit gérer les données liées à Qt::EditRole. Les implémentations peuvent également accepter des données associées à de nombreux types de rôles différents spécifiés par Qt::ItemDataRole. Après avoir modifié un élément de données, le modèle doit émettre le signal datachchanged() pour notifier les autres composants du changement. |
setHeaderData () | Utilisé pour modifier les informations d'en-tête horizontales et verticales. Après avoir modifié un élément de données, le modèle doit émettre le signal headerdatachchanged() pour notifier les autres composants du changement. |
2. Modèle redimensionnable
Tous les types de modèles peuvent prendre en charge l'insertion et la suppression de lignes. Les modèles de table et les modèles hiérarchiques peuvent également prendre en charge l'insertion et la suppression de colonnes. Il est important de notifier les autres composants avant et après les changements de dimension du modèle. Par conséquent, les fonctions suivantes peuvent être implémentées pour permettre le redimensionnement du modèle, mais les implémentations doivent garantir que les fonctions appropriées sont appelées pour notifier les vues et les délégués attachés :
Nom de la fonction | fonction |
---|---|
insertRows() | Utilisé pour ajouter de nouvelles lignes de données et de nouveaux éléments à tous les types de modèles. Les implémentations doivent appeler beginInsertRows() avant d'insérer de nouvelles lignes dans les structures de données sous-jacentes, et endInsertRows() immédiatement après. |
removeRows() | Utilisé pour supprimer des lignes et les éléments de données qu'elles contiennent de tous les types de modèles. Les implémentations doivent appeler beginRemoveRows() avant de supprimer des lignes de toute structure de données sous-jacente, et appeler endRemoveRows() immédiatement après. |
insertColumns() | Utilisé pour ajouter de nouvelles colonnes et éléments de données au modèle de table et au modèle hiérarchique. Les implémentations doivent appeler beginInsertColumns() avant d'insérer de nouvelles colonnes dans les structures de données sous-jacentes, et endInsertColumns() immédiatement après. |
supprimerColonnes() | Utilisé pour supprimer des colonnes et les éléments de données qu'elles contiennent du modèle de table et du modèle hiérarchique. Les implémentations doivent appeler beginRemoveColumns() avant de supprimer des colonnes de toute structure de données sous-jacente, et appeler endRemoveColumns() immédiatement après. |
En règle générale, ces fonctions doivent renvoyer true si l'opération a réussi. Dans certains cas, cependant, l'opération peut ne réussir que partiellement ; par exemple, si moins de lignes que le nombre spécifié ont pu être insérées. Dans ce cas, model doit renvoyer false pour indiquer qu'aucun module complémentaire ne peut être activé pour gérer ce cas.
Le signal émis par la fonction appelée dans l'implémentation de l'API de redimensionnement donne au composant attaché une chance d'agir avant que les données ne deviennent indisponibles. L'encapsulation des opérations d'insertion et de suppression avec des fonctions de début et de fin permet également au modèle de gérer correctement les index de modèle persistants.
En règle générale, les fonctions de début et de fin sont capables de notifier aux autres composants les modifications apportées à la structure sous-jacente du modèle. Pour les modifications plus complexes de la structure du modèle, qui peuvent impliquer une réorganisation interne, l'ordonnancement des données ou tout autre changement structurel, il est nécessaire d'effectuer la séquence suivante :
- Disposition du signalAboutToBeChanged()
- Met à jour les données internes représentant la structure du modèle.
- Utilisez changePersistentIndexList() pour mettre à jour les index persistants
- Émettez le signal layoutChanged().
Cette séquence peut être utilisée pour toute mise à jour structurelle à la place de méthodes de conservation plus avancées et pratiques. Par exemple, si un modèle avec 2 millions de lignes doit supprimer toutes les lignes impaires, cela représente 1 million de plages disjointes, chacune avec 1 élément. Utiliser beginRemoveRows et endRemoveRows 1 million de fois est possible, mais c'est évidemment inefficace. Au lieu de cela, cela peut être exprimé comme un seul changement de mise en page qui met à jour tous les index persistants nécessaires à la fois.
3. Remplissage retardé des données du modèle
Le remplissage paresseux des données de modèle permet effectivement de retarder les demandes d'informations de modèle jusqu'à ce que la vue en ait réellement besoin.
Certains modèles doivent récupérer des données à partir de sources distantes ou doivent effectuer des opérations chronophages pour obtenir des informations sur la façon dont les données sont organisées. Étant donné que les vues demandent généralement autant d'informations que possible pour afficher avec précision les données du modèle, il est utile de limiter la quantité d'informations renvoyées à la vue afin de réduire les demandes de données ultérieures inutiles.
Dans un modèle hiérarchique, trouver le nombre d'enfants d'un élément donné est une opération coûteuse, il est donc utile de s'assurer que l'implémentation rowCount() du modèle n'est appelée que lorsque cela est nécessaire. Dans ce cas, la fonction hasChildren() peut être réimplémentée pour fournir aux vues un moyen peu coûteux de vérifier la présence d'enfants et, dans le cas de QTreeViews, dessiner des décorations appropriées pour leurs parents.
Que la réimplémentation de hasChildren() renvoie true ou false, la vue peut ne pas avoir besoin d'appeler rowCount() pour déterminer le nombre d'enfants existants. Par exemple, un QTreeView n'a pas besoin de savoir combien d'enfants il a si le parent ne se développe pas pour les afficher.
Réimplémenter hasChildren() pour renvoyer true sans condition est parfois une approche utile si vous savez que de nombreux éléments auront des enfants. Cela garantit que tout en remplissant initialement les données du modèle aussi rapidement que possible, chaque élément peut ensuite être vérifié pour les enfants. Le seul inconvénient est que les éléments sans enfants peuvent s'afficher de manière incorrecte dans certaines vues jusqu'à ce que l'utilisateur essaie d'afficher des enfants inexistants.
3. Navigation et création d'index de modèles
Les modèles hiérarchiques sont nécessaires pour fournir des fonctions que les vues peuvent appeler pour naviguer dans l'arborescence qu'elles exposent et pour obtenir l'index de modèle d'un élément.
1. Parents et enfants
Étant donné que la structure exposée à la vue est déterminée par la structure de données sous-jacente, chaque sous-classe de modèle peut créer son propre index de modèle en fournissant une implémentation des fonctions suivantes :
Nom de la fonction | fonction |
---|---|
indice () | Étant donné l'index de modèle d'un élément parent, cette fonction permet aux vues et aux délégués d'accéder aux enfants de l'élément. Si aucun enfant valide n'est trouvé (correspondant à la ligne, à la colonne et à l'index de modèle parent spécifiés), la fonction doit renvoyer QModelIndex(), qui est un index de modèle non valide. |
parent() | Fournit l'index de modèle correspondant au parent d'un enfant donné. Si l'index de modèle spécifié correspond à un élément de niveau supérieur dans le modèle, ou s'il n'y a pas de parent valide dans le modèle, la fonction doit renvoyer un index de modèle invalide créé avec un constructeur QModelIndex() vide. |
Les deux fonctions ci-dessus utilisent la fonction de fabrique createIndex() pour générer des index à utiliser par d'autres composants. model fournit généralement un identifiant unique à cette fonction pour s'assurer que l'index de modèle peut être réassocié ultérieurement à son élément correspondant.
4. Prise en charge du glisser-déposer et gestion du type MIME
Les classes de modèle/vue prennent en charge le glisser-déposer, fournissant un comportement par défaut suffisant pour de nombreuses applications. Cependant, lors d'une opération de glisser-déposer, il est également possible de personnaliser la façon dont les éléments sont codés, s'ils sont copiés ou déplacés par défaut, et comment ils sont insérés dans un modèle existant.
En outre, le comportement spécialisé que la classe de vue de commodité implémente doit correspondre étroitement aux attentes des développeurs existants. La section d'affichage pratique fournit un aperçu de ce comportement.
1. Données MIME
Par défaut, les modèles et vues intégrés utilisent un type MIME interne (application/x-qabstractitemmodeldatalist) pour transmettre des informations sur l'index du modèle. Cela spécifie les données de la liste d'éléments, contenant les numéros de ligne et de colonne pour chaque élément, ainsi que des informations sur les rôles pris en charge par chaque élément.
Les données encodées à l'aide de ce type MIME peuvent être obtenues en appelant QAbstractItemModel::mimeData() avec un QModelIndexList contenant les éléments à sérialiser.
Lors de l'implémentation de la prise en charge du glisser-déposer dans un modèle personnalisé, il est possible d'exporter des éléments de données dans un format spécial en réimplémentant les fonctions suivantes :
- La fonction mimeData()
peut être réimplémentée pour renvoyer des données dans d'autres formats que le type MIME interne application/x-qabstractitemmodeldatalist par défaut.
Les sous-classes peuvent obtenir l'objet QMimeData par défaut de la classe de base et y ajouter des données dans d'autres formats.
Pour de nombreux modèles, il est utile de fournir le contenu de l'élément dans un format commun représenté par des types MIME tels que text/plain et image/png. Notez que des images, des couleurs et des documents HTML peuvent être facilement ajoutés à un objet QMimeData à l'aide des fonctions QMimeData::setImageData(), QMimeData::setColorData() et QMimeData::setHtml().
2. Accepter les données supprimées
Lorsqu'une opération de glisser-déposer est effectuée sur une vue, le modèle sous-jacent est interrogé pour déterminer les types d'opérations qu'il prend en charge et les types MIME qu'il peut accepter. Ces informations sont fournies par les fonctions QAbstractItemModel::supporteddroptions() et QAbstractItemModel::mimeTypes(). Les modèles qui ne remplacent pas l'implémentation fournie par QAbstractItemModel prennent en charge l'opération de copie et le type MIME interne par défaut de l'élément.
Lorsque des données d'élément sérialisées sont déposées dans la vue, les données seront insérées dans le modèle actuel à l'aide de l'implémentation de QAbstractItemModel::dropMimeData(). L'implémentation par défaut de cette fonction n'écrase jamais les données du modèle ; à la place, elle tente d'insérer des éléments de données en tant que frères d'un élément ou enfants de cet élément.
Afin de tirer parti de l'implémentation par défaut de QAbstractItemModel des types MIME intégrés, les nouveaux modèles doivent fournir des réimplémentations des fonctions suivantes :
Nom de la fonction | fonction |
---|---|
insertrow () insertColumns () | Ces fonctions permettent au modèle d'insérer automatiquement de nouvelles données en utilisant l'implémentation existante fournie par QAbstractItemModel::dropMimeData(). |
setData () | Permet de remplir de nouvelles lignes et colonnes avec des éléments. |
setItemData() | Cette fonction fournit une prise en charge plus efficace pour remplir de nouveaux éléments. |
Pour accepter d'autres formes de données, ces fonctions doivent être réimplémentées :
Nom de la fonction | fonction |
---|---|
pris en chargeDropActions () | Combinaison utilisée pour renvoyer les opérations de glisser-déposer, indiquant le type d'opérations de glisser-déposer acceptées par le modèle. |
type mime () | 用于返回可由 model 解码和处理的MIME类型列表。通常,支持输入 model 的MIME类型与为外部组件使用的数据编码时可以使用的MIME类型相同。 |
dropMimeData () | 对通过拖放操作传输的数据执行实际解码,确定将在 model 中设置数据的位置,并在必要的地方插入新的行和列。如何在子类中实现此函数取决于每个 model 公开的数据的需求。 |
如果dropMimeData()函数的实现通过插入或删除行或列来更改 model 的维度,或者如果修改了数据项,则必须注意确保发出所有相关信号。简单地调用子类中其他函数的重新实现,例如setData()、insertRows()和insertColumns(),可以确保 model 的行为一致。
为了确保拖动操作正常工作,重新实现以下从 model 中删除数据的函数是很重要的:
removeRows ()
removeRow ()
removeColumns ()
removeColumn ()
有关使用 item view 拖放的详细信息,请参阅使用 item view 拖放。
3. 方便的 view
方便的 view (QListWidget、QTableWidget和QTreeWidget)覆盖了默认的拖放功能,提供了不那么灵活但更自然的行为,适合于许多应用程序。例如,由于更常见的是将数据放入QTableWidget中的单元格中,用正在传输的数据替换现有的内容,底层 model 将设置目标项的数据,而不是向 model 中插入新的行和列。有关在方便 view 中拖放的详细信息,请参见在项 view 中使用拖放。
5. 针对大量数据的性能优化
canFetchMore()函数检查父节点是否有更多可用数据,并相应地返回true或false。fetchMore()函数根据指定的父节点获取数据。这两个函数可以组合在一起,例如,在涉及增量数据的数据库查询中填充QAbstractItemModel。我们重新实现canFetchMore()来指示是否有更多的数据需要获取,并根据需要将fetchMore()填充到 model 中。
另一个例子是动态填充的树 model ,当树 model 中的分支被扩展时,我们重新实现fetchMore()。
如果fetchMore()的重新实现将行添加到 model 中,则需要调用beginInsertRows()和endInsertRows()。此外,canFetchMore()和fetchMore()都必须重新实现,因为它们的默认实现返回false并且不做任何事情。
七、 model / view 类
这些类使用 model / view 设计模式,在该模式中,底层数据(在 model 中)与用户(在 view 中)呈现和操作数据的方式保持分离。
类名 | 功能作用 |
---|---|
QAbstractItemDelegate | 用于显示和编辑 model 中的数据项 |
QAbstractItemModel | item model 类的抽象接口 |
QAbstractItemView | 项 view 类的基本功能 |
QAbstractListModel | 抽象 model ,可以子类化为一维列表 model |
QAbstractProxyModel | 可以执行排序、过滤或其他数据处理任务的代理项 model 的基类 |
QAbstractTableModel | 抽象 model ,可以被子类化为创建表 model |
QColumnView | 列 view 的 model / view 实现 |
QConcatenateTablesProxyModel | 代理多个源 model ,连接它们的行 |
QDataWidgetMapper | 数据 model 的一部分到控件之间的映射 |
QFileSystemModel | 本地文件系统的数据 model |
QHeaderView | 项 view 的标题行或标题列 |
QIdentityProxyModel | 代理未修改的源 model |
QItemDelegate | 显示和编辑来自 model 的数据项的工具 |
QItemEditorCreator | 使创建 item 编辑器创建基而不子类化QItemEditorCreatorBase成为可能 |
QItemEditorCreatorBase | 在实现新的项编辑器创建器时必须子类化的抽象基类 |
QItemEditorFactory | 用于编辑 view 和委托中的项数据的控件 |
QItemSelection | 管理关于 model 中选定项的信息 |
QItemSelectionModel | 跟踪 view 的选定项 |
QItemSelectionRange | 管理关于 model 中选定项范围的信息 |
QListView | 列表或图标 view 到 model 上 |
QListWidget | 基于 item 的列表控件 |
QListWidgetItem | 项与QListWidget项 view 类一起使用 |
QModelIndex | 用于定位数据 model 中的数据 |
QPersistentModelIndex | 用于定位数据 model 中的数据 |
QSortFilterProxyModel | 持排序和过滤在另一个 model 和 view 之间传递的数据 |
QStandardItem | 项与QStandardItemModel类一起使用 |
QStandardItemEditorCreator | 注册控件的可能性,而不必子类化QItemEditorCreatorBase |
QStandardItemModel | 用于存储自定义数据的通用 model |
QStringListModel | 为 view 提供字符串的 model |
QStyledItemDelegate | 显示和编辑来自 model 的数据项的工具 |
QTableView | 表 view 的默认 model / view 实现 |
QTableWidget | 带有默认 model 的基于项的表 view |
QTableWidgetItem | Les éléments sont utilisés dans la classe QTableWidget |
QTableWidgetSelectionRange | Méthodes d'interaction avec les sélections dans un modèle sans utiliser l'index de modèle et le modèle de sélection |
QTreeViewComment | Implémentation de modèle/vue par défaut pour l'arborescence |
QTreeWidget | Une arborescence utilisant un modèle d'arborescence prédéfini |
QTreeWidgetItem | élément à utiliser avec la classe de commodité QTreeWidget |
QTreeWidgetItemIterator | Méthode pour itérer les éléments dans une instance de QTreeWidget |