Exemple de signets QXmlStream
Démontre comment lire et écrire des fichiers XBEL.
L'exemple QXmlStream Bookmarks fournit une visionneuse pour les fichiers XML Bookmark Exchange Language (XBEL). Il peut lire des signets à l'aide de Qt XML QXmlStreamReader et les réécrire à l'aide de QXmlStreamWriter. Comme cet exemple vise à montrer comment utiliser ces types de lecteurs et d'écrivains, il ne permet pas d'ouvrir un signet, d'en ajouter un nouveau ou de fusionner deux fichiers de signets, et n'offre qu'une possibilité minimale d'édition des signets. Néanmoins, il pourrait certainement être étendu avec de telles fonctionnalités, si on le souhaite.

Définition de la classe XbelWriter
La classe XbelWriter prend une tree widget décrivant une hiérarchie de dossiers contenant des signets. Son writeFile() fournit les moyens d'écrire cette hiérarchie, au format XBEL, sur un périphérique de sortie donné.
En interne, elle enregistre le widget d'arbre qui lui a été fourni et met en paquet une instance privée de QXmlStreamWriter, qui lui donne les moyens de diffuser du XML. Il dispose d'un site interne writeItem() pour écrire chaque élément de son arbre.
class XbelWriter { public: explicit XbelWriter(const QTreeWidget *treeWidget); bool writeFile(QIODevice *device); private: void writeItem(const QTreeWidgetItem *item); QXmlStreamWriter xml; const QTreeWidget *treeWidget; };
Mise en œuvre de la classe XbelWriter
Le constructeur de XbelWriter accepte le treeWidget qu'il va décrire. Il le stocke et active la propriété de formatage automatique de QXmlStreamWriter. Cette dernière divise les données en plusieurs lignes, avec une indentation pour indiquer la structure de l'arbre, ce qui rend la sortie XML plus facile à lire.
XbelWriter::XbelWriter(const QTreeWidget *treeWidget) : treeWidget(treeWidget) { xml.setAutoFormatting(true); }
La fonction writeFile() accepte un objet QIODevice et demande à son membre QXmlStreamWriter d'écrire sur ce périphérique à l'aide de setDevice(). Cette fonction écrit ensuite la définition du type de document (DTD), l'élément de départ, la version et délègue l'écriture de chacun des éléments de premier niveau de treeWidget à writeItem(). Enfin, elle ferme le document et renvoie.
bool XbelWriter::writeFile(QIODevice *device) { xml.setDevice(device); xml.writeStartDocument(); xml.writeDTD("<!DOCTYPE xbel>"_L1); xml.writeStartElement("xbel"_L1); xml.writeAttribute("version"_L1, "1.0"_L1); for (int i = 0; i < treeWidget->topLevelItemCount(); ++i) writeItem(treeWidget->topLevelItem(i)); xml.writeEndDocument(); return true; }
La fonction writeItem() accepte un objet QTreeWidgetItem et écrit dans son flux XML une représentation de l'objet, qui dépend de son UserRole, qui peut être un "folder", un "bookmark" ou un "separator". Dans chaque dossier, elle s'appelle récursivement sur chaque élément enfant, pour inclure récursivement une représentation de chaque enfant dans l'élément XML du dossier.
void XbelWriter::writeItem(const QTreeWidgetItem *item) { QString tagName = item->data(0, Qt::UserRole).toString(); if (tagName == "folder"_L1) { bool folded = !item->isExpanded(); xml.writeStartElement(tagName); xml.writeAttribute("folded"_L1, folded ? "yes"_L1 : "no"_L1); xml.writeTextElement("title"_L1, item->text(0)); for (int i = 0; i < item->childCount(); ++i) writeItem(item->child(i)); xml.writeEndElement(); } else if (tagName == "bookmark"_L1) { xml.writeStartElement(tagName); if (!item->text(1).isEmpty()) xml.writeAttribute("href"_L1, item->text(1)); xml.writeTextElement("title"_L1, item->text(0)); xml.writeEndElement(); } else if (tagName == "separator"_L1) { xml.writeEmptyElement(tagName); } }
Définition de la classe XbelReader
Le site XbelReader utilise un site tree widget qu'il remplit avec des éléments décrivant une hiérarchie de signets. Elle prend en charge la lecture des données XBEL d'un site QIODevice en tant que source de ces éléments. Si l'analyse des données XBEL échoue, elle peut signaler ce qui n'a pas fonctionné.
En interne, il enregistre le site QTreeWidget qu'il va remplir et crée une instance de QXmlStreamReader, la classe d'accompagnement de QXmlStreamWriter, qu'il utilisera pour lire les données XBEL.
class XbelReader { public: XbelReader(QTreeWidget *treeWidget); bool read(QIODevice *device); QString errorString() const; private: void readXBEL(); void readTitle(QTreeWidgetItem *item); void readSeparator(QTreeWidgetItem *item); void readFolder(QTreeWidgetItem *item); void readBookmark(QTreeWidgetItem *item); QTreeWidgetItem *createChildItem(QTreeWidgetItem *item); QXmlStreamReader xml; QTreeWidget *treeWidget; QIcon folderIcon; QIcon bookmarkIcon; };
Mise en œuvre de la classe XbelReader
Étant donné que le lecteur XBEL ne s'occupe que de la lecture d'éléments XML, il utilise largement la fonction de commodité readNextStartElement().
Le constructeur XbelReader a besoin d'un QTreeWidget qu'il va remplir. Il remplit le style du widget arbre avec des icônes appropriées : une icône de dossier qui change de forme pour indiquer si chaque dossier est ouvert ou fermé ; et une icône de fichier standard pour les signets individuels dans ces dossiers.
XbelReader::XbelReader(QTreeWidget *treeWidget) : treeWidget(treeWidget) { QStyle *style = treeWidget->style(); folderIcon.addPixmap(style->standardPixmap(QStyle::SP_DirClosedIcon), QIcon::Normal, QIcon::Off); folderIcon.addPixmap(style->standardPixmap(QStyle::SP_DirOpenIcon), QIcon::Normal, QIcon::On); bookmarkIcon.addPixmap(style->standardPixmap(QStyle::SP_FileIcon)); }
La fonction read() accepte un QIODevice et demande à son membre QXmlStreamReader de lire le contenu de ce périphérique. Notez que l'entrée XML doit être bien formée pour être acceptée par QXmlStreamReader. Il lit d'abord la structure extérieure et vérifie que le contenu est un fichier XBEL 1.0 ; si c'est le cas, read() délègue la lecture effective du contenu à la fonction interne readXBEL().
Dans le cas contraire, la fonction raiseError() est utilisée pour enregistrer un message d'erreur. Le lecteur lui-même peut faire de même s'il rencontre des erreurs dans l'entrée. Lorsque read() a terminé, il renvoie vrai s'il n'y a pas eu d'erreurs.
bool XbelReader::read(QIODevice *device) { xml.setDevice(device); if (xml.readNextStartElement()) { if (xml.name() == "xbel"_L1 && xml.attributes().value("version"_L1) == "1.0"_L1) readXBEL(); else xml.raiseError(QObject::tr("The file is not an XBEL version 1.0 file.")); } return !xml.error(); }
Si read() renvoie un message faux, l'appelant peut obtenir une description de l'erreur, avec le numéro de ligne et de colonne dans le flux, en appelant la fonction errorString().
QString XbelReader::errorString() const { return QObject::tr("%1\nLine %2, column %3") .arg(xml.errorString()) .arg(xml.lineNumber()) .arg(xml.columnNumber()); }
La fonction readXBEL() lit le nom d'un élément de départ et appelle la fonction appropriée pour le lire, selon que son nom de balise est "folder", "bookmark" ou "separator". Tout autre élément rencontré est ignoré. La fonction commence par une condition préalable, vérifiant que le lecteur XML vient d'ouvrir un élément "xbel".
void XbelReader::readXBEL() { Q_ASSERT(xml.isStartElement() && xml.name() == "xbel"_L1); while (xml.readNextStartElement()) { if (xml.name() == "folder"_L1) readFolder(nullptr); else if (xml.name() == "bookmark"_L1) readBookmark(nullptr); else if (xml.name() == "separator"_L1) readSeparator(nullptr); else xml.skipCurrentElement(); } }
La fonction readBookmark() crée un nouvel élément modifiable représentant un seul signet. Elle enregistre l'attribut XML "href" de l'élément actuel en tant que texte de la deuxième colonne de l'élément et définit provisoirement le texte de la première colonne à "Unknown title" avant d'analyser le reste de l'élément à la recherche d'un élément de titre pour le remplacer, en ignorant tout élément enfant non reconnu.
void XbelReader::readTitle(QTreeWidgetItem *item) { Q_ASSERT(xml.isStartElement() && xml.name() == "title"_L1); item->setText(0, xml.readElementText()); }
La fonction readTitle() lit le titre d'un signet et l'enregistre comme titre (texte de la première colonne) de l'élément pour lequel elle a été appelée.
void XbelReader::readSeparator(QTreeWidgetItem *item) { Q_ASSERT(xml.isStartElement() && xml.name() == "separator"_L1); constexpr char16_t midDot = u'\xB7'; static const QString dots(30, midDot); QTreeWidgetItem *separator = createChildItem(item); separator->setFlags(item ? item->flags() & ~Qt::ItemIsSelectable : Qt::ItemFlags{}); separator->setText(0, dots); xml.skipCurrentElement(); }
La fonction readSeparator() crée un séparateur et définit ses drapeaux. Le texte de l'élément séparateur est fixé à 30 points centrés. Le reste de l'élément est ensuite ignoré à l'aide de skipCurrentElement().
void XbelReader::readSeparator(QTreeWidgetItem *item) { Q_ASSERT(xml.isStartElement() && xml.name() == "separator"_L1); constexpr char16_t midDot = u'\xB7'; static const QString dots(30, midDot); QTreeWidgetItem *separator = createChildItem(item); separator->setFlags(item ? item->flags() & ~Qt::ItemIsSelectable : Qt::ItemFlags{}); separator->setText(0, dots); xml.skipCurrentElement(); }
La fonction readFolder() crée un élément et parcourt le contenu de l'élément dossier, en ajoutant des enfants à cet élément pour représenter le contenu de l'élément dossier. La boucle sur le contenu du dossier est similaire à celle de readXBEL(), sauf qu'elle accepte maintenant un élément title pour définir le titre du dossier.
void XbelReader::readFolder(QTreeWidgetItem *item) { Q_ASSERT(xml.isStartElement() && xml.name() == "folder"_L1); QTreeWidgetItem *folder = createChildItem(item); bool folded = xml.attributes().value("folded"_L1) != "no"_L1; folder->setExpanded(!folded); while (xml.readNextStartElement()) { if (xml.name() == "title"_L1) readTitle(folder); else if (xml.name() == "folder"_L1) readFolder(folder); else if (xml.name() == "bookmark"_L1) readBookmark(folder); else if (xml.name() == "separator"_L1) readSeparator(folder); else xml.skipCurrentElement(); } }
La fonction d'aide createChildItem() crée un nouvel élément de widget d'arbre qui est soit un enfant de l'élément donné, soit, si aucun élément parent n'est donné, un enfant direct du widget d'arbre. Elle définit le nouvel élément UserRole au nom de balise de l'élément XML actuel, correspondant à la façon dont XbelWriter::writeFile() utilise ce UserRole.
QTreeWidgetItem *XbelReader::createChildItem(QTreeWidgetItem *item) { QTreeWidgetItem *childItem = item ? new QTreeWidgetItem(item) : new QTreeWidgetItem(treeWidget); childItem->setData(0, Qt::UserRole, xml.name().toString()); return childItem; }
Définition de la classe MainWindow
La classe MainWindow est une sous-classe de QMainWindow, avec un menu File et un menu Help.
class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(); public slots: void open(); void saveAs(); void about(); #if QT_CONFIG(clipboard) && QT_CONFIG(contextmenu) void onCustomContextMenuRequested(const QPoint &pos); #endif private: void createMenus(); QTreeWidget *const treeWidget; };
Mise en œuvre de la classe MainWindow
Le constructeur de MainWindow configure son objet QTreeWidget, treeWidget, comme son propre widget central, avec des en-têtes de colonne pour le titre et l'emplacement de chaque marque-livre. Il configure un menu personnalisé qui permet à l'utilisateur d'effectuer des actions sur des signets individuels dans le widget de l'arbre.
Il invoque createMenus() pour configurer ses propres menus et les actions correspondantes. Il définit son titre, s'annonce comme étant prêt et fixe sa taille à une proportion raisonnable de l'espace disponible sur l'écran.
MainWindow::MainWindow() : treeWidget(new QTreeWidget) { treeWidget->header()->setSectionResizeMode(QHeaderView::Stretch); treeWidget->setHeaderLabels(QStringList{tr("Title"), tr("Location")}); #if QT_CONFIG(clipboard) && QT_CONFIG(contextmenu) treeWidget->setContextMenuPolicy(Qt::CustomContextMenu); connect(treeWidget, &QWidget::customContextMenuRequested, this, &MainWindow::onCustomContextMenuRequested); #endif setCentralWidget(treeWidget); createMenus(); statusBar()->showMessage(tr("Ready")); setWindowTitle(tr("QXmlStream Bookmarks")); const QSize availableSize = screen()->availableGeometry().size(); resize(availableSize.width() / 2, availableSize.height() / 3); }
Un menu personnalisé, déclenché lorsque l'utilisateur clique avec le bouton droit de la souris sur un signet, permet de copier le signet en tant que lien ou de demander à un navigateur de bureau d'ouvrir l'URL auquel il fait référence. Ce menu est mis en œuvre (lorsque les fonctions correspondantes sont activées) par onCustomContextMenuRequested().
#if QT_CONFIG(clipboard) && QT_CONFIG(contextmenu) void MainWindow::onCustomContextMenuRequested(const QPoint &pos) { const QTreeWidgetItem *item = treeWidget->itemAt(pos); if (!item) return; const QString url = item->text(1); QMenu contextMenu; QAction *copyAction = contextMenu.addAction(tr("Copy Link to Clipboard")); QAction *openAction = contextMenu.addAction(tr("Open")); QAction *action = contextMenu.exec(treeWidget->viewport()->mapToGlobal(pos)); if (action == copyAction) QGuiApplication::clipboard()->setText(url); else if (action == openAction) QDesktopServices::openUrl(QUrl(url)); } #endif // QT_CONFIG(clipboard) && QT_CONFIG(contextmenu)
La fonction createMenus() crée les objets fileMenu et helpMenu et leur ajoute l'objet QAction, lié de diverses manières aux fonctions open(), saveAs() et about(), ainsi qu'aux fonctions QWidget::close() et QApplication::aboutQt(). Les connexions sont indiquées ci-dessous :
void MainWindow::createMenus() { QMenu *fileMenu = menuBar()->addMenu(tr("&File")); QAction *openAct = fileMenu->addAction(tr("&Open..."), this, &MainWindow::open); openAct->setShortcuts(QKeySequence::Open); QAction *saveAsAct = fileMenu->addAction(tr("&Save As..."), this, &MainWindow::saveAs); saveAsAct->setShortcuts(QKeySequence::SaveAs); QAction *exitAct = fileMenu->addAction(tr("E&xit"), this, &QWidget::close); exitAct->setShortcuts(QKeySequence::Quit); menuBar()->addSeparator(); QMenu *helpMenu = menuBar()->addMenu(tr("&Help")); helpMenu->addAction(tr("&About"), this, &MainWindow::about); helpMenu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt); }
Cela crée le menu illustré dans les captures d'écran ci-dessous :
![]() | ![]() |
La fonction open(), lorsqu'elle est déclenchée, offre à l'utilisateur une boîte de dialogue de fichier à utiliser pour sélectionner un fichier de signets. Si un fichier est sélectionné, il est analysé à l'aide d'un XBelReader pour remplir le treeWidget de signets. En cas de problème lors de l'ouverture ou de l'analyse du fichier, un message d'avertissement approprié est affiché à l'intention de l'utilisateur, avec le nom du fichier et le message d'erreur. Sinon, les signets lus dans le fichier sont affichés et la barre d'état de la fenêtre indique brièvement que le fichier a été chargé.
void MainWindow::open() { QFileDialog fileDialog(this, tr("Open Bookmark File"), QDir::currentPath()); fileDialog.setMimeTypeFilters({"application/x-xbel"_L1}); if (fileDialog.exec() != QDialog::Accepted) return; treeWidget->clear(); const QString fileName = fileDialog.selectedFiles().constFirst(); QFile file(fileName); if (!file.open(QFile::ReadOnly | QFile::Text)) { QMessageBox::warning(this, tr("QXmlStream Bookmarks"), tr("Cannot read file %1:\n%2.") .arg(QDir::toNativeSeparators(fileName), file.errorString())); return; } XbelReader reader(treeWidget); if (!reader.read(&file)) { QMessageBox::warning( this, tr("QXmlStream Bookmarks"), tr("Parse error in file %1:\n\n%2") .arg(QDir::toNativeSeparators(fileName), reader.errorString())); } else { statusBar()->showMessage(tr("File loaded"), 2000); } }
La fonction saveAs() affiche un message QFileDialog, invitant l'utilisateur à saisir un message fileName, dans lequel il peut enregistrer une copie des signets. Comme la fonction open(), cette fonction affiche également un message d'avertissement si le fichier ne peut pas être écrit.
void MainWindow::saveAs() { QFileDialog fileDialog(this, tr("Save Bookmark File"), QDir::currentPath()); fileDialog.setAcceptMode(QFileDialog::AcceptSave); fileDialog.setDefaultSuffix("xbel"_L1); fileDialog.setMimeTypeFilters({"application/x-xbel"_L1}); if (fileDialog.exec() != QDialog::Accepted) return; const QString fileName = fileDialog.selectedFiles().constFirst(); QFile file(fileName); if (!file.open(QFile::WriteOnly | QFile::Text)) { QMessageBox::warning(this, tr("QXmlStream Bookmarks"), tr("Cannot write file %1:\n%2.") .arg(QDir::toNativeSeparators(fileName), file.errorString())); return; } XbelWriter writer(treeWidget); if (writer.writeFile(&file)) statusBar()->showMessage(tr("File saved"), 2000); }
La fonction about() affiche une page QMessageBox contenant une brève description de l'exemple ou des informations générales sur Qt XML et la version utilisée.
void MainWindow::about() { QMessageBox::about(this, tr("About QXmlStream Bookmarks"), tr("The <b>QXmlStream Bookmarks</b> example demonstrates how to use Qt's " "QXmlStream classes to read and write XML documents.")); }
main() Fonction
La fonction main() instancie MainWindow et invoque la fonction show() pour l'afficher, puis open(), car c'est très probablement ce que l'utilisateur voudra faire en premier.
int main(int argc, char *argv[]) { QApplication app(argc, argv); MainWindow mainWin; mainWin.show(); mainWin.open(); return app.exec(); }
Voir la page de ressources XML Bookmark Exchange Language pour plus d'informations sur les fichiers XBEL.
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.

