http://www.tuicool.com/articles/iayIj2
1.代码实现工程目录结构如下:
AddressBar类包含了地址栏和按钮两个控件,将地址栏回车和按钮点击信号与goToSite()槽连接。当回车和点击事件发生时,goToSite()将获得Url地址并发送go(QUrl)信号。addressbar.h#ifndef ADDRESSBAR_H#define ADDRESSBAR_H#include <QWidget>#include <QLineEdit>#include <QPushButton>#include <QHBoxLayout>#include <QUrl>#include <QString>class AddressBar : public QWidget{ Q_OBJECTpublic: 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#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#ifndef HTMLVIEW_H#define HTMLVIEW_H#include <QWebView>class HtmlView : public QWebView{ Q_OBJECTpublic: explicit HtmlView(QWidget *parent = 0); signals: public slots: void loadNewPage(const QUrl &url); };#endif // HTMLVIEW_H
htmlview.cpp#include "htmlview.h"HtmlView::HtmlView(QWidget *parent) : QWebView(parent){}void HtmlView::loadNewPage(const QUrl &url){ this->load(url);}接下来实现程序的主窗口QMainWindow,将AddressBar和HtmlView放置其中。mainwindow.h#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#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#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()函数进行分发: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::keyPressEventvoid 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开发人员也可以投身桌面应用开发之中。
1.代码实现工程目录结构如下:
AddressBar类包含了地址栏和按钮两个控件,将地址栏回车和按钮点击信号与goToSite()槽连接。当回车和点击事件发生时,goToSite()将获得Url地址并发送go(QUrl)信号。addressbar.h#ifndef ADDRESSBAR_H#define ADDRESSBAR_H#include <QWidget>#include <QLineEdit>#include <QPushButton>#include <QHBoxLayout>#include <QUrl>#include <QString>class AddressBar : public QWidget{ Q_OBJECTpublic: 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#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#ifndef HTMLVIEW_H#define HTMLVIEW_H#include <QWebView>class HtmlView : public QWebView{ Q_OBJECTpublic: explicit HtmlView(QWidget *parent = 0); signals: public slots: void loadNewPage(const QUrl &url); };#endif // HTMLVIEW_H
htmlview.cpp#include "htmlview.h"HtmlView::HtmlView(QWidget *parent) : QWebView(parent){}void HtmlView::loadNewPage(const QUrl &url){ this->load(url);}接下来实现程序的主窗口QMainWindow,将AddressBar和HtmlView放置其中。mainwindow.h#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#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#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()函数进行分发: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::keyPressEventvoid 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开发人员也可以投身桌面应用开发之中。