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

RSS Newsでは、以下の Qt Quick 機能を実演しています:
- カスタム QML タイプの使用。
- リストモデルとリスト要素を使用したデータの表現。
- XML リストモデルを使用した XML データのダウンロード。
- リストビューを使用したデータの表示。
- Component 型を使用して、ニュース項目リストビューのフッターを作成する。
- Image 型を使用して、アプリを終了するためのボタンを作成する。
例の動作確認
サンプルは次の手順で実行できます。
- Qt Creator
Welcome モードを開き、Examples からサンプルを選択します。詳細については、Qt Creator の「チュートリアル:ビルドと実行」を参照してください。
- Qt Extension for Visual Studio Code
Command Palette から「Qt: Open Qt examples 」コマンドを実行し、リストからサンプルを選択します。詳細については、Qt Extension for Visual Studio Code の「チュートリアル: ビルドと実行」を参照してください。
カスタムタイプの使用
RSS News アプリでは、それぞれ個別の .qml ファイルで定義されている以下のカスタム型を使用しています:
BusyIndicator.qmlCategoryDelegate.qmlNewsDelegate.qmlRssFeeds.qmlScrollBar.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.isCurrentItemrequired を使用して、モデルに期待するプロパティを宣言します。
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(/'/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 型を使用して、スクロールバー用のコンテナを作成します:
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() 関数を実行します。
「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.