九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

简单图形编程的学习(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的时候,综合考虑,最终没有选择PyGTKwxPython等一样优秀的开源产品,(见以前的一篇文章《pyqt学习 的开始,顺便小谈目前gui的选择...而是PyQtQt的原始版本是C++,(但是目前官方已经支持JAVA)而PyQt其实不是官方支持的产品,学到后来,因为PyQt没有好用的IDE(所以一直用Gvim代替着,Eric怎么看都不习惯,一堆按钮没有章法),而且没有合适的教程参考学习(大部分都是qt3时代的东西),最重要的是个人还是比较喜欢看纸质书籍,而国内很难看到PyQt书籍的出版了。。。。。。。。所以,其实PyQt在学习了一段时间其实慢慢的慢下来了,再加上那时候预知了OPhone将横空出世,将较大的经历转移到了JAVAAndroid的学习中去了。。。。可怜的Python啊。。。。呵呵,连一款完整的GUI都还没有学会-_-!Qt的网址目前是http://qt.nokia.com/。目前个人计划是考虑先学好Qt的根本,C++Qt吧,将来想做界面的时候用PyQt应该也快,毕竟接口还是一样的。因为在家,没有好用的Eclipse C++环境(在家用WindowsWindowsC++还是较为习惯VS,而装着Ubuntu + Eclipse + g++ + qt eclipse插件的笔记本在公司),暂时用qt creator来代替吧,虽然个人觉得qt creator还是太过于功能简单的IDE。。。。

 

三、   Qt的文字显示

Qt原始的设计是用于手工编码产生GUI的,所以简单的程序编码也比较简单,(不像Win32MFC,再简单的程序也是一大堆废物代码),一个简单的文字显示示例如下:(用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);

}

 

说是简单,其实为了展示一个较为完成的自定义WidgetQt程序,还是牵扯了较多的额外代码,其实真正有意义的仅仅只有  QPainter painter(this);

   QString text = tr("Hello World");

   painter.drawText(10,10, text);

3句而已,这里,说明一下paintEvent,在QtEventsignal特别重要,可以理解为两套不同的消息机制,Event主要用于控件的底层,不直接受你控制,而signal的使用范围不限,并且你可以无限的扩展。Event这种不直接受控制的特性也是非常有用的,因为给出了一套较为容易理解的程序执行流程和规范,基本上,这里EventWin32MFC)中的消息较为类似,paintEvent类似Win32中的WM_DRAW消息的响应(在MFC中就是OnDraw),使用方法上与MFC也较为类似,即通过继承来改变。

需要特别注意的是,QPainterdrawTextWindowsDrawText设计思想上不太一样,这里的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),

仅仅关于字体就有很多类:(链接直接指向nokiaclass reference

QFontQFontComboBox, QFontDatabase, QFontDialog, QFontEngineInfo, QFontEnginePlugin, QFontInfo

这里,我仅仅想改变QPainterdrawText的字体,所以仅仅使用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的截图,顺便说明一下,QtWindow下并不是使用Windows的原生控件,而是通过Qt控件模拟而成。不要怀疑Qt有原生的控件啊,这也是我学习Qt的原因之一,Qt可是有它的原生平台KDE的(也是Qt的杀手级应用),因为学习Qt我甚至将以前用了好些年的Gnome经验和体验都放弃了,开始适应并改用KDE,ubuntuKDE版本还有特定的名字Kubuntu。。。。。。。。没有办法使用compiz还让我郁闷了好一阵子,因此还查看了一些KDE,Gnome的相关资料,KDEGnome之间的竞争估计也算是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帧左右),原书是静态画面。原书的解决方案是使用QPaintersave(),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



分类:  未分类 
标签:  Qt 

Posted By 九天雁翎 at 九天雁翎的博客 on 2009年09月04日

前一篇: 变“从U盘/硬盘安装Linux ”的痛苦为享受的工具,UNetbootin 后一篇: 设计Qt风格的C++API【转】