Introduction aux MVC et création d'un modèle hiérarchique simple
Par Guid le dimanche, décembre 16 2007, 17:02 - Tutoriels - Lien permanent
Ce document présente le concept général de MVC, puis s'attarde sur celui de Qt 4 et enfin, une mise en oeuvre simple du QAbstractItemModel est expliquée en détail et est basée sur sur l'exemple officiel Qt "simpletreemodel".
Version : Toutes versions à partir de Qt 4.0 (exemple fait avec C++/Qt 4.3.3)
Auteur : Guillaume -Guid- DENRY (sur le forum, Azuriel)
Présentation
L'affichage et la manipulation de données est une problèmatique aussi vieille que l'informatique elle-même. En 1979, une tentative de formalisation de ce domaine aboutit au concept de MVC (littéralement Modèle-Vue-Controlleur) qui prend tout d'abord naissance dans le monde smalltalk, et qui permet de dissocier les données de la présentation ainsi que de la logique de contrôle. Cette stratégie permet un découpage clair des concepts et de leurs implémentations, mais surtout une factorisation du code; le fameux concept DRY cher à Ruby, ainsi, alors que le modèle est entièrement dévoué à fournir des données et à en accepter, la vue est uniquement concentrée sur la façon d'afficher les données (c'est la partie émergée de l'iceberg pour l'utilisateur de l'application) tandis que le controlleur, tel un arbitre, se charge de synchroniser les évènements entre le modèle et la vue.
Qt 4 a choisi d'utiliser ces concepts au sein de plusieurs de ses composants de manipulation et d'affichage de données. Dans la bibliothèque de Trolltech, le C du MVC se retrouve combiné au V pour des raisons de praticité et on parle ainsi de ''Modèle/Vue''. A ces concepts se voit adjoint celui de delegate qui définit concrètement la façon dont sont présentées et éditées les données (par exemple via une liste déroulante, une cellule d'édition, etc).
On trouve plusieurs composants de type "vue" ; voici ceux que l'on peut trouver dans la dernière version de Qt 4 au moment où est écrit ce tutoriel (4.3.3) :
- Le
QTableViewqui permet d'afficher un ensemble de données sous forme de tableau et sans notion de hiérarchie. - Le
QTreeViewmodélise un ensemble de données hiérarchisées sous forme d'arbre. - Le
QListViewaffiche des données sous forme de liste, exactement comme le mode "liste" d'un explorateur de fichiers. - Le
QColumnViewqui affiche une succession de QListViews pour afficher une hiérarchie, à l'instar de Finder, le fameux navigateur de fichiers de MacOSX ou de celui de l'Ipod.
Il est à noter que tous ces composants trouvent leurs équivalents déjà affectés de modèles simples et qui peuvent être amplement suffisants pour un usage de base. Si vous cherchez un moyen simple d'afficher des données sans vous soucier d'optimisation, de maintenabilité, de paramétrage fin et si concevoir un système MVC ne vous séduit pas plus que cela, ce tutoriel n'est peut-être pas tout-à-fait indiqué et vous devriez peut-être vous pencher sur les QTableWidget/QTreeWidget/QListWidget qui offrent déjà pas mal de possibilités.
L'utilisation des modèles dans Qt passe systématiquement par l'héritage d'un des modèles Qt dont l'ancêtre commun est toujours la classe abstraite QAbstractItemModel.
La plupart du temps, vous n'aurez pas besoin d'hériter des vues ainsi que de définir de nouveaux delegates (ni même vous en soucier pour ces derniers); ceux qui existent déjà suffisent pour la plupart des usages. Néammoins, ce besoin pourra se faire sentir dans certaines circonstances, c'est pourquoi l'écriture d'un delegate particulier et son intégration au sein d'une vue sera abordé dans un autre tutoriel.
Le concept de MVC est abondamment expliqué dans la documentation officielle Qt et la création d'un modèle simple non hiérarchique est relativement simple à appréhender. Il se peut même que vous n'ayez pas besoin de ce tutoriel car les exemples de Qt en ce qui concerne les modèles/vues sont nombreux et je vous recommande de jeter un oeil aux exemples situés dans le répertoire itemviews du tarball officiel si le parcours d'un tutoriel n'est pas votre tasse de thé.
Ainsi, tout au long de ce document, nous allons nous concentrer sur le QTreeView et sur la création d'un modèle hiérarchique afin d'expliciter quelques points qui peuvent paraître obscures lorsqu'on s'initie à leur création. Nous tâcherons ainsi d'éclaircir le rôle des diverses méthodes virtuelles à hériter obligatoirement (méthodes abstraites) ou de façon optionnelle afin d'affiner le comportement de notre modèle.
Rétro-conception
Puisqu'il va falloir indiquer au modèle où trouver nos données et comment se représenter la hiérarchie de celles-ci, il est crucial d'avoir déjà une idée claire et précise de la façon dont nos données sont agencées en mémoire et ce qui les lie les unes aux autres.
Du côté du modèle, le QModelIndex nous offre un moyen simple de mettre en oeuvre les briques élémentaires de la modélisation de nos données.
Cette classe est le lien entre la donnée élémentaire et le modèle, mais ne se substitue pas à la donnée en elle-même; il est primordial de concevoir une structure de données hiérarchiques interne qui sera utilisée par notre modèle pour créér ses index.
De façon plus formelle, le QModelIndex connaît le modèle auquel il appartient, son père[1], son numéro de ligne (au sein des enfants de ce père, pas le numéro de ligne absolu dans le modèle) ainsi que son numéro de colonne.
Voilà en gros tout dont le modèle a besoin pour construire sa hiérarchie et on expliquera les rôles des autres champs ultérieurement. Pour la suite, pour évoquer le QModelIndex on parlera simplement d'index.
Les structures internes de nos données devront refléter ces propriétés relationnelles, par exemple, il sera indispensable qu'on puisse facilement retrouver le père d'un élément de données. Ranger nos données dans un structure arborescente est donc probablement le meilleur des choix. S'il n'est pas possible de stocker toutes les données en mémoire (typiquement dans le cas d'une base de données), il faudra tout de même pouvoir modéliser ces concepts relationnels "facilement".
Comme nous l'avons vu plus haut, concevoir un modèle dans Qt 4, c'est hériter, directement ou indirectement, de la classe QAbstractItemModel et implémenter un lot de méthodes virtuelles dont certaines sont abstraites et donc doivent être impérativement héritées sous peine d'erreur de la part du compilateur.
Ces fonctions abstraites sont :
QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex())
C'est un peu l'"usine à index" du modèle. Ici vont être créés puis renvoyés les index en fonction d'un numéro de ligne, de colonne et d'un index père.
A chaque fois que l'on aura besoin d'un index quelque part, que ce soit dans le modèle ou à l'extérieur de ce modèle, c'est cette méthode qui sera invoquée (à l'exception de la création des index pères qui seront fabriqués par le biais de la fonction parent()).
QModelIndex parent(const QModelIndex & index)
Cette méthode est utilisée pour retrouver un index père en fonction de son fils. Comme nous l'avons vu plus haut dans l'encadré, cette méthode est directement appelée par la méthode "parent()" de chaque QModelIndex appartenant au modèle.
int rowCount(const QModelIndex & parent = QModelIndex())
Cette fonction renvoie le nombre de lignes de l'index père passé en paramètre qu'on peut assimiler au nombre d'index enfants de ce père. Cette méthode peut également se révéler couteuse et dans ce cas, il est parfois utile d'envisager la redéfinition de la méthode "hasChildren()" (cette méthode fait appel à rowCount() dans son implémentation par défaut) qui permet de signaler au modèle qu'un index a des enfants sans pour autant en spécifier combien, et ainsi par exemple afficher un noeud dans la vue qui, impliquera alors un appel à rowCount().
int columnCount(const QModelIndex & parent = QModelIndex())
Cette méthode renvoie le nombre de colonnes des enfants de l'index père passé en paramètre. Dans la plupart des cas, cette méthode renverra un nombre non dépendant du père.
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole)
C'est probablement la fonction la plus sollicitée pendant la vie du modèle; elle permet de connaître les données à afficher dans la vue en fonction d'un index et d'un rôle.
Les rôles définissent dans les détails la façon dont une donnée est présentée par les vues associées. On peut par exemple invoquer la méthode data() pour demander la valeur du texte à afficher par le biais de Qt::DisplayRole ou alors la couleur de fond de la cellule qui accueuillera le texte avec le paramètre Qt::BackgroundRole, ou si nécessaire, la police de caractère à utiliser grâce à Qt::FontRole.
Les rôles ont été conçus pour être les plus exhaustifs et génériques possibles mais il est possible de les étendre à partir de la valeur Qt::UserRole qui constitue le premier role "utilisateur".
Dans le cas d'une utilisation classique, le rôle essentiel est Qt::DisplayRole car il concerne directement la donnée (très souvent et en particulier ici du texte) à afficher dans la vue.
Il faut également garder à l'esprit que data() étant appelée très fréquemment, il est important de ne pas y trouver de traitements lourds. Typiquement, je ne fais aucun appel aux outils de traduction dans cette méthode, je les déporte dans le constructeur du modèle.
Puisque Trolltech nous fournit déjà des exemples et des démonstrations de qualité, nous allons appuyer ce tutoriel sur la démo simpletreemodel (sur cette page, vous trouverez également une bonne explication en anglais de cet exemple) disponible dans tout tarball Qt (et dans certains paquets) afin d'expliquer en détail l'implémentation du modèle et les choix effectués.
simpletreemodel
Description
Cet exemple charge et affiche le contenu dans un arbre (QTreeView) d'un fichier de type "Sommaire" de livre.
Ce fichier est un ensemble de lignes de texte plus ou moins en retrait par le biais de caractères d'espacement.
Par exemple, une ligne qui commence par 8 espaces et qui précède une ligne qui débute par 4 espaces sera considérée comme "fille" de la ligne précédente.
Chaque ligne de texte est séparée en deux par un ensemble de tabulations, cet ensemble de tabulation permettra de diviser la ligne en deux colonnes dans l'arbre.
Ainsi nous avons donc un arbre à deux colonnes décrit au sein d'un fichier texte et dont voici un extrait :
Form Editing Mode How to edit a form in Qt Designer
Managing Forms Loading and saving forms
...
Layouts Objects that arrange widgets on a form
Applying and Breaking Layouts Managing widgets in layouts
Exécution
Au lancement du programme, un modèle (implémenté dans treemodel.h/treemodel.cpp) est créé avec en paramètre le texte du fichier "default.txt" chargé pour l'occasion. Ce texte est "parsé" (analysé et stocké dans une structure de données) pendant la construction du modèle, ce qui permettra son utilisation immédiate plus tard sans autre forme d'initialisation.
Puis, un QTreeView est également créé et on lui affecte le modèle.
On attribue un titre à cet arbre puis il est affiché et la boucle d'évènements est lancée; le programme est réellement visuellement disponible à ce moment là et nous pouvons voir à l'écran le QTreeView en action et le manipuler à notre guise.
Analyse
Le stockage des données
Examinons tout d'abord le contenu de treeitem.h/.cpp.
La classe TreeItem est conçue pour stocker une hiérarchie de données. Voici ses caractéristiques :
parentItempointe vers son élément père.childItemsest une liste de pointeurs vers les fils de cet élément.itemDatacontient les données de l'élément sous forme de liste de QVariant (qui est le type générique de Qt, voir la documentation).
Pour cet exemple, l'auteur aurait pu se contenter pour itemData d'une liste de QString, mais le QVariant permet d'imaginer un enrichissement de l'exemple afin de pouvoir stocker et afficher des types de données hétéroclytes au sein de la même liste. Il permet surtout d'être directement renvoyé par la méthode data() du modèle sans aucune conversion comme nous le verrons plus tard.
A l'image d'une liste chaînée, l'arbre entier est accessible par sa racine et lorsqu'on détruit un TreeItem, ses enfants sont également détruits. Notez à ce titre l'utilisation de qDeleteAll(), fonction bien pratique qui permet de détruire tous les objets pointés stockés dans une structure de données Qt (QList, QVector, etc).
Voilà qui permet de conclure sur cette classe "jumelle" à la hiérarchie des index et qui servira de conteneur de données librement consultable par le modèle de notre vue.
Le modèle
Intéressons-nous maintenant à la classe TreeModel définie dans treemodel.h/.cpp.
La méthode setupModelData() contient le code le plus complexe de l'exemple :-), il analyse le texte en paramètre et en déduit une hiérarchie de TreeItems qu'il lie au noeud parent passé en paramètre. Cette fonction est appelée uniquement dans le constructeur avec rootData préalablement créé. Ce noeud racine sera le point d'accès à l'arbre de données pour le modèle.
En dehors de setupModelData(), le reste des méthodes présentes sont des éléments hérités appartenant au QAbstractItemModel :
La méthode parent()
QModelIndex TreeModel::parent(const QModelIndex &index) const { if (!index.isValid()) return QModelIndex();
Le but de la méthode parent() est de créer et renvoyer un QModelIndex correspondant au père du QModelIndex en paramètre.
Si l'index en paramètre est considéré comme orphelin dans l'arbre, alors il convient de renvoyer un index invalide.
Dans notre cas, un index racine est un index correspondant à un TreeItem dont le père est rootData.
rootData est le seul élément dans tout l'arbre des TreeItem qui n'a aucun QModelIndex correspondant, il sert juste de père ultime référent et donc, les vrais éléments racines de l'arbre affichés à l'écran sont ses enfants directs.
parent() est également une méthode publique. Rien que pour cette raison il est important de tester le cas où l'index en paramètre est invalide.
Le père d'un index invalide est donc naturellement ... un index invalide, créé par le biais du constructeur vide du QModelIndex.
TreeItem *childItem = static_cast<TreeItem*>(index.internalPointer()); TreeItem *parentItem = childItem->parent();
internalPointer est une variable qui fait le lien entre l'index et la donnée. Chaque index du modèle sera donc lié à un TreeItem cette liaison est faite à deux endroits du modèle; la méthode index() et la méthode parent().
La première instruction se contente donc de récupérer le TreeItem associé à l'index, en passant par un static_cast, plus rapide qu'un dynamic_cast et suffisant ici.
Il existe un autre moyen de lier une donnée à un index, internalId qui est une autre propriété du QModelIndex, utile dans les cas où la donnée serait accessible via un identificateur entier et non un pointeur. (on peut imaginer une clef entière dans une base de donnée, par exemple).
La seconde instruction stocke le père du TreeItem prédédemment extrait dans parentItem.
if (parentItem == rootItem) return QModelIndex();
Nous avons vu que chaque TreeItem pointe vers son père, si l'élément père est rootItem, alors on considère du point de vue du modèle que l'index correspondant est un noeud racine et donc, on renvoie un QModelIndex invalide.
return createIndex(parentItem->row(), 0, parentItem); }
createIndex()[2] est une méthode qui permet de créer en une instruction un QModelIndex attaché au modèle.
La méthode row() du TreeItem renvoie le numéro de ligne de l'élément au sein de ses frères et c'est exactement ce que createIndex attend comme premier paramètre.
0 est passé comme numéro de colonne car le père d'un index est par convention toujours de colonne 0 dans un système hiérarchique conventionnel.
Et enfin, parentItem est passé en tant que comme pointeur interne, c'est un des deux endroits dans le code où l'on associe un TreeItem à son QModelIndex.
La méthode index()
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) return QModelIndex();
La méthode index() est publique. Il convient donc de tester ses paramètres afin de résoudre les cas invalides.
En particulier, nous aimerions savoir si les paramètres row et column ne sortent pas des limites du modèle et heureusement, pour le savoir, il existe la fonction hasIndex() qui nous renseigne à ce sujet. La documentation étant peu locace pour cette fonction, c'est dans les sources de Qt que nous allons trouver son rôle précis; cette méthode renvoie vrai si et seulement si les paramètres row et column sont compris entre 0 et le nombre maximum de lignes et de colonnes du parent passé en paramètre.
TreeItem *parentItem;
if (!parent.isValid())
parentItem = rootItem;
else
parentItem = static_cast<TreeItem*>(parent.internalPointer());
Ici nous avons besoin de récupérer l'élément de type TreeItem correspondant à l'index que nous allons renvoyer.
Nous savons que si le paramètre parent est valide, alors il aura été créé avec parent() et possèdera donc en pointeur interne sur son TreeItem "jumeau" et, dans le cas où parent est invalide, c'est rootItem qui jouera ce rôle.
TreeItem *childItem = parentItem->child(row); if (childItem) return createIndex(row, column, childItem); else return QModelIndex(); }
Maintenant que nous avons l'élément père ainsi que le numéro de ligne du fils, il est trivial d'en déduire l'élément fils avec la méthode child(). Par prudence, le retour de cette méthode est testé et createIndex() est à nouveau invoqué puis retourné par la fonction.
La méthode data()
QVariant TreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant();
Nous retrouvons le même début de scénario que pour les deux méthodes précédemment analysées, data() étant public, la validité de index est éprouvée et un QVariant() invalide est envoyé le cas échéant.
if (role != Qt::DisplayRole) return QVariant();
Dans cette démo, la vue ne sollicitera que le texte pour son affichage, et donc, tous les rôles autre que Qt::DisplayRole sont écartés. Pour se faire, un QVariant() invalide est renvoyé et signifie à l'appelant qu'il fait ce qu'il veut: pour un QTreeView, ça sera par exemple d'afficher la couleur de fond par défaut du composant dans le cas du Qt::BackgroundRole.
TreeItem *item = static_cast<TreeItem*>(index.internalPointer()); return item->data(index.column()); }
Nous avons besoin de retrouver l'élément correspondant à l'index, puis de renvoyer la donnée correspondant à son numéro de colonne.
La méthode data() renvoie un QVariant et nous voyons ici l'utilité sémantique d'avoir stocké une liste de QVariant au sein du TreeItem plutôt qu'une liste de QString, même si en l'occurrence, le renvoi d'un QString aurait été possible grace à la construction implicite du C++.
La méthode rowCount()
int TreeModel::rowCount(const QModelIndex &parent) const { TreeItem *parentItem; if (parent.column() > 0) return 0;
Nous avons vu plus haut que le père d'un index a par convention son numéro de colonne égal à 0 dans un cas de hiérarchie classique. De la même façon, tout index de colonne strictement supérieur à 0 ne possède aucun fils; ce rôle est dévolu à l'index de colonne 0.
if (!parent.isValid()) parentItem = rootItem; else parentItem = static_cast<TreeItem*>(parent.internalPointer()); return parentItem->childCount(); }
Comme pour la fonction data(), il nous faut extraire le TreeItem correspondant à l'index parent.
L'appel de rowCount() avec un index invalide signifie que l'utilisateur souhaite le nombre de noeuds racines, dans ce cas, rootItem jouera le rôle de l'élément père.
Le nombre d'enfants de l'élément père est alors renvoyé.
La méthode columnCount()
int TreeModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) return static_cast<TreeItem*>(parent.internalPointer())->columnCount(); else return rootItem->columnCount(); }
Elle est similaire à la méthode rowCount() si ce n'est une difficulté en moins puisque que l'on peut se contenter de renvoyer directement le nombre de colonnes de l'élément attaché à l'index.
Les méthodes flags() et headerData()
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const { if (!index.isValid()) return 0; return Qt::ItemIsEnabled | Qt::ItemIsSelectable; }
C'est dans cette fonction que nous allons définir finement le comportement du modèle.
Nous lui précisons ici que nous souhaitons pouvoir intéragir avec l'index en paramètre (Qt::ItemIsEnabled), ainsi que le sélectionner (Qt::ItemIsSelectable).
QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) return rootItem->data(section); return QVariant(); }
headerData() est similaire à la fonction data() si ce n'est qu'elle concerne uniquement les entêtes des lignes et colonnes. Pour notre exemple, seule l'entête des colonnes est affichée et nous utilisons donc à cette fin les valeurs de rootItem uniquement dans le cas où l'orientation est Qt::Horizontal.
Conclusion
Pour des raisons de simplicité, j'ai jugé utile de limiter ce tutoriel à l'analyse d'un exemple simple et en lecture seule et de nombreux aspects de la manipulation des modèles/vue n'y sont pas abordés. Par exemple, lorsqu'on veut modifier les données internes, comment avertir le modèle -et donc la vue- que les données ont changées ? Comment rendre le modèle éditable; doit-on utiliser les méthodes d'insertions et de suppression du modèle ou un système alternatif ? L'édition des données est trop basique, je voudrais la changer, comment faire ? Comment construire un système de recherche au sein d'un modèle ? Comment gérer les types Mime ainsi que le drag'n'drop ? Ces questions seront abordées dans un tutoriel plus avancé.
Notes
[1] Si les valeurs renvoyées par row(), column() et model() appartiennent bien en interne au QModelIndex par le biais de champs privés, le champ parent() est en revanche une invocation directe de la méthode virtuelle pure parent() du modèle de l'index. Ceci peut mettre en lumière le rôle de la fonction parent() du modèle et son implication.
[2] Il existe un constructeur du QModelIndex qui accepte à peu près les mêmes paramètres que createIndex() et qui aurait pu prendre sa place mais pour des raisons d'intégrité et de cohérence (pour ne jamais avoir d'index sans modèle référent), ce constructeur est privé; le seul moyen de créer un index valide est de passer par le modèle et donc par une des déclinaisons de createIndex().
Commentaires
Bonjour, remarque à l'auteur.
C'est très bien, on voit parfaitement que vous avez assimilé le sujet que vous abordez ici.
Et en réalité, nous comprenons très vite aussi que vous vous parlez à vous même tout au long de ce tutoriel.
Mais un tutoriel n'est t-il pas en soit prévu pour apprendre quelque chose à d'autres personnes ? Je pense que vous devriez nommer cette rubrique: "Mon blog" et non pas "tutoriel".
Ok, ce tutoriel ne vous apprend rien, j'en prends bonne note Monsieur Jeanjean13 :-)
Si j'ai écris ce document, c'est parce que le sujet me posait quelques difficultés, en particulier en ce qui concerne les détails sur les affiliations d'index et compte tenu que ça posait également des problèmes à d'autres que moi, j'ai préféré rédiger ce document plutôt que de faire comme d'habitude, c'est à dire fermer Qt Assistant et commencer à coder, même si cette démarche ne m'était à l'époque pas évidente car j'écris rarement de la documentation et encore moins de tutoriel, ceci étant mon premier.
Je pense sincèrement qu'une partie importante des rédacteurs de tutoriels le font aussi pour eux-mêmes, pour éclairer quelques zones d'ombre. Et vous savez quoi ? J'assume pleinement ce côté "égoïste", même si le moteur principal à l'époque de la rédaction de ce document était surtout d'aider des copains à mettre un pied dans l'utilisation des MVC et que c'était encore suffisamment "frais" dans mon esprit pour que je le rédige de façon plaisante.
Pour finir, j'avoue ne pas bien comprendre ce que vous reprochez à ce document et en quoi il ressemble plus à un billet de blog qu'à un tutoriel. Dommage que votre commentaire manque de détails.
Je suis vraiment désolé de voir de tel commentaire. L'écriture d'un tutoriel est long et fastidieuse, et aborder un sujet tel que celui-là est réellement difficile. Je vais une fois encore remercier Guid pour son travail.
Quant à la remarque de jeanjean13, je la trouve réellement déplacée, et si j'avais fait attention, le commentaire aurait déjà été effacé (oui, c'est de la censure). Maintenant, pour me faire mentir, je l'invite à m'envoyer un tutoriel/documentation bien écrit, pour voir.
Merci Nicolas. En réalité, je suis vraiment curieux de connaître les critiques de jeanjean13, je sais que ce tutoriel est pas forcément facile à lire, assez dense et ça manque de copies d'écran (pas évident d'illustrer les MVC avec des screenshots néanmoins). Je me suis peut-être également un peu égaré en précisions pas forcément nécessaires pour un tutoriel qui se doit d'être rapide à comprendre et à appliquer.
J'essaierai d'être plus synthétique la prochaine fois.
bonjour,
J'ai moi même déposé sur ce site une doc. Cette doc, tout comme Guid je pense, je l'ai rédigée pour mettre à plat ce que j'avais collecté et compris en recherchant à maîtriser la multitude d' infos qui concernent le dessin avec Qt4. Et, tout comme Guid, j'ai proposé au tenancier de la boutique QtFr de faire profiter les lecteurs du fruit de mon travail de collectes, d'analyse et compréhension. Alors, que l'on fasse des remarques du style "ce que vous dites là ou là est faux" ou bien "cette partie manque de clarté" ok. ça permet d'améliorer l' info, c'est positif et constructif.
Mais qu'on ai juste comme commentaire à faire de dire que le document n'apporte pas plus que la doc Qt? d'abord c'est en français, et ça, quand on maîtrise déjà pas le sujet technique, avoir des explications dans sa langue natale c'est déjà un énorme +.
D'autre part, l'auteur est très clair et répète bien que la doc et les exemples Qt sont bien fichus et peuvent suffire pour démarrer.
De plus, je rejoins Nicolas quand il dit que l'écriture de ce genre de doc c'est des heures de boulot alors un peu de respect au moins pour ce travail plutôt que de dire "ouaih bon et alors?" et alors et bien tout comme Nicolas, j'attends votre prose monsieur JeanJean13. On dit comment déjà "la critique est facile mais l'art est difficile".
Par contre, j'attends avec impatience ce que promet Guid à la fin de son tutorial : comment modifier les données internes, comment avertir le modèle -et donc la vue- que les données ont changées ? Comment rendre le modèle éditable; doit-on utiliser les méthodes d'insertions et de suppression du modèle ou un système alternatif ? etc....
Je pense qu'il va falloir faire ça par petit bout .
Et quand ce sera fait, je veux bien participer au comité de relecture pour des critiques CONSTRUCTIVES et POSITIVES of course...
Sans intention de polémiquer, un supplément de commentaires...
Il faut remarquer que la doc Qt relative au schéma vue / modèle n'est pas très claire, sinon on ne chercherait pas ailleurs pour comprendre. Reconnaissons que ceux qui fournissent des alternatives ont un rôle constructif à encourager.
Pour ce qui est du ton de la remarque blog vs. tutoriel, admettons quand même qu'elle n'est pas tout à fait infondée, c'est mon avis. Qu'elle soit publiée est néanmoins à mettre au crédit de l'auteur de cette page. Que l'on demande à l'auteur de la remarque de produire lui-même qque chose de mieux n'est pas sérieux (il vient justement parce qu'il veut apprendre), il n'est pas dit qu'il n'en soit pas capable sur d'autres sujets, et qu'il ne l'ait pas déjà fait.
Sur la méthode: puisque l'intention n'est pas d'étaler, mais d'aider, acceptez des remarques au sujet de l'attente et des critiques au sujet de ce qui est fourni. Sinon vous confirmez justement que le but n'est pas d'aider mais de montrer votre savoir.
Sur le fond: je suis moi-même un développeur expérimenté, et bien, non ! ce billet n'est pas plus clair que la doc Qt. Je cherche à comprendre ce que représente un "model", un "model index" et un "item", par rapport à une "data" (tous ces termes font partie du vocabulaire Qt). Et bien, je n'en sais pas plus. Je recherche une explication simple proche de la théorie des arbres par exemple (une racine, un noeud, un enfant, un parent...). Pas compris quelle est la classe qui représente un noeud (est-ce l'item? est-ce l'index?) et quand on en est là, admettez que ça ne sert à rien de se taper la liste des méhodes des classes, ça n'aide pas. Les data elements sont-ils des attributs de l'item (via leur "role") ou de l'index (à supposer que ce ne soit pas la même notion)...
Un bon dessin vaut mieux qu'un discours farçi de de détails secondaires.
Voilà, je pars donc ailleurs pour voir si l'herbe est plus verte. Finirais bien par trouver, et si c'est le cas, je vous laisserai un lien. Est-ce assez positif comme commentaire ? ou bien faut-il dire "merci super on voit que vous connaissez le sujet, et qu'on n'est pas près d'y arriver parce qu'on est trop nul" pour que ca passe ?
La critique est facile... certes, mais la vulgarisation efficace est un art très répandu, ne comparez pas cette page avec une de "Ça bouge dans le prêt-à-porter" (qui est de la main de m. Dutourd, pour ceux qui auraient déjà rigolé)
Bravo pour ce tutoriel, très instructif (la partie analyse m'a beaucoup aidé).
Continue comme sa :P
snakedown
Super,
étant un peu perplexe devant la documentation de l'assistant pour les QTreeView, ce tuto m'a apporté juste les éléments qu'il me fallait pour démarrer sans me taper des pages et des pages de docs.
Merci!
Merci à l'auteur anonyme de la remarque étayée. Je comprends tout-à-fait son point de vue et j'accepte sans aucune hésitation ses remarques aussi critiques soient-elle. En effet, mon tuto manque de schémas, de simplification. Je voulais avec le premier commentaire négatif avoir plus de détails sur les faiblesses et là je suis servi :-)
Donc merci à toi, et la prochaine fois, j'espère que tu seras satisfait.
Personnellement, je ne trouvais pas la doc de Qt tres claire, notamment sur le role de la methode hasIndex(), et ce tutoriel m'a debloque. Merci beaucoup !