(1)涂鸦板
上面一节我们深入分析了一下Qt的坐标系统,这一节我们在前面程序的基础上稍加改动,设计一个涂鸦板程序。
简单的涂鸦板:
1.我们再在程序中添加函数。
我们在dialog.h里的public中再添加鼠标移动事件和鼠标释放事件的函数声明:
void mouseMoveEvent(QMouseEvent *);
void mouseReleaseEvent(QMouseEvent *);
在private中添加变量声明:
QPixmap pix;
QPoint lastPoint;
QPoint endPoint;
因为在函数里声明的QPixmap类对象是临时变量,不能存储以前的值,所以为了实现保留上次的绘画结果,我们需要将其设为全局变量。
后两个QPoint变量存储鼠标指针的两个坐标值,我们需要用这两个坐标值完成绘图。
2.在dialog.cpp中进行修改。
在构造函数里进行变量初始化。
resize(600,500); //窗口大小设置为600*500
pix = QPixmap(200,200);
pix.fill(Qt::white);
然后进行其他几个函数的定义:
void Dialog::paintEvent(QPaintEvent *)
{
QPainter pp(&pix);
pp.drawLine(lastPoint,endPoint); //根据鼠标指针前后两个位置就行绘制直线
lastPoint = endPoint; //让前一个坐标值等于后一个坐标值,这样就能实现画出连续的线
QPainter painter(this);
painter.drawPixmap(0,0,pix);
}
void Dialog::mousePressEvent(QMouseEvent *event)
{
if(event->button()==Qt::LeftButton) //鼠标左键按下
lastPoint = event->pos();
}
void Dialog::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons()&Qt::LeftButton) //鼠标左键按下的同时移动鼠标
{
endPoint = event->pos();
update();
}
}
void Dialog::mouseReleaseEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton) //鼠标左键释放
{
endPoint = event->pos();
update();
}
}
这里的update()函数,是进行界面重绘,执行该函数时就会执行那个重绘事件函数。
3.这时运行程序,效果如下。(点击图片可将其放大)
这样简单的涂鸦板程序就完成了。下面我们进行放大后的涂鸦。
放大后再进行涂鸦:
1.添加放大按钮。
在dialog.h中添加头文件声明: #include <QPushButton>
在private中添加变量声明:
int scale;
QPushButton *pushBtn;
然后再在下面写上按钮的槽函数声明:
private slots:
void zoomIn();
2.在dialog.cpp中进行更改。
在构造函数里添加如下代码:
scale =1; //设置初始放大倍数为1,即不放大
pushBtn = new QPushButton(this); //新建按钮对象
pushBtn->setText(tr("zoomIn")); //设置按钮显示文本
pushBtn->move(500,450); //设置按钮放置位置
connect(pushBtn,SIGNAL(clicked()),this,SLOT(zoomIn())); //对按钮的单击事件和其槽函数进行关联
这里我们利用代码添加了一个按钮对象,用它来实现放大操作。
再在构造函数以外进行zoomIn()函数的定义:
void Dialog::zoomIn() //按钮单击事件的槽函数
{
scale *=2;
update();
}
3.通过上一节的学习,我们应该已经知道想让画布的内容放大有两个办法,一个是直接放大画布的坐标,一个是放大窗口的坐标。
我们主要讲解放大窗口坐标。
void Dialog::paintEvent(QPaintEvent *)
{
QPainter pp(&pix);
pp.drawLine(lastPoint,endPoint); //根据鼠标指针前后两个位置就行绘制直线
lastPoint = endPoint; //让前一个坐标值等于后一个坐标值,这样就能实现画出连续的线
QPainter painter(this);
painter.scale(scale,scale); //进行放大操作
painter.drawPixmap(0,0,pix);
}
这时运行程序。
先随意画一个图形,如下图。
再按下“zoomIn”按钮,进行放大两倍。可以看到图片放大了,效果如下。
这时我们再进行绘图,绘制出的线条已经不能和鼠标指针的轨迹重合了。效果如下图。
有了前面一节的知识,我们就不难理解出现这个问题的原因了。窗口的坐标扩大了,但是画布的坐标并没有扩大,而我们画图用的坐标值是鼠标指针的,鼠标指针又是获取的窗口的坐标值。现在窗口和画布的同一点的坐标并不相等,所以就出现了这样的问题。
其实解决办法很简单,窗口放大了多少倍,就将获得的鼠标指针的坐标值缩小多少倍就行了。
void Dialog::paintEvent(QPaintEvent *)
{
QPainter pp(&pix);
pp.drawLine(lastPoint/scale,endPoint/scale);
lastPoint = endPoint;
QPainter painter(this);
painter.scale(scale,scale); //进行放大操作
painter.drawPixmap(0,0,pix);
}
运行程序,效果如下:
这种用改变窗口坐标大小来改变画布面积的方法,实际上是有损图片质量的。就像将一张位图放大一样,越放大越不清晰。原因就是,它的像素的个数没有变,如果将可视面积放大,那么单位面积里的像素个数就变少了,所以画质就差了。
下面我们简单说说另一种方法。
放大画布坐标。
void Dialog::paintEvent(QPaintEvent *)
{
QPainter pp(&pix);
pp.scale(scale,scale);
pp.drawLine(lastPoint/scale,endPoint/scale);
lastPoint = endPoint;
QPainter painter(this);
painter.drawPixmap(0,0,pix);
}
效果如下:
此时,画布中的内容并没有放大,而且画布也没有变大,不是我们想要的,所以我们再更改一下函数。
void Dialog::paintEvent(QPaintEvent *)
{
if(scale!=1) //如果进行放大操作
{
QPixmap copyPix(pix.size()*scale); //临时画布,大小变化了scale倍
QPainter pter(©Pix);
pter.scale(scale,scale);
pter.drawPixmap(0,0,pix); //将以前画布上的内容复制到现在的画布上
pix = copyPix; //将放大后的内容再复制回原来的画布上,这样只传递内容,不传递坐标系
scale =1; //让scale重新置1
}
QPainter pp(&pix);
pp.scale(scale,scale);
pp.drawLine(lastPoint/scale,endPoint/scale);
lastPoint = endPoint;
QPainter painter(this);
painter.drawPixmap(0,0,pix);
}
此时运行效果如下:
这样就好了。可以看到,这样放大后再进行绘制,出来的效果是不同的。
我们就讲到这里,如果你有兴趣,可以接着研究!
怎么应用上面讲到的内容,你可以查看绘图软件的教程(链接过去)。
(2)双缓冲绘图简介
上面一节我们实现了涂鸦板的功能,但是如果我们想在涂鸦板上绘制矩形,并且可以动态地绘制这个矩形,也就是说我们可以用鼠标画出随意大小的矩形,那该怎么办呢?
我们先进行下面的三步,最后引出所谓的双缓冲绘图的概念。
第一步:
我们更改上一节的那个程序的重绘函数。
void Dialog::paintEvent(QPaintEvent *)
{
QPainter painter(this);
int x,y,w,h;
x = lastPoint.x();
y = lastPoint.y();
w = endPoint.x() - x;
h = endPoint.y() - y;
painter.drawRect(x,y,w,h);
}
然后运行,效果如下。
这时我们已经可以拖出一个矩形了,但是这样直接在窗口上绘图,以前画的矩形是不能保存住的。所以我们下面加入画布,在画布上进行绘图。
第二步:
我们先在构造函数里将画布设置大点:pix = QPixmap(400,400);
然后更改函数,如下:
void Dialog::paintEvent(QPaintEvent *)
{
int x,y,w,h;
x = lastPoint.x();
y = lastPoint.y();
w = endPoint.x() - x;
h = endPoint.y() - y;
QPainter pp(&pix);
pp.drawRect(x,y,w,h);
QPainter painter(this);
painter.drawPixmap(0,0,pix);
}
这时运行程序,效果如下:
现在虽然能画出矩形,但是却出现了无数个矩形,这不是我们想要的结果,我们希望能像第一步那样绘制矩形,所以我们再加入一个临时画布。
第三步:
首先,我们在dialog.h中的private里添加变量声明:
QPixmap tempPix; //临时画布
bool isDrawing; //标志是否正在绘图
然后在dialog.cpp中的构造函数里进行变量初始化:
isDrawing = false;
最后更改函数如下:
void Dialog::paintEvent(QPaintEvent *)
{
int x,y,w,h;
x = lastPoint.x();
y = lastPoint.y();
w = endPoint.x() - x;
h = endPoint.y() - y;
QPainter painter(this);
if(isDrawing) //如果正在绘图
{
tempPix = pix; //将以前pix中的内容复制到tempPix中,这样实现了交互绘图
QPainter pp(&tempPix);
pp.drawRect(x,y,w,h);
painter.drawPixmap(0,0,tempPix);
}
else
{
QPainter pp(&pix);
pp.drawRect(x,y,w,h);
painter.drawPixmap(0,0,pix);
}
}
void Dialog::mousePressEvent(QMouseEvent *event)
{
if(event->button()==Qt::LeftButton) //鼠标左键按下
{
lastPoint = event->pos();
isDrawing = true; //正在绘图
}
}
void Dialog::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons()&Qt::LeftButton) //鼠标左键按下的同时移动鼠标
{
endPoint = event->pos();
update();
}
}
void Dialog::mouseReleaseEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton) //鼠标左键释放
{
endPoint = event->pos();
isDrawing = false; //结束绘图
update();
}
}
我们使用两个画布,就解决了绘制矩形等图形的问题。
其中tempPix = pix;一句代码很重要,就是它,才实现了消除那些多余的矩形。
双缓冲绘图简介:
根据我的理解,如果将第一步中不用画布,直接在窗口上进行绘图叫做无缓冲绘图,那么第二步中用了一个画布,将所有内容都先画到画布上,在整体绘制到窗口上,就该叫做单缓冲绘图,那个画布就是一个缓冲区。这样,第三步,用了两个画布,一个进行临时的绘图,一个进行最终的绘图,这样就叫做双缓冲绘图。
我们已经看到,利用双缓冲绘图可以实现动态交互绘制。其实,Qt中所有部件进行绘制时,都是使用的双缓冲绘图。就算是第一步中我们没有用画布,Qt在进行自身绘制时也是使用的双缓冲绘图,所以我们刚才那么说,只是为了更好地理解双缓冲的概念。
----------------------------------------------------------------------------------------------------------
到这里,我们已经可以进行一些Qt 2D绘图方面的设计了。我这里有两个例子,一个是那个绘图软件(链接到那里),它是实践了我所讲的这几节的内容,可以说是对这几节内容的一个综合。还有一个例子,就是俄罗斯方块程序(链接到那里),那个是对这些知识的应用。如果你有兴趣,可以看一下。
(3)图形视图框架简介
我们前面用基本的绘图类实现了一个绘图软件,但是,我们无法做出像Word或者Flash中那样,绘制出来的图形可以作为一个元件进行任意变形。我们要想很容易地做出那样的效果,就要使用Qt中的图形视图框架。
The QGraphics View Framework(图形视图框架),在Qt Creator中的帮助里可以查看它的介绍,当然那是英文的,这里有一篇中文的翻译,大家可以看一下:
http://hi.baidu.com/yafeilinux/blog/item/f7040630723a0612eac4af20.html
如果你的程序中要使用大量的2D图元,并且想要这些图元都能进行单独或群组的控制,你就要使用这个框架了。比方说像Flash一样的矢量绘图软件,各种游戏软件。但是因为这里涉及的东西太多了,不可能用一两篇文章就介绍清楚,所以这里我们只是提及一下,让一些刚入门的朋友知道有这样一个可用的框架。
最简单的使用:
The QGraphics View Framework包含三个大类:QGraphicsItem 项类(或者叫做图元类),QGraphicsScene 场景类,和QGraphicsView 视图类。
QGraphicsItem 用来绘制你所要用到的图形,QGraphicsScene 用来包含并管理所有的图元,QGraphicsView 用来显示所有场景。而他们三个都拥有自己各自的坐标系统。我们下面就来建立一个工程,完成一个最简单的例子。
1.新建空的Qt工程:
2.更改工程名和存放路径。
3.然后新建C++类。
4.更改类名为MyItem,基类填写为QGraphicsItem,如下图:
5.可以看到新建的类默认已经添加到了工程里。
6.新建C++ Source File,更改名字为main.cpp,如下图:
7.然后更改各文件的内容。
更改完成后,myitem.h文件内容如下:
myitem.cpp文件的内容如下:
main.cpp的内容如下:
运行程序,最终效果如下:
这里我们只是演示了一下使用这个框架完成最简单的程序的过程,只起到抛砖引玉的作用。
这个框架很复杂,但是功能也很强大,Qt Creator中自带了几个相关的例子(在帮助中查找Graphics View Examples即可),你可以参考一下。因为篇幅问题,我们就只讲这么多,如果以后有机会,我会推出一个相关的专题来讲述这个框架。