天天看点

[Qt教程] 第19篇 2D绘图(九)图形视图框架(上) [Qt教程] 第19篇 2D绘图(九)图形视图框架(上)

<a target="_blank" href="http://bbs.qter.org/forum.php?mod=viewthread&amp;tid=125">楼主</a>

[Qt教程] 第19篇 2D绘图(九)图形视图框架(上) [Qt教程] 第19篇 2D绘图(九)图形视图框架(上)

 发表于 2013-5-4 15:26:20 | 查看:

1798| 回复: 26

图形视图框架(上)

版权声明

导语

在前面讲的基本绘图中,我们可以自己绘制各种图形,并且控制它们。但是,如果需要同时绘制很多个相同或不同的图形,并且要控制它们的移动,检测它们的碰撞和叠加;或者我们想让自己绘制的图形可以拖动位置,进行缩放和旋转等操作。实现这些功能,要是还使用以前的方法,那么会十分困难。解决这些问题,可以使用qt提供的图形视图框架。

       图形视图可以对大量定制的2d图形项进行管理和相互作用。视图部件可以让所有图形项可视化,它还提供了缩放和旋转功能。我们在帮助中搜索graphics

view 关键字,内容如下图:

[Qt教程] 第19篇 2D绘图(九)图形视图框架(上) [Qt教程] 第19篇 2D绘图(九)图形视图框架(上)

这里一开始对这个框架进行了简单介绍,整个图形视图结构主要包含三部分:场景(scene)、视图(view)和图形项(item),它们分别对应 qgraphicsscene 、qgraphicsview 、qgraphicsitem三个类。其实图形视图框架是一组类的集合,在帮助中可以看到所有与它相关的类。下面我们就开始结合程序对整个框架进行介绍。

环境:windows xp + qt 4.8.4+qtcreator 2.6.2

目录

一、基本应用

二、图形项(qgraphicsitem)

(一)自定义图形项

(二)光标和提示

(三)拖放

(四)键盘与鼠标事件

(五)碰撞检测

(六)移动

(七)动画

(八)右键菜单

正文

       我们新建空的qt项目(在其他项目中),项目名称为graphicsview01。然后在这个项目中添加新的c++ 源文件,命名为main.cpp。

我们将main.cpp的内容更改如下。

#include &lt;qtgui&gt;

int main(int argc,char* argv[ ])

{

   qapplication app(argc,argv);

   qgraphicsscene *scene = new qgraphicsscene;  //场景

   qgraphicsrectitem *item = new qgraphicsrectitem(100,100,50,50);  //矩形项

   scene-&gt;additem(item);  //项添加到场景

   qgraphicsview *view = new qgraphicsview; //视图

   view-&gt;setscene(scene);  //视图关联场景

   view-&gt;show();  //显示视图

   return app.exec();

}

这里我们建立了一个最简单的基于这个图形视图框架的程序。分别新建了一个场景,一个图形项和一个视图,并将图形项添加到场景中,将视图与场景关联,最后显示视图就可以了。基于这个框架的所有程序都是这样实现的。运行效果如下。

[Qt教程] 第19篇 2D绘图(九)图形视图框架(上) [Qt教程] 第19篇 2D绘图(九)图形视图框架(上)

就像我们看到的,场景是管理图形项的,所有的图形项必须添加到一个场景中,但是场景本身无法可视化,我们要想看到场景上的内容,必须使用视图。下面我们分别对图形项、场景和视图进行介绍。

qgraphicsitem类是所有图形项的基类。图形视图框架对一些典型的形状提供了一些标准的图形项。比如上面我们使用的矩形(qgraphicsrectitem)、椭圆(qgraphicsellipseitem)、文本(qgraphicstextitem)等多个图形项。但只有继承qgraphicsitem 类实现我们自定义的图形项时,才能显示出这个类的强大。qgraphicsitem支持以下功能:

鼠标的按下、移动、释放和双击事件,也支持鼠标悬停、滚轮和右键菜单事件。

键盘输入焦点和键盘事件

拖放

利用qgraphicsitemgroup进行分组

碰撞检测

1.在前面的项目中添加新的c++类,类名设为 myitem,基类设为qgraphicsitem。

2.然后,我们在myitem.h文件中添加头文件#include &lt;qtgui&gt;。(说明:qtgui模块里面包含了所有图形界面类,所以为了简便,这里只包含了该头文件,正式开发程序时不推荐这么做!)

3.再添加两个函数的声明:

qrectfboundingrect() const;

voidpaint(qpainter *painter, const qstyleoptiongraphicsitem *option, qwidget*widget);

4.下面到myitem.cpp中对两个函数进行定义:

qrectfmyitem::boundingrect() const

    qreal penwidth = 1;

    return qrectf(0 - penwidth / 2, 0 -penwidth / 2,

               20 + penwidth, 20 + penwidth);

voidmyitem::paint(qpainter *painter, const qstyleoptiongraphicsitem *option,qwidget *widget)

q_unused(option);  //标明该参数没有使用

    q_unused(widget);

    painter-&gt;setbrush(qt::red);

    painter-&gt;drawrect(0,0,20,20);

5.下面到main.cpp中添加#include "myitem.h"

然后将以前那个矩形项的定义语句改为:

myitem *item =new myitem;

运行程序,效果如下:

[Qt教程] 第19篇 2D绘图(九)图形视图框架(上) [Qt教程] 第19篇 2D绘图(九)图形视图框架(上)

可以看到,我们要继承qgraphicsitem类实现自定义的图形项,必须先实现两个纯虚函数boundingrect()和paint(),前者用于定义item的绘制范围,后者用于绘制图形项。其实boundingrect()还有很多用途,后面会涉及到。

1.在myitem.cpp 中的构造函数中添加两行代码,如下:

myitem::myitem()

   settooltip("click and drag me!");  //提示

   setcursor(qt::openhandcursor);   //改变光标形状

然后运行程序,效果如下:

[Qt教程] 第19篇 2D绘图(九)图形视图框架(上) [Qt教程] 第19篇 2D绘图(九)图形视图框架(上)

当光标放到小方块上时,光标变为了手型,并且弹出了提示。更多的光标形状可以查看

qt::cursorshape,我们也可以使用图片自定义光标形状。

下面写这样一个程序,有几个不同颜色的圆形和一个大矩形,我们可以拖动圆形到矩形上,从而改变矩形的颜色为该圆形的颜色。

1.      将上面的程序进行改进,用来实现圆形图形项。

在myitem.h中添加一个私有变量和几个键盘事件处理函数的声明:

protected:

   void mousepressevent(qgraphicsscenemouseevent *event);

   void mousemoveevent(qgraphicsscenemouseevent *event);

   void mousereleaseevent(qgraphicsscenemouseevent *event);

private:

   qcolor color;

2.然后到myitem.cpp中,在构造函数中初始化颜色变量:

color = qcolor(qrand() % 256, qrand() %256, qrand() % 256); //初始化随机颜色

在paint()函数中将绘制矩形的代码更改如下:

painter-&gt;setbrush(color);

painter-&gt;drawellipse(0, 0, 20, 20);

3.下面我们定义几个键盘事件处理函数:

voidmyitem::mousepressevent(qgraphicsscenemouseevent *event)

   if(event-&gt;button() != qt::leftbutton)

    {

       event-&gt;ignore();  //如果不是鼠标左键按下,则忽略该事件

       return;

    }

   setcursor(qt::closedhandcursor); //如果是鼠标左键按下,改变光标形状

voidmyitem::mousemoveevent(qgraphicsscenemouseevent *event)

   if(qlinef(event-&gt;screenpos(),event-&gt;buttondownscreenpos(qt::leftbutton))

       .length() &lt; qapplication::startdragdistance())

//如果鼠标按下的点到现在的点的距离小于程序默认的拖动距离,表明没有拖动,则返回

   qdrag *drag = new qdrag(event-&gt;widget()); //为event所在窗口部件新建拖动对象

   qmimedata *mime = new qmimedata; //新建qmimedata对象,它用来存储拖动的数据

   drag-&gt;setmimedata(mime); //关联

   mime-&gt;setcolordata(color);  //放入颜色数据

   qpixmap pix(21,21);  //新建qpixmap对象,它用来重新绘制圆形,在拖动时显示

   pix.fill(qt::white);

   qpainter painter(&amp;pix);

   paint(&amp;painter,0,0);

   drag-&gt;setpixmap(pix);

   drag-&gt;sethotspot(qpoint(10, 15)); //我们让指针指向圆形的(10,15)点

   drag-&gt;exec();  //开始拖动

voidmyitem::mousereleaseevent(qgraphicsscenemouseevent *event)

此时运行程序,效果如下:

[Qt教程] 第19篇 2D绘图(九)图形视图框架(上) [Qt教程] 第19篇 2D绘图(九)图形视图框架(上)

4.下面我们新添一个类,它用来提供矩形图形项,并且可以接收拖动的数据。

在myitem.h中,我们加入该类的声明:

class rectitem : public qgraphicsitem

public:

   rectitem();

   qrectf boundingrect() const;

   void paint(qpainter *painter, const qstyleoptiongraphicsitem *option,qwidget *widget);

    void dragenterevent(qgraphicsscenedragdropevent *event); //拖动进入事件

    void dragleaveevent(qgraphicsscenedragdropevent *event); //拖动离开事件

    void dropevent(qgraphicsscenedragdropevent *event);  //放入事件

    qcolor color;

    bool dragover;  //标志是否有拖动进入

};

5.然后进入myitem.cpp进行相关函数的定义:

rectitem::rectitem()

   setacceptdrops(true);  //设置接收拖放

   color = qcolor(qt::lightgray);

qrectf rectitem::boundingrect() const

    return qrectf(0, 0, 50, 50);

void rectitem::paint(qpainter *painter,const qstyleoptiongraphicsitem *option, qwidget *widget)

    painter-&gt;setbrush(dragover? color.light(130) : color);  //如果其上有拖动,颜色变亮

   painter-&gt;drawrect(0,0,50,50);

voidrectitem::dragenterevent(qgraphicsscenedragdropevent *event)

   if(event-&gt;mimedata()-&gt;hascolor()) //如果拖动的数据中有颜色数据,便接收

       event-&gt;setaccepted(true);

       dragover = true;

       update();

   else event-&gt;setaccepted(false);

voidrectitem::dragleaveevent(qgraphicsscenedragdropevent *event)

   q_unused(event);

   dragover = false;

   update();

void rectitem::dropevent(qgraphicsscenedragdropevent*event)

if(event-&gt;mimedata()-&gt;hascolor())

//我们通过类型转换来获得颜色

       color =qvariantvalue&lt;qcolor&gt;(event-&gt;mimedata()-&gt;colordata());

6.下面进入main.cpp文件,更改main()函数中的内容如下:

   qsrand(qtime(0,0,0).secsto(qtime::currenttime())); //设置随机数初值

   qgraphicsscene *scene = new qgraphicsscene;

   for(int i=0; i&lt;5; i++) //在不同位置新建5个圆形

       myitem *item = new myitem;

       item-&gt;setpos(i*50+20,100);

       scene-&gt;additem(item);

   rectitem *rect = new rectitem; //新建矩形

   rect-&gt;setpos(100,200);

   scene-&gt;additem(rect);

   qgraphicsview *view = new qgraphicsview;

   view-&gt;setscene(scene);

   view-&gt;resize(400,300); //设置视图大小

   view-&gt;show();

这是运行程序,效果如下:

[Qt教程] 第19篇 2D绘图(九)图形视图框架(上) [Qt教程] 第19篇 2D绘图(九)图形视图框架(上)

这时我们已经实现了想要的效果。可以看到,要想实现拖放,必须源图形项和目标图形项都进行相关设置。在源图形项的鼠标事件中新建并执行拖动,而在目标图形项中必须指定setacceptdrops(true); 这个函数,这样才能接收拖放,然后需要实现拖放的几个事件处理函数。

1.新建项目graphicsview02,然后按照(一)中自定义图形项进行操作(可以直接把那里的代码拷贝过来)。下面我们先来看键盘事件。

2.在myitem.h文件中声明键盘按下事件处理函数:

voidkeypressevent(qkeyevent *event);

然后在myitem.cpp中进行定义:

void myitem::keypressevent(qkeyevent*event)

   moveby(0, 10);  //相对现在的位置移动

这时运行程序,发现无论怎样方块都不会移动。其实要想使图形项接收键盘事件,就必须使其可获得焦点。我们在构造函数里添加一行代码:

setflag(qgraphicsitem::itemisfocusable);  //图形项可获得焦点

(我们在新建图形项时指定也是可以的,如item-&gt;setflag(qgraphicsitem::itemisfocusable);)

这时运行程序,然后用鼠标点击一下方块,再按下任意按键,方块就会向下移动。效果如下图所示。

[Qt教程] 第19篇 2D绘图(九)图形视图框架(上) [Qt教程] 第19篇 2D绘图(九)图形视图框架(上)

3.再看鼠标事件。我们先在myitem.h文件中声明鼠标按下事件处理函数:

voidmousepressevent(qgraphicsscenemouseevent *event);

然后再myitem.cpp文件中对其进行定义:

   moveby(10,0);

此时运行程序,点击小方块,它便会向右移动。如果我们想让鼠标可以拖动小方块,那么我们可以重新实现mousemoveevent()函数,还有一种更简单的方法是,我们在构造函数中指明该图形项是可移动的:

setflag(qgraphicsitem::itemismovable);

(当然我们也可以在新建图形项时指定它)

[Qt教程] 第19篇 2D绘图(九)图形视图框架(上) [Qt教程] 第19篇 2D绘图(九)图形视图框架(上)

下面先看一个例子,再进行讲解。

我们将上面程序中myitem.cpp文件中的paint()函数中的设置画刷的代码更改如下:

//如果与其他图形项碰撞则显示红色,否则显示绿色

painter-&gt;setbrush(!collidingitems().isempty()?qt::red : qt::green);

然后再main.cpp文件中在场景中添加一个直线图形项:

qgraphicslineitem *line = newqgraphicslineitem(0,50,300,50);

scene-&gt;additem(line);

这时运行程序,效果如下:

[Qt教程] 第19篇 2D绘图(九)图形视图框架(上) [Qt教程] 第19篇 2D绘图(九)图形视图框架(上)

刚开始,方块是绿色的,当我们拖动它与直线相交时,它就变成了红色。

在qgraphicsitem类中有三个碰撞检测函数,分别是collideswithitem()、collideswithpath()和collidingitems(),我们使用的是第三个。第一个是该图形项是否与指定的图形项碰撞,第二个是该图形项是否与指定的路径碰撞,第三个是返回所有与该图形项碰撞的图形项的列表。在帮助中我们可以查看它们的函数原型和介绍,这里想说明的是,这三个函数都有一个共同的参数qt::itemselectionmode,它指明了怎样去检测碰撞。我们在帮助中进行查看,可以发现它是一个枚举变量,一共有四个值,分别是:

qt::containsitemshape:只有图形项的shape被完全包含时;

qt::intersectsitemshape:当图形项的shape被完全包含时,或者图形项与其边界相交;

qt::containsitemboundingrect: 只有图形项的bounding rectangle被完全包含时;

qt::intersectsitemboundingrect:图形项的boundingrectangle被完全包含时,或者图形项与其边界相交。

如果我们不设置该参数,那么他默认使用qt::intersectsitemshape 。这里所说的shape是指什么呢?在qgraphicsitem类中我们可以找到shape()函数,它返回的是一个qpainterpath对象,也就是说它能确定我们图形项的形状。但是默认的,它只是返回boundingrect()函数返回的矩形的形状。下面我们具体验证一下。

在main.cpp函数中添加两行代码:

qdebug()&lt;&lt; item-&gt;shape();   //输出item的shape信息

qdebug()&lt;&lt; item-&gt;boundingrect();  //输出item的boundingrect信息

这时运行程序,在下面的程序输出窗口会输出如下信息:

[Qt教程] 第19篇 2D绘图(九)图形视图框架(上) [Qt教程] 第19篇 2D绘图(九)图形视图框架(上)

我们发现,现在shape和boundingrect的大小是一样的。这时我们在到myitem.cpp中更改函数boundingrect()函数中的内容,将大小由20,改为50:

return qrectf(0 - penwidth / 2, 0 -penwidth / 2,

               50 + penwidth, 50 + penwidth);

这时再次运行程序,效果如下:

[Qt教程] 第19篇 2D绘图(九)图形视图框架(上) [Qt教程] 第19篇 2D绘图(九)图形视图框架(上)

小方块一出来便成为了红色,下面的输出信息也显示了,现在shape的大小也变成了50。怎样才能使小方块按照它本身的形状,而不是其boundingrect的大小来进行碰撞检测呢?我们需要重新实现shape()函数。

在myitem.h中,我们在public里进行函数声明:qpainterpath

shape() const;

然后到myitem.cpp中进行其定义:

qpainterpath myitem::shape() const

   qpainterpath path;

   path.addrect(0,0,20,20);  //图形项的真实大小

   return path;

这时我们再运行程序,效果如下:

[Qt教程] 第19篇 2D绘图(九)图形视图框架(上) [Qt教程] 第19篇 2D绘图(九)图形视图框架(上)

可以看到,现在shape和boundingrect的大小已经不同了。所以对于不是矩形的形状,我们都可以利用shape()函数来返回它的真实形状。

对于图形项的移动,我们有很多办法实现,也可以在很多层面上对其进行控制,比如说在view上控制或者在scene上控制。但是对于大量的不同类型的图形项,怎样能一起控制呢?在图形视图框架中提供了advance()槽函数,这个函数在qgraphicsscene和qgraphicsitem中都有,在图形项类中它的原型是advance(int

phase)。它的实现流程是,我们利用qgraphicsscene类的对象调用qgraphicsscene的advance()函数,这时就会执行两次该场景中所有图形项的advance(int

phase)函数,第一次phase为0,告诉所有图形项即将要移动;第二次phase的值为1,这时执行移动。下面我们看一个例子。

我们在myitem.h中的protected中声明函数:void advance(int

phase);

然后在myitem.cpp中对其进行定义:

void myitem::advance(int phase)

   if(!phase) return;  //如果phase为0,则返回

   moveby(0,10);

在到main.cpp中添加以下代码:

qtimer timer;

qobject::connect(&amp;timer, signal(timeout()),scene, slot(advance()));

timer.start(1000);

这时运行程序,小方块就会每秒下移一下。

其实实现图形项的动画效果,也可以在不同的层面进行。如果我们只想控制一两个图形项的动画,一般在场景或视图中实现。但是要是想让一个图形项类的多个对象都进行同样的动画,那么我们就可以在图形项类中进行实现。我们先看一个例子。

在myitem.cpp文件中的构造函数中添加代码:

   setflag(qgraphicsitem::itemisfocusable); //图形项可获得焦点

   setflag(qgraphicsitem::itemismovable); //图形项可移动

   qgraphicsitemanimation *anim = new qgraphicsitemanimation; //新建动画类对象

   anim-&gt;setitem(this);  //将该图形项加入动画类对象中

   qtimeline *timeline = new qtimeline(1000);  //新建长为1秒的时间线

   timeline-&gt;setloopcount(0);  //动画循环次数为0,表示无限循环

   anim-&gt;settimeline(timeline); //将时间线加入动画类对象中

   anim-&gt;setrotationat(0.5,180); //在动画时间的一半时图形项旋转180度

   anim-&gt;setrotationat(1,360);  //在动画执行完时图形项旋转360度

   timeline-&gt;start();  //开始动画

这时执行程序,效果如下:

[Qt教程] 第19篇 2D绘图(九)图形视图框架(上) [Qt教程] 第19篇 2D绘图(九)图形视图框架(上)

小方块会在一秒内旋转一圈。我们这里使用了qgraphicsitemanimation动画类和qtimeline时间线类,关于这些内容我们会在后面的动画框架中细讲,所以在这里就不再介绍。

图形项支持右键菜单,不过qgraphicsitem类并不是从qobject类继承而来的,所以它默认不能使用信号和槽机制,为了能使用信号和槽,我们需要将我们的myitem类进行多重继承。

在myitem.h中,将myitem类改为

class myitem : public qobject, publicqgraphicsitem

q_object    //进行宏定义

… …

这样我们就可以使用信号和槽机制了,这时我们在下面添加一个槽:

public slots:

   void moveto(){setpos(0,0);}

因为其实现的功能很简单,我们在声明它的同时进行了定义,就是让图形项移动到(0,0)点。

然后我们在protected中声明右键菜单事件处理函数:

voidcontextmenuevent(qgraphicsscenecontextmenuevent *event);

最后我们在myitem.cpp文件中对该事件处理函数进行定义:

voidmyitem::contextmenuevent(qgraphicsscenecontextmenuevent *event)

   qmenu menu;

   qaction *action = menu.addaction("moveto(0,0)");

   connect(action,signal(triggered()),this,slot(moveto()));

   menu.exec(event-&gt;screenpos()); //在按下鼠标左键的地方弹出菜单

这里我们只是设置了一个菜单,当按下该菜单是,图形项移动到(0,0)点。

我们运行程序,效果如下:

[Qt教程] 第19篇 2D绘图(九)图形视图框架(上) [Qt教程] 第19篇 2D绘图(九)图形视图框架(上)

结语

       这一节先介绍了图形项的相关内容,而场景、视图等内容放到下一节来讲。

涉及到的源码: 

[Qt教程] 第19篇 2D绘图(九)图形视图框架(上) [Qt教程] 第19篇 2D绘图(九)图形视图框架(上)

kb, 下载次数: 30) 

[Qt教程] 第19篇 2D绘图(九)图形视图框架(上) [Qt教程] 第19篇 2D绘图(九)图形视图框架(上)

kb, 下载次数: 23)