简易 RSS 列表应用程序
演示如何获取和显示网络资源。
本例以 RSS 列表应用程序为例,演示如何获取用户请求的资源并显示响应中包含的数据。(RDF 网站摘要,即 Really Simple Syndication,是一种向网站发送更新信息的标准格式)。详见 https://www.rssboard.org/rss-specification)。图中的用户界面很简单,因为本例的重点是如何使用网络,但对于一个严肃的 RSS 阅读器来说,自然需要一个更复杂的界面。
该示例还说明了如何在接收到数据时对其进行异步解析,在成员变量中保留状态,以便增量解析器可以在数据通过网络到达时消耗数据块。解析内容的组成成分可能从一个数据块开始,但要到下一个数据块才完成,这就要求解析器在调用之间保留状态。
主程序非常简单。它只需实例化QApplication 和RSSListing
部件,显示后者并将控制权移交给前者。为了便于说明,它将 Qt 博客的 URL 作为要检查的资源的默认值提供给 Widget。
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); }
所有槽都通过将任何艰巨的工作委托给私有方法来保持简单。
当用户通过点击 "Fetch "按钮或按下行编辑器中的回车键完成 URL 输入时,fetch()
插槽会禁用 "Fetch "按钮,并禁用对行编辑器的进一步编辑。它将清除可用更新的显示,并委托get()
发起 HTTP 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. }
fetch()
插槽使用私有的get()
方法启动 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 = newQTreeWidgetItemitem->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 。
© 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.