Viadeo Twitter Google Bookmarks ! Facebook Digg del.icio.us MySpace Yahoo MyWeb Blinklist Netvouz Reddit Simpy StumbleUpon Bookmarks Windows Live Favorites 
Logo Documentation Qt ·  Page d'accueil  ·  Toutes les classes  ·  Toutes les fonctions  ·  Vues d'ensemble  · 

Simple DOM Model Example

Files:

The Simple DOM Model example shows how an existing class can be adapted for use with the model/view framework.

Qt provides two complementary sets of classes for reading XML files: The classes based around QXmlReader provide a SAX-style API for incremental reading of large files, and the classes based around QDomDocument enable developers to access the contents of XML files using a Document Object Model (DOM) API.

In this example, we create a model that uses the DOM API to expose the structure and contents of XML documents to views via the standard QAbstractModel interface.

Design and Concepts

Reading an XML document with Qt's DOM classes is a straightforward process. Typically, the contents of a file are supplied to QDomDocument, and nodes are accessed using the functions provided by QDomNode and its subclasses.

The aim is to use the structure provided by QDomDocument by wrapping QDomNode objects in item objects similar to the TreeItem objects used in the Simple Tree Model example.

DomModel Class Definition

Let us begin by examining the DomModel class:

 class DomModel : public QAbstractItemModel
 {
     Q_OBJECT

 public:
     DomModel(QDomDocument document, QObject *parent = 0);
     ~DomModel();

     QVariant data(const QModelIndex &index, int role) const;
     Qt::ItemFlags flags(const QModelIndex &index) const;
     QVariant headerData(int section, Qt::Orientation orientation,
                         int role = Qt::DisplayRole) const;
     QModelIndex index(int row, int column,
                       const QModelIndex &parent = QModelIndex()) const;
     QModelIndex parent(const QModelIndex &child) const;
     int rowCount(const QModelIndex &parent = QModelIndex()) const;
     int columnCount(const QModelIndex &parent = QModelIndex()) const;

 private:
     QDomDocument domDocument;
     DomItem *rootItem;
 };

The class definition contains all the basic functions that are needed for a read-only model. Only the constructor and document() function are specific to this model. The private domDocument variable is used to hold the document that is exposed by the model; the rootItem variable contains a pointer to the root item in the model.

DomItem Class Definition

The DomItem class is used to hold information about a specific QDomNode in the document:

 class DomItem
 {
 public:
     DomItem(QDomNode &node, int row, DomItem *parent = 0);
     ~DomItem();
     DomItem *child(int i);
     DomItem *parent();
     QDomNode node() const;
     int row();

 private:
     QDomNode domNode;
     QHash<int,DomItem*> childItems;
     DomItem *parentItem;
     int rowNumber;
 };

Each DomItem provides a wrapper for a QDomNode obtained from the underlying document which contains a reference to the node, it's location in the parent node's list of child nodes, and a pointer to a parent wrapper item.

The parent(), child(), and row() functions are convenience functions for the DomModel to use that provide basic information about the item to be discovered quickly. The node() function provides access to the underlying QDomNode object.

As well as the information supplied in the constructor, the class maintains a cache of information about any child items. This is used to provide a collection of persistent item objects that the model can identify consistently and improve the performance of the model when accessing child items.

DomItem Class Implementation

Since the DomItem class is only a thin wrapper around QDomNode objects, with a few additional features to help improve performance and memory usage, we can provide a brief outline of the class before discussing the model itself.

The constructor simply records details of the QDomNode that needs to be wrapped:

 DomItem::DomItem(QDomNode &node, int row, DomItem *parent)
 {
     domNode = node;
     rowNumber = row;
     parentItem = parent;
 }

As a result, functions to provide the parent wrapper, the row number occupied by the item in its parent's list of children, and the underlying QDomNode for each item are straightforward to write:

 DomItem *DomItem::parent()
 {
     return parentItem;
 }

 int DomItem::row()
 {
     return rowNumber;
 }

 QDomNode DomItem::node() const
 {
     return domNode;
 }

It is necessary to maintain a collection of items which can be consistently identified by the model. For that reason, we maintain a hash of child wrapper items that, to minimize memory usage, is initially empty. The model uses the item's child() function to help create model indexes, and this constructs wrappers for the children of the item's QDomNode, relating the row number of each child to the newly-constructed wrapper:

 DomItem *DomItem::child(int i)
 {
     if (childItems.contains(i))
         return childItems[i];

     if (i >= 0 && i < domNode.childNodes().count()) {
         QDomNode childNode = domNode.childNodes().item(i);
         DomItem *childItem = new DomItem(childNode, i, this);
         childItems[i] = childItem;
         return childItem;
     }
     return 0;
 }

If a QDomNode was previously wrapped, the cached wrapper is returned; otherwise, a new wrapper is constructed and stored for valid children, and zero is returned for invalid ones.

The class's destructor deletes all the child items of the wrapper:

 DomItem::~DomItem()
 {
     QHash<int,DomItem*>::iterator it;
     for (it = childItems.begin(); it != childItems.end(); ++it)
         delete it.value();
 }

These, in turn, will delete their children and free any QDomNode objects in use.

DomModel Class Implementation

The structure provided by the DomItem class makes the implementation of DomModel similar to the TreeModel shown in the Simple Tree Model example.

The constructor accepts an existing document and a parent object for the model:

 DomModel::DomModel(QDomDocument document, QObject *parent)
     : QAbstractItemModel(parent), domDocument(document)
 {
     rootItem = new DomItem(domDocument, 0);
 }

A shallow copy of the document is stored for future reference, and a root item is created to provide a wrapper around the document. We assign the root item a row number of zero only to be consistent since the root item will have no siblings.

Since the model only contains information about the root item, the destructor only needs to delete this one item:

 DomModel::~DomModel()
 {
     delete rootItem;
 }

All of the child items in the tree will be deleted by the DomItem destructor as their parent items are deleted.

Basic Properties of The Model

Some aspects of the model do not depend on the structure of the underlying document, and these are simple to implement.

The number of columns exposed by the model is returned by the columnCount() function:

 int DomModel::columnCount(const QModelIndex &/*parent*/) const
 {
     return 3;
 }

This value is fixed, and does not depend on the location or type of the underlying node in the document. We will use these three columns to display different kinds of data from the underlying document.

Since we only implement a read-only model, the flags() function is straightforward to write:

 Qt::ItemFlags DomModel::flags(const QModelIndex &index) const
 {
     if (!index.isValid())
         return 0;

     return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
 }

Since the model is intended for use in a tree view, the headerData() function only provides a horizontal header:

 QVariant DomModel::headerData(int section, Qt::Orientation orientation,
                               int role) const
 {
     if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
         switch (section) {
             case 0:
                 return tr("Name");
             case 1:
                 return tr("Attributes");
             case 2:
                 return tr("Value");
             default:
                 return QVariant();
         }
     }

     return QVariant();
 }

The model presents the names of nodes in the first column, element attributes in the second, and any node values in the third.

Navigating The Document

The index() function creates a model index for the item with the given row, column, and parent in the model:

 QModelIndex DomModel::index(int row, int column, const QModelIndex &parent)
             const
 {
     if (!hasIndex(row, column, parent))
         return QModelIndex();

     DomItem *parentItem;

     if (!parent.isValid())
         parentItem = rootItem;
     else
         parentItem = static_cast<DomItem*>(parent.internalPointer());

The function first has to relate the parent index to an item that contains a node from the underlying document. If the parent index is invalid, it refers to the root node in the document, so we retrieve the root item that wraps it; otherwise, we obtain a pointer to the relevant item using the QModelIndex::internalPointer() function. We are able to extract a pointer in this way because any valid model index will have been created by this function, and we store pointers to item objects in any new indexes that we create with QAbstractItemModel::createIndex():

     DomItem *childItem = parentItem->child(row);
     if (childItem)
         return createIndex(row, column, childItem);
     else
         return QModelIndex();
 }

A child item for the given row is provided by the parent item's child() function. If a suitable child item was found then we call createIndex() to produce a model index for the requested row and column, passing a pointer to the child item for it to store internally. If no suitable child item is found, an invalid model index is returned.

Note that the items themselves maintain ownership of their child items. This means that the model does not need to keep track of the child items that have been created, and can let the items themselves tidy up when they are deleted.

The number of rows beneath a given item in the model is returned by the rowCount() function, and is the number of child nodes contained by the node that corresponds to the specified model index:

 int DomModel::rowCount(const QModelIndex &parent) const
 {
     if (parent.column() > 0)
         return 0;

     DomItem *parentItem;

     if (!parent.isValid())
         parentItem = rootItem;
     else
         parentItem = static_cast<DomItem*>(parent.internalPointer());

     return parentItem->node().childNodes().count();
 }

To obtain the relevant node in the underlying document, we access the item via the internal pointer stored in the model index. If an invalid index is supplied, the root item is used instead. We use the item's node() function to access the node itself, and simply count the number of child nodes it contains.

Since the model is used to represent a hierarchical data structure, it needs to provide an implementation for the parent() function. This returns a model index that corresponds to the parent of a child model index supplied as its argument:

 QModelIndex DomModel::parent(const QModelIndex &child) const
 {
     if (!child.isValid())
         return QModelIndex();

     DomItem *childItem = static_cast<DomItem*>(child.internalPointer());
     DomItem *parentItem = childItem->parent();

     if (!parentItem || parentItem == rootItem)
         return QModelIndex();

     return createIndex(parentItem->row(), 0, parentItem);
 }

For valid indexes other than the index corresponding to the root item, we obtain a pointer to the relevant item using the method described in the index() function, and use the item's parent() function to obtain a pointer to the parent item.

If no valid parent item exists, or if the parent item is the root item, we can simply follow convention and return an invalid model index. For all other parent items, we create a model index containing the appropriate row and column numbers, and a pointer to the parent item we just obtained.

Data is provided by the data() function. For simplicity, we only provide data for the display role, returning an invalid variant for all other requests:

 QVariant DomModel::data(const QModelIndex &index, int role) const
 {
     if (!index.isValid())
         return QVariant();

     if (role != Qt::DisplayRole)
         return QVariant();

     DomItem *item = static_cast<DomItem*>(index.internalPointer());

     QDomNode node = item->node();

As before, we obtain an item pointer for the index supplied, and use it to obtain the underlying document node. Depending on the column specified, the data we return is obtained in different ways:

     QStringList attributes;
     QDomNamedNodeMap attributeMap = node.attributes();

     switch (index.column()) {
         case 0:
             return node.nodeName();
         case 1:
             for (int i = 0; i < attributeMap.count(); ++i) {
                 QDomNode attribute = attributeMap.item(i);
                 attributes << attribute.nodeName() + "=\""
                               +attribute.nodeValue() + "\"";
             }
             return attributes.join(" ");
         case 2:
             return node.nodeValue().split("\n").join(" ");
         default:
             return QVariant();
     }
 }

For the first column, we return the node's name. For the second column, we read any attributes that the node may have, and return a string that contains a space-separated list of attribute-value assignments. For the third column, we return any value that the node may have; this allows the contents of text nodes to be displayed in a view.

If data from any other column is requested, an invalid variant is returned.

Implementation Notes

Ideally, we would rely on the structure provided by QDomDocument to help us write the parent() and index() functions that are required when subclassing QAbstractItemModel. However, since Qt's DOM classes use their own system for dynamically allocating memory for DOM nodes, we cannot guarantee that the QDomNode objects returned for a given piece of information will be the same for subsequent accesses to the document.

We use item wrappers for each QDomNode to provide consistent pointers that the model can use to navigate the document structure.

Publicité

Best Of

Actualités les plus lues

Semaine
Mois
Année
  1. « Quelque chose ne va vraiment pas avec les développeurs "modernes" », un développeur à "l'ancienne" critique la multiplication des bibliothèques 103
  2. Pourquoi les programmeurs sont-ils moins payés que les gestionnaires de programmes ? Manquent-ils de pouvoir de négociation ? 56
  3. «Le projet de loi des droits du développeur» : quelles conditions doivent remplir les entreprises pour que le développeur puisse réussir ? 93
  4. Les développeurs détestent-ils les antivirus ? Un programmeur manifeste sa haine envers ces solutions de sécurité 32
  5. Qt Commercial : Digia organise un webinar gratuit le 27 mars sur la conception d'interfaces utilisateur et d'applications avec le framework 0
  6. Quelles nouveautés de C++11 Visual C++ doit-il rapidement intégrer ? Donnez-nous votre avis 10
  7. 2017 : un quinquennat pour une nouvelle version du C++ ? Possible, selon Herb Sutter 11
Page suivante
  1. Linus Torvalds : le "C++ est un langage horrible", en justifiant le choix du C pour le système de gestion de version Git 100
  2. Comment prendre en compte l'utilisateur dans vos applications ? Pour un développeur, « 90 % des utilisateurs sont des idiots » 231
  3. Quel est LE livre que tout développeur doit lire absolument ? Celui qui vous a le plus marqué et inspiré 96
  4. Apple cède et s'engage à payer des droits à Nokia, le conflit des brevets entre les deux firmes s'achève 158
  5. Nokia porte à nouveau plainte contre Apple pour violation de sept nouveaux brevets 158
  6. « Quelque chose ne va vraiment pas avec les développeurs "modernes" », un développeur à "l'ancienne" critique la multiplication des bibliothèques 103
  7. Quel est le code dont vous êtes le plus fier ? Pourquoi l'avez-vous écrit ? Et pourquoi vous a-t-il donné autant de satisfaction ? 83
Page suivante

Le blog Digia au hasard

Logo

Créer des applications avec un style Metro avec Qt, exemples en QML et C++, un article de Digia Qt traduit par Thibaut Cuvelier

Le blog Digia est l'endroit privilégié pour la communication sur l'édition commerciale de Qt, où des réponses publiques sont apportées aux questions les plus posées au support. Lire l'article.

Communauté

Ressources

Liens utiles

Contact

  • Vous souhaitez rejoindre la rédaction ou proposer un tutoriel, une traduction, une question... ? Postez dans le forum Contribuez ou contactez-nous par MP ou par email (voir en bas de page).

Qt dans le magazine

Cette page est une traduction d'une page de la documentation de Qt, écrite par Nokia Corporation and/or its subsidiary(-ies). Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia. Qt 4.7-snapshot
Copyright © 2012 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon, vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.
Vous avez déniché une erreur ? Un bug ? Une redirection cassée ? Ou tout autre problème, quel qu'il soit ? Ou bien vous désirez participer à ce projet de traduction ? N'hésitez pas à nous contacter ou par MP !
 
 
 
 
Partenaires

Hébergement Web