Qt Quick Demo - RSS-Nachrichten

Ein QML-RSS-Newsreader, der die benutzerdefinierten QML-Typen XmlListModel und XmlListModelRole verwendet, um XML-Daten herunterzuladen, ListModel und ListElement, um eine Kategorieliste zu erstellen, und ListView, um die Daten anzuzeigen.

RSS News demonstriert die folgenden Qt Quick Funktionen:

  • Verwendung benutzerdefinierter QML-Typen.
  • Verwendung von Listenmodellen und Listenelementen zur Darstellung von Daten.
  • Verwendung von XML-Listenmodellen zum Herunterladen von XML-Daten.
  • Verwendung von Listenansichten zur Anzeige von Daten.
  • Verwendung des Typs Component zur Erstellung einer Fußzeile für die Listenansicht der Nachrichten.
  • Verwendung des Typs Image, um eine Schaltfläche zum Schließen der Anwendung zu erstellen.

Ausführen des Beispiels

Zum Ausführen des Beispiels von Qt Creatorauszuführen, öffnen Sie den Modus Welcome und wählen Sie das Beispiel aus Examples aus. Weitere Informationen finden Sie unter Erstellen und Ausführen eines Beispiels.

Verwenden von benutzerdefinierten Typen

In der RSS-News-App verwenden wir die folgenden benutzerdefinierten Typen, die jeweils in einer separaten .qml-Datei definiert sind:

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

Erstellen des Hauptfensters

In Main.qml verwenden wir einen Typ Rectangle mit benutzerdefinierten Eigenschaften, um das Hauptfenster der App zu erstellen:

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

Wir werden die benutzerdefinierten Eigenschaften später zum Laden von XML-Daten und zum Anpassen des Bildschirmlayouts in Abhängigkeit von seiner Ausrichtung verwenden.

Erstellen einer Kategorieliste

In Main.qml verwenden wir den benutzerdefinierten Typ RssFeeds, den wir in RssFeeds.qml angeben, um eine Liste von Feed-Kategorien zu erstellen:

    RssFeeds { id: rssFeeds }

In RssFeeds.qml verwenden wir einen ListModel Typ mit einem ListElement Typ, um eine Kategorieliste zu erstellen, in der Listenelemente Feed-Kategorien darstellen:

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" }
}

Listenelemente werden wie andere QML-Typen definiert, mit der Ausnahme, dass sie eine Sammlung von Rollendefinitionen anstelle von Eigenschaften enthalten. Rollen legen fest, wie auf die Daten zugegriffen wird, und enthalten die Daten selbst.

Für jedes Listenelement verwenden wir die Rolle name, um den Kategorienamen anzugeben, die Rolle feed, um die URL anzugeben, von der die Daten geladen werden, und die Rolle image, um ein Bild für die Kategorie anzuzeigen.

In Main.qml verwenden wir einen ListView Typ, um die Kategorieliste anzuzeigen:

    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

            }
        }

Um die Kategorieliste im Hochformat horizontal am oberen Rand des Fensters und im Querformat vertikal auf der linken Seite anzuordnen, verwenden wir die Eigenschaft orientation. Je nach Ausrichtung binden wir entweder die Breite oder die Höhe der Liste an einen festen Wert (itemWidth).

Mit der Eigenschaft anchors.top positionieren wir die Listenansicht in beiden Ausrichtungen am oberen Rand des Bildschirms.

Wir verwenden die Eigenschaft model, um XML-Daten aus dem Modell rssFeeds zu laden, und CategoryDelegate als Delegat, um jedes Element in der Liste zu instanziieren.

Erstellen von Listenelementen

In CategoryDelegate.qml verwenden wir den Typ Rectangle mit benutzerdefinierten Eigenschaften, um Listenelemente zu erstellen:

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

Wir verwenden required, um die Eigenschaften zu deklarieren, die wir vom Modell erwarten.

Wir setzen die Eigenschaft selected auf die angehängte Eigenschaft ListView.isCurrentItem, um anzugeben, dass selected true ist, wenn delegate das aktuelle Element ist.

Wir verwenden die Eigenschaft Image type source, um das Bild anzuzeigen, das im Delegaten zentriert ist und für das Listenelement durch die Rolle image im Listenmodell rssFeeds festgelegt wurde:

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

Wir verwenden einen Text Typ, um den Listenelementen Titel hinzuzufügen:

    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 } }

Wir verwenden die Eigenschaft anchors, um den Titel am oberen Rand des Listenelements mit einem 20-Pixel-Rand zu positionieren. Wir verwenden die Eigenschaften font, um die Schriftgröße und die Textformatierung anzupassen.

Die Eigenschaften color und scale werden verwendet, um den Text aufzuhellen und etwas größer zu skalieren, wenn das Listenelement das aktuelle Element ist. Durch die Anwendung einer Behavior auf die Eigenschaft werden die Aktionen zum Auswählen und Aufheben der Auswahl von Listenelementen animiert.

Wir verwenden einen MouseArea -Typ, um XML-Daten herunterzuladen, wenn Benutzer auf ein Element der Kategorieliste tippen:

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

Die Eigenschaft anchors.fill wird auf delegate gesetzt, damit die Benutzer auf eine beliebige Stelle innerhalb des Listenelements tippen können.

Um die XML-Daten für die Kategorieliste bei einem clicked -Ereignis zu laden, setzen wir die currentIndex der ListView angehängten Eigenschaft und geben das clicked -Signal() in onClicked aus.

Herunterladen von XML-Daten

In Main.qml verwenden wir einen XmlListModel Typ als Datenquelle für ListView Elemente, um Nachrichten in der ausgewählten Kategorie anzuzeigen:

    XmlListModel {
        id: feedModel

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

Wir verwenden die Eigenschaft source und die benutzerdefinierte Eigenschaft window.currentFeed, um Nachrichten für die ausgewählte Kategorie abzurufen.

Die Eigenschaft query gibt an, dass XmlListModel ein Modellelement für jedes <item> im XML-Dokument erzeugt.

Wir verwenden den Typ XmlListModelRole, um die Attribute der Modellelemente anzugeben. Jedes Modellelement hat die Attribute title, content, link und pubDate, die mit den Werten der entsprechenden <item> im XML-Dokument übereinstimmen:

        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: "" }
    }

Wir verwenden das Modell feedModel in einem Typ ListView zur Anzeige der Daten:

    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 {}
    }

Um die Nachrichten unterhalb der Kategorieliste im Hochformat und rechts davon im Querformat aufzulisten, verwenden wir die benutzerdefinierte Eigenschaft isPortrait, um den oberen Teil der Nachrichtenliste links von window und unten von categories im Hochformat und rechts von categories und unten von window im Querformat zu verankern.

Wir verwenden die Eigenschaft anchors.bottom, um den unteren Rand der Listenansicht in beiden Ausrichtungen am unteren Rand des Fensters zu verankern.

Im Hochformat wird das Bild der Nachrichten an das Begrenzungsrechteck der Listenansicht geklammert, um grafische Artefakte zu vermeiden, wenn Nachrichten über andere Elemente gescrollt werden. Im Querformat ist dies nicht erforderlich, da sich die Liste vertikal über den gesamten Bildschirm erstreckt.

Wir verwenden die Eigenschaft model, um XML-Daten aus dem Modell feedModel zu laden, und verwenden NewsDelegate als Delegat, um jedes Element in der Liste zu instanziieren.

In NewsDelegate.qml verwenden wir einen Column Typ, um die XML-Daten anzuordnen:

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

Innerhalb der Spalte verwenden wir eine Zeile und eine weitere Spalte, um Bilder und Titeltext zu positionieren:

    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
        }
    }

Mit der JavaScript-Funktion timeSinceEvent() erzeugen wir eine Textdarstellung, die angibt, wie lange es her ist, dass der Eintrag veröffentlicht wurde:

    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)
        }
    }

Wir verwenden den onLinkActivated signal handler, um die URL in einem externen Browser zu öffnen, wenn Benutzer den Link auswählen.

Rückmeldung an die Benutzer

In CategoryDelegate.qml verwenden wir den benutzerdefinierten Typ BusyIndicator, um die Aktivität beim Laden der XML-Daten anzuzeigen:

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

Wir verwenden die Eigenschaft scale, um die Größe des Indikators auf 0.8 zu reduzieren. Wir binden die Eigenschaft visible an die angehängte Eigenschaft isCurrentItem der Listenansicht delegate und die Eigenschaft loading des Hauptfensters, um das Indikatorbild anzuzeigen, wenn ein Element der Kategorieliste das aktuelle Element ist und die XML-Daten geladen werden.

Wir definieren den Typ BusyIndicator in BusyIndicator.qml. Wir verwenden den Typ Image, um ein Bild anzuzeigen, und wenden NumberAnimation auf seine Eigenschaft rotation an, um das Bild in einer Endlosschleife zu drehen:

Image {
    id: container

    source: "images/busy.png";

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

In Ihren Anwendungen können Sie auch den Typ BusyIndicator aus dem Qt Quick Controls Modul verwenden.

Erstellen von Bildlaufleisten

In Main.qml verwenden wir unseren eigenen benutzerdefinierten Typ ScrollBar, um Bildlaufleisten in den Listenansichten der Kategorien und Nachrichten zu erstellen. In Ihren Anwendungen können Sie auch den Typ ScrollView aus dem Qt Quick Controls Modul verwenden.

Zunächst erstellen wir eine Bildlaufleiste in der Kategorie-Listenansicht. Wir binden die Eigenschaft orientation an die Eigenschaft isPortrait und an den Wert Horizontal des Enum-Typs Qt::Orientation, um eine horizontale Bildlaufleiste im Hochformat anzuzeigen, und an den Wert Vertical, um eine vertikale Bildlaufleiste im Querformat anzuzeigen:

    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
    }

Wie bei der Listenansicht categories passen wir die Breite und Höhe der Bildlaufleiste anhand der Eigenschaft isPortrait an.

Wir verwenden die Eigenschaft scrollArea, um die Bildlaufleiste in der Listenansicht categories anzuzeigen.

Mit der Eigenschaft anchors.right verankern wir die Bildlaufleiste an der rechten Seite der Kategorieliste.

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

Zweitens erstellen wir eine weitere Bildlaufleiste in der Listenansicht der Nachrichten. Wir möchten, dass eine vertikale Bildlaufleiste unabhängig von der Bildschirmausrichtung auf der rechten Seite der Ansicht angezeigt wird. Daher können wir die Eigenschaft width auf 8 setzen und die Eigenschaft anchors.right an die Eigenschaft window.right binden. Wir verwenden die Eigenschaft anchors.top, um die Bildlaufleiste im Hochformat am unteren Rand der Kategorieliste und im Querformat am oberen Rand der Nachrichtenliste zu verankern. Wir verwenden die Eigenschaft anchors.bottom, um die Bildlaufleiste in beiden Ausrichtungen am unteren Rand der Listenansicht zu verankern.

Wir definieren den Typ ScrollBar in ScrollBar.qml. Wir verwenden einen Typ Item mit benutzerdefinierten Eigenschaften, um einen Container für die Bildlaufleiste zu erstellen:

Item {
    id: container

    property variant scrollArea
    property int orientation: Qt.Vertical

    opacity: 0

Wir verwenden einen BorderImage Typ, um den Daumen der Bildlaufleiste an der x- und y-Position anzuzeigen, die wir mit der Funktion position() berechnen:

    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
    }

Wir verwenden die Funktion size, um die Breite und Höhe des Daumens in Abhängigkeit von der Bildschirmausrichtung zu berechnen.

Wir verwenden states, um die Bildlaufleiste sichtbar zu machen, wenn der Benutzer den Bildlaufbereich bewegt:

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

Wir verwenden transitions, um NumberAnimation auf die Eigenschaft opacity anzuwenden, wenn der Status von "visible" in den Standardstatus wechselt:

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

Fußzeilen erstellen

In Main.qml verwenden wir einen Component Typ mit einem Rectangle Typ, um eine Fußzeile für die News-Listenansicht zu erstellen:

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: ""}

Wir binden die width der Fußzeile an die Breite der Komponente und die height an die Höhe der Schaltfläche Schließen, um sie auszurichten, wenn keine Nachrichten angezeigt werden.

Erstellen von Schaltflächen

In Main.qml verwenden wir einen Image Typ, um eine einfache Drucktaste zu erstellen, auf die Benutzer tippen können, um die App zu schließen:

        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()
            }
        }
    }

Mit anchors positionieren wir die Schließen-Schaltfläche in der oberen rechten Ecke der Nachrichtenlistenansicht mit einem Rand von 4 Pixeln. Da die Schließen-Schaltfläche die Kategorieliste im Hochformat überlappt, animieren wir die Eigenschaft opacity, um die Schaltfläche fast vollständig transparent zu machen, wenn der Benutzer die Kategorieliste durchblättert.

Wir verwenden den onClicked Signalhandler innerhalb eines MouseArea, um die quit() Funktion aufzurufen, wenn der Benutzer die Schließen-Schaltfläche auswählt.

Beispielprojekt @ code.qt.io

Siehe auch QML-Anwendungen.

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