Sur cette page

Qt Quick Démo - RSS News

Un lecteur de nouvelles RSS 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.

RSS News démontre les fonctionnalités suivantes Qt Quick caractéristiques suivantes :

  • 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 en liste des actualités.
  • Utilisation du type Image pour créer un bouton de fermeture de l'application.

Exécution de l'exemple

Pour exécuter l'exemple à partir de Qt Creatorouvrez le mode Welcome et sélectionnez l'exemple de Examples. Pour plus d'informations, voir Qt Creator: Tutoriel : Construire et exécuter.

Utilisation de 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 Main.qml, nous utilisons un type Rectangle avec des 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 les propriétés personnalisées plus tard pour charger les données XML et pour ajuster la disposition 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 spécifions 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 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, sauf qu'ils contiennent une collection de définitions de rôles au lieu de propriétés. Les rôles définissent les modalités d'accès aux données et incluent les données elles-mêmes.

Pour chaque élément de 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 pour 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 présenter la liste des catégories horizontalement en haut de la fenêtre en orientation portrait et verticalement sur le côté gauche en orientation paysage, nous utilisons la propriété orientation. En fonction de l'orientation, nous lions la largeur ou la hauteur de la liste à une valeur fixe (itemWidth).

Nous utilisons la propriété anchors.top pour positionner la liste en haut de l'écran dans les deux orientations.

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

Création des éléments de la 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 attribuons à la propriété selected la propriété ListView.isCurrentItem attached pour spécifier que selected est true si delegate est l'élément courant.

Nous utilisons la propriété Image type 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 le formatage du texte.

Les propriétés color et scale permettent d'éclaircir le texte et de l'agrandir légèrement lorsque l'élément de la liste est l'élément courant. En appliquant un 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 liste de catégorie :

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

La propriété anchors.fill est définie sur delegate pour permettre aux utilisateurs d'appuyer n'importe où dans l'élément de liste.

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

Téléchargement des 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 nouvelles 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 XmlListModel génère un élément de modèle pour chaque <item> dans le document XML.

Nous utilisons le type XmlListModelRole pour spécifier 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 <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 dans un 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 dresser la liste des nouvelles sous la liste des catégories en orientation portrait et à sa droite en orientation paysage, nous utilisons la propriété personnalisée isPortrait pour ancrer le haut de la liste des nouvelles à la gauche de window et au bas de categories en orientation portrait et à la droite de categories et au bas de window en orientation paysage.

Nous utilisons la propriété anchors.bottom pour ancrer le bas de la liste au bas de la fenêtre dans les deux orientations.

Dans l'orientation portrait, nous découpons le tableau des actualités dans le rectangle de délimitation de la vue en liste afin d'éviter les artefacts graphiques lorsque les actualités défilent au-dessus d'autres articles. Dans l'orientation paysage, cela n'est pas nécessaire, car la liste s'étend verticalement sur tout l'écran.

Nous utilisons la propriété model pour charger les données XML 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 présenter 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

Dans la colonne, nous utilisons une ligne 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 de signal onLinkActivated pour ouvrir l'URL dans un navigateur externe lorsque les utilisateurs sélectionnent le lien.

Fournir un retour 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é isCurrentItem attached de la vue en liste delegate et à la propriété loading de la fenêtre principale pour afficher l'image de l'indicateur lorsqu'un élément de la liste de catégories est l'élément courant et que les 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 pour 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 du module Qt Quick Controls.

Création de barres de défilement

Dans Main.qml, nous utilisons notre propre type ScrollBar pour créer des barres de défilement dans les listes de catégories et d'articles. Dans vos applications, vous pouvez également utiliser le type ScrollView du module Qt Quick Controls.

Tout d'abord, nous créons une barre de défilement dans la vue de la liste des catégories. Nous lions la propriété orientation à la propriété isPortrait et à la valeur Horizontal du type Qt::Orientation enum pour afficher une barre de défilement horizontale en orientation portrait et à la valeur Vertical pour afficher une barre de défilement verticale en orientation 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 en 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 en liste categories.

Nous utilisons la propriété anchors.right pour ancrer la barre de défilement sur le côté droit 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
    }

Deuxièmement, nous créons une autre barre de défilement dans la vue en liste des actualités. Nous voulons qu'une barre de défilement verticale apparaisse sur le côté droit 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 la barre de défilement en haut de la liste des catégories en orientation portrait et en haut de la liste des actualités en orientation paysage. Nous utilisons la propriété anchors.bottom pour ancrer la barre de défilement inférieure au bas de la liste dans les deux orientations.

Nous définissons le type ScrollBar dans ScrollBar.qml. Nous utilisons un type Item avec des propriétés personnalisées pour créer un conteneur pour 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 pouce de la barre de défilement à la position 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 pouce en fonction de l'orientation de l'écran.

Nous utilisons states pour rendre la barre de défilement visible lorsque les utilisateurs déplacent 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 liste d'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 poussoir 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 liste des actualités, avec des marges de 4 pixels. Comme le bouton de fermeture recouvre la liste des catégories en orientation 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 à l'intérieur d'un MouseArea pour appeler la fonction quit() lorsque les utilisateurs sélectionnent le bouton de fermeture.

Exemple de projet @ code.qt.io

Voir aussi 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.