Sur cette page

Qt Quick Démonstration - Actualités RSS

Un lecteur d'actualités RSS en QML qui utilise les types QML personnalisés XmlListModel et XmlListModelRole pour télécharger des données XML, ListModel et ListElement pour créer une liste de catégories, et ListView pour afficher les données.

Lecteur d'actualités RSS avec barre latérale des catégories et titres d'actualités

RSS News présente les fonctionnalités suivantes Qt Quick :

  • Utilisation de types QML personnalisés.
  • Utilisation de modèles de liste et d’éléments de liste pour représenter les données.
  • Utilisation de modèles de liste XML pour télécharger des données XML.
  • Utilisation de vues de liste pour afficher les données.
  • Utilisation du type « Component » pour créer un pied de page pour la vue de liste des actualités.
  • Utilisation du type « Image » pour créer un bouton permettant de fermer l'application.

Exécution de l'exemple

Vous pouvez exécuter l'exemple à partir de :

Utilisation des types personnalisés

Dans l’application RSS News, nous utilisons les types personnalisés suivants, chacun étant défini dans un fichier .qml distinct :

  • BusyIndicator.qml
  • CategoryDelegate.qml
  • NewsDelegate.qml
  • RssFeeds.qml
  • ScrollBar.qml

Création de la fenêtre principale

Dans le fichier Main.qml, nous utilisons un type Rectangle doté de propriétés personnalisées pour créer la fenêtre principale de l’application :

Rectangle {
    id: window

    width: 800
    height: 480

    property string currentFeed: rssFeeds.get(0).feed
    property bool loading: feedModel.status === XmlListModel.Loading
    property bool isPortrait: Screen.primaryOrientation === Qt.PortraitOrientation

Nous utiliserons ces propriétés personnalisées ultérieurement pour charger des données XML et pour adapter la mise en page de l'écran en fonction de son orientation.

Création d’une liste de catégories

Dans ` Main.qml`, nous utilisons le type personnalisé ` RssFeeds `, que nous définissons dans ` RssFeeds.qml `, pour créer une liste de catégories de flux :

    RssFeeds { id: rssFeeds }

Dans ` RssFeeds.qml`, nous utilisons un type ` ListModel ` avec un type ` ListElement ` pour créer une liste de catégories où les éléments de la liste représentent les catégories de flux :

ListModel {
    ListElement { name: "Top Stories"; feed: "news.yahoo.com/rss/topstories"; image: "images/TopStories.jpg" }
    ListElement { name: "World"; feed: "news.yahoo.com/rss/world"; image: "images/World.jpg" }
    ListElement { name: "Europe"; feed: "news.yahoo.com/rss/europe"; image: "images/Europe.jpg" }
    ListElement { name: "Asia"; feed: "news.yahoo.com/rss/asia"; image: "images/Asia.jpg" }
    ListElement { name: "U.S. National"; feed: "news.yahoo.com/rss/us"; image: "images/USNational.jpg"  }
    ListElement { name: "Politics"; feed: "news.yahoo.com/rss/politics"; image: "images/Politics.jpg" }
    ListElement { name: "Business"; feed: "news.yahoo.com/rss/business"; image: "images/Business.jpg" }
    ListElement { name: "Technology"; feed: "news.yahoo.com/rss/tech"; image: "images/Technology.jpg" }
    ListElement { name: "Entertainment"; feed: "news.yahoo.com/rss/entertainment"; image: "images/Entertainment.jpg" }
    ListElement { name: "Health"; feed: "news.yahoo.com/rss/health"; image: "images/Health.jpg" }
    ListElement { name: "Science"; feed: "news.yahoo.com/rss/science"; image: "images/Science.jpg" }
    ListElement { name: "Sports"; feed: "news.yahoo.com/rss/sports"; image: "images/Sports.jpg" }
}

Les éléments de liste sont définis comme les autres types QML, à la différence qu’ils contiennent un ensemble de définitions de rôles au lieu de propriétés. Les rôles définissent à la fois la manière dont les données sont accessibles et incluent les données elles-mêmes.

Pour chaque élément de la liste, nous utilisons le rôle « name » pour spécifier le nom de la catégorie, le rôle « feed » pour spécifier l’URL à partir de laquelle charger les données, et le rôle « image » pour afficher une image représentant la catégorie.

Dans ` Main.qml`, nous utilisons un type ` ListView ` pour afficher la liste des catégories :

    ListView {
        id: categories
        property int itemWidth: 190

        width: window.isPortrait ? parent.width : itemWidth
        height: window.isPortrait ? itemWidth : parent.height
        orientation: window.isPortrait ? ListView.Horizontal : ListView.Vertical
        anchors.top: parent.top
        model: rssFeeds
        delegate: CategoryDelegate {
            itemSize: categories.itemWidth
            isLoading: window.loading
            onClicked: function () {
                if (window.currentFeed == feed)
                    feedModel.reload()
                else
                    window.currentFeed = feed

            }
        }

Pour disposer la liste des catégories horizontalement en haut de la fenêtre en mode portrait et verticalement à gauche en mode paysage, nous utilisons la propriété orientation. En fonction de l’orientation, nous lions soit la largeur, soit la hauteur de la liste à une valeur fixe (itemWidth).

Nous utilisons la propriété ` anchors.top ` pour positionner la vue de liste en haut de l’écran, quelle que soit l’orientation.

Nous utilisons la propriété ` model ` pour charger les données XML à partir du modèle ` rssFeeds `, et ` CategoryDelegate ` comme délégué pour instancier chaque élément de la liste.

Création d’éléments de liste

Dans ` CategoryDelegate.qml`, nous utilisons le type ` Rectangle ` avec des propriétés personnalisées pour créer des éléments de liste :

Rectangle {
    id: delegate

    required property real itemSize
    required property string name
    required property string feed
    required property string image
    required property int index
    required property bool isLoading

    property bool selected: ListView.isCurrentItem

Nous utilisons ` required ` pour déclarer les propriétés que nous attendons du modèle.

Nous définissons la propriété ` selected ` sur la propriété attachée ` ListView.isCurrentItem ` pour spécifier que ` selected ` vaut ` true ` si ` delegate ` est l’élément actuel.

Nous utilisons la propriété de type Image source pour afficher l’image, centrée dans le délégué, spécifiée pour l’élément de liste par le rôle image dans le modèle de liste rssFeeds:

    Image {
        anchors.centerIn: parent
        source: delegate.image
    }

Nous utilisons un type « Text » pour ajouter des titres aux éléments de la liste :

    Text {
        id: titleText

        anchors {
            left: parent.left; leftMargin: 20
            right: parent.right; rightMargin: 20
            top: parent.top; topMargin: 20
        }

        font { pixelSize: 18; bold: true }
        text: delegate.name
        color: delegate.selected ? "#ffffff" : "#ebebdd"
        scale: delegate.selected ? 1.15 : 1.0
        Behavior on color { ColorAnimation { duration: 150 } }
        Behavior on scale { PropertyAnimation { duration: 300 } }

Nous utilisons la propriété anchors pour positionner le titre en haut de l’élément de liste, avec une marge de 20 pixels. Nous utilisons les propriétés font pour ajuster la taille de la police et la mise en forme du texte.

Nous utilisons les propriétés ` color ` et ` scale ` pour éclaircir le texte et l'agrandir légèrement lorsque l'élément de la liste est l'élément actif. En appliquant une animation ` Behavior ` à la propriété, nous animons les actions de sélection et de désélection des éléments de la liste.

Nous utilisons un type « MouseArea » pour télécharger des données XML lorsque les utilisateurs appuient sur un élément de la liste des catégories :

    MouseArea {
        anchors.fill: delegate
        onClicked: {
            delegate.ListView.view.currentIndex = delegate.index
            delegate.clicked()
        }
    }

La propriété anchors.fill est définie sur delegate afin de permettre aux utilisateurs de toucher n’importe où dans l’élément de la liste.

Pour charger les données XML de la liste de catégories lors d’un événement « clicked », nous définissons la valeur « currentIndex » de la propriété attachée « ListView » et émettons le signal « clicked () » dans « onClicked ».

Téléchargement de données XML

Dans Main.qml, nous utilisons un type XmlListModel comme source de données pour les éléments ListView afin d’afficher les actualités de la catégorie sélectionnée :

    XmlListModel {
        id: feedModel

        source: "https://" + window.currentFeed
        query: "/rss/channel/item"

Nous utilisons la propriété source et la propriété personnalisée window.currentFeed pour récupérer les actualités de la catégorie sélectionnée.

La propriété query spécifie que la propriété XmlListModel génère un élément de modèle pour chaque élément <item> présent dans le document XML.

Nous utilisons le type « XmlListModelRole » pour définir les attributs de l’élément de modèle. Chaque élément de modèle possède les attributs « title », « content », « link » et « pubDate », qui correspondent aux valeurs des éléments « <item> » correspondants dans le document XML :

        XmlListModelRole { name: "title"; elementName: "title"; attributeName: ""}
        XmlListModelRole { name: "content"; elementName: "content"; attributeName: "url" }
        XmlListModelRole { name: "link"; elementName: "link"; attributeName: "" }
        XmlListModelRole { name: "pubDate"; elementName: "pubDate"; attributeName: "" }
    }

Nous utilisons le modèle feedModel de type ListView pour afficher les données :

    ListView {
        id: list

        anchors.left: window.isPortrait ? window.left : categories.right
        anchors.right: closeButton.left
        anchors.top: window.isPortrait ? categories.bottom : window.top
        anchors.bottom: window.bottom
        anchors.leftMargin: 30
        anchors.rightMargin: 4
        clip: window.isPortrait
        model: feedModel
        footer: footerText
        delegate: NewsDelegate {}
    }

Pour afficher les actualités sous la liste des catégories en mode portrait et à sa droite en mode paysage, nous utilisons la propriété personnalisée « isPortrait » afin d’ancrer le haut de la liste des actualités à gauche de « window » et le bas de « categories » en mode portrait, et à droite de « categories » et le bas de « window » en mode paysage.

Nous utilisons la propriété ` anchors.bottom ` pour ancrer le bas de la vue de liste au bas de la fenêtre, quelle que soit l’orientation.

En mode portrait, nous découpons l’affichage des articles d’actualité selon le rectangle de délimitation de la vue de liste afin d’éviter les artefacts graphiques lorsque les articles défilent par-dessus d’autres éléments. En mode paysage, cela n’est pas nécessaire, car la liste s’étend verticalement sur toute la largeur de l’écran.

Nous utilisons la propriété ` model ` pour charger les données XML à partir du modèle ` feedModel `, et utilisons ` NewsDelegate ` comme délégué pour instancier chaque élément de la liste.

Dans ` NewsDelegate.qml`, nous utilisons un type ` Column ` pour mettre en page les données XML :

Column {
    id: delegate

    required property string title
    required property string content
    required property string link
    required property string pubDate

    width: delegate.ListView.view.width
    spacing: 8

Au sein de la colonne, nous utilisons une ligne (Row ) et une autre colonne pour positionner les images et le texte du titre :

    Row {
        width: parent.width
        spacing: 8

        Column {
            Item {
                width: 4
                height: titleText.font.pixelSize / 4
            }

            Image {
                id: titleImage
                source: delegate.content
                width: Math.min(delegate.width / 2, sourceSize.width)
                fillMode: Image.PreserveAspectFit
            }
        }

        Text {
            id: titleText

            text: delegate.title.replace(/&#39;/g, "'")
            width: delegate.width - titleImage.width
            wrapMode: Text.WordWrap
            font.pixelSize: 26
            font.bold: true
        }
    }

Nous générons une représentation textuelle de la date de publication de l’élément à l’aide de la fonction JavaScript ` timeSinceEvent() ` :

    Text {
        width: delegate.width
        font.pixelSize: 12
        textFormat: Text.RichText
        font.italic: true
        text: delegate.timeSinceEvent(delegate.pubDate) + " (<a href=\"" + delegate.link + "\">Link</a>)"
        onLinkActivated: function(link) {
            Qt.openUrlExternally(link)
        }
    }

Nous utilisons le gestionnaire d’événements « signal » d’ onLinkActivated pour ouvrir l’URL dans un navigateur externe lorsque les utilisateurs cliquent sur le lien.

Fournir des retours d’information aux utilisateurs

Dans CategoryDelegate.qml, nous utilisons le type personnalisé « BusyIndicator » pour indiquer l’activité pendant le chargement des données XML :

    BusyIndicator {
        scale: 0.8
        visible: delegate.ListView.isCurrentItem && delegate.isLoading
        anchors.centerIn: parent
    }

Nous utilisons la propriété ` scale ` pour réduire la taille de l'indicateur à ` 0.8`. Nous lions la propriété ` visible ` à la propriété associée ` isCurrentItem ` de la vue de liste ` delegate ` et à la propriété ` loading ` de la fenêtre principale afin d'afficher l'image de l'indicateur lorsqu'un élément de la liste des catégories est l'élément actif et que des données XML sont en cours de chargement.

Nous définissons le type ` BusyIndicator ` dans ` BusyIndicator.qml`. Nous utilisons un type ` Image ` pour afficher une image et appliquons un ` NumberAnimation ` à sa propriété ` rotation ` afin de faire pivoter l’image dans une boucle infinie :

Image {
    id: container

    source: "images/busy.png";

    NumberAnimation on rotation {
        running: container.visible
        from: 0; to: 360;
        loops: Animation.Infinite;
        duration: 1200
    }
}

Dans vos applications, vous pouvez également utiliser le type BusyIndicator issu du Qt Quick Controls module.

Création de barres de défilement

Dans Main.qml, nous utilisons notre propre type personnalisé ` ScrollBar ` pour créer des barres de défilement dans les vues de catégorie et de liste d’actualités. Dans vos applications, vous pouvez également utiliser le type ` ScrollView ` issu du Qt Quick Controls module.

Tout d’abord, nous créons une barre de défilement dans la vue de liste des catégories. Nous lions la propriété ` orientation ` à la propriété ` isPortrait ` et à la valeur ` Horizontal ` du type énuméré ` Qt::Orientation ` pour afficher une barre de défilement horizontale en mode portrait, et à la valeur ` Vertical ` pour afficher une barre de défilement verticale en mode paysage :

    ScrollBar {
        id: listScrollBar

        orientation: window.isPortrait ? Qt.Horizontal : Qt.Vertical
        height: window.isPortrait ? 8 : categories.height;
        width: window.isPortrait ? categories.width : 8
        scrollArea: categories;
        anchors.right: categories.right
    }

Comme pour la vue de liste categories, nous ajustons la largeur et la hauteur de la barre de défilement en fonction de la propriété isPortrait.

Nous utilisons la propriété « scrollArea » pour afficher la barre de défilement dans la vue de liste « categories ».

Nous utilisons la propriété « anchors.right » pour ancrer la barre de défilement à droite de la liste des catégories.

    ScrollBar {
        scrollArea: list
        width: 8
        anchors.right: window.right
        anchors.top: window.isPortrait ? categories.bottom : window.top
        anchors.bottom: window.bottom
    }

Ensuite, nous créons une autre barre de défilement dans la vue de liste des actualités. Nous souhaitons qu’une barre de défilement verticale apparaisse à droite de la vue, quelle que soit l’orientation de l’écran ; nous pouvons donc définir la propriété ` width ` sur ` 8 ` et lier la propriété ` anchors.right ` à la propriété ` window.right `. Nous utilisons la propriété ` anchors.top ` pour ancrer le haut de la barre de défilement au bas de la liste des catégories en mode portrait et au haut de la liste des actualités en mode paysage. Nous utilisons la propriété ` anchors.bottom ` pour ancrer le bas de la barre de défilement au bas de la vue de liste dans les deux orientations.

Nous définissons le type « ScrollBar » dans ` ScrollBar.qml`. Nous utilisons un type « Item » doté de propriétés personnalisées pour créer un conteneur destiné à la barre de défilement :

Item {
    id: container

    property variant scrollArea
    property int orientation: Qt.Vertical

    opacity: 0

Nous utilisons un type « BorderImage » pour afficher le curseur de la barre de défilement aux coordonnées x et y que nous calculons à l’aide de la fonction « position() » :

    BorderImage {
        source: "images/scrollbar.png"
        border { left: 1; right: 1; top: 1; bottom: 1 }
        x: container.orientation == Qt.Vertical ? 2 : container.position()
        y: container.orientation == Qt.Vertical ? container.position() : 2
        width: container.orientation == Qt.Vertical ? container.width - 4 : container.size()
        height: container.orientation == Qt.Vertical ? container.size() : container.height - 4
    }

Nous utilisons la fonction size pour calculer la largeur et la hauteur du curseur en fonction de l’orientation de l’écran.

Nous utilisons ` states ` pour rendre la barre de défilement visible lorsque l'utilisateur déplace la zone de défilement :

    states: State {
        name: "visible"
        when: container.orientation == Qt.Vertical ?
                  container.scrollArea.movingVertically :
                  container.scrollArea.movingHorizontally
        PropertyChanges { container { opacity: 1.0 } }
    }

Nous utilisons transitions pour appliquer un NumberAnimation à la propriété opacity lorsque l’état passe de « "visible" » à l’état par défaut :

    transitions: Transition {
        from: "visible"; to: ""
        NumberAnimation { properties: "opacity"; duration: 600 }
    }
}

Création de pieds de page

Dans ` Main.qml`, nous utilisons un type ` Component ` avec un type ` Rectangle ` pour créer un pied de page pour la vue de liste des actualités :

pragma ComponentBehavior: Bound

import QtQuick
import QtQuick.Window
import QtQml.XmlListModel

Rectangle {
    id: window

    width: 800
    height: 480

    property string currentFeed: rssFeeds.get(0).feed
    property bool loading: feedModel.status === XmlListModel.Loading
    property bool isPortrait: Screen.primaryOrientation === Qt.PortraitOrientation

    onLoadingChanged: {
        if (feedModel.status == XmlListModel.Ready)
            list.positionViewAtBeginning()
    }

    RssFeeds { id: rssFeeds }

    XmlListModel {
        id: feedModel

        source: "https://" + window.currentFeed
        query: "/rss/channel/item"

        XmlListModelRole { name: "title"; elementName: "title"; attributeName: ""}

Nous lions la propriété ` width ` du pied de page à la largeur du composant et la propriété ` height ` à la hauteur du bouton de fermeture afin de les aligner lorsqu’aucun article n’est affiché.

Création de boutons

Dans ` Main.qml`, nous utilisons un type ` Image ` pour créer un simple bouton sur lequel les utilisateurs peuvent appuyer pour fermer l’application :

        XmlListModelRole { name: "content"; elementName: "content"; attributeName: "url" }
        XmlListModelRole { name: "link"; elementName: "link"; attributeName: "" }
        XmlListModelRole { name: "pubDate"; elementName: "pubDate"; attributeName: "" }
    }

    ListView {
        id: categories
        property int itemWidth: 190

        width: window.isPortrait ? parent.width : itemWidth
        height: window.isPortrait ? itemWidth : parent.height
        orientation: window.isPortrait ? ListView.Horizontal : ListView.Vertical
        anchors.top: parent.top
        model: rssFeeds
        delegate: CategoryDelegate {
            itemSize: categories.itemWidth
            isLoading: window.loading
            onClicked: function () {
                if (window.currentFeed == feed)
                    feedModel.reload()
                else
                    window.currentFeed = feed

            }
        }
        spacing: 3
    }

    ScrollBar {
        id: listScrollBar

        orientation: window.isPortrait ? Qt.Horizontal : Qt.Vertical
        height: window.isPortrait ? 8 : categories.height;
        width: window.isPortrait ? categories.width : 8
        scrollArea: categories;
        anchors.right: categories.right
    }

    ListView {
        id: list

        anchors.left: window.isPortrait ? window.left : categories.right
        anchors.right: closeButton.left
        anchors.top: window.isPortrait ? categories.bottom : window.top
        anchors.bottom: window.bottom
        anchors.leftMargin: 30
        anchors.rightMargin: 4
        clip: window.isPortrait
        model: feedModel
        footer: footerText
        delegate: NewsDelegate {}
    }

    ScrollBar {
        scrollArea: list
        width: 8
        anchors.right: window.right
        anchors.top: window.isPortrait ? categories.bottom : window.top
        anchors.bottom: window.bottom
    }

    Component {
        id: footerText

        Rectangle {
            width: parent.width
            height: closeButton.height
            color: "lightgray"

            Text {
                text: "RSS Feed from Yahoo News"
                anchors.centerIn: parent
                font.pixelSize: 14
            }
        }
    }

    Image {
        id: closeButton
        source: "content/images/btn_close.png"
        scale: 0.8
        anchors.top: parent.top
        anchors.right: parent.right
        anchors.margins: 4
        opacity: (window.isPortrait && categories.moving) ? 0.2 : 1.0
        Behavior on opacity {
            NumberAnimation { duration: 300; easing.type: Easing.OutSine }
        }

        MouseArea {
            anchors.fill: parent
            onClicked: {
                Qt.quit()
            }
        }
    }

Nous utilisons ` anchors ` pour positionner le bouton de fermeture dans le coin supérieur droit de la vue de la liste des actualités, avec des marges de 4 pixels. Comme le bouton de fermeture chevauche la liste des catégories en mode portrait, nous animons la propriété ` opacity ` pour rendre le bouton presque entièrement transparent lorsque les utilisateurs font défiler la liste des catégories.

Nous utilisons le gestionnaire de signal ` onClicked ` au sein d’un ` MouseArea ` pour appeler la fonction ` quit() ` lorsque l’utilisateur sélectionne le bouton de fermeture.

Exemple de projet sur code.qt.io

Voir également Applications QML.

© 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.