一款简约的RSS列表应用程序
演示如何获取并显示网络资源。
本示例通过一个 RSS 列表应用程序,演示了如何获取用户请求的资源并显示响应中包含的数据。(RDF Site Summary,即 Really Simple Syndication,是一种用于传达网站更新的标准格式。 详情请参见 https://www.rssboard.org/rss-specification。)图中的用户界面较为简单,因为本示例的重点在于如何使用网络功能,但对于一款专业的 RSS 阅读器来说,自然需要更复杂的界面。
该示例还演示了如何在接收数据时进行异步解析,通过在成员变量中保存状态,使增量解析器能够处理通过网络传来的数据块。 已解析内容的组成部分可能始于一个数据块,但直到后续数据块才完成,这要求解析器在调用之间保留状态。

主程序相当简洁。它仅实例化了一个QApplication 和RSSListing 控件,显示后者并将控制权移交给前者。为了便于说明,它将Qt博客的URL作为要检查的资源的默认值传递给该控件。
int main(int argc, char **argv)
{
QApplication app(argc, argv);
RSSListing rsslisting(u"https://www.qt.io/blog/rss.xml"_s);
rsslisting.show();
return app.exec();
}RSSListing 类
class RSSListing : public QWidget
{
Q_OBJECT
public:
explicit RSSListing(const QString &url = QString(), QWidget *widget = nullptr);
public slots:
void fetch();
void finished(QNetworkReply *reply);
void consumeData();
void error(QNetworkReply::NetworkError);
private:
void parseXml();
void get(const QUrl &url);
// Parser state:
QXmlStreamReader xml;
QString currentTag;
QString linkString;
QString titleString;
// Network state:
QNetworkAccessManager manager;
QNetworkReply *currentReply;
// UI elements:
QLineEdit *lineEdit;
QTreeWidget *treeWidget;
QPushButton *fetchButton;
};该控件本身提供了一个简单的用户界面,用于指定要获取的 URL,并在显示可用更新后,控制更新项的下载。QLineEdit 用于输入 URL,而QTreeWidget 则用于在获取结果后显示结果。
该控件以异步方式下载并解析 RSS(一种 XML 格式),并在数据到达时将其传送至 XML 读取器。这支持读取非常庞大的数据源。由于数据是通过 XML 读取器从网络流式传输的,因此无需将 XML 的全文保存在内存中。 在其他场景下,类似的方法可允许用户中断此类增量加载。
构建
RSSListing::RSSListing(const QString &url, QWidget *parent)
: QWidget(parent), currentReply(0)
{
connect(&manager, &QNetworkAccessManager::finished, this, &RSSListing::finished);
lineEdit = new QLineEdit(this);
lineEdit->setText(url);
connect(lineEdit, &QLineEdit::returnPressed, this, &RSSListing::fetch);
fetchButton = new QPushButton(tr("Fetch"), this);
connect(fetchButton, &QPushButton::clicked, this, &RSSListing::fetch);
treeWidget = new QTreeWidget(this);
connect(treeWidget, &QTreeWidget::itemActivated,
// Open the link in the browser:
this, [](QTreeWidgetItem *item) { QDesktopServices::openUrl(QUrl(item->text(1))); });
treeWidget->setHeaderLabels(QStringList { tr("Title"), tr("Link") });
treeWidget->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
QHBoxLayout *hboxLayout = new QHBoxLayout;
hboxLayout->addWidget(lineEdit);
hboxLayout->addWidget(fetchButton);
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addLayout(hboxLayout);
layout->addWidget(treeWidget);
setWindowTitle(tr("RSS listing example"));
resize(640, 480);
}构造函数会初始化小部件的各项组件,并将它们的各种信号连接到用于处理这些信号的槽中。
用户界面由一个行编辑框、一个按钮和一个列表视图控件组成。行编辑框用于输入要获取的 URL;按钮用于启动获取更新的过程。行编辑框默认为空,但构造函数的调用方可以覆盖此行为,正如我们的 `main() ` 所做的那样。 无论如何,用户都可以用另一个 RSS 源的 URL 替换默认值。
列表视图显示 RSS 源中报告的更新项目。双击其中一个项目,将通过QDesktopServices::openUrl() 将其 URL 发送至用户的浏览器或其他用户代理。
插槽
voidRSSListing::fetch()
{
lineEdit->setReadOnly(true);
fetchButton->setEnabled(false);
treeWidget->clear();
get(QUrl(lineEdit->text()));
}
voidRSSListing::consumeData()
{
intstatusCode= currentReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if(statusCode>= 200&&statusCode< 300)
parseXml();
}
voidRSSListing::error(QNetworkReply::NetworkError)
{
qWarning("error retrieving RSS feed");
xml.clear();
currentReply->disconnect(this);
currentReply->deleteLater();
currentReply=nullptr;
}
voidRSSListing::finished(QNetworkReply*reply)
{
Q_UNUSED(reply);
lineEdit->setReadOnly(false);
fetchButton->setEnabled(true);
}所有槽函数都保持简单,将繁重的工作委托给私有方法处理。
当用户完成 URL 输入后(无论是点击“获取”按钮,还是在行编辑框中按下回车键),fetch() 槽函数会禁用“获取”按钮,并禁止对行编辑框进行进一步编辑。它会清除已有的更新列表,并将发起 HTTP GET 请求的任务委托给get() 。
当接收到数据时,网络响应会触发其readyRead()信号,而get() 将该信号连接至consumeData() 插槽。该插槽会检查响应是否返回了成功的状态码,若返回成功,则调用parseXml() 来处理数据。
如果网络响应出现错误,该错误将传递至error() 槽,该槽会报告错误、清空XML流读取器,随后断开与响应的连接并将其删除。
网络响应完成后(无论是否成功),finished() 插槽会通过重新启用文本输入框和“获取”按钮,将用户界面恢复至可接受新 URL 进行抓取的状态。
get() 方法
void RSSListing::get(const QUrl &url)
{
if (currentReply) {
currentReply->disconnect(this);
currentReply->deleteLater();
}
currentReply = url.isValid() ? manager.get(QNetworkRequest(url)) : nullptr;
if (currentReply) {
connect(currentReply, &QNetworkReply::readyRead, this, &RSSListing::consumeData);
connect(currentReply, &QNetworkReply::errorOccurred, this, &RSSListing::error);
}
xml.setDevice(currentReply); // Equivalent to clear() if currentReply is null.
}私有方法 `get() ` 由 `fetch() ` 槽调用,用于发起 HTTP GET 请求。它首先清空 XML 流读取器,如果当前有活跃的响应,则断开连接并删除该响应。 如果传入的 URL 有效,它会请求网络访问管理器执行 GET 操作。它将相关插槽与返回的响应(如有)的信号关联起来,并配置其 XML 流读取器以从响应中读取数据——网络响应对象也是QIODevice ,可从中读取数据。
parseXml() 方法
voidRSSListing::parseXml()
{
while(!xml.atEnd()) {
xml.readNext();
if(xml.isStartElement()) {
if(xml.name()==u"item") {
linkString=xml.attributes().value("rss:about").toString();
titleString.clear();
}
currentTag=xml.name().toString();
}else if(xml.isEndElement()) {
if(xml.name()==u"item") {
QTreeWidgetItem*item = newQTreeWidgetItem;
item->setText(0,titleString);
item->setText(1,linkString);
treeWidget->addTopLevelItem(item);
}
}else if(xml.isCharacters()&& !xml.isWhitespace()) {
if(currentTag== "title")
titleString+=xml.text();
else if(currentTag== "link")
linkString+=xml.text();
}
}
if(xml.error()&&xml.error()!=QXmlStreamReader::PrematureEndOfDocumentError)
qWarning() << "XML ERROR:" << xml.lineNumber() << ": " << xml.errorString();
}当接收到数据并将其提供给 XML 流读取器后,parseXml() 会从 XML 流中读取数据,检查是否存在item 元素,并在其中查找title 和link 元素。 它将使用item 的rss:about 属性作为树视图中“链接”列的URL,如果该属性不存在,则使用其link 元素的内容;并使用title 元素的内容作为树视图中“标题”列的内容。 随着每个item 元素的结束,其详细信息会被转换为树形控件中的一行,其中“标题”和“链接”列分别显示提取的标题和URL。
用于跟踪解析状态的变量——linkString 、titleString 和currentTag ——是RSSListing 类的成员变量,尽管它们仅在此方法中被访问,因为随着新数据的到达,该方法可能会被反复调用,且接收到的某一段数据可能开始解析一个元素,而该元素要等到后续数据段到达后才能完成。 这使得解析器能够在数据到达时异步运行,而无需等待所有数据都到达。
另请参阅 QNetworkReply 和QXmlStreamReader 。
© 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.