简单图形编程的学习(1)---文字 (Qt实现)
简单图形编程的学习(1)---文字 (Qt实现)
write by 九天雁翎(JTianLing) -- www.jtianling.com
一、 全部简单图形编程的学习说在前面的话
此系列文章均假设读者已经具备一定的对应的程序编写知识,无论是最简单的small basic,还是因为常用而被人熟知的Windows GDI,或者是Linux下用的更多的Qt(一般我用PyQt),甚至是现在国内知道的人并不多的Android,我都不准备讲太多基础的语法,或者与平台相关的太多背景知识,这些靠读者先行学习,我仅仅准备在自己学习的过程中找点乐子:)看看我用一些简单的接口都能想出干什么事情,然后展示给大家看看,图形程序实在是最好展示的一类程序了,不像其他程序一样,哪怕我讲了一堆的boost,真正见识到boost强大的又有几个呢?-_-!要知道,今天起,所有程序都是窗口程序,不再是命令行!!!!人类用了多久才走到这一步我不知道。。。。我用了25年.......(从我出生算起)或者1年(从工作开始)
另外,想要看怎么编写窗口应用程序的就不要走错地方了,这里不是想怎么描述怎么使用一个又一个的控件,这里都是讲绘图的-_-!
二、 谈谈Qt
由于今天是第一篇,所以谈谈Qt,Qt原来是奇趣(trolltech)公司的产品,目前奇趣已经被诺基亚收购。很久前学习Linux的时候就知道Qt了(可谓久仰大名,如雷贯耳),最重要的是,Qt以其良好的面向对象特性,可移植性著称,这也是我特别喜欢的特性,所以当年在学习Python并选择一个GUI的时候,综合考虑,最终没有选择PyGTK,wxPython等一样优秀的开源产品,(见以前的一篇文章《pyqt学习 的开始,顺便小谈目前gui的选择...》而是PyQt。Qt的原始版本是C++,(但是目前官方已经支持JAVA)而PyQt其实不是官方支持的产品,学到后来,因为PyQt没有好用的IDE(所以一直用Gvim代替着,Eric怎么看都不习惯,一堆按钮没有章法),而且没有合适的教程参考学习(大部分都是qt3时代的东西),最重要的是个人还是比较喜欢看纸质书籍,而国内很难看到PyQt书籍的出版了。。。。。。。。所以,其实PyQt在学习了一段时间其实慢慢的慢下来了,再加上那时候预知了OPhone将横空出世,将较大的经历转移到了JAVA,Android的学习中去了。。。。可怜的Python啊。。。。呵呵,连一款完整的GUI都还没有学会-_-!Qt的网址目前是http://qt.nokia.com/。目前个人计划是考虑先学好Qt的根本,C++的Qt吧,将来想做界面的时候用PyQt应该也快,毕竟接口还是一样的。因为在家,没有好用的Eclipse C++环境(在家用Windows,Windows下C++还是较为习惯VS,而装着Ubuntu + Eclipse + g++ + qt eclipse插件的笔记本在公司),暂时用qt creator来代替吧,虽然个人觉得qt creator还是太过于功能简单的IDE。。。。
三、 Qt的文字显示
Qt原始的设计是用于手工编码产生GUI的,所以简单的程序编码也比较简单,(不像Win32和MFC,再简单的程序也是一大堆废物代码),一个简单的文字显示示例如下:(用qt creator的一个很大问题是没有语法高亮,所以我不得不将代码先拷贝到VS中然后再拷贝过来以达到语法高亮的效果)
// main.cpp(程序主程序)
#include <QtGui/QApplication>
#include "fontwidget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
FontWidget w;
w.show();
return a.exec();
}
Fontwidget.h:
#ifndef FONTWIDGET_H
#define FONTWIDGET_H
#include <QtGui/QWidget>
namespace Ui
{
class FontWidget;
}
class FontWidget : public QWidget
{
Q_OBJECT
public:
FontWidget(QWidget *parent = 0);
~FontWidget();
protected:
void FontWidget::paintEvent(QPaintEvent *event);
private:
Ui::FontWidget *ui;
};
#endif // FONTWIDGET_H
FontWidget.cpp:
#include "fontwidget.h"
#include "ui_fontwidget.h"
#include <QPainter>
#include <QObject>
FontWidget::FontWidget(QWidget *parent)
: QWidget(parent), ui(new Ui::FontWidget)
{
ui->setupUi(this);
}
FontWidget::~FontWidget()
{
delete ui;
}
void FontWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QString text = tr("Hello World");
painter.drawText(10,10, text);
}
说是简单,其实为了展示一个较为完成的自定义Widget的Qt程序,还是牵扯了较多的额外代码,其实真正有意义的仅仅只有 QPainter painter(this);
QString text = tr("Hello World");
painter.drawText(10,10, text);
3句而已,这里,说明一下paintEvent,在Qt中Event与signal特别重要,可以理解为两套不同的消息机制,Event主要用于控件的底层,不直接受你控制,而signal的使用范围不限,并且你可以无限的扩展。Event这种不直接受控制的特性也是非常有用的,因为给出了一套较为容易理解的程序执行流程和规范,基本上,这里Event和Win32(MFC)中的消息较为类似,paintEvent类似Win32中的WM_DRAW消息的响应(在MFC中就是OnDraw),使用方法上与MFC也较为类似,即通过继承来改变。
需要特别注意的是,QPainter的drawText和Windows的DrawText设计思想上不太一样,这里的Y坐标指的是字符的Baseline位置(基线)。
这点我刚开始不太习惯,(因为很简单的原因),而QPainter的用rect为参数的drawText版本仍然与一般的drawText很类似,比如下面这样:
void FontWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QString text = tr("Hello World");
painter.drawText(QRect(0,0, 100, 20), text);
}
那么,此处的rect的左上角就代表了文字的左上角,所以个人感觉这里有点比较奇怪。更奇怪的是,不仅仅是这一个借口,drawText的接口只要是用x,y直接来表示位置的时候就是指文字的baseline,用QRect的时候就是top。。。。。。。。。。
总而言之,基本的文字显示方法在Qt中就很明显了,QPainter对象的drawText,需要注意y坐标的意思。。。。。。。。。。
四、 字体
其实字体是个更加复杂的问题。。。。有多少人知道在字体的现实问题上MS,Apple,Adobe的研究人员投入了多少精力啊。。。。今天的TrueType可不是一开始就存在的。。。。去《简单图形编程的学习(1)---文字 (Windows GDI实现)》看看window版本中的字体结构有多少参数吧。。。。。。。要是人工去记就像记圆周率一样,相对来说,因为Qt引入了QFont这样较为面向对象的方式,简单了很多.
Qt中对字体也给与了重要支持(其实对于很多人来说PC就是一个文字处理的机器,所以字体相当重要,想想MS最赚钱的软件吧。。Office),
仅仅关于字体就有很多类:(链接直接指向nokia的class reference)
QFont,QFontComboBox, QFontDatabase, QFontDialog, QFontEngineInfo, QFontEnginePlugin, QFontInfo
这里,我仅仅想改变QPainter的drawText的字体,所以仅仅使用QFont。
另外,QPen可以用于改变drawText的颜色,而这些都是QPainter的属性,通过setXXX来设置。
示例如下:
void FontWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QFont font("Times",50);
QString text = tr("Hello World");
painter.setPen(Qt::blue);
painter.setFont(font);
painter.drawText(QRect(0,0, 1000, 100), text);
}
效果如插图1.这里是第一次展示Qt的截图,顺便说明一下,Qt在Window下并不是使用Windows的原生控件,而是通过Qt控件模拟而成。不要怀疑Qt有原生的控件啊,这也是我学习Qt的原因之一,Qt可是有它的原生平台KDE的(也是Qt的杀手级应用),因为学习Qt我甚至将以前用了好些年的Gnome经验和体验都放弃了,开始适应并改用KDE,ubuntu的KDE版本还有特定的名字Kubuntu。。。。。。。。没有办法使用compiz还让我郁闷了好一阵子,因此还查看了一些KDE,Gnome的相关资料,KDE与Gnome之间的竞争估计也算是IT世界著名而漫长的战争之一了吧(类似的例子还有vim-emacs)。可怜的KDE盛极一时,因为Qt而慢慢被众多公司所抛弃,但是竟然因为Qt是某家公司的产品(虽然开源)而被冷落。。。可见开源世界的人们对自由的极高标准。。。。
五、 旋转的字体
按照来自Charles Petzold的创意,见《简单图形编程的学习(1)---文字 (Windows GDI实现)》中的例子,自然我也想要在Qt中实现旋转的字体,并且,这一次不会再有抄袭Charles Petzold的嫌疑了lol,呵呵,虽然还是抄袭了创意,对比Windows中的例子,你会很惊讶的发现Qt的实现实在太简单了,简单的可怕。
void FontWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QFont font("Times",50);
QString text = tr(" Rotation");
painter.setFont(font);
int x = (width() / 2);
int y = (height() / 2);
painter.setViewport(x, y, width(), height());
for(int i = 0; i < 12; ++i)
{
painter.rotate(30);
painter.drawText(0, 0, text);
}
}
用QPainter::setViewport调整后Viewport后,painter有现成的函数rotate使用,看看实现效果:)见插图2。
还是老样子,让它旋转起来,这里我还是用两种方式,一种是定时器,一种是手动接管事件循环的方式(Windows的实现是在消息循环外接管消息循环)。
1. 用定时器的版本:
Fontwidget.h:
#ifndef FONTWIDGET_H
#define FONTWIDGET_H
#include <QtGui/QWidget>
#include <QPainter>
#include <QObject>
namespace Ui
{
class FontWidget;
}
class FontWidget : public QWidget
{
Q_OBJECT
public:
FontWidget(QWidget *parent = 0);
~FontWidget();
protected:
void paintEvent(QPaintEvent *event);
void showEvent(QShowEvent* event);
void timerEvent(QTimerEvent *event);
private:
Ui::FontWidget *ui;
int myTimerID;
unsigned int miOritation;
QFont *pFont;
QString *pText;
};
#endif // FONTWIDGET_H
Fontwidget.cpp:
#include "fontwidget.h"
#include "ui_fontwidget.h"
FontWidget::FontWidget(QWidget *parent)
: QWidget(parent), ui(new Ui::FontWidget)
{
ui->setupUi(this);
miOritation = 0;
pFont = new QFont("Times",50);
pText = new QString(tr(" Rotation"));
}
FontWidget::~FontWidget()
{
delete ui;
}
void FontWidget::showEvent(QShowEvent* event)
{
myTimerID = startTimer(33);
}
void FontWidget::timerEvent(QTimerEvent *event)
{
if(event->timerId() == myTimerID)
{
miOritation += 1;
repaint();
}
}
void FontWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
int x = (width() / 2);
int y = (height() / 2);
painter.setViewport(x, y, width(), height());
painter.setFont(*pFont);
for(int i = 0; i < 12; ++i)
{
painter.rotate(30+miOritation);
painter.drawText(0, 0, *pText);
}
}
按照正常的思路就是这样做了,但是会发现严重的问题(虽然效果也很有意思),那就是一个循环内多次的rotate后的文字竟然速度不一样,实现了奇怪的后面文字追前面文字的效果。。。。。。。。如插图3所示。呵呵,还好有宝典在手。。。。。《C++ GUI Qt4编程》第二版第八章中有类似的描述:“for循环中的代码有一个小缺陷,如果执行了更多的迭代,这一问题会变得很明显。每次调用rotate(),就高效地用一个旋转矩阵去乘当前的世界变换,从而创建一个新的世界变换。浮点数的舍入误差不断的累积,得到了越来越不准确的世界变换。”就是这个原因,才有了我们这样的效果。我们这里比原书中的例子rotate了更多次,因为这里是动画(每秒30帧左右),原书是静态画面。原书的解决方案是使用QPainter的save(),restore()函数为每次迭代保存和加载原始的矩阵。
正确的做法是:
void FontWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
int x = (width() / 2);
int y = (height() / 2);
painter.setViewport(x, y, width(), height());
painter.setFont(*pFont);
for(int i = 0; i < 12; ++i)
{
int liOritation = 30 * i + miOritation;
painter.save();
painter.rotate(liOritation);
painter.drawText(0, 0, *pText);
painter.restore();
}
}
这样的效果就正常了,而且正常的惊人,因为我们没有为图形的显示进行任何优化以防止闪烁,(《简单图形编程的学习(1)---文字 (Windows GDI实现)》中类似的例子闪烁的就非常明显)在《C++ GUI Qt4编程》中说明了Qt4竟然会自动为我们对图形的现实进行双缓冲处理-_-!呵呵,甚至不用我们通过编程手动指定就能实现这样的效果,这也算是Qt框架设计之人性化的又一个体现吧,我很享受这样的人性化^^。
2. 手动接管事件循环的版本:
这个版本就类似于《简单图形编程的学习(1)---文字 (Windows GDI实现)》中使用PeekMessage的版本了,以后要想用Qt做游戏并达到很好的动画效果估计也就靠这种方式来实现了。
Main:
#include <QtGui/QApplication>
#include "fontwidget.h"
#include <QtTest/QTest>
#include <QTime>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
FontWidget w;
w.show();
QTime timer;
while(true)
{
timer.start();
a.processEvents();
w.addOritation();
w.repaint();
while(timer.elapsed() < 33)
{
QTest::qSleep(1);
}
}
}
fontwidget.h
#ifndef FONTWIDGET_H
#define FONTWIDGET_H
#include <QtGui/QWidget>
#include <QPainter>
#include <QObject>
namespace Ui
{
class FontWidget;
}
class FontWidget : public QWidget
{
Q_OBJECT
public:
FontWidget(QWidget *parent = 0);
~FontWidget();
void addOritation() { miOritation++; }
protected:
void paintEvent(QPaintEvent *event);
void keyPressEvent(QKeyEvent *event);
void closeEvent(QCloseEvent *event);
private:
Ui::FontWidget *ui;
int myTimerID;
unsigned int miOritation;
QFont *pFont;
QString *pText;
};
#endif // FONTWIDGET_H
fontwidget.cpp:
#include "fontwidget.h"
#include "ui_fontwidget.h"
#include <QtGui>
FontWidget::FontWidget(QWidget *parent)
: QWidget(parent), ui(new Ui::FontWidget)
{
ui->setupUi(this);
miOritation = 0;
pFont = new QFont("Times",50);
pText = new QString(tr(" Rotation"));
}
FontWidget::~FontWidget()
{
delete ui;
}
void FontWidget::keyPressEvent(QKeyEvent *event)
{
// 响应Esc键以退出程序
if(event->key() == Qt::Key_Escape)
{
exit(0);
}
}
void FontWidget::closeEvent(QCloseEvent *event)
{
exit(0);
}
void FontWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
int x = (width() / 2);
int y = (height() / 2);
painter.setViewport(x, y, width(), height());
painter.setFont(*pFont);
for(int i = 0; i < 12; ++i)
{
int liOritation = 30 * i + miOritation;
painter.save();
painter.rotate(liOritation);
painter.drawText(0, 0, *pText);
painter.restore();
}
}
效果非常好:)相对来说会比定时的版本动画平稳很多,并且,Qt还是让这样的动画没有任何闪烁,学习过的GUI不算不可计数,但是真正让人感觉很愉快的是在仅仅发现Qt一种,控制能力还是很强大,灵活,并且很为程序员着想,不愧是以前专门卖框架的公司开发的。。。。
六、 参考:
1. 《C++ GUI Qt4编程》第二版(原版名《C++ GUI Programming with Qt4,Second Edition》,Jasmin Blanchette,Mark Summerfield著,电子工业出版社
插图
插图1:
插图2:
插图3:
write by 九天雁翎(JTianLing) -- www.jtianling.com
Posted By 九天雁翎 at 九天雁翎的博客 on 2009年09月04日