您的位置:首页 > 编程语言 > Qt开发

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);

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: