このページでは

Qt Quick デモ - RSSニュース

XmlListModel およびXmlListModelRole というカスタムQML型を使用してXMLデータをダウンロードし、ListModel およびListElement を使用してカテゴリ一覧を作成し、ListView を使用してデータを表示するQML RSSニュースリーダーです。

カテゴリサイドバーとニュースの見出しを備えたRSSニュースリーダー

RSS Newsでは、以下の Qt Quick 機能を実演しています:

  • カスタム QML タイプの使用。
  • リストモデルとリスト要素を使用したデータの表現。
  • XML リストモデルを使用した XML データのダウンロード。
  • リストビューを使用したデータの表示。
  • Component 型を使用して、ニュース項目リストビューのフッターを作成する。
  • Image 型を使用して、アプリを終了するためのボタンを作成する。

例の動作確認

サンプルは次の手順で実行できます。

カスタムタイプの使用

RSS News アプリでは、それぞれ個別の .qml ファイルで定義されている以下のカスタム型を使用しています:

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

メインウィンドウの作成

Main.qml では、カスタムプロパティを持つRectangle 型を使用して、アプリのメインウィンドウを作成します:

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

これらのカスタムプロパティは、後でXMLデータの読み込みや、画面の向きに応じたレイアウトの調整に使用します。

カテゴリリストの作成

Main.qml では、RssFeeds.qml で指定したRssFeeds カスタムタイプを使用して、フィードカテゴリのリストを作成します:

    RssFeeds { id: rssFeeds }

RssFeeds.qml では、ListElement タイプを持つListModel タイプを使用して、リスト要素がフィードカテゴリを表すカテゴリリストを作成します:

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

リスト要素は、プロパティの代わりにロールの定義の集合を含む点を除き、他のQML型と同様に定義されます。ロールは、データへのアクセス方法を定義すると同時に、データそのものも包含します。

各リスト要素について、name ロールを使用してカテゴリ名を指定し、feed ロールを使用してデータを読み込むURLを指定し、image ロールを使用してカテゴリの画像を表示します。

Main.qml では、ListView タイプを使用してカテゴリ一覧を表示します:

    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

            }
        }

縦向き表示ではウィンドウ上部に、横向き表示では左側にカテゴリリストを水平または垂直に配置するために、orientation プロパティを使用します。表示向きに応じて、リストの幅または高さを固定値(itemWidth )にバインドします。

anchors.top プロパティを使用して、どちらの向きでもリストビューを画面上部に配置します。

model プロパティを使用して、rssFeeds モデルからXMLデータを読み込み、デリゲートとしてCategoryDelegate を指定して、リスト内の各アイテムをインスタンス化します。

リスト要素の作成

CategoryDelegate.qml では、カスタムプロパティを持つRectangle 型を使用してリスト要素を作成します。

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

required を使用して、モデルに期待するプロパティを宣言します。

selected のプロパティをListView.isCurrentItem の添付プロパティに設定し、delegate が現在の項目である場合にselected がtrue となるよう指定します。

Image 型のsource プロパティを使用して、rssFeeds リストモデル内のimage ロールによってリスト要素に指定された画像を、デリゲート内で中央揃えにして表示します。

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

リスト要素にタイトルを追加するために、Text 型を使用します:

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

anchors プロパティを使用して、タイトルをリスト要素の上部に配置し、20ピクセルの余白を設けます。font プロパティを使用して、フォントサイズとテキストの書式設定を調整します。

リスト項目が現在選択されている項目である場合、color およびscale プロパティを使用して、テキストを明るく表示し、サイズをわずかに大きくします。また、Behavior をプロパティに適用することで、リスト項目の選択および選択解除の動作にアニメーション効果を与えます。

ユーザーがカテゴリリスト要素をタップした際にXMLデータをダウンロードするために、MouseArea タイプを使用しています:

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

anchors.fill プロパティをdelegate に設定することで、ユーザーがリスト要素内の任意の場所をタップできるようにしています。

clicked イベントでカテゴリリストのXMLデータを読み込むため、ListView の添付プロパティであるcurrentIndex を設定し、onClicked 内でclicked のsignal()を発行します。

XMLデータのダウンロード

Main.qml では、XmlListModel 型をListView 要素のデータソースとして使用し、選択されたカテゴリのニュース記事を表示します:

    XmlListModel {
        id: feedModel

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

source プロパティとwindow.currentFeed カスタムプロパティを使用して、選択されたカテゴリのニュース項目を取得します。

query プロパティは、XmlListModel がXMLドキュメント内の各<item> に対してモデルアイテムを生成することを指定します。

モデル項目の属性を指定するには、XmlListModelRole 型を使用します。各モデル項目には、XMLドキュメント内の対応する<item> の値と一致する、title 、content 、link 、およびpubDate 属性があります。

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

データの表示には、ListView 型でfeedModel モデルを使用します:

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

縦向き表示ではカテゴリリストの下に、横向き表示ではその右側にニュース項目を一覧表示するために、isPortrait のカスタムプロパティを使用し、縦向き表示ではニュース項目リストの上端をwindow の左側およびcategories の下端に、横向き表示ではcategories の右側およびwindow の下端に固定します。

anchors.bottom プロパティを使用して、どちらの向きでもリストビューの下端をウィンドウの下端に固定します。

縦向き表示では、ニュース項目が他の項目の上をスクロールした際にグラフィックの不自然さが生じないように、ニュース項目の描画をリストビューの境界矩形にクリップしています。横向き表示では、リストが画面全体を縦方向に占めるため、この処理は必要ありません。

model プロパティを使用して、feedModel モデルから XML データを読み込み、NewsDelegate をデリゲートとして使用して、リスト内の各アイテムをインスタンス化します。

NewsDelegate.qml では、Column 型を使用して 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

列内では、Rowおよび別の列を使用して、画像とタイトルテキストを配置します:

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

timeSinceEvent() のJavaScript関数を使用して、アイテムが投稿されてからの経過時間をテキスト形式で生成します:

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

ユーザーがリンクを選択した際に、外部ブラウザでURLを開くために、onLinkActivated のシグナルハンドラを使用しています。

ユーザーへのフィードバックの提供

CategoryDelegate.qml では、XML データの読み込み中にアクティビティを示すために、BusyIndicator カスタムタイプを使用します:

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

scale プロパティを使用して、インジケーターのサイズを0.8 に縮小します。visible プロパティを、delegate リストビューのisCurrentItem アタッチドプロパティおよびメインウィンドウのloading プロパティにバインドし、カテゴリリスト項目が現在の項目であり、XMLデータが読み込まれている際にインジケーター画像を表示します。

BusyIndicator.qml 内で `BusyIndicator ` 型を定義します。画像を表示するために `Image ` 型を使用し、その `rotation ` プロパティに `NumberAnimation ` を適用して、画像を無限ループで回転させます。

Image {
    id: container

    source: "images/busy.png";

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

アプリでは、Qt Quick の「Controls」モジュールにあるBusyIndicator 型を使用することもできます。

スクロールバーの作成

『Main.qml 』では、カテゴリおよびニュース項目リストビューにスクロールバーを作成するために、独自にカスタマイズしたScrollBar 型を使用しています。アプリでは、Qt Quick Controlsモジュールに含まれるScrollView 型を使用することもできます。

まず、カテゴリ一覧ビューにスクロールバーを作成します。orientation プロパティをisPortrait プロパティおよびQt::Orientation 列挙型のHorizontal 値にバインドして、縦向き表示時に水平スクロールバーを表示し、Vertical 値にバインドして、横向き表示時に垂直スクロールバーを表示します。

    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
    }

categories リストビューと同様に、isPortrait プロパティに基づいてスクロールバーの幅と高さを調整します。

categories リストビューでスクロールバーを表示するには、scrollArea プロパティを使用します。

anchors.right プロパティを使用して、スクロールバーをカテゴリリストの右側に固定します。

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

次に、ニュース項目リストビューに別のスクロールバーを作成します。画面の向きに関係なく、ビューの右側に垂直スクロールバーが表示されるようにするため、width プロパティを8 に設定し、anchors.right プロパティをwindow.right プロパティにバインドします。anchors.top プロパティを使用して、縦向き表示ではスクロールバーの上端をカテゴリリストの下端に、横向き表示ではニュース項目リストの上端に固定します。また、anchors.bottom プロパティを使用して、どちらの向きでもスクロールバーの下端をリストビューの下端に固定します。

ScrollBar.qml 内でScrollBar 型を定義します。また、カスタムプロパティを持つItem 型を使用して、スクロールバー用のコンテナを作成します:

Item {
    id: container

    property variant scrollArea
    property int orientation: Qt.Vertical

    opacity: 0

BorderImage 型を使用して、position() 関数で計算したx座標とy座標の位置にスクロールバーのサムを表示します:

    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
    }

size 関数を使用して、画面の向きに応じてスクロールバーのサム(つまみ)の幅と高さを計算します。

states を使用して、ユーザーがスクロール領域を移動した際にスクロールバーを表示します:

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

状態が「"visible" 」からデフォルトの状態に変わった際に、opacity プロパティにNumberAnimation を適用するためにtransitions を使用します:

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

フッターの作成

Main.qml では、Rectangle 型のComponent を使用して、ニュース一覧ビューのフッターを作成します:

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

ニュース項目が表示されていない際に、フッターのwidth をコンポーネントの幅に、height を閉じるボタンの高さにバインドすることで、両者を揃えています。

ボタンの作成

Main.qml では、Image タイプを使用して、ユーザーがタップするとアプリを閉じることができるシンプルなプッシュボタンを作成します:

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

anchors を使用して、閉じるボタンをニュース一覧ビューの右上隅に、マージン 4 ピクセルで配置します。縦向き表示では閉じるボタンがカテゴリ一覧と重なるため、ユーザーがカテゴリ一覧をスクロールしている間は、opacity プロパティにアニメーションを適用し、ボタンをほぼ完全に透明にします。

ユーザーが閉じるボタンを選択した際に、MouseArea 内でonClicked シグナルハンドラを呼び出し、quit() 関数を実行します。

サンプルプロジェクト @ code.qt.io

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