qt版本的浏览器开发
2014-04-15 11:47
507 查看
http://blog.csdn.net/dc_726/article/details/7584205
1.代码实现
工程目录结构如下:
AddressBar类包含了地址栏和按钮两个控件,将地址栏回车和按钮点击信号与goToSite()槽连接。
当回车和点击事件发生时,goToSite()将获得Url地址并发送go(QUrl)信号。
addressbar.h
[cpp] view
plaincopy
#ifndef ADDRESSBAR_H
#define ADDRESSBAR_H
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QHBoxLayout>
#include <QUrl>
#include <QString>
class AddressBar : public QWidget
{
Q_OBJECT
public:
explicit AddressBar(QWidget *parent = 0);
signals:
void go(QUrl);
public slots:
void goToSite();
private:
QLineEdit *addressEdit;
QPushButton *goButton;
QHBoxLayout *layout;
};
#endif // ADDRESSBAR_H
addressbar.cpp
[cpp] view
plaincopy
#include "addressbar.h"
AddressBar::AddressBar(QWidget *parent) :
QWidget(parent)
{
addressEdit = new QLineEdit(parent);
goButton = new QPushButton("Go", parent);
layout = new QHBoxLayout;
layout->addWidget(addressEdit);
layout->addWidget(goButton);
this->setLayout(layout);
connect(goButton, SIGNAL(clicked()), this, SLOT(goToSite()));
connect(addressEdit, SIGNAL(returnPressed()), this, SLOT(goToSite()));
}
void AddressBar::goToSite()
{
QString address = addressEdit->text();
emit go(QUrl(address));
}
接下来是QtWebkit包中的主要类QWebView,我们借助这个类来渲染Url指向的网页。
为了当用户在地址栏回车或者点击Go按钮时能够自动加载网页,我们需要给QWebView
添加loadNewPage(QUrl)槽(因为QWebView没有类似load(QUrl)的槽),并将其与go(QUrl)
信号连接。所以我们实现一个QWebView的子类HtmlView。
htmlview.h
[cpp] view
plaincopy
#ifndef HTMLVIEW_H
#define HTMLVIEW_H
#include <QWebView>
class HtmlView : public QWebView
{
Q_OBJECT
public:
explicit HtmlView(QWidget *parent = 0);
signals:
public slots:
void loadNewPage(const QUrl &url);
};
#endif // HTMLVIEW_H
htmlview.cpp
[cpp] view
plaincopy
#include "htmlview.h"
HtmlView::HtmlView(QWidget *parent) :
QWebView(parent)
{
}
void HtmlView::loadNewPage(const QUrl &url)
{
this->load(url);
}
接下来实现程序的主窗口QMainWindow,将AddressBar和HtmlView放置其中。
mainwindow.h
[cpp] view
plaincopy
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>
#include <QtWebKit>
#include <QMainWindow>
#include "addressbar.h"
#include "htmlview.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
};
#endif // MAINWINDOW_H
mainwindow.cpp
[cpp] view
plaincopy
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
// 1.Create widget
QWidget *centralWidget = new QWidget(this);
AddressBar *bar = new AddressBar;
HtmlView *view = new HtmlView;
// 2.Add widget to layout
QGridLayout *layout = new QGridLayout;
layout->addWidget(bar, 0, 0, 1, 10);
layout->addWidget(view, 1, 0, 1, 10);
centralWidget->setLayout(layout);
// 3.Connect widget
QObject::connect(bar, SIGNAL(go(QUrl)), view, SLOT(loadNewPage(QUrl)));
this->setCentralWidget(centralWidget);
this->setWindowTitle("My Browser v1.0");
this->resize(640, 480);
}
最后是程序的入口main.cpp
[cpp] view
plaincopy
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow *window = new MainWindow;
window->show();
return app.exec();
}
2.事件流分析
我们分别为AddressBar和HtmlView自定义了两个槽goToSite(QUrl)和loadNewPage(QUrl),以及新的信号go(QUrl)。
就是为了将Url地址传递给QWebView的load函数。
这里需要注意的是SIGNAL-SLOT机制是Qt的内部机制,它是同步执行的。源头上returnPressed()和clicked()槽的触发,
是从操作系统的事件队列中得到的,并进行异步的处理。以QPushButton的clicked()槽的触发为例,QApplication.exec()
执行后将会监听操作系统事件队列,当鼠标事件发生时,事件将会发送到QPushButton的event()函数进行分发:
[cpp] view
plaincopy
QPushButton::event ->
QAbstractButton::event ->
QWidget::event [Dispatch event]:
case QEvent::KeyPress: {
QKeyEvent *k = (QKeyEvent *)event;
bool res = false;
if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier?
if (k->key() == Qt::Key_Backtab
|| (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier)))
res = focusNextPrevChild(false);
else if (k->key() == Qt::Key_Tab)
res = focusNextPrevChild(true);
if (res)
break;
}
keyPressEvent(k);
-> QPushButton::keyPressEvent
void QPushButton::keyPressEvent(QKeyEvent *e)
{
Q_D(QPushButton);
switch (e->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
if (autoDefault() || d->defaultButton) {
click();
break;
}
// fall through
default:
QAbstractButton::keyPressEvent(e);
}
}
-> QAbstractButton::click:
void QAbstractButton::click()
{
if (!isEnabled())
return;
Q_D(QAbstractButton);
QPointer<QAbstractButton> guard(this);
d->down = true;
d->emitPressed();
if (guard) {
d->down = false;
nextCheckState();
if (guard)
d->emitReleased();
if (guard)
d->emitClicked();
}
}
3.SIGNAL-SLOT类的编译
关于Q_OBJECT宏以及SIGNAL,SLOT,emit等关键字奇怪的语法,其实他们是通过一个叫做MOC元对象编译器
的组件来进行预编译的,因此我们可以使用SIGNAL,SLOT,emit来清晰地连接各个信号槽,而非函数指针。
SIGNAL-SLOT使用很方便,但也是会损失一点执行效率,使用时要谨慎。
4.总结及学习资料
通过这个例子可以对Qt的SIGNAL-SLOT机制有个简单的了解,它可以减少对象间的依赖。假如不使用它,我们就
需要在AddressBar中直接调用HtmlView的load()函数,两个类耦合在了一起。在这个例子中MainWindow负责AddressBar
和HtmlView的构建和连接,使它们互相不知道对方的存在!
让我更感兴趣的其实是QtWebKit,通过它我们可以在Qt开发桌面应用时使用Web技术,而不用局限于Qt,MFC等等。
Web开发人员也可以投身桌面应用开发之中。
QtWebKit官方文档:http://doc.qt.nokia.com/4.7-snapshot/qtwebkit.html
学习笔记:http://caterpillar.onlyfun.net/Gossip/Qt4Gossip/Qt4Gossip.html
QtWebKit系列教程:http://software.intel.com/zh-cn/blogs/2010/06/08/qt-webkit-qt-webkit/
信号槽深入学习:http://www.ibm.com/developerworks/cn/linux/guitoolkit/qt/signal-slot/
QtWeb - 一个开源的Qt浏览器项目
http://blog.csdn.net/dc_726/article/details/7600192
1.代码实现
工程目录结构如下:
AddressBar类包含了地址栏和按钮两个控件,将地址栏回车和按钮点击信号与goToSite()槽连接。
当回车和点击事件发生时,goToSite()将获得Url地址并发送go(QUrl)信号。
addressbar.h
[cpp] view
plaincopy
#ifndef ADDRESSBAR_H
#define ADDRESSBAR_H
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QHBoxLayout>
#include <QUrl>
#include <QString>
class AddressBar : public QWidget
{
Q_OBJECT
public:
explicit AddressBar(QWidget *parent = 0);
signals:
void go(QUrl);
public slots:
void goToSite();
private:
QLineEdit *addressEdit;
QPushButton *goButton;
QHBoxLayout *layout;
};
#endif // ADDRESSBAR_H
addressbar.cpp
[cpp] view
plaincopy
#include "addressbar.h"
AddressBar::AddressBar(QWidget *parent) :
QWidget(parent)
{
addressEdit = new QLineEdit(parent);
goButton = new QPushButton("Go", parent);
layout = new QHBoxLayout;
layout->addWidget(addressEdit);
layout->addWidget(goButton);
this->setLayout(layout);
connect(goButton, SIGNAL(clicked()), this, SLOT(goToSite()));
connect(addressEdit, SIGNAL(returnPressed()), this, SLOT(goToSite()));
}
void AddressBar::goToSite()
{
QString address = addressEdit->text();
emit go(QUrl(address));
}
接下来是QtWebkit包中的主要类QWebView,我们借助这个类来渲染Url指向的网页。
为了当用户在地址栏回车或者点击Go按钮时能够自动加载网页,我们需要给QWebView
添加loadNewPage(QUrl)槽(因为QWebView没有类似load(QUrl)的槽),并将其与go(QUrl)
信号连接。所以我们实现一个QWebView的子类HtmlView。
htmlview.h
[cpp] view
plaincopy
#ifndef HTMLVIEW_H
#define HTMLVIEW_H
#include <QWebView>
class HtmlView : public QWebView
{
Q_OBJECT
public:
explicit HtmlView(QWidget *parent = 0);
signals:
public slots:
void loadNewPage(const QUrl &url);
};
#endif // HTMLVIEW_H
htmlview.cpp
[cpp] view
plaincopy
#include "htmlview.h"
HtmlView::HtmlView(QWidget *parent) :
QWebView(parent)
{
}
void HtmlView::loadNewPage(const QUrl &url)
{
this->load(url);
}
接下来实现程序的主窗口QMainWindow,将AddressBar和HtmlView放置其中。
mainwindow.h
[cpp] view
plaincopy
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>
#include <QtWebKit>
#include <QMainWindow>
#include "addressbar.h"
#include "htmlview.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
};
#endif // MAINWINDOW_H
mainwindow.cpp
[cpp] view
plaincopy
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
// 1.Create widget
QWidget *centralWidget = new QWidget(this);
AddressBar *bar = new AddressBar;
HtmlView *view = new HtmlView;
// 2.Add widget to layout
QGridLayout *layout = new QGridLayout;
layout->addWidget(bar, 0, 0, 1, 10);
layout->addWidget(view, 1, 0, 1, 10);
centralWidget->setLayout(layout);
// 3.Connect widget
QObject::connect(bar, SIGNAL(go(QUrl)), view, SLOT(loadNewPage(QUrl)));
this->setCentralWidget(centralWidget);
this->setWindowTitle("My Browser v1.0");
this->resize(640, 480);
}
最后是程序的入口main.cpp
[cpp] view
plaincopy
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow *window = new MainWindow;
window->show();
return app.exec();
}
2.事件流分析
我们分别为AddressBar和HtmlView自定义了两个槽goToSite(QUrl)和loadNewPage(QUrl),以及新的信号go(QUrl)。
就是为了将Url地址传递给QWebView的load函数。
这里需要注意的是SIGNAL-SLOT机制是Qt的内部机制,它是同步执行的。源头上returnPressed()和clicked()槽的触发,
是从操作系统的事件队列中得到的,并进行异步的处理。以QPushButton的clicked()槽的触发为例,QApplication.exec()
执行后将会监听操作系统事件队列,当鼠标事件发生时,事件将会发送到QPushButton的event()函数进行分发:
[cpp] view
plaincopy
QPushButton::event ->
QAbstractButton::event ->
QWidget::event [Dispatch event]:
case QEvent::KeyPress: {
QKeyEvent *k = (QKeyEvent *)event;
bool res = false;
if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier?
if (k->key() == Qt::Key_Backtab
|| (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier)))
res = focusNextPrevChild(false);
else if (k->key() == Qt::Key_Tab)
res = focusNextPrevChild(true);
if (res)
break;
}
keyPressEvent(k);
-> QPushButton::keyPressEvent
void QPushButton::keyPressEvent(QKeyEvent *e)
{
Q_D(QPushButton);
switch (e->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
if (autoDefault() || d->defaultButton) {
click();
break;
}
// fall through
default:
QAbstractButton::keyPressEvent(e);
}
}
-> QAbstractButton::click:
void QAbstractButton::click()
{
if (!isEnabled())
return;
Q_D(QAbstractButton);
QPointer<QAbstractButton> guard(this);
d->down = true;
d->emitPressed();
if (guard) {
d->down = false;
nextCheckState();
if (guard)
d->emitReleased();
if (guard)
d->emitClicked();
}
}
3.SIGNAL-SLOT类的编译
关于Q_OBJECT宏以及SIGNAL,SLOT,emit等关键字奇怪的语法,其实他们是通过一个叫做MOC元对象编译器
的组件来进行预编译的,因此我们可以使用SIGNAL,SLOT,emit来清晰地连接各个信号槽,而非函数指针。
SIGNAL-SLOT使用很方便,但也是会损失一点执行效率,使用时要谨慎。
4.总结及学习资料
通过这个例子可以对Qt的SIGNAL-SLOT机制有个简单的了解,它可以减少对象间的依赖。假如不使用它,我们就
需要在AddressBar中直接调用HtmlView的load()函数,两个类耦合在了一起。在这个例子中MainWindow负责AddressBar
和HtmlView的构建和连接,使它们互相不知道对方的存在!
让我更感兴趣的其实是QtWebKit,通过它我们可以在Qt开发桌面应用时使用Web技术,而不用局限于Qt,MFC等等。
Web开发人员也可以投身桌面应用开发之中。
QtWebKit官方文档:http://doc.qt.nokia.com/4.7-snapshot/qtwebkit.html
学习笔记:http://caterpillar.onlyfun.net/Gossip/Qt4Gossip/Qt4Gossip.html
QtWebKit系列教程:http://software.intel.com/zh-cn/blogs/2010/06/08/qt-webkit-qt-webkit/
信号槽深入学习:http://www.ibm.com/developerworks/cn/linux/guitoolkit/qt/signal-slot/
QtWeb - 一个开源的Qt浏览器项目
一、功能改进
经过对QtWebKit的一些学习,对之前的浏览器进行一些改进:
1.增加分页显示多个网页的功能。每个分页都是一个QWebView控件,实现对多个网页的加载。
2.加入欢迎主页。学习如何创建Qt资源文件,从本地读取欢迎主页的HTML。
3.添加了前进Forward和后退Back按钮。
4.此外还明确了SIGNAL和SLOT的命名,SLOT都以handleXXX开头与SIGNAL区分开。
二、Qt资源文件
Qt可以很方便的通过资源文件来管理各种资源,就像在VS中创建资源文件一样。
项目结构如下:
在Qt Creator中创建四个资源文件html.qrc,script.qrc,style.qrc,resource.qrc分别对应html,
script,style和resource文件夹,用来存储网页、JS脚本、CSS样式文件和各种图片音频视频资源。
以html.qrc为例,添加html文件夹中的welcome.html,那么在程序中我们就可以用路径
qrc:/html/welcome.html来引用它。这里的欢迎页面引用了之前Chrome天气预报插件的
代码。
welcome.html
[html] view
plaincopy
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GB2312"/>
<link rel="stylesheet" type="text/css" href="qrc:/style/style.css"/>
<script type="text/javascript" src="qrc:/script/jquery-1.7.2.min.js"></script>
<script type="text/javascript">
function init() {
$("#loadingdiv").html("正在加载城市天气预报...");
$.getJSON("http://m.weather.com.cn/data/101010100.html",
function(data) {
$("#loadingdiv").html("");
var weatherinfo = data["weatherinfo"];
var datearray = ["", weatherinfo["date_y"], "第二天", "第三天", "第四天", "第五天", "第六天"];
$("#cityname").html(weatherinfo["city"] + "城市天气预报");
for (i = 1; i <= 6; i++) {
var divid = "#div" + i;
$(divid).append(datearray[i]).append("<br>");
var imgurl = "http://m.weather.com.cn/weather_img/" + weatherinfo["img"+(i*2-1)] + ".gif";
$(divid).append('<img src="' + imgurl + '"/>').append("<br>");
$(divid).append(weatherinfo["temp" + i]).append("<br>");
$(divid).append(weatherinfo["weather" + i]);
}
}
);
}
</script>
</head>
<body onload="init()">
<h1>Welcome!!!</h1>
<div id="weatherreportdiv">
<div id="loadingdiv"></div>
<div id="cityname"></div>
<hr></hr>
<div id="div1" class="weatherdiv"></div>
<div id="div2" class="weatherdiv"></div>
<div id="div3" class="weatherdiv"></div>
<div id="div4" class="weatherdiv"></div>
<div id="div5" class="weatherdiv"></div>
<div id="div6" class="weatherdiv"></div>
</div>
</body>
</html>
style.css
[html] view
plaincopy
h1 {
text-align: center;
}
#weatherreportdiv {
height: 300px;
width: 700px;
text-align: center;
font-size: 20px;
font-weight: bold;
margin: 5px;
}
#cityname {
text-align: center;
font-size: 20px;
font-weight: bold;
margin: 5px;
}
.weatherdiv {
float: left;
width: 15%;
margin: 5px;
}
管理资源就是这么简单,下面来看改进后的代码。
三、源码实现
在AddressBar中新加一个SLOT函数handleAddressChanged,处理当分页切换时URL的变化。
addressbar.h
[cpp] view
plaincopy
#ifndef ADDRESSBAR_H
#define ADDRESSBAR_H
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QHBoxLayout>
#include <QUrl>
#include <QString>
class AddressBar : public QWidget
{
Q_OBJECT
public:
explicit AddressBar(QWidget *parent = 0);
signals:
void go(const QUrl&);
void back();
void forward();
void newPage();
public slots:
void handleGoToSite();
void handleAddressChanged(const QUrl&);
private:
QLineEdit *addressEdit;
QPushButton *newButton;
QPushButton *backButton;
QPushButton *forwardButton;
QPushButton *goButton;
QHBoxLayout *layout;
};
#endif // ADDRESSBAR_H
addressbar.cpp
[cpp] view
plaincopy
#include "addressbar.h"
AddressBar::AddressBar(QWidget *parent) :
QWidget(parent)
{
// 1.Create widget
addressEdit = new QLineEdit;
newButton = new QPushButton("New");
backButton = new QPushButton("Back");
forwardButton = new QPushButton("Forward");
goButton = new QPushButton("Go");
// 2.Set property
// 3.Connect signal and slot
connect(goButton, SIGNAL(clicked()), this, SLOT(handleGoToSite()));
connect(addressEdit, SIGNAL(returnPressed()), this, SLOT(handleGoToSite()));
connect(backButton, SIGNAL(clicked()), this, SIGNAL(back()));
connect(forwardButton, SIGNAL(clicked()), this, SIGNAL(forward()));
connect(newButton, SIGNAL(clicked()), this, SIGNAL(newPage()));
// 4.Add to layout
layout = new QHBoxLayout;
layout->addWidget(newButton);
layout->addWidget(backButton);
layout->addWidget(forwardButton);
layout->addWidget(addressEdit);
layout->addWidget(goButton);
this->setLayout(layout);
}
void AddressBar::handleGoToSite()
{
QString address = addressEdit->text();
emit go(QUrl(address));
}
void AddressBar::handleAddressChanged(const QUrl &url)
{
addressEdit->setText(url.toString());
}
新建了TabPage继承了QTabWidget,管理所有分页。
tabpage.h
[cpp] view
plaincopy
#ifndef TABPAGE_H
#define TABPAGE_H
#include <QTabWidget>
#include <QList>
#include "htmlview.h"
class TabPage : public QTabWidget
{
Q_OBJECT
public:
explicit TabPage(QWidget *parent = 0);
~TabPage();
signals:
void urlChanged(const QUrl&);
public slots:
void handleNewTab();
void handleLoadNewPage(const QUrl&);
void handleBack();
void handleForward();
private slots:
void handleTabChanged(int);
void handleTabClosed(int);
void handleLinkClicked(const QUrl&);
private:
QList<QWebView*> viewList;
};
#endif // TABPAGE_H
tabpage.cpp
[cpp] view
plaincopy
#include "tabpage.h"
#include <QtDebug>
TabPage::TabPage(QWidget *parent) :
QTabWidget(parent)
{
// Set property
this->setTabsClosable(true);
// Connect built-in signal to customized one to convert index to url
connect(this, SIGNAL(currentChanged(int)), SLOT(handleTabChanged(int)));
connect(this, SIGNAL(tabCloseRequested(int)), this, SLOT(handleTabClosed(int)));
// Create new tab for home page
this->handleNewTab();
}
TabPage::~TabPage()
{
}
void TabPage::handleNewTab()
{
HtmlView *view = new HtmlView;
view->load(QUrl("qrc:/html/welcome.html"));
//view->page()->setForwardUnsupportedContent(true);
view->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
// Monitor linkClicked signal
connect(view, SIGNAL(linkClicked(const QUrl&)), this, SLOT(handleLinkClicked(const QUrl&)));
// Add and activate this new tab
int index = this->addTab(view, "Welcome");
this->setCurrentIndex(index);
viewList.append(view);
}
void TabPage::handleLoadNewPage(const QUrl &url)
{
qDebug() << "loadNewPage: " << url.toString();
HtmlView *view = (HtmlView*) this->currentWidget();
view->load(url);
}
void TabPage::handleBack()
{
HtmlView *view = (HtmlView*) this->currentWidget();
view->back();
emit urlChanged(view->url());
}
void TabPage::handleForward()
{
HtmlView *view = (HtmlView*) this->currentWidget();
view->forward();
emit urlChanged(view->url());
}
void TabPage::handleTabChanged(int index)
{
if (index > viewList.length() - 1)
return;
// index is the new tab index
QWebView* view = viewList[index];
emit urlChanged(view->url());
}
void TabPage::handleTabClosed(int index)
{
if (index > viewList.length() - 1)
return;
this->removeTab(index);
QWebView* view = viewList[index];
viewList.removeAt(index);
delete view;
}
void TabPage::handleLinkClicked(const QUrl& url)
{
qDebug() << "handleLinkClicked: " << url.toString();
HtmlView *view = (HtmlView*) this->currentWidget();
view->load(url);
emit urlChanged(url);
}
接下来是MainWindow,改动很小。
mainwindow.h
[cpp] view
plaincopy
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>
#include <QtWebKit>
#include <QMainWindow>
#include "addressbar.h"
#include "htmlview.h"
#include "tabpage.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.cpp
[cpp] view
plaincopy
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
//ui->setupUi(this);
// 0.Global setting
QWebSettings* defaultSettings = QWebSettings::globalSettings();
// We use JavaScript, so set it to be enabled.
defaultSettings->setAttribute(QWebSettings::JavascriptEnabled, true);
// Plug-ins must be set to be enabled to use plug-ins.
defaultSettings->setAttribute(QWebSettings::PluginsEnabled,true);
defaultSettings->setAttribute(QWebSettings::LocalContentCanAccessRemoteUrls,true);
defaultSettings->setObjectCacheCapacities(0, 0, 0);
// 1.Create widget
QWidget *centralWidget = new QWidget(this);
AddressBar *bar = new AddressBar;
TabPage *tab = new TabPage;
// 2.Set property
this->setCentralWidget(centralWidget);
this->setWindowTitle("My Browser v1.0");
this->resize(800, 600);
// 3.Connect widget
QObject::connect(bar, SIGNAL(newPage()),tab, SLOT(handleNewTab()));
QObject::connect(bar, SIGNAL(back()),tab, SLOT(handleBack()));
QObject::connect(bar, SIGNAL(forward()),tab, SLOT(handleForward()));
QObject::connect(bar, SIGNAL(go(QUrl)), tab, SLOT(handleLoadNewPage(QUrl)));
QObject::connect(tab, SIGNAL(urlChanged(QUrl)), bar, SLOT(handleAddressChanged(QUrl)));
// 4.Add widget to layout
QGridLayout *layout = new QGridLayout;
layout->addWidget(bar, 0, 0, 1, 10);
layout->addWidget(tab, 1, 0, 1, 10);
centralWidget->setLayout(layout);
}
MainWindow::~MainWindow()
{
//delete ui;
}
四、关键问题
1.需要为QWebView中的QWebPage设置才能使页面内链接可以正常跳转。
view->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
2.需要为AJAX调用设置:
QWebSettings* defaultSettings = QWebSettings::globalSettings();
// We use JavaScript, so set it to be enabled.
defaultSettings->setAttribute(QWebSettings::JavascriptEnabled, true);
// Plug-ins must be set to be enabled to use plug-ins.
defaultSettings->setAttribute(QWebSettings::PluginsEnabled,true);
defaultSettings->setAttribute(QWebSettings::LocalContentCanAccessRemoteUrls,true);
defaultSettings->setObjectCacheCapacities(0, 0, 0);
五、最终效果
1.程序开始运行,进入欢迎页面。
2.多页面浏览
2012年7月4日 补充
htmlview.h
[cpp] view
plaincopy
#ifndef HTMLVIEW_H
#define HTMLVIEW_H
#include <QWebView>
class HtmlView : public QWebView
{
Q_OBJECT
public:
explicit HtmlView(QWidget *parent = 0);
signals:
public slots:
void loadNewPage(const QUrl &url);
};
#endif // HTMLVIEW_H
htmlview.cpp
[cpp] view
plaincopy
#include "htmlview.h"
HtmlView::HtmlView(QWidget *parent) :
QWebView(parent)
{
}
void HtmlView::loadNewPage(const QUrl &url)
{
this->load(url);
}
1.代码实现
工程目录结构如下:
AddressBar类包含了地址栏和按钮两个控件,将地址栏回车和按钮点击信号与goToSite()槽连接。
当回车和点击事件发生时,goToSite()将获得Url地址并发送go(QUrl)信号。
addressbar.h
[cpp] view
plaincopy
#ifndef ADDRESSBAR_H
#define ADDRESSBAR_H
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QHBoxLayout>
#include <QUrl>
#include <QString>
class AddressBar : public QWidget
{
Q_OBJECT
public:
explicit AddressBar(QWidget *parent = 0);
signals:
void go(QUrl);
public slots:
void goToSite();
private:
QLineEdit *addressEdit;
QPushButton *goButton;
QHBoxLayout *layout;
};
#endif // ADDRESSBAR_H
addressbar.cpp
[cpp] view
plaincopy
#include "addressbar.h"
AddressBar::AddressBar(QWidget *parent) :
QWidget(parent)
{
addressEdit = new QLineEdit(parent);
goButton = new QPushButton("Go", parent);
layout = new QHBoxLayout;
layout->addWidget(addressEdit);
layout->addWidget(goButton);
this->setLayout(layout);
connect(goButton, SIGNAL(clicked()), this, SLOT(goToSite()));
connect(addressEdit, SIGNAL(returnPressed()), this, SLOT(goToSite()));
}
void AddressBar::goToSite()
{
QString address = addressEdit->text();
emit go(QUrl(address));
}
接下来是QtWebkit包中的主要类QWebView,我们借助这个类来渲染Url指向的网页。
为了当用户在地址栏回车或者点击Go按钮时能够自动加载网页,我们需要给QWebView
添加loadNewPage(QUrl)槽(因为QWebView没有类似load(QUrl)的槽),并将其与go(QUrl)
信号连接。所以我们实现一个QWebView的子类HtmlView。
htmlview.h
[cpp] view
plaincopy
#ifndef HTMLVIEW_H
#define HTMLVIEW_H
#include <QWebView>
class HtmlView : public QWebView
{
Q_OBJECT
public:
explicit HtmlView(QWidget *parent = 0);
signals:
public slots:
void loadNewPage(const QUrl &url);
};
#endif // HTMLVIEW_H
htmlview.cpp
[cpp] view
plaincopy
#include "htmlview.h"
HtmlView::HtmlView(QWidget *parent) :
QWebView(parent)
{
}
void HtmlView::loadNewPage(const QUrl &url)
{
this->load(url);
}
接下来实现程序的主窗口QMainWindow,将AddressBar和HtmlView放置其中。
mainwindow.h
[cpp] view
plaincopy
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>
#include <QtWebKit>
#include <QMainWindow>
#include "addressbar.h"
#include "htmlview.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
};
#endif // MAINWINDOW_H
mainwindow.cpp
[cpp] view
plaincopy
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
// 1.Create widget
QWidget *centralWidget = new QWidget(this);
AddressBar *bar = new AddressBar;
HtmlView *view = new HtmlView;
// 2.Add widget to layout
QGridLayout *layout = new QGridLayout;
layout->addWidget(bar, 0, 0, 1, 10);
layout->addWidget(view, 1, 0, 1, 10);
centralWidget->setLayout(layout);
// 3.Connect widget
QObject::connect(bar, SIGNAL(go(QUrl)), view, SLOT(loadNewPage(QUrl)));
this->setCentralWidget(centralWidget);
this->setWindowTitle("My Browser v1.0");
this->resize(640, 480);
}
最后是程序的入口main.cpp
[cpp] view
plaincopy
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow *window = new MainWindow;
window->show();
return app.exec();
}
2.事件流分析
我们分别为AddressBar和HtmlView自定义了两个槽goToSite(QUrl)和loadNewPage(QUrl),以及新的信号go(QUrl)。
就是为了将Url地址传递给QWebView的load函数。
这里需要注意的是SIGNAL-SLOT机制是Qt的内部机制,它是同步执行的。源头上returnPressed()和clicked()槽的触发,
是从操作系统的事件队列中得到的,并进行异步的处理。以QPushButton的clicked()槽的触发为例,QApplication.exec()
执行后将会监听操作系统事件队列,当鼠标事件发生时,事件将会发送到QPushButton的event()函数进行分发:
[cpp] view
plaincopy
QPushButton::event ->
QAbstractButton::event ->
QWidget::event [Dispatch event]:
case QEvent::KeyPress: {
QKeyEvent *k = (QKeyEvent *)event;
bool res = false;
if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier?
if (k->key() == Qt::Key_Backtab
|| (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier)))
res = focusNextPrevChild(false);
else if (k->key() == Qt::Key_Tab)
res = focusNextPrevChild(true);
if (res)
break;
}
keyPressEvent(k);
-> QPushButton::keyPressEvent
void QPushButton::keyPressEvent(QKeyEvent *e)
{
Q_D(QPushButton);
switch (e->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
if (autoDefault() || d->defaultButton) {
click();
break;
}
// fall through
default:
QAbstractButton::keyPressEvent(e);
}
}
-> QAbstractButton::click:
void QAbstractButton::click()
{
if (!isEnabled())
return;
Q_D(QAbstractButton);
QPointer<QAbstractButton> guard(this);
d->down = true;
d->emitPressed();
if (guard) {
d->down = false;
nextCheckState();
if (guard)
d->emitReleased();
if (guard)
d->emitClicked();
}
}
3.SIGNAL-SLOT类的编译
关于Q_OBJECT宏以及SIGNAL,SLOT,emit等关键字奇怪的语法,其实他们是通过一个叫做MOC元对象编译器
的组件来进行预编译的,因此我们可以使用SIGNAL,SLOT,emit来清晰地连接各个信号槽,而非函数指针。
SIGNAL-SLOT使用很方便,但也是会损失一点执行效率,使用时要谨慎。
4.总结及学习资料
通过这个例子可以对Qt的SIGNAL-SLOT机制有个简单的了解,它可以减少对象间的依赖。假如不使用它,我们就
需要在AddressBar中直接调用HtmlView的load()函数,两个类耦合在了一起。在这个例子中MainWindow负责AddressBar
和HtmlView的构建和连接,使它们互相不知道对方的存在!
让我更感兴趣的其实是QtWebKit,通过它我们可以在Qt开发桌面应用时使用Web技术,而不用局限于Qt,MFC等等。
Web开发人员也可以投身桌面应用开发之中。
QtWebKit官方文档:http://doc.qt.nokia.com/4.7-snapshot/qtwebkit.html
学习笔记:http://caterpillar.onlyfun.net/Gossip/Qt4Gossip/Qt4Gossip.html
QtWebKit系列教程:http://software.intel.com/zh-cn/blogs/2010/06/08/qt-webkit-qt-webkit/
信号槽深入学习:http://www.ibm.com/developerworks/cn/linux/guitoolkit/qt/signal-slot/
QtWeb - 一个开源的Qt浏览器项目
一、功能改进
经过对QtWebKit的一些学习,对之前的浏览器进行一些改进:
1.增加分页显示多个网页的功能。每个分页都是一个QWebView控件,实现对多个网页的加载。
2.加入欢迎主页。学习如何创建Qt资源文件,从本地读取欢迎主页的HTML。
3.添加了前进Forward和后退Back按钮。
4.此外还明确了SIGNAL和SLOT的命名,SLOT都以handleXXX开头与SIGNAL区分开。
二、Qt资源文件
Qt可以很方便的通过资源文件来管理各种资源,就像在VS中创建资源文件一样。
项目结构如下:
在Qt Creator中创建四个资源文件html.qrc,script.qrc,style.qrc,resource.qrc分别对应html,
script,style和resource文件夹,用来存储网页、JS脚本、CSS样式文件和各种图片音频视频资源。
以html.qrc为例,添加html文件夹中的welcome.html,那么在程序中我们就可以用路径
qrc:/html/welcome.html来引用它。这里的欢迎页面引用了之前Chrome天气预报插件的
代码。
welcome.html
[html] view
plaincopy
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GB2312"/>
<link rel="stylesheet" type="text/css" href="qrc:/style/style.css"/>
<script type="text/javascript" src="qrc:/script/jquery-1.7.2.min.js"></script>
<script type="text/javascript">
function init() {
$("#loadingdiv").html("正在加载城市天气预报...");
$.getJSON("http://m.weather.com.cn/data/101010100.html",
function(data) {
$("#loadingdiv").html("");
var weatherinfo = data["weatherinfo"];
var datearray = ["", weatherinfo["date_y"], "第二天", "第三天", "第四天", "第五天", "第六天"];
$("#cityname").html(weatherinfo["city"] + "城市天气预报");
for (i = 1; i <= 6; i++) {
var divid = "#div" + i;
$(divid).append(datearray[i]).append("<br>");
var imgurl = "http://m.weather.com.cn/weather_img/" + weatherinfo["img"+(i*2-1)] + ".gif";
$(divid).append('<img src="' + imgurl + '"/>').append("<br>");
$(divid).append(weatherinfo["temp" + i]).append("<br>");
$(divid).append(weatherinfo["weather" + i]);
}
}
);
}
</script>
</head>
<body onload="init()">
<h1>Welcome!!!</h1>
<div id="weatherreportdiv">
<div id="loadingdiv"></div>
<div id="cityname"></div>
<hr></hr>
<div id="div1" class="weatherdiv"></div>
<div id="div2" class="weatherdiv"></div>
<div id="div3" class="weatherdiv"></div>
<div id="div4" class="weatherdiv"></div>
<div id="div5" class="weatherdiv"></div>
<div id="div6" class="weatherdiv"></div>
</div>
</body>
</html>
style.css
[html] view
plaincopy
h1 {
text-align: center;
}
#weatherreportdiv {
height: 300px;
width: 700px;
text-align: center;
font-size: 20px;
font-weight: bold;
margin: 5px;
}
#cityname {
text-align: center;
font-size: 20px;
font-weight: bold;
margin: 5px;
}
.weatherdiv {
float: left;
width: 15%;
margin: 5px;
}
管理资源就是这么简单,下面来看改进后的代码。
三、源码实现
在AddressBar中新加一个SLOT函数handleAddressChanged,处理当分页切换时URL的变化。
addressbar.h
[cpp] view
plaincopy
#ifndef ADDRESSBAR_H
#define ADDRESSBAR_H
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QHBoxLayout>
#include <QUrl>
#include <QString>
class AddressBar : public QWidget
{
Q_OBJECT
public:
explicit AddressBar(QWidget *parent = 0);
signals:
void go(const QUrl&);
void back();
void forward();
void newPage();
public slots:
void handleGoToSite();
void handleAddressChanged(const QUrl&);
private:
QLineEdit *addressEdit;
QPushButton *newButton;
QPushButton *backButton;
QPushButton *forwardButton;
QPushButton *goButton;
QHBoxLayout *layout;
};
#endif // ADDRESSBAR_H
addressbar.cpp
[cpp] view
plaincopy
#include "addressbar.h"
AddressBar::AddressBar(QWidget *parent) :
QWidget(parent)
{
// 1.Create widget
addressEdit = new QLineEdit;
newButton = new QPushButton("New");
backButton = new QPushButton("Back");
forwardButton = new QPushButton("Forward");
goButton = new QPushButton("Go");
// 2.Set property
// 3.Connect signal and slot
connect(goButton, SIGNAL(clicked()), this, SLOT(handleGoToSite()));
connect(addressEdit, SIGNAL(returnPressed()), this, SLOT(handleGoToSite()));
connect(backButton, SIGNAL(clicked()), this, SIGNAL(back()));
connect(forwardButton, SIGNAL(clicked()), this, SIGNAL(forward()));
connect(newButton, SIGNAL(clicked()), this, SIGNAL(newPage()));
// 4.Add to layout
layout = new QHBoxLayout;
layout->addWidget(newButton);
layout->addWidget(backButton);
layout->addWidget(forwardButton);
layout->addWidget(addressEdit);
layout->addWidget(goButton);
this->setLayout(layout);
}
void AddressBar::handleGoToSite()
{
QString address = addressEdit->text();
emit go(QUrl(address));
}
void AddressBar::handleAddressChanged(const QUrl &url)
{
addressEdit->setText(url.toString());
}
新建了TabPage继承了QTabWidget,管理所有分页。
tabpage.h
[cpp] view
plaincopy
#ifndef TABPAGE_H
#define TABPAGE_H
#include <QTabWidget>
#include <QList>
#include "htmlview.h"
class TabPage : public QTabWidget
{
Q_OBJECT
public:
explicit TabPage(QWidget *parent = 0);
~TabPage();
signals:
void urlChanged(const QUrl&);
public slots:
void handleNewTab();
void handleLoadNewPage(const QUrl&);
void handleBack();
void handleForward();
private slots:
void handleTabChanged(int);
void handleTabClosed(int);
void handleLinkClicked(const QUrl&);
private:
QList<QWebView*> viewList;
};
#endif // TABPAGE_H
tabpage.cpp
[cpp] view
plaincopy
#include "tabpage.h"
#include <QtDebug>
TabPage::TabPage(QWidget *parent) :
QTabWidget(parent)
{
// Set property
this->setTabsClosable(true);
// Connect built-in signal to customized one to convert index to url
connect(this, SIGNAL(currentChanged(int)), SLOT(handleTabChanged(int)));
connect(this, SIGNAL(tabCloseRequested(int)), this, SLOT(handleTabClosed(int)));
// Create new tab for home page
this->handleNewTab();
}
TabPage::~TabPage()
{
}
void TabPage::handleNewTab()
{
HtmlView *view = new HtmlView;
view->load(QUrl("qrc:/html/welcome.html"));
//view->page()->setForwardUnsupportedContent(true);
view->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
// Monitor linkClicked signal
connect(view, SIGNAL(linkClicked(const QUrl&)), this, SLOT(handleLinkClicked(const QUrl&)));
// Add and activate this new tab
int index = this->addTab(view, "Welcome");
this->setCurrentIndex(index);
viewList.append(view);
}
void TabPage::handleLoadNewPage(const QUrl &url)
{
qDebug() << "loadNewPage: " << url.toString();
HtmlView *view = (HtmlView*) this->currentWidget();
view->load(url);
}
void TabPage::handleBack()
{
HtmlView *view = (HtmlView*) this->currentWidget();
view->back();
emit urlChanged(view->url());
}
void TabPage::handleForward()
{
HtmlView *view = (HtmlView*) this->currentWidget();
view->forward();
emit urlChanged(view->url());
}
void TabPage::handleTabChanged(int index)
{
if (index > viewList.length() - 1)
return;
// index is the new tab index
QWebView* view = viewList[index];
emit urlChanged(view->url());
}
void TabPage::handleTabClosed(int index)
{
if (index > viewList.length() - 1)
return;
this->removeTab(index);
QWebView* view = viewList[index];
viewList.removeAt(index);
delete view;
}
void TabPage::handleLinkClicked(const QUrl& url)
{
qDebug() << "handleLinkClicked: " << url.toString();
HtmlView *view = (HtmlView*) this->currentWidget();
view->load(url);
emit urlChanged(url);
}
接下来是MainWindow,改动很小。
mainwindow.h
[cpp] view
plaincopy
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>
#include <QtWebKit>
#include <QMainWindow>
#include "addressbar.h"
#include "htmlview.h"
#include "tabpage.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.cpp
[cpp] view
plaincopy
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
//ui->setupUi(this);
// 0.Global setting
QWebSettings* defaultSettings = QWebSettings::globalSettings();
// We use JavaScript, so set it to be enabled.
defaultSettings->setAttribute(QWebSettings::JavascriptEnabled, true);
// Plug-ins must be set to be enabled to use plug-ins.
defaultSettings->setAttribute(QWebSettings::PluginsEnabled,true);
defaultSettings->setAttribute(QWebSettings::LocalContentCanAccessRemoteUrls,true);
defaultSettings->setObjectCacheCapacities(0, 0, 0);
// 1.Create widget
QWidget *centralWidget = new QWidget(this);
AddressBar *bar = new AddressBar;
TabPage *tab = new TabPage;
// 2.Set property
this->setCentralWidget(centralWidget);
this->setWindowTitle("My Browser v1.0");
this->resize(800, 600);
// 3.Connect widget
QObject::connect(bar, SIGNAL(newPage()),tab, SLOT(handleNewTab()));
QObject::connect(bar, SIGNAL(back()),tab, SLOT(handleBack()));
QObject::connect(bar, SIGNAL(forward()),tab, SLOT(handleForward()));
QObject::connect(bar, SIGNAL(go(QUrl)), tab, SLOT(handleLoadNewPage(QUrl)));
QObject::connect(tab, SIGNAL(urlChanged(QUrl)), bar, SLOT(handleAddressChanged(QUrl)));
// 4.Add widget to layout
QGridLayout *layout = new QGridLayout;
layout->addWidget(bar, 0, 0, 1, 10);
layout->addWidget(tab, 1, 0, 1, 10);
centralWidget->setLayout(layout);
}
MainWindow::~MainWindow()
{
//delete ui;
}
四、关键问题
1.需要为QWebView中的QWebPage设置才能使页面内链接可以正常跳转。
view->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
2.需要为AJAX调用设置:
QWebSettings* defaultSettings = QWebSettings::globalSettings();
// We use JavaScript, so set it to be enabled.
defaultSettings->setAttribute(QWebSettings::JavascriptEnabled, true);
// Plug-ins must be set to be enabled to use plug-ins.
defaultSettings->setAttribute(QWebSettings::PluginsEnabled,true);
defaultSettings->setAttribute(QWebSettings::LocalContentCanAccessRemoteUrls,true);
defaultSettings->setObjectCacheCapacities(0, 0, 0);
五、最终效果
1.程序开始运行,进入欢迎页面。
2.多页面浏览
2012年7月4日 补充
htmlview.h
[cpp] view
plaincopy
#ifndef HTMLVIEW_H
#define HTMLVIEW_H
#include <QWebView>
class HtmlView : public QWebView
{
Q_OBJECT
public:
explicit HtmlView(QWidget *parent = 0);
signals:
public slots:
void loadNewPage(const QUrl &url);
};
#endif // HTMLVIEW_H
htmlview.cpp
[cpp] view
plaincopy
#include "htmlview.h"
HtmlView::HtmlView(QWidget *parent) :
QWebView(parent)
{
}
void HtmlView::loadNewPage(const QUrl &url)
{
this->load(url);
}
1.代码实现
工程目录结构如下:
AddressBar类包含了地址栏和按钮两个控件,将地址栏回车和按钮点击信号与goToSite()槽连接。
当回车和点击事件发生时,goToSite()将获得Url地址并发送go(QUrl)信号。
addressbar.h
[cpp] view
plaincopy
#ifndef ADDRESSBAR_H
#define ADDRESSBAR_H
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QHBoxLayout>
#include <QUrl>
#include <QString>
class AddressBar : public QWidget
{
Q_OBJECT
public:
explicit AddressBar(QWidget *parent = 0);
signals:
void go(QUrl);
public slots:
void goToSite();
private:
QLineEdit *addressEdit;
QPushButton *goButton;
QHBoxLayout *layout;
};
#endif // ADDRESSBAR_H
addressbar.cpp
[cpp] view
plaincopy
#include "addressbar.h"
AddressBar::AddressBar(QWidget *parent) :
QWidget(parent)
{
addressEdit = new QLineEdit(parent);
goButton = new QPushButton("Go", parent);
layout = new QHBoxLayout;
layout->addWidget(addressEdit);
layout->addWidget(goButton);
this->setLayout(layout);
connect(goButton, SIGNAL(clicked()), this, SLOT(goToSite()));
connect(addressEdit, SIGNAL(returnPressed()), this, SLOT(goToSite()));
}
void AddressBar::goToSite()
{
QString address = addressEdit->text();
emit go(QUrl(address));
}
接下来是QtWebkit包中的主要类QWebView,我们借助这个类来渲染Url指向的网页。
为了当用户在地址栏回车或者点击Go按钮时能够自动加载网页,我们需要给QWebView
添加loadNewPage(QUrl)槽(因为QWebView没有类似load(QUrl)的槽),并将其与go(QUrl)
信号连接。所以我们实现一个QWebView的子类HtmlView。
htmlview.h
[cpp] view
plaincopy
#ifndef HTMLVIEW_H
#define HTMLVIEW_H
#include <QWebView>
class HtmlView : public QWebView
{
Q_OBJECT
public:
explicit HtmlView(QWidget *parent = 0);
signals:
public slots:
void loadNewPage(const QUrl &url);
};
#endif // HTMLVIEW_H
htmlview.cpp
[cpp] view
plaincopy
#include "htmlview.h"
HtmlView::HtmlView(QWidget *parent) :
QWebView(parent)
{
}
void HtmlView::loadNewPage(const QUrl &url)
{
this->load(url);
}
接下来实现程序的主窗口QMainWindow,将AddressBar和HtmlView放置其中。
mainwindow.h
[cpp] view
plaincopy
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>
#include <QtWebKit>
#include <QMainWindow>
#include "addressbar.h"
#include "htmlview.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
};
#endif // MAINWINDOW_H
mainwindow.cpp
[cpp] view
plaincopy
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
// 1.Create widget
QWidget *centralWidget = new QWidget(this);
AddressBar *bar = new AddressBar;
HtmlView *view = new HtmlView;
// 2.Add widget to layout
QGridLayout *layout = new QGridLayout;
layout->addWidget(bar, 0, 0, 1, 10);
layout->addWidget(view, 1, 0, 1, 10);
centralWidget->setLayout(layout);
// 3.Connect widget
QObject::connect(bar, SIGNAL(go(QUrl)), view, SLOT(loadNewPage(QUrl)));
this->setCentralWidget(centralWidget);
this->setWindowTitle("My Browser v1.0");
this->resize(640, 480);
}
最后是程序的入口main.cpp
[cpp] view
plaincopy
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow *window = new MainWindow;
window->show();
return app.exec();
}
2.事件流分析
我们分别为AddressBar和HtmlView自定义了两个槽goToSite(QUrl)和loadNewPage(QUrl),以及新的信号go(QUrl)。
就是为了将Url地址传递给QWebView的load函数。
这里需要注意的是SIGNAL-SLOT机制是Qt的内部机制,它是同步执行的。源头上returnPressed()和clicked()槽的触发,
是从操作系统的事件队列中得到的,并进行异步的处理。以QPushButton的clicked()槽的触发为例,QApplication.exec()
执行后将会监听操作系统事件队列,当鼠标事件发生时,事件将会发送到QPushButton的event()函数进行分发:
[cpp] view
plaincopy
QPushButton::event ->
QAbstractButton::event ->
QWidget::event [Dispatch event]:
case QEvent::KeyPress: {
QKeyEvent *k = (QKeyEvent *)event;
bool res = false;
if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier?
if (k->key() == Qt::Key_Backtab
|| (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier)))
res = focusNextPrevChild(false);
else if (k->key() == Qt::Key_Tab)
res = focusNextPrevChild(true);
if (res)
break;
}
keyPressEvent(k);
-> QPushButton::keyPressEvent
void QPushButton::keyPressEvent(QKeyEvent *e)
{
Q_D(QPushButton);
switch (e->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
if (autoDefault() || d->defaultButton) {
click();
break;
}
// fall through
default:
QAbstractButton::keyPressEvent(e);
}
}
-> QAbstractButton::click:
void QAbstractButton::click()
{
if (!isEnabled())
return;
Q_D(QAbstractButton);
QPointer<QAbstractButton> guard(this);
d->down = true;
d->emitPressed();
if (guard) {
d->down = false;
nextCheckState();
if (guard)
d->emitReleased();
if (guard)
d->emitClicked();
}
}
3.SIGNAL-SLOT类的编译
关于Q_OBJECT宏以及SIGNAL,SLOT,emit等关键字奇怪的语法,其实他们是通过一个叫做MOC元对象编译器
的组件来进行预编译的,因此我们可以使用SIGNAL,SLOT,emit来清晰地连接各个信号槽,而非函数指针。
SIGNAL-SLOT使用很方便,但也是会损失一点执行效率,使用时要谨慎。
4.总结及学习资料
通过这个例子可以对Qt的SIGNAL-SLOT机制有个简单的了解,它可以减少对象间的依赖。假如不使用它,我们就
需要在AddressBar中直接调用HtmlView的load()函数,两个类耦合在了一起。在这个例子中MainWindow负责AddressBar
和HtmlView的构建和连接,使它们互相不知道对方的存在!
让我更感兴趣的其实是QtWebKit,通过它我们可以在Qt开发桌面应用时使用Web技术,而不用局限于Qt,MFC等等。
Web开发人员也可以投身桌面应用开发之中。
QtWebKit官方文档:http://doc.qt.nokia.com/4.7-snapshot/qtwebkit.html
学习笔记:http://caterpillar.onlyfun.net/Gossip/Qt4Gossip/Qt4Gossip.html
QtWebKit系列教程:http://software.intel.com/zh-cn/blogs/2010/06/08/qt-webkit-qt-webkit/
信号槽深入学习:http://www.ibm.com/developerworks/cn/linux/guitoolkit/qt/signal-slot/
QtWeb - 一个开源的Qt浏览器项目
http://blog.csdn.net/dc_726/article/details/7600192
1.代码实现
工程目录结构如下:
AddressBar类包含了地址栏和按钮两个控件,将地址栏回车和按钮点击信号与goToSite()槽连接。
当回车和点击事件发生时,goToSite()将获得Url地址并发送go(QUrl)信号。
addressbar.h
[cpp] view
plaincopy
#ifndef ADDRESSBAR_H
#define ADDRESSBAR_H
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QHBoxLayout>
#include <QUrl>
#include <QString>
class AddressBar : public QWidget
{
Q_OBJECT
public:
explicit AddressBar(QWidget *parent = 0);
signals:
void go(QUrl);
public slots:
void goToSite();
private:
QLineEdit *addressEdit;
QPushButton *goButton;
QHBoxLayout *layout;
};
#endif // ADDRESSBAR_H
addressbar.cpp
[cpp] view
plaincopy
#include "addressbar.h"
AddressBar::AddressBar(QWidget *parent) :
QWidget(parent)
{
addressEdit = new QLineEdit(parent);
goButton = new QPushButton("Go", parent);
layout = new QHBoxLayout;
layout->addWidget(addressEdit);
layout->addWidget(goButton);
this->setLayout(layout);
connect(goButton, SIGNAL(clicked()), this, SLOT(goToSite()));
connect(addressEdit, SIGNAL(returnPressed()), this, SLOT(goToSite()));
}
void AddressBar::goToSite()
{
QString address = addressEdit->text();
emit go(QUrl(address));
}
接下来是QtWebkit包中的主要类QWebView,我们借助这个类来渲染Url指向的网页。
为了当用户在地址栏回车或者点击Go按钮时能够自动加载网页,我们需要给QWebView
添加loadNewPage(QUrl)槽(因为QWebView没有类似load(QUrl)的槽),并将其与go(QUrl)
信号连接。所以我们实现一个QWebView的子类HtmlView。
htmlview.h
[cpp] view
plaincopy
#ifndef HTMLVIEW_H
#define HTMLVIEW_H
#include <QWebView>
class HtmlView : public QWebView
{
Q_OBJECT
public:
explicit HtmlView(QWidget *parent = 0);
signals:
public slots:
void loadNewPage(const QUrl &url);
};
#endif // HTMLVIEW_H
htmlview.cpp
[cpp] view
plaincopy
#include "htmlview.h"
HtmlView::HtmlView(QWidget *parent) :
QWebView(parent)
{
}
void HtmlView::loadNewPage(const QUrl &url)
{
this->load(url);
}
接下来实现程序的主窗口QMainWindow,将AddressBar和HtmlView放置其中。
mainwindow.h
[cpp] view
plaincopy
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>
#include <QtWebKit>
#include <QMainWindow>
#include "addressbar.h"
#include "htmlview.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
};
#endif // MAINWINDOW_H
mainwindow.cpp
[cpp] view
plaincopy
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
// 1.Create widget
QWidget *centralWidget = new QWidget(this);
AddressBar *bar = new AddressBar;
HtmlView *view = new HtmlView;
// 2.Add widget to layout
QGridLayout *layout = new QGridLayout;
layout->addWidget(bar, 0, 0, 1, 10);
layout->addWidget(view, 1, 0, 1, 10);
centralWidget->setLayout(layout);
// 3.Connect widget
QObject::connect(bar, SIGNAL(go(QUrl)), view, SLOT(loadNewPage(QUrl)));
this->setCentralWidget(centralWidget);
this->setWindowTitle("My Browser v1.0");
this->resize(640, 480);
}
最后是程序的入口main.cpp
[cpp] view
plaincopy
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow *window = new MainWindow;
window->show();
return app.exec();
}
2.事件流分析
我们分别为AddressBar和HtmlView自定义了两个槽goToSite(QUrl)和loadNewPage(QUrl),以及新的信号go(QUrl)。
就是为了将Url地址传递给QWebView的load函数。
这里需要注意的是SIGNAL-SLOT机制是Qt的内部机制,它是同步执行的。源头上returnPressed()和clicked()槽的触发,
是从操作系统的事件队列中得到的,并进行异步的处理。以QPushButton的clicked()槽的触发为例,QApplication.exec()
执行后将会监听操作系统事件队列,当鼠标事件发生时,事件将会发送到QPushButton的event()函数进行分发:
[cpp] view
plaincopy
QPushButton::event ->
QAbstractButton::event ->
QWidget::event [Dispatch event]:
case QEvent::KeyPress: {
QKeyEvent *k = (QKeyEvent *)event;
bool res = false;
if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier?
if (k->key() == Qt::Key_Backtab
|| (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier)))
res = focusNextPrevChild(false);
else if (k->key() == Qt::Key_Tab)
res = focusNextPrevChild(true);
if (res)
break;
}
keyPressEvent(k);
-> QPushButton::keyPressEvent
void QPushButton::keyPressEvent(QKeyEvent *e)
{
Q_D(QPushButton);
switch (e->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
if (autoDefault() || d->defaultButton) {
click();
break;
}
// fall through
default:
QAbstractButton::keyPressEvent(e);
}
}
-> QAbstractButton::click:
void QAbstractButton::click()
{
if (!isEnabled())
return;
Q_D(QAbstractButton);
QPointer<QAbstractButton> guard(this);
d->down = true;
d->emitPressed();
if (guard) {
d->down = false;
nextCheckState();
if (guard)
d->emitReleased();
if (guard)
d->emitClicked();
}
}
3.SIGNAL-SLOT类的编译
关于Q_OBJECT宏以及SIGNAL,SLOT,emit等关键字奇怪的语法,其实他们是通过一个叫做MOC元对象编译器
的组件来进行预编译的,因此我们可以使用SIGNAL,SLOT,emit来清晰地连接各个信号槽,而非函数指针。
SIGNAL-SLOT使用很方便,但也是会损失一点执行效率,使用时要谨慎。
4.总结及学习资料
通过这个例子可以对Qt的SIGNAL-SLOT机制有个简单的了解,它可以减少对象间的依赖。假如不使用它,我们就
需要在AddressBar中直接调用HtmlView的load()函数,两个类耦合在了一起。在这个例子中MainWindow负责AddressBar
和HtmlView的构建和连接,使它们互相不知道对方的存在!
让我更感兴趣的其实是QtWebKit,通过它我们可以在Qt开发桌面应用时使用Web技术,而不用局限于Qt,MFC等等。
Web开发人员也可以投身桌面应用开发之中。
QtWebKit官方文档:http://doc.qt.nokia.com/4.7-snapshot/qtwebkit.html
学习笔记:http://caterpillar.onlyfun.net/Gossip/Qt4Gossip/Qt4Gossip.html
QtWebKit系列教程:http://software.intel.com/zh-cn/blogs/2010/06/08/qt-webkit-qt-webkit/
信号槽深入学习:http://www.ibm.com/developerworks/cn/linux/guitoolkit/qt/signal-slot/
QtWeb - 一个开源的Qt浏览器项目
一、功能改进
经过对QtWebKit的一些学习,对之前的浏览器进行一些改进:
1.增加分页显示多个网页的功能。每个分页都是一个QWebView控件,实现对多个网页的加载。
2.加入欢迎主页。学习如何创建Qt资源文件,从本地读取欢迎主页的HTML。
3.添加了前进Forward和后退Back按钮。
4.此外还明确了SIGNAL和SLOT的命名,SLOT都以handleXXX开头与SIGNAL区分开。
二、Qt资源文件
Qt可以很方便的通过资源文件来管理各种资源,就像在VS中创建资源文件一样。
项目结构如下:
在Qt Creator中创建四个资源文件html.qrc,script.qrc,style.qrc,resource.qrc分别对应html,
script,style和resource文件夹,用来存储网页、JS脚本、CSS样式文件和各种图片音频视频资源。
以html.qrc为例,添加html文件夹中的welcome.html,那么在程序中我们就可以用路径
qrc:/html/welcome.html来引用它。这里的欢迎页面引用了之前Chrome天气预报插件的
代码。
welcome.html
[html] view
plaincopy
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GB2312"/>
<link rel="stylesheet" type="text/css" href="qrc:/style/style.css"/>
<script type="text/javascript" src="qrc:/script/jquery-1.7.2.min.js"></script>
<script type="text/javascript">
function init() {
$("#loadingdiv").html("正在加载城市天气预报...");
$.getJSON("http://m.weather.com.cn/data/101010100.html",
function(data) {
$("#loadingdiv").html("");
var weatherinfo = data["weatherinfo"];
var datearray = ["", weatherinfo["date_y"], "第二天", "第三天", "第四天", "第五天", "第六天"];
$("#cityname").html(weatherinfo["city"] + "城市天气预报");
for (i = 1; i <= 6; i++) {
var divid = "#div" + i;
$(divid).append(datearray[i]).append("<br>");
var imgurl = "http://m.weather.com.cn/weather_img/" + weatherinfo["img"+(i*2-1)] + ".gif";
$(divid).append('<img src="' + imgurl + '"/>').append("<br>");
$(divid).append(weatherinfo["temp" + i]).append("<br>");
$(divid).append(weatherinfo["weather" + i]);
}
}
);
}
</script>
</head>
<body onload="init()">
<h1>Welcome!!!</h1>
<div id="weatherreportdiv">
<div id="loadingdiv"></div>
<div id="cityname"></div>
<hr></hr>
<div id="div1" class="weatherdiv"></div>
<div id="div2" class="weatherdiv"></div>
<div id="div3" class="weatherdiv"></div>
<div id="div4" class="weatherdiv"></div>
<div id="div5" class="weatherdiv"></div>
<div id="div6" class="weatherdiv"></div>
</div>
</body>
</html>
style.css
[html] view
plaincopy
h1 {
text-align: center;
}
#weatherreportdiv {
height: 300px;
width: 700px;
text-align: center;
font-size: 20px;
font-weight: bold;
margin: 5px;
}
#cityname {
text-align: center;
font-size: 20px;
font-weight: bold;
margin: 5px;
}
.weatherdiv {
float: left;
width: 15%;
margin: 5px;
}
管理资源就是这么简单,下面来看改进后的代码。
三、源码实现
在AddressBar中新加一个SLOT函数handleAddressChanged,处理当分页切换时URL的变化。
addressbar.h
[cpp] view
plaincopy
#ifndef ADDRESSBAR_H
#define ADDRESSBAR_H
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QHBoxLayout>
#include <QUrl>
#include <QString>
class AddressBar : public QWidget
{
Q_OBJECT
public:
explicit AddressBar(QWidget *parent = 0);
signals:
void go(const QUrl&);
void back();
void forward();
void newPage();
public slots:
void handleGoToSite();
void handleAddressChanged(const QUrl&);
private:
QLineEdit *addressEdit;
QPushButton *newButton;
QPushButton *backButton;
QPushButton *forwardButton;
QPushButton *goButton;
QHBoxLayout *layout;
};
#endif // ADDRESSBAR_H
addressbar.cpp
[cpp] view
plaincopy
#include "addressbar.h"
AddressBar::AddressBar(QWidget *parent) :
QWidget(parent)
{
// 1.Create widget
addressEdit = new QLineEdit;
newButton = new QPushButton("New");
backButton = new QPushButton("Back");
forwardButton = new QPushButton("Forward");
goButton = new QPushButton("Go");
// 2.Set property
// 3.Connect signal and slot
connect(goButton, SIGNAL(clicked()), this, SLOT(handleGoToSite()));
connect(addressEdit, SIGNAL(returnPressed()), this, SLOT(handleGoToSite()));
connect(backButton, SIGNAL(clicked()), this, SIGNAL(back()));
connect(forwardButton, SIGNAL(clicked()), this, SIGNAL(forward()));
connect(newButton, SIGNAL(clicked()), this, SIGNAL(newPage()));
// 4.Add to layout
layout = new QHBoxLayout;
layout->addWidget(newButton);
layout->addWidget(backButton);
layout->addWidget(forwardButton);
layout->addWidget(addressEdit);
layout->addWidget(goButton);
this->setLayout(layout);
}
void AddressBar::handleGoToSite()
{
QString address = addressEdit->text();
emit go(QUrl(address));
}
void AddressBar::handleAddressChanged(const QUrl &url)
{
addressEdit->setText(url.toString());
}
新建了TabPage继承了QTabWidget,管理所有分页。
tabpage.h
[cpp] view
plaincopy
#ifndef TABPAGE_H
#define TABPAGE_H
#include <QTabWidget>
#include <QList>
#include "htmlview.h"
class TabPage : public QTabWidget
{
Q_OBJECT
public:
explicit TabPage(QWidget *parent = 0);
~TabPage();
signals:
void urlChanged(const QUrl&);
public slots:
void handleNewTab();
void handleLoadNewPage(const QUrl&);
void handleBack();
void handleForward();
private slots:
void handleTabChanged(int);
void handleTabClosed(int);
void handleLinkClicked(const QUrl&);
private:
QList<QWebView*> viewList;
};
#endif // TABPAGE_H
tabpage.cpp
[cpp] view
plaincopy
#include "tabpage.h"
#include <QtDebug>
TabPage::TabPage(QWidget *parent) :
QTabWidget(parent)
{
// Set property
this->setTabsClosable(true);
// Connect built-in signal to customized one to convert index to url
connect(this, SIGNAL(currentChanged(int)), SLOT(handleTabChanged(int)));
connect(this, SIGNAL(tabCloseRequested(int)), this, SLOT(handleTabClosed(int)));
// Create new tab for home page
this->handleNewTab();
}
TabPage::~TabPage()
{
}
void TabPage::handleNewTab()
{
HtmlView *view = new HtmlView;
view->load(QUrl("qrc:/html/welcome.html"));
//view->page()->setForwardUnsupportedContent(true);
view->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
// Monitor linkClicked signal
connect(view, SIGNAL(linkClicked(const QUrl&)), this, SLOT(handleLinkClicked(const QUrl&)));
// Add and activate this new tab
int index = this->addTab(view, "Welcome");
this->setCurrentIndex(index);
viewList.append(view);
}
void TabPage::handleLoadNewPage(const QUrl &url)
{
qDebug() << "loadNewPage: " << url.toString();
HtmlView *view = (HtmlView*) this->currentWidget();
view->load(url);
}
void TabPage::handleBack()
{
HtmlView *view = (HtmlView*) this->currentWidget();
view->back();
emit urlChanged(view->url());
}
void TabPage::handleForward()
{
HtmlView *view = (HtmlView*) this->currentWidget();
view->forward();
emit urlChanged(view->url());
}
void TabPage::handleTabChanged(int index)
{
if (index > viewList.length() - 1)
return;
// index is the new tab index
QWebView* view = viewList[index];
emit urlChanged(view->url());
}
void TabPage::handleTabClosed(int index)
{
if (index > viewList.length() - 1)
return;
this->removeTab(index);
QWebView* view = viewList[index];
viewList.removeAt(index);
delete view;
}
void TabPage::handleLinkClicked(const QUrl& url)
{
qDebug() << "handleLinkClicked: " << url.toString();
HtmlView *view = (HtmlView*) this->currentWidget();
view->load(url);
emit urlChanged(url);
}
接下来是MainWindow,改动很小。
mainwindow.h
[cpp] view
plaincopy
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>
#include <QtWebKit>
#include <QMainWindow>
#include "addressbar.h"
#include "htmlview.h"
#include "tabpage.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.cpp
[cpp] view
plaincopy
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
//ui->setupUi(this);
// 0.Global setting
QWebSettings* defaultSettings = QWebSettings::globalSettings();
// We use JavaScript, so set it to be enabled.
defaultSettings->setAttribute(QWebSettings::JavascriptEnabled, true);
// Plug-ins must be set to be enabled to use plug-ins.
defaultSettings->setAttribute(QWebSettings::PluginsEnabled,true);
defaultSettings->setAttribute(QWebSettings::LocalContentCanAccessRemoteUrls,true);
defaultSettings->setObjectCacheCapacities(0, 0, 0);
// 1.Create widget
QWidget *centralWidget = new QWidget(this);
AddressBar *bar = new AddressBar;
TabPage *tab = new TabPage;
// 2.Set property
this->setCentralWidget(centralWidget);
this->setWindowTitle("My Browser v1.0");
this->resize(800, 600);
// 3.Connect widget
QObject::connect(bar, SIGNAL(newPage()),tab, SLOT(handleNewTab()));
QObject::connect(bar, SIGNAL(back()),tab, SLOT(handleBack()));
QObject::connect(bar, SIGNAL(forward()),tab, SLOT(handleForward()));
QObject::connect(bar, SIGNAL(go(QUrl)), tab, SLOT(handleLoadNewPage(QUrl)));
QObject::connect(tab, SIGNAL(urlChanged(QUrl)), bar, SLOT(handleAddressChanged(QUrl)));
// 4.Add widget to layout
QGridLayout *layout = new QGridLayout;
layout->addWidget(bar, 0, 0, 1, 10);
layout->addWidget(tab, 1, 0, 1, 10);
centralWidget->setLayout(layout);
}
MainWindow::~MainWindow()
{
//delete ui;
}
四、关键问题
1.需要为QWebView中的QWebPage设置才能使页面内链接可以正常跳转。
view->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
2.需要为AJAX调用设置:
QWebSettings* defaultSettings = QWebSettings::globalSettings();
// We use JavaScript, so set it to be enabled.
defaultSettings->setAttribute(QWebSettings::JavascriptEnabled, true);
// Plug-ins must be set to be enabled to use plug-ins.
defaultSettings->setAttribute(QWebSettings::PluginsEnabled,true);
defaultSettings->setAttribute(QWebSettings::LocalContentCanAccessRemoteUrls,true);
defaultSettings->setObjectCacheCapacities(0, 0, 0);
五、最终效果
1.程序开始运行,进入欢迎页面。
2.多页面浏览
2012年7月4日 补充
htmlview.h
[cpp] view
plaincopy
#ifndef HTMLVIEW_H
#define HTMLVIEW_H
#include <QWebView>
class HtmlView : public QWebView
{
Q_OBJECT
public:
explicit HtmlView(QWidget *parent = 0);
signals:
public slots:
void loadNewPage(const QUrl &url);
};
#endif // HTMLVIEW_H
htmlview.cpp
[cpp] view
plaincopy
#include "htmlview.h"
HtmlView::HtmlView(QWidget *parent) :
QWebView(parent)
{
}
void HtmlView::loadNewPage(const QUrl &url)
{
this->load(url);
}
1.代码实现
工程目录结构如下:
AddressBar类包含了地址栏和按钮两个控件,将地址栏回车和按钮点击信号与goToSite()槽连接。
当回车和点击事件发生时,goToSite()将获得Url地址并发送go(QUrl)信号。
addressbar.h
[cpp] view
plaincopy
#ifndef ADDRESSBAR_H
#define ADDRESSBAR_H
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QHBoxLayout>
#include <QUrl>
#include <QString>
class AddressBar : public QWidget
{
Q_OBJECT
public:
explicit AddressBar(QWidget *parent = 0);
signals:
void go(QUrl);
public slots:
void goToSite();
private:
QLineEdit *addressEdit;
QPushButton *goButton;
QHBoxLayout *layout;
};
#endif // ADDRESSBAR_H
addressbar.cpp
[cpp] view
plaincopy
#include "addressbar.h"
AddressBar::AddressBar(QWidget *parent) :
QWidget(parent)
{
addressEdit = new QLineEdit(parent);
goButton = new QPushButton("Go", parent);
layout = new QHBoxLayout;
layout->addWidget(addressEdit);
layout->addWidget(goButton);
this->setLayout(layout);
connect(goButton, SIGNAL(clicked()), this, SLOT(goToSite()));
connect(addressEdit, SIGNAL(returnPressed()), this, SLOT(goToSite()));
}
void AddressBar::goToSite()
{
QString address = addressEdit->text();
emit go(QUrl(address));
}
接下来是QtWebkit包中的主要类QWebView,我们借助这个类来渲染Url指向的网页。
为了当用户在地址栏回车或者点击Go按钮时能够自动加载网页,我们需要给QWebView
添加loadNewPage(QUrl)槽(因为QWebView没有类似load(QUrl)的槽),并将其与go(QUrl)
信号连接。所以我们实现一个QWebView的子类HtmlView。
htmlview.h
[cpp] view
plaincopy
#ifndef HTMLVIEW_H
#define HTMLVIEW_H
#include <QWebView>
class HtmlView : public QWebView
{
Q_OBJECT
public:
explicit HtmlView(QWidget *parent = 0);
signals:
public slots:
void loadNewPage(const QUrl &url);
};
#endif // HTMLVIEW_H
htmlview.cpp
[cpp] view
plaincopy
#include "htmlview.h"
HtmlView::HtmlView(QWidget *parent) :
QWebView(parent)
{
}
void HtmlView::loadNewPage(const QUrl &url)
{
this->load(url);
}
接下来实现程序的主窗口QMainWindow,将AddressBar和HtmlView放置其中。
mainwindow.h
[cpp] view
plaincopy
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>
#include <QtWebKit>
#include <QMainWindow>
#include "addressbar.h"
#include "htmlview.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
};
#endif // MAINWINDOW_H
mainwindow.cpp
[cpp] view
plaincopy
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
// 1.Create widget
QWidget *centralWidget = new QWidget(this);
AddressBar *bar = new AddressBar;
HtmlView *view = new HtmlView;
// 2.Add widget to layout
QGridLayout *layout = new QGridLayout;
layout->addWidget(bar, 0, 0, 1, 10);
layout->addWidget(view, 1, 0, 1, 10);
centralWidget->setLayout(layout);
// 3.Connect widget
QObject::connect(bar, SIGNAL(go(QUrl)), view, SLOT(loadNewPage(QUrl)));
this->setCentralWidget(centralWidget);
this->setWindowTitle("My Browser v1.0");
this->resize(640, 480);
}
最后是程序的入口main.cpp
[cpp] view
plaincopy
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow *window = new MainWindow;
window->show();
return app.exec();
}
2.事件流分析
我们分别为AddressBar和HtmlView自定义了两个槽goToSite(QUrl)和loadNewPage(QUrl),以及新的信号go(QUrl)。
就是为了将Url地址传递给QWebView的load函数。
这里需要注意的是SIGNAL-SLOT机制是Qt的内部机制,它是同步执行的。源头上returnPressed()和clicked()槽的触发,
是从操作系统的事件队列中得到的,并进行异步的处理。以QPushButton的clicked()槽的触发为例,QApplication.exec()
执行后将会监听操作系统事件队列,当鼠标事件发生时,事件将会发送到QPushButton的event()函数进行分发:
[cpp] view
plaincopy
QPushButton::event ->
QAbstractButton::event ->
QWidget::event [Dispatch event]:
case QEvent::KeyPress: {
QKeyEvent *k = (QKeyEvent *)event;
bool res = false;
if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier?
if (k->key() == Qt::Key_Backtab
|| (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier)))
res = focusNextPrevChild(false);
else if (k->key() == Qt::Key_Tab)
res = focusNextPrevChild(true);
if (res)
break;
}
keyPressEvent(k);
-> QPushButton::keyPressEvent
void QPushButton::keyPressEvent(QKeyEvent *e)
{
Q_D(QPushButton);
switch (e->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
if (autoDefault() || d->defaultButton) {
click();
break;
}
// fall through
default:
QAbstractButton::keyPressEvent(e);
}
}
-> QAbstractButton::click:
void QAbstractButton::click()
{
if (!isEnabled())
return;
Q_D(QAbstractButton);
QPointer<QAbstractButton> guard(this);
d->down = true;
d->emitPressed();
if (guard) {
d->down = false;
nextCheckState();
if (guard)
d->emitReleased();
if (guard)
d->emitClicked();
}
}
3.SIGNAL-SLOT类的编译
关于Q_OBJECT宏以及SIGNAL,SLOT,emit等关键字奇怪的语法,其实他们是通过一个叫做MOC元对象编译器
的组件来进行预编译的,因此我们可以使用SIGNAL,SLOT,emit来清晰地连接各个信号槽,而非函数指针。
SIGNAL-SLOT使用很方便,但也是会损失一点执行效率,使用时要谨慎。
4.总结及学习资料
通过这个例子可以对Qt的SIGNAL-SLOT机制有个简单的了解,它可以减少对象间的依赖。假如不使用它,我们就
需要在AddressBar中直接调用HtmlView的load()函数,两个类耦合在了一起。在这个例子中MainWindow负责AddressBar
和HtmlView的构建和连接,使它们互相不知道对方的存在!
让我更感兴趣的其实是QtWebKit,通过它我们可以在Qt开发桌面应用时使用Web技术,而不用局限于Qt,MFC等等。
Web开发人员也可以投身桌面应用开发之中。
QtWebKit官方文档:http://doc.qt.nokia.com/4.7-snapshot/qtwebkit.html
学习笔记:http://caterpillar.onlyfun.net/Gossip/Qt4Gossip/Qt4Gossip.html
QtWebKit系列教程:http://software.intel.com/zh-cn/blogs/2010/06/08/qt-webkit-qt-webkit/
信号槽深入学习:http://www.ibm.com/developerworks/cn/linux/guitoolkit/qt/signal-slot/
QtWeb - 一个开源的Qt浏览器项目
一、功能改进
经过对QtWebKit的一些学习,对之前的浏览器进行一些改进:
1.增加分页显示多个网页的功能。每个分页都是一个QWebView控件,实现对多个网页的加载。
2.加入欢迎主页。学习如何创建Qt资源文件,从本地读取欢迎主页的HTML。
3.添加了前进Forward和后退Back按钮。
4.此外还明确了SIGNAL和SLOT的命名,SLOT都以handleXXX开头与SIGNAL区分开。
二、Qt资源文件
Qt可以很方便的通过资源文件来管理各种资源,就像在VS中创建资源文件一样。
项目结构如下:
在Qt Creator中创建四个资源文件html.qrc,script.qrc,style.qrc,resource.qrc分别对应html,
script,style和resource文件夹,用来存储网页、JS脚本、CSS样式文件和各种图片音频视频资源。
以html.qrc为例,添加html文件夹中的welcome.html,那么在程序中我们就可以用路径
qrc:/html/welcome.html来引用它。这里的欢迎页面引用了之前Chrome天气预报插件的
代码。
welcome.html
[html] view
plaincopy
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GB2312"/>
<link rel="stylesheet" type="text/css" href="qrc:/style/style.css"/>
<script type="text/javascript" src="qrc:/script/jquery-1.7.2.min.js"></script>
<script type="text/javascript">
function init() {
$("#loadingdiv").html("正在加载城市天气预报...");
$.getJSON("http://m.weather.com.cn/data/101010100.html",
function(data) {
$("#loadingdiv").html("");
var weatherinfo = data["weatherinfo"];
var datearray = ["", weatherinfo["date_y"], "第二天", "第三天", "第四天", "第五天", "第六天"];
$("#cityname").html(weatherinfo["city"] + "城市天气预报");
for (i = 1; i <= 6; i++) {
var divid = "#div" + i;
$(divid).append(datearray[i]).append("<br>");
var imgurl = "http://m.weather.com.cn/weather_img/" + weatherinfo["img"+(i*2-1)] + ".gif";
$(divid).append('<img src="' + imgurl + '"/>').append("<br>");
$(divid).append(weatherinfo["temp" + i]).append("<br>");
$(divid).append(weatherinfo["weather" + i]);
}
}
);
}
</script>
</head>
<body onload="init()">
<h1>Welcome!!!</h1>
<div id="weatherreportdiv">
<div id="loadingdiv"></div>
<div id="cityname"></div>
<hr></hr>
<div id="div1" class="weatherdiv"></div>
<div id="div2" class="weatherdiv"></div>
<div id="div3" class="weatherdiv"></div>
<div id="div4" class="weatherdiv"></div>
<div id="div5" class="weatherdiv"></div>
<div id="div6" class="weatherdiv"></div>
</div>
</body>
</html>
style.css
[html] view
plaincopy
h1 {
text-align: center;
}
#weatherreportdiv {
height: 300px;
width: 700px;
text-align: center;
font-size: 20px;
font-weight: bold;
margin: 5px;
}
#cityname {
text-align: center;
font-size: 20px;
font-weight: bold;
margin: 5px;
}
.weatherdiv {
float: left;
width: 15%;
margin: 5px;
}
管理资源就是这么简单,下面来看改进后的代码。
三、源码实现
在AddressBar中新加一个SLOT函数handleAddressChanged,处理当分页切换时URL的变化。
addressbar.h
[cpp] view
plaincopy
#ifndef ADDRESSBAR_H
#define ADDRESSBAR_H
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QHBoxLayout>
#include <QUrl>
#include <QString>
class AddressBar : public QWidget
{
Q_OBJECT
public:
explicit AddressBar(QWidget *parent = 0);
signals:
void go(const QUrl&);
void back();
void forward();
void newPage();
public slots:
void handleGoToSite();
void handleAddressChanged(const QUrl&);
private:
QLineEdit *addressEdit;
QPushButton *newButton;
QPushButton *backButton;
QPushButton *forwardButton;
QPushButton *goButton;
QHBoxLayout *layout;
};
#endif // ADDRESSBAR_H
addressbar.cpp
[cpp] view
plaincopy
#include "addressbar.h"
AddressBar::AddressBar(QWidget *parent) :
QWidget(parent)
{
// 1.Create widget
addressEdit = new QLineEdit;
newButton = new QPushButton("New");
backButton = new QPushButton("Back");
forwardButton = new QPushButton("Forward");
goButton = new QPushButton("Go");
// 2.Set property
// 3.Connect signal and slot
connect(goButton, SIGNAL(clicked()), this, SLOT(handleGoToSite()));
connect(addressEdit, SIGNAL(returnPressed()), this, SLOT(handleGoToSite()));
connect(backButton, SIGNAL(clicked()), this, SIGNAL(back()));
connect(forwardButton, SIGNAL(clicked()), this, SIGNAL(forward()));
connect(newButton, SIGNAL(clicked()), this, SIGNAL(newPage()));
// 4.Add to layout
layout = new QHBoxLayout;
layout->addWidget(newButton);
layout->addWidget(backButton);
layout->addWidget(forwardButton);
layout->addWidget(addressEdit);
layout->addWidget(goButton);
this->setLayout(layout);
}
void AddressBar::handleGoToSite()
{
QString address = addressEdit->text();
emit go(QUrl(address));
}
void AddressBar::handleAddressChanged(const QUrl &url)
{
addressEdit->setText(url.toString());
}
新建了TabPage继承了QTabWidget,管理所有分页。
tabpage.h
[cpp] view
plaincopy
#ifndef TABPAGE_H
#define TABPAGE_H
#include <QTabWidget>
#include <QList>
#include "htmlview.h"
class TabPage : public QTabWidget
{
Q_OBJECT
public:
explicit TabPage(QWidget *parent = 0);
~TabPage();
signals:
void urlChanged(const QUrl&);
public slots:
void handleNewTab();
void handleLoadNewPage(const QUrl&);
void handleBack();
void handleForward();
private slots:
void handleTabChanged(int);
void handleTabClosed(int);
void handleLinkClicked(const QUrl&);
private:
QList<QWebView*> viewList;
};
#endif // TABPAGE_H
tabpage.cpp
[cpp] view
plaincopy
#include "tabpage.h"
#include <QtDebug>
TabPage::TabPage(QWidget *parent) :
QTabWidget(parent)
{
// Set property
this->setTabsClosable(true);
// Connect built-in signal to customized one to convert index to url
connect(this, SIGNAL(currentChanged(int)), SLOT(handleTabChanged(int)));
connect(this, SIGNAL(tabCloseRequested(int)), this, SLOT(handleTabClosed(int)));
// Create new tab for home page
this->handleNewTab();
}
TabPage::~TabPage()
{
}
void TabPage::handleNewTab()
{
HtmlView *view = new HtmlView;
view->load(QUrl("qrc:/html/welcome.html"));
//view->page()->setForwardUnsupportedContent(true);
view->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
// Monitor linkClicked signal
connect(view, SIGNAL(linkClicked(const QUrl&)), this, SLOT(handleLinkClicked(const QUrl&)));
// Add and activate this new tab
int index = this->addTab(view, "Welcome");
this->setCurrentIndex(index);
viewList.append(view);
}
void TabPage::handleLoadNewPage(const QUrl &url)
{
qDebug() << "loadNewPage: " << url.toString();
HtmlView *view = (HtmlView*) this->currentWidget();
view->load(url);
}
void TabPage::handleBack()
{
HtmlView *view = (HtmlView*) this->currentWidget();
view->back();
emit urlChanged(view->url());
}
void TabPage::handleForward()
{
HtmlView *view = (HtmlView*) this->currentWidget();
view->forward();
emit urlChanged(view->url());
}
void TabPage::handleTabChanged(int index)
{
if (index > viewList.length() - 1)
return;
// index is the new tab index
QWebView* view = viewList[index];
emit urlChanged(view->url());
}
void TabPage::handleTabClosed(int index)
{
if (index > viewList.length() - 1)
return;
this->removeTab(index);
QWebView* view = viewList[index];
viewList.removeAt(index);
delete view;
}
void TabPage::handleLinkClicked(const QUrl& url)
{
qDebug() << "handleLinkClicked: " << url.toString();
HtmlView *view = (HtmlView*) this->currentWidget();
view->load(url);
emit urlChanged(url);
}
接下来是MainWindow,改动很小。
mainwindow.h
[cpp] view
plaincopy
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>
#include <QtWebKit>
#include <QMainWindow>
#include "addressbar.h"
#include "htmlview.h"
#include "tabpage.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.cpp
[cpp] view
plaincopy
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
//ui->setupUi(this);
// 0.Global setting
QWebSettings* defaultSettings = QWebSettings::globalSettings();
// We use JavaScript, so set it to be enabled.
defaultSettings->setAttribute(QWebSettings::JavascriptEnabled, true);
// Plug-ins must be set to be enabled to use plug-ins.
defaultSettings->setAttribute(QWebSettings::PluginsEnabled,true);
defaultSettings->setAttribute(QWebSettings::LocalContentCanAccessRemoteUrls,true);
defaultSettings->setObjectCacheCapacities(0, 0, 0);
// 1.Create widget
QWidget *centralWidget = new QWidget(this);
AddressBar *bar = new AddressBar;
TabPage *tab = new TabPage;
// 2.Set property
this->setCentralWidget(centralWidget);
this->setWindowTitle("My Browser v1.0");
this->resize(800, 600);
// 3.Connect widget
QObject::connect(bar, SIGNAL(newPage()),tab, SLOT(handleNewTab()));
QObject::connect(bar, SIGNAL(back()),tab, SLOT(handleBack()));
QObject::connect(bar, SIGNAL(forward()),tab, SLOT(handleForward()));
QObject::connect(bar, SIGNAL(go(QUrl)), tab, SLOT(handleLoadNewPage(QUrl)));
QObject::connect(tab, SIGNAL(urlChanged(QUrl)), bar, SLOT(handleAddressChanged(QUrl)));
// 4.Add widget to layout
QGridLayout *layout = new QGridLayout;
layout->addWidget(bar, 0, 0, 1, 10);
layout->addWidget(tab, 1, 0, 1, 10);
centralWidget->setLayout(layout);
}
MainWindow::~MainWindow()
{
//delete ui;
}
四、关键问题
1.需要为QWebView中的QWebPage设置才能使页面内链接可以正常跳转。
view->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
2.需要为AJAX调用设置:
QWebSettings* defaultSettings = QWebSettings::globalSettings();
// We use JavaScript, so set it to be enabled.
defaultSettings->setAttribute(QWebSettings::JavascriptEnabled, true);
// Plug-ins must be set to be enabled to use plug-ins.
defaultSettings->setAttribute(QWebSettings::PluginsEnabled,true);
defaultSettings->setAttribute(QWebSettings::LocalContentCanAccessRemoteUrls,true);
defaultSettings->setObjectCacheCapacities(0, 0, 0);
五、最终效果
1.程序开始运行,进入欢迎页面。
2.多页面浏览
2012年7月4日 补充
htmlview.h
[cpp] view
plaincopy
#ifndef HTMLVIEW_H
#define HTMLVIEW_H
#include <QWebView>
class HtmlView : public QWebView
{
Q_OBJECT
public:
explicit HtmlView(QWidget *parent = 0);
signals:
public slots:
void loadNewPage(const QUrl &url);
};
#endif // HTMLVIEW_H
htmlview.cpp
[cpp] view
plaincopy
#include "htmlview.h"
HtmlView::HtmlView(QWidget *parent) :
QWebView(parent)
{
}
void HtmlView::loadNewPage(const QUrl &url)
{
this->load(url);
}
相关文章推荐
- 安装PlayBook Qt开发工具:Qt Creator for 64 bit Linux版本(beta)
- Firefox将不再开发Windows平台的64位版本浏览器
- 浏览器插件开发框架FireBreath的使用--linux版本
- 用QtWebKit开发简单的浏览器
- 【嵌入式开发技术之Qtopia】安装和交叉编译文档(3)——交叉编译Qt各种版本
- Qt For Symbian 开发环境搭建,各程序版本及安装顺序
- 用QtWebKit开发简单的浏览器
- 用QtWebKit开发简单的浏览器
- Qt开发-简易浏览器+无边框阴影可移动可拖动窗口代码分享
- Qt开发环境的搭建&加入Git做版本管理 :in Windows
- Qt5.9.1结合REF开发基于chorm的浏览器(一)
- 选择版本Win7系统VS2010下搭建qt开发环境
- Qt5.9.1结合REF开发基于chorm的浏览器(二)
- AngularJS开发指南7:AngularJS本地化,国际化,以及兼容IE低版本浏览器
- js,jsp--前端开发过程中浏览器版本的判定
- Qt开发-使用QWebEngineView开发一个简易的浏览器(一)
- AngularJS开发指南7:AngularJS本地化,国际化,以及兼容IE低版本浏览器
- 安装PlayBook Qt开发工具:Qt Creator for Windows版本(beta)
- QT浏览器插件开发入门
- 前端开发过程中浏览器版本的两种判定方法