Qt4使用QWebView实现浏览器相关的功能,看网上都说Bug很多,到了Qt5.5,使用QWebEngine模块及其中的类代替QWebView实现浏览器的功能,官方介绍,内部技术修改了,总之现在比较稳定了。关于一些基础配置可以参考基础篇的文章Day29Qt显示Web内容2022-01-05要实现的功能有,浏览器导航栏,包括"前进"、“后退”、”刷新“;可实时刷新界面title和图标;可以查看历史记录,设置主页,查看网页源代码;可实现网页的缩放;实时显示网页加载进度;可以进行超链接跳转。功能之间相对独立,没有复杂的逻辑关系,代码梳理的应该比较明晰了,下面按照顺序介绍开发步骤,并介绍一些注意事项,随后将代码贴上。源代码下载
0.加载主页,设置主页
加载主页说的是,程序启动的时候,自动进入一个网页,代码中我设置的是我CSDN的主页。windows系统中保存配置文件使用.ini文件或者注册表,linux使用.conf文件作为配置文件,qt使用QSetting类提供了对这类操作的支持。代码中我使用的是ini文件保存了主页的url,程序启动时,读取文件,并使用QWebEngineView的load方法加载即可。设置主页就是在菜单栏中提供”设置主页“选项,用户点击时去修改ini文件,将当前网址栏中的url保存即可。需要注意的是,需要将QWebEngineView类实例化的对象当作一个窗口对象放在界面上才可以显示,程序中放在一个栅格布局中便于布局管理。
1.执行显示当前网址操作
程序接受lineEdit回车按钮信号或者“转到”按钮,执行显示当前网址操作。将两个信号连接至一个槽,在该槽中执行获取网址栏url,并使用load方法加载即可。
2.修改窗口标题
接受webEngineView的titleChange信号,执行修改窗口标题操作。QWebEngineView对象将用title方法返回当前页面title,将其设置到界面title即可。
3.设置进度条
构造一个进度条,设置其样式,QWebEngineView类的loadProgress将返回页面加载进度值,据此值设置进度条进度值;同时当界面加载完成时,QWebEngineView类将发出finished信号,启用定时器,进度条等待5s之后隐藏。另外重写resize事件,当界面大小更改时,显示状态的进度条应随窗口大小改变。
4.刷新网页url
当界面因为用户单击超链接点击发生跳转时,QWebEngineView类将发射urlChanged,并携带参数url表示当前界面的url值,将其设置在网址栏即可。
5.添加界面中工具栏
创建工具栏,并设置停靠,移动的属性,将QWebEngineView类提供的back、forward等QAction添加在界面工具栏中,当作网页浏览器的导航栏。将网址栏和”转到“按钮也添加进工具栏。
6.获取并显示网站图标
此处将使用到Qt Network模块,发出http请求,请求网页的ico图标,并将该图标设置为应用程序图。当然,在获取出错的时候应该设置一个默认的图标。过程中要发出网络请求,并处理网络请求返回的数据,将其转换成QIcon设置为图标即可。对于网络模块不熟悉的可以参考基础专栏中的Day28Qt实现http请求2022-01-06
7.实现网页缩放
使用QWebEngineView类提供的setZoomFactor方法即可实现界面的放大和缩小,范围是0.25~500。同时将缩放信息打印在状态栏,并为缩放操作设置快捷键。
8.查看网页源代码
在QWebView的时代,查看源代码是提供了方法的,大概是webview->page->currentFrame->toHtml();可以直接获取。到了QWebEngine时代,将查看源代码当作了一个QAction来处理,当你设计了QWebEngineView之后,你会发现,页面中右键即可出现”view page source“,即便你已经加载页面成功,单击这个action也没有响应。这是为啥呢?在Qt帮助中,写的很清楚,如下图
也就是说,必须重新实现creatWindow之后才能使用这个功能。或者说点击才有效。那么怎么检测是否具备执行这个action的条件呢?同样截取自Qt Help,使用这个函数,参数1填写想执行的Action,参数2写入bool值,当Action允许被执行时,参数2将为true,如果环境不具备将返回false。
在我的程序中,重写了creatWindow,所以可以使用这个QAction。
9.显示历史记录
创建全局指针对象QListwidget用以保存历史记录,界面添加”历史记录“菜单项,当用户单击时,通过history函数返回一个QWebEngineHistory对象,表示浏览历史,获取历史记录,并将其写到listwidget中。items获取所有项目列表,最后非模态形式显示即可。如下:
foreach(QWebEngineHistoryItem item,myView->history()->items()){
QListWidgetItem *history = new QListWidgetItem(item.title());
historyList->addItem(history);
}
当用户点击历史记录项的时候,应该使用listwidget方法确定当前行,配合history()->goToItem(item);方法转到单击的页面。
10.响应超链接
以上0-9步骤完成之后你会发现,界面是无法响应超链接的,当鼠标放在超链上是有部件的状态变化,可是点击却没有反应。有反应说明Qt已经获取到了这个点击信息,不需要你再去根据鼠标位置单击事件做解析。那他的机制是,当你点击超链接,或者运行JavaScript、或其他会弹出界面的操作时,他会去调用虚函数,creatwindow,但默认这个函数中没有内容,因此需要在继承自QWebEngineView的子类中重写。例如在该函数中打印某信息,当你点击超链或者查看源代码时,控制台会打印这句话。在该函数中直接返回this,将实现超链的响应,即网页的跳转。当然该函数提供了一个参数,这个参数是网页传递的,即QWebEnginePage::WebWindowType type,查看帮助可知该枚举类型有四类,可以在重写的creatWindow中根据不同的type执行不同的操作。
以上步骤介绍比较详细了,当然有一些必要的判断操作,提示操作以及错误判断未提及,这是比较容易的。稍后贴出代码。
#include "mainwindow.h"
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void doProcessSetMainPage();
void doProcessLineEnter();
void doProcessChangeTitle();
void doProcessSetMyProgressBar(int p);
void doProcessHideMyProgressBar(bool isFinished);
void doProcessRefreshUrl(QUrl url);
void doProcessGetWebIcon(const QUrl url);
void doProcessSetWindowIcon();
void doProcessShowSourceCode();
void doProcessShowHistory();
void doProcessGotoHistoryItem(const QModelIndex &index);
void on_zoomInAction_triggered();
void on_zoomOutAction_triggered();
void on_resetZoomAction_triggered();
protected:
void resizeEvent(QResizeEvent *event);
private:
Ui::MainWindow *ui;
webView * myView;
// QWebEngineView *myView;
QProgressBar * myProgressBar;
QNetworkAccessManager * manager;
QListWidget *historyList;
void Init();
void setMyToolBar();
void loadMainPage();
};
#endif // MAINWINDOW_H
其中注释中序号与”步骤“相对应。
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
Init();
}
MainWindow::~MainWindow()
{
delete ui;
}
/****************** SLOT(数字对应功能) ****************/
//0.设置主页
void MainWindow::doProcessSetMainPage()
{
QFile *myFile = new QFile;
QString filename = "mainpage.ini";
myFile->setFileName(filename);
bool isOPen =myFile->open(QIODevice::WriteOnly);
if(!isOPen){
//文件打开失败
QMessageBox::warning(this,"Error Message","Ini Error!");
}
else{
QTextStream out(myFile);
out<<QString("mainPage=").append(ui->urlEdit->text());
myFile->close();
QMessageBox::information(this,"Info","设置成功,重启浏览器生效");
}
delete myFile;
myFile = NULL;
}
//1.转到当前链接
void MainWindow::doProcessLineEnter()
{
QString str = ui->urlEdit->text();
myView->load(QUrl(str));//加载网页
myView->setFocus();//获取焦点
}
//2.修改窗口标题
void MainWindow::doProcessChangeTitle()
{
// qDebug()<<"当前标题"<title();
this->setWindowTitle(myView->title());
}
//3.设置进度条
void MainWindow::doProcessSetMyProgressBar(int p)
{
myProgressBar->show();
myProgressBar->setValue(p);
myProgressBar->setFixedWidth(this->width());
}
//3.隐藏进度条
void MainWindow::doProcessHideMyProgressBar(bool isFinished)
{
if(!isFinished){
//出错了
this->setWindowTitle("Page Error");
return;
}
QTimer *myTimer = new QTimer(this);
connect(myTimer,&QTimer::timeout,[=](){
myTimer->stop();
myProgressBar->hide();
});
myTimer->start(5000);//停留5s后隐藏
}
//4.刷新网页url
void MainWindow::doProcessRefreshUrl(QUrl url)
{
ui->urlEdit->setText(url.toString());
}
//6. 发送网络请求 获取网页相应图标
void MainWindow::doProcessGetWebIcon(const QUrl url)
{
QNetworkRequest myIconRequest(url);
// qDebug()<<"网站图标网址为:"<
QNetworkReply *myIconReply = manager->get(myIconRequest);
myIconReply->setParent(this);
connect(myIconReply,SIGNAL(finished()),this,SLOT(doProcessSetWindowIcon()));
}
//6. 网络请求返回成功与否,均设置icon
void MainWindow::doProcessSetWindowIcon()
{
QIcon icon;
QNetworkReply *webReply = qobject_cast<QNetworkReply *>(sender());//获取信号发送的指针对象
if(webReply&&webReply->error()==QNetworkReply::NoError){
QByteArray daIcon = webReply->readAll();
QPixmap iconPix;
// QPixmap iconPix(daIcon);//这个构造不行
iconPix.loadFromData(daIcon);//数据做转换,先转成pixmap格式
icon.addPixmap(iconPix);//在通过icon的构造转成icon格式
webReply->deleteLater();
}
else{
// qDebug()<<"网络出错"<error();
icon = QIcon(":/res/webIcon.png");
webReply->deleteLater();
}
setWindowIcon(icon);
}
//8.显示网页源代码
void MainWindow::doProcessShowSourceCode()
{
myView->page()->triggerAction(QWebEnginePage::ViewSource);
}
//9.显示历史记录列表
void MainWindow::doProcessShowHistory()
{
historyList->clear();
//history函数返回一个QWebEngineHistory对象,表示浏览历史
//使用items获取所有项目列表
//clear可以清空列表
//count返回列表个数
foreach(QWebEngineHistoryItem item,myView->history()->items()){
QListWidgetItem *history = new QListWidgetItem(item.title());
historyList->addItem(history);
}
historyList->show();
}
//转到历史记录项
void MainWindow::doProcessGotoHistoryItem(const QModelIndex &index)
{
QWebEngineHistoryItem item = myView->history()->itemAt(index.row());
myView->history()->goToItem(item);
}
/****************** System Slots ****************/
//窗口变化事件捕捉
void MainWindow::resizeEvent(QResizeEvent *event)
{
if(myProgressBar->isVisible()){
//已经隐藏
return;
}
else{
myProgressBar->setFixedWidth(this->width());
}
}
//7.放大页面 快捷键ctrl++
void MainWindow::on_zoomInAction_triggered()
{
myView->setZoomFactor(myView->zoomFactor()+0.1);
ui->statusbar->showMessage(tr("放大 %1%").arg(myView->zoomFactor()*100));
}
//7.缩小页面 快捷键ctrl+-
void MainWindow::on_zoomOutAction_triggered()
{
myView->setZoomFactor(myView->zoomFactor()-0.1);
ui->statusbar->showMessage(tr("缩小 %1%").arg(myView->zoomFactor()*100));
}
//7.还原页面 快捷键ctrl+0
void MainWindow::on_resetZoomAction_triggered()
{
myView->setZoomFactor(1.0);
ui->statusbar->showMessage(tr("还原 %1%").arg(myView->zoomFactor()*100));
}
/****************** My Function ****************/
void MainWindow::Init()
{
// myView = new QWebEngineView(this);
myView = new webView(this);
ui->gridLayout->addWidget(myView); //添加到布局中
//连接各界面信号执行相关动作
//0.加载主页 并修改主页
this->loadMainPage();
connect(ui->setMainPageAction,SIGNAL(triggered()),this,SLOT(doProcessSetMainPage()));
// 1.接受lineEdit回车按钮信号或者“转到”按钮,执行显示当前网址操作
connect(ui->urlEdit,SIGNAL(returnPressed()),this,SLOT(doProcessLineEnter()));//回车键转到链接
connect(ui->gotoBtn,SIGNAL(clicked()),this,SLOT(doProcessLineEnter()));
// 2.接受webEngineView的titleChange信号,执行修改窗口标题操作
connect(myView,&QWebEngineView::titleChanged,this,&MainWindow::doProcessChangeTitle);//修改title
// 3.添加进度条,接受loadProgress信号,显示当前进度,任务结束后,接受loadFinish信号,隐藏进度条 */
myProgressBar = new QProgressBar(ui->statusbar);//将进度条放在状态栏中
myProgressBar->setStyleSheet("QProgressBar{background-color:#05B8CC;width:20px;"
"border:2px solid grey;border-radius:5px;text-align:center;}");
connect(myView,&QWebEngineView::loadProgress,this,&MainWindow::doProcessSetMyProgressBar);//获取中
// connect(QWebEngineView,SIGNAL(loadProgress)) 注意此写法再次不支持,可能是QWebEngineView是qt5.5后出来的模块
connect(myView,&QWebEngineView::loadFinished,this,&MainWindow::doProcessHideMyProgressBar);//获取完成
//4.网页改变时,刷新url值到网址栏中
connect(myView,&QWebEngineView::urlChanged,this,&MainWindow::doProcessRefreshUrl);
//5.添加界面中工具栏,并添加导航工具进工具栏
setMyToolBar();
//6.获取并显示网站图标
manager = new QNetworkAccessManager(this);
connect(myView,&QWebEngineView::iconUrlChanged,this,&MainWindow::doProcessGetWebIcon);
//7.实现网页缩放,见转到槽 范围是0.25~500
//8.查看网页源代码
connect(ui->soucreCodeAction,SIGNAL(triggered()),this,SLOT(doProcessShowSourceCode()));
//9.显示历史记录
historyList = new QListWidget();
historyList->setWindowTitle("历史记录");
historyList->setMinimumWidth(300);
connect(ui->historyAction,SIGNAL(triggered()),this,SLOT(doProcessShowHistory()));
connect(historyList,&QListWidget::clicked,this,&MainWindow::doProcessGotoHistoryItem);
}
//5.设置带导航栏功能的工具栏
void MainWindow::setMyToolBar()
{
QToolBar *toolBar = new QToolBar(this);//将工具栏放入窗口中
addToolBar(Qt::TopToolBarArea,toolBar);//调整默认放在上面
toolBar->setAllowedAreas(Qt::LeftToolBarArea);//只允许上停靠
toolBar->setFloatable(false);//不允许浮动
toolBar->setMovable(false);//关闭移动 总开关
//pageAction将返回webEngineView的Action
toolBar->addAction(myView->pageAction(QWebEnginePage::Back));//后退
toolBar->addAction(myView->pageAction(QWebEnginePage::Forward));//前进
toolBar->addAction(myView->pageAction(QWebEnginePage::Reload));//刷新
toolBar->addAction(myView->pageAction(QWebEnginePage::Stop));//停止
toolBar->addWidget(ui->urlEdit);//添加网址到工具栏
toolBar->addWidget(ui->gotoBtn);//添加按钮到工具栏
}
//0.加载主页
void MainWindow::loadMainPage()
{
QFile *myFile = new QFile;
QString filename = "mainpage.ini";//读取本地配置文件,该文件放在构建目录下,
//初始内容为mainPage=https://blog.csdn.net/weixin_40615338?spm=1011.2124.3001.5343
myFile->setFileName(filename);
bool isOPen =myFile->open(QIODevice::ReadOnly);
// qDebug()<errorString();
if(!isOPen){
//文件打开失败
QMessageBox::warning(this,"Error Message","Ini Error!");
}
else{
QTextStream in(myFile);
myView->load(QUrl(in.readLine().split("=").at(1)));//加载网页
myView->setFocus();//获取焦点
myFile->close();
}
delete myFile;
myFile = NULL;
}
自定义的继承自QWebEngineView的子类,为了重写creatwindow函数以实现网页跳转。
#ifndef WEBVIEW_H
#define WEBVIEW_H
#include
#include
#include
class webView : public QWebEngineView
{
Q_OBJECT
public:
explicit webView(QWidget *parent = nullptr);
protected:
QWebEngineView *createWindow(QWebEnginePage::WebWindowType type) override;
signals:
};
#endif // WEBVIEW_H
#include "webview.h"
webView::webView(QWidget *parent) : QWebEngineView(parent)
{
}
QWebEngineView *webView::createWindow(QWebEnginePage::WebWindowType type)
{
return this;
}