Qt图形视图框架
阅读原文时间:2021年04月24日阅读:1

7.1.1 Graphics View的特点

(1)Graphics View框架结构中,系统可以利用Qt绘图系统的反锯齿、OpenGL工具来改善绘图性能。

(2)Graphics View支持事件传播体系结构,可以使图元在场景(scene)中交互能力提高1倍,图元能够处理键盘事件和鼠标事件。其中、鼠标包括鼠标的按下、移动

、释放和双击,还可以跟踪鼠标的移动。

(3)在Graphics View框架中,通过二元空间划分树(Binary SpacePartitioning,BSP)提供快速的图元查找,这样就能够实时地包含上百万个图元的大场景。

7.1.2 Graphics View的三元素

场景类(GraphicsScene),视图类(GraphicsView)和图元类(GraphicsItem)。其中场景类提供了一个用于管理位于其中的众多图元容器,视图类用于显示场景中的

图元,一个场景可以通过多个视图表现,一个场景包括多个几何图形。

1、场景类:QGraphicsScene类

它是一个用于放置图元的容器,本身是不可见的,必须通过与之相连的视图类来显示及与外界进行相互操作。通过QGraphicsScene::addItem()可以添加一个图元到到

场景中。图元可以通过多个函数进行检索。QGraphicsScene::items()和一些重载函数可以返回和点、矩形、多边形或向量路径相交的所有的图元。QGraphicsScene

::itemAt()返回指定点的最顶层图元。

场景类主要完成的工作包括提供对它包含的图元的操作接口和传递事件、管理各个图元的状态(如选择和焦点处理)、提供无变换的绘制功能(如打印)等。

事件传播体系结构将场景事件发送给图元,同时也管理图元之间的事件传播。如果场景接收了在某一点的鼠标单击事件,场景会将事件传给在这一点的图元。

QGraphicsScene  scene; QGraphicsRectItem  *rect=scene.addRect(QRectF(0,0,100,100)); QGraphicsItem  *item=scene.itemAt(50,50); QGraphicsScene的事件传播结

构会把场景事件投递到items,也管理多个items之间的传递。假如场景收到了鼠标在某个位置press事件,场景会把这个事件投递给处在那个位置的itemQGraphicsScene

也管理某种item状态,像选择与焦点。你可以通过调用QGraphicsScene::setSelectionArea()来选择items,它需要提供一个任意的形状为参数。这个函数也作为在

QGraphicsView实现橡皮筋选择功能的一个基础。为得到这些已经被选择的items,调用QGraphicsScene::selectedItem()另一个状态处理是是否一个item拥有键盘输入点。

你可以调用QGraphicsScene::setFocusItem()或QGraphics::setFocus()来设定焦点,也可用QGraphicsScene::focusItem()来得到当前拥有焦点的那个item。最后,

QGraphicsScene允许你通过调用QGraphicsScene::render()函数把部分场景送到绘图设备进行渲染。

2、视图类:QGraphicsView类

它提供一个可视的窗口,用于显示场景中的图元。在一场景中可以有多个视图,也可以为相同的数据集提供几种不同的视图。

QGraphicsView是可滚动的窗口部件,可以提供滚动条来浏览大的场景如果需要使用OPenGL,则可以使用QGraphicsView::setViewport()将视图设置为QGLWidget。

视图接收键盘和鼠标的输入事件,并将它们翻译为场景的坐标,实现场景缩放和旋转。QGraphicsView提供QGraphicsView::mapToScene()和QGraphicsView::

mapFromScene()用于与场景的坐标进行转换。

3、图元类:QGraphicsItem类

The Item QGraphicsItem 是场景中图形items的基类。Graphics View 提供了一些标准的、用于典型形状的items。像矩形(QGraphicsRectItem),椭(QGraphicsEllipseItem),

文本(QGraphicsTextItem),当你写定制的item时,那些最有用的一些QGraphicsItem特性也是有效的。除此这外,QGraphicsItem支持以下特性: *鼠标按、移动、释放、

双击事件,鼠标悬停事件,滚轮事件,弹出菜单事件。 *键盘输入焦点,键盘事件。 *拖拽 *组,包括父子关系,使用QGraphicsItemGroup *碰撞检测 Items如同

QGraphicsView一样,位于本地坐标系,它也为item与场景之间,item与item之间的坐标转换提供许多工具函数。而且,也像QGraphicsView一样,它使用矩阵来变换它

的坐标系统:QGraphicsItem::matrix()。它对旋转与缩放单个的Item比较有用。 Items可以包含别的items(孩子)。父items的转换被它的子孙所继承。然而,它的所有函

数(也就是,QGraphicsItem::contains(),QGraphicsItem::boundingRect(),QGraphicsItem::collidesWith()),不会积累这些转换,依然在本地坐标下工作。 QGraphicsItem

通过QGraphicsItem::shape(),QGraphicsItem::collideWith())来支持碰撞检测。这两个都是虚函数。从shape()返回你的item的形状(以本地坐标QPainterPath表示),

QGraphicsItem会为你处理所有的碰撞检测。假如你想提供自己的碰撞检测,你应该重新实现QGraphicsItem::collideWith()。

7.13 Graphics View的坐标系统

Graphics View基于笛卡尔坐标系。item在场景中的位置与几何形状通过x,y坐标表示。当使用未经变形的视图来观察场景时,场景中的一个单位等于屏幕上的一个像素。

在Graphics View中有三个有效的坐标系统:Item坐标系,场景坐标系,视图坐标系。为了简化你的实现,Graphics View提供了方便的函数,允许三个坐标系之间相互映

射。 当渲染时,Graphics View的场景坐标对应于QPainter的逻辑坐标,视图坐标与设备坐标相同。

1、场景坐标

场景坐标是所有图元的基础坐标系统。场景坐标系统描述了顶层的图元,每个图元都有场景坐标和相应的包容框。场景坐标的原点在场景中心,坐标原点是X轴正方向向

右,Y轴正方向向下。

 QGraphicsScene类的坐标系以中心为原点(0,0)。

2、视图坐标

是图坐标是窗口部件的坐标。视图坐标的单位是像素。QGraphicsView视图的左上角是(0,0),X轴正方向向右,Y轴正方向向下。所有的鼠标事件最开始都是使用视图

坐标。

QGraphicsView类继承自QWidget类,因此它与其他的QWidget类一样,以窗口的左上角作为自己的坐标系的原点。

3、图元坐标

图元使用自己的本地坐标,这个坐标系统通常以图元中心为原点,这也是所有变换的原点。图元坐标方向是X轴正方向向右,Y轴正方向向下。创建图元后,只需注意图

元坐标就可以了,QGraphicsScene和QGraphicsView会完成所有的变换。

QGraphicsItem类的坐标系,若在调用QGraphicsItem类的paint()函数重绘图元时,则以此坐标系为基准。

经常,处理场景中item时,在场景与item之间,item与item之间,视图与场景之间进行坐标映射,形状映射是非常有用的。举例来讲,当你在QGraphicsView的视口中击

鼠标时,你应该通过调用QGraphicsView::mapToScence()与QGraphicsScene::itemAt()来获知光标下是场景中的哪个item。假如你想获知一个item位于视口中的什么位

置,你应该先在item上调用QGraphicsItem::mapToScene(),然后调用QGraphicsView::mapFromScene()。最后,假如你想在一个视图椭圆中有哪些items,你应该把

QPainterPath传递到mapToScene(),然后再把映射后的路径传递到QGraphicsScene::items()。 你可以调用QGraphicsItem::mapToScene()与QGraphicsItem::

mapFromScene()在item与场景之间进行坐标与形状的映射。也可以在item与其父item之间通过QGraphicsItem::mapToParent()与QGraphicsItem::mapFromItem()进行映

射。所有映射函数可以包括点,矩形,多边形,路径。视图与场景之间的映射也与此类似。对于从视图与item之间的映射,你应该首先映射到场景,然后再从场景item

进行映射。

main.cpp

#include

#include "butterfly.h"

#include

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

{

QApplication a(argc, argv);

//创建一个场景指针对象

QGraphicsScene *scence = new QGraphicsScene;

//设置场景区域的大小

scence->setSceneRect (QRectF(-200, -200, 400, 400));

//创建一个蝴蝶图元指针对象

Butterfly *butterfly = new Butterfly;

//设置蝴蝶图元的起始位置

butterfly->setPos (-100, 0);

//添加一个图元到场景中

scence->addItem (butterfly);

//创建一个视图指针对象

QGraphicsView *view = new QGraphicsView;

//将这个场景设置到视图中

view->setScene (scence);

//设置视图的大小

view->resize (800, 400);

view->show ();

return a.exec();

}

butterfly.h

#ifndef BUTTERFLY_H

#define BUTTERFLY_H

#include

#include

#include

#include

#include

/*

* void timerEvent(QimerEvent*): 定时器实现动画的原理是在定时器的timerEvent()中对QGraphicsItem进行重绘

* QRectF boundingRect() const: 为图元限定区域或范围,所有继承自QGraphicsItem的自定义图元都必须实现此函数

* bool up: 用于标志蝴蝶翅膀的位置(位于上或下)以便实现动态效果

*/

class Butterfly : public QObject, public QGraphicsItem

{

Q_OBJECT

public:

explicit Butterfly(QObject *parent = 0);

void timerEvent (QTimerEvent*);

QRectF boundingRect () const;

signals:

public slots:

//重绘函数

protected:

void paint (QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);

private:

bool up;

//用于表示两幅蝴蝶的图片

QPixmap pix_up;

QPixmap pix_down;

qreal angle;

};

#endif // BUTTERFLY_H

butterfly.cpp

#include "butterfly.h"

#include

/*

* up = true 给标志蝴蝶翅膀位置的变量赋初值

* 调用QPixmap的load()函数加载所用到的图片

* 启动定时器,并设置时间间隔为100毫秒

*/

const static double PI=3.1416;

Butterfly::Butterfly(QObject *parent) : QObject(parent)

{

up = true;

pix_up.load ("up.png");

pix_down.load ("down.png");

startTimer (100);

}

/*

* boundingRect()函数为图元限定区域范围,此范围是以图元自身的坐标系为基础设定的。

*

*/

QRectF Butterfly::boundingRect () const

{

qreal adjust = 2;

return QRectF(-pix_up.width () / 2 - adjust, -pix_up.height () / 2 -adjust,

pix_down.width () + adjust * 2, pix_down.height () + adjust * 2);

}

/*

* 首先判断当前已显示的图片是pix_up还是pix_down。实现蝴蝶翅膀上下飞舞效果时,若当前显示的pix_up图片,则重绘制

* pix_down图片,绘制pix_up图片

*/

void Butterfly::paint (QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)

{

if(up)

{

painter->drawPixmap (boundingRect ().topLeft (), pix_up);

up = !up;

}

else

{

painter->drawPixmap (boundingRect ().topLeft (), pix_down);

up = !up;

}

}

/*

* 蝴蝶飞舞的边界控制

* 限定蝴蝶飞舞的右边界

* 限定蝴蝶飞舞的上边界

* 限定蝴蝶飞舞的下边界

*/

void Butterfly::timerEvent (QTimerEvent *)

{

qreal edgex = scene ()->sceneRect ().right () + boundingRect ().width () / 2;

qreal edgetop = scene ()->sceneRect ().top () + boundingRect ().height () / 2;

qreal edgebottom = scene ()->sceneRect ().bottom () + boundingRect ().height () / 2;

//如果超出了右边界,则水平移动左边界处

if(pos().x() >= edgex)

{

setPos (scene ()->sceneRect ().left (), pos().y ());

}

//如果超出了上边界,则垂直移动回下边界处

if(pos().y () <= edgetop)

{

setPos (pos().x (), scene ()->sceneRect ().bottom ());

}

//如果超出了下边界,则垂直移动到上边界处

if(pos().y () >= edgebottom)

{

setPos (pos().x (), scene ()->sceneRect ().top ());

}

//angle(0, 0.45)

angle += (qrand () % 10) / 20.0;

//abs()函数求整数的绝对值,fabs()求浮点数的绝对值

qreal dx = fabs(sin(angle * PI) * 10.0);

//dy(0,9)

qreal dy = (qrand() % 20) - 10.0;

//dx,dy完成蝴蝶随机飞行的路径,且dx,dy是相对于蝴蝶的坐标系而言,因此使用mapToParent()函数映射为场景坐标

setPos(mapToParent (dx, dy));//映射为场景坐标

}

7.2.2 地图浏览器例子

主要介绍Graphics View框架

效果如下:

main.cpp

#include "mapwidget.h"

#include

#include

/*

* 首先设置字体大小

*/

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

{

QApplication a(argc, argv);

QFont font("ARPL KaitiM GB", 12);

font.setBold (true);

a.setFont (font);

MapWidget mapWidget;

mapWidget.show();

return a.exec();

}

mapwidget.h

#ifndef MAPWIDGET_H

#define MAPWIDGET_H

#include

#include

#include

class MapWidget : public QGraphicsView

{

Q_OBJECT

public:

MapWidget();

void readMap(); //读取地图信息

QPointF mapToMap(QPointF); //用于实物场景坐标系与地图坐标之间的映射,以获得某点的经纬度值

public slots:

void slotZoom(int);

protected:

//完成地图显示的功能

void drawBackground (QPainter *painter, const QRectF &rect);

void mouseMoveEvent (QMouseEvent *event);

private:

QPixmap map; //

qreal zoom;

QLabel *viewCoord; //视图坐标

QLabel *sceneCoord; //场景坐标

QLabel *mapCoord; //地图坐标

double x1, y1;

double x2, y2;

};

#endif // MAPWIDGET_H

mapwidget.cpp

#include "mapwidget.h"

#include

#include

#include

#include

#include

/*

* 1、setCacheMode(CacheBackground)这个属性控制view的那一部分缓存中,QGraphicsView可以预存一些内容在QPixmap中,

* 然后被绘制到viewpoint上,这样做的目的是加速整体区域重绘的速度,例如:质地、倾斜度、和最初的混合背景可能重绘很缓慢,

* 尤其是在一个变形的view中,CacheBackground标志使能view的背景缓存,例如

* QGraphicsView view;

* view.setBackgroundBrush(QImage(":/images/backgroundtile.png"));

* view.setCacheMode(QGraphicsView::CacheBackground);

* 每次view转换后cache就无效了,然而,当滚动区域时候,只有部分无效默认的,没有使用cache

* 2、setTickInterval(int)来设置发射信号的间隔,一般都设置为1000ms,就是1s发射一次

* 3、setScaledContents(bool)这个属性保存标签是否按这个图片的比例填满所用的可用空间,默认false

*/

MapWidget::MapWidget()

{

readMap (); //读取地图信息

zoom = 50; //初始比例设置为50%

int width = map.width (); //获取当前地图的宽度

int height = map.height (); //获取当前地图的高度

QGraphicsScene *scene = new QGraphicsScene(this); //定义一个场景指针类

scene->setSceneRect (-width / 2, -height / 2, width, height); //设置显示的场景大小

setScene (scene);

setCacheMode (CacheBackground);

//用于地图缩放的滑动条

QSlider *slider = new QSlider;

slider->setOrientation (Qt::Vertical); //设置垂直显示

slider->setRange (1, 100);

slider->setTickInterval (10);

slider->setValue (50);

connect (slider, SIGNAL(valueChanged(int)), this, SLOT(slotZoom(int)));

QLabel *zoominLabel = new QLabel;

zoominLabel->setScaledContents (true);

zoominLabel->setPixmap (QPixmap("zoomin.png"));

QLabel *zoomoutLabel = new QLabel;

zoomoutLabel->setScaledContents (true);

zoomoutLabel->setPixmap (QPixmap("zoomout.png"));

//坐标显示

QLabel *label1 = new QLabel(tr("GraphicsView:"));

viewCoord = new QLabel;

QLabel *label2 = new QLabel(tr("GraphicsScene:"));

sceneCoord = new QLabel;

QLabel *label3 = new QLabel(tr("map:"));

mapCoord = new QLabel;

//坐标显示区布局

QGridLayout *gridLayout = new QGridLayout;

gridLayout->addWidget (label1, 0, 0);

gridLayout->addWidget (viewCoord, 0, 1);

gridLayout->addWidget (label2, 1, 0);

gridLayout->addWidget (sceneCoord, 1, 1);

gridLayout->addWidget (label3, 2, 0);

gridLayout->addWidget (mapCoord, 2, 1);

QFrame *coorFrame = new QFrame;

coorFrame->setLayout (gridLayout);

//缩放控制子布局(垂直设置布局)

QVBoxLayout *zoomLayout = new QVBoxLayout;

zoomLayout->addWidget (zoominLabel);

zoomLayout->addWidget (slider);

zoomLayout->addWidget (zoomoutLabel);

//坐标显示区域布局(垂直设置布局)

QVBoxLayout *coordLayout = new QVBoxLayout;

coordLayout->addWidget (coorFrame);

coordLayout->addStretch ();

//主布局(水平设置布局)

QHBoxLayout *mainLayout = new QHBoxLayout(this);

mainLayout->addLayout (zoomLayout);

mainLayout->addLayout (coordLayout);

mainLayout->addStretch ();

mainLayout->setMargin (15);

mainLayout->setSpacing (15);

setWindowTitle (tr("Map Widget"));

setMinimumSize (600, 400);

}

/*

* 读取地图信息

* 以只读方式打开文件

* 读取文件名字(mapName),x1、x2、y1、y2

* 加载图片

*/

void MapWidget::readMap ()

{

QString mapName;

QFile mapFile("maps.txt");

int ok = mapFile.open (QIODevice::ReadOnly);

if(ok)

{

QTextStream ts(&mapFile);

if(!ts.atEnd ())

{

ts >> mapName;

ts >> x1 >> x2 >> y1 >> y2;

}

}

map.load (mapName);

}

/*

* 从场景坐标到地图坐标的装换

*/

QPointF MapWidget::mapToMap (QPointF p)

{

QPointF latLon;

qreal w = sceneRect ().width ();

qreal h = sceneRect ().height ();

qreal lon = y1 - ((h / 2 + p.y ()) * abs(y1 - y2) / h);

qreal lat = x1 + ((w / 2 + p.x ()) * abs(x1 - x2) / w);

latLon.setX (lat);

latLon.setY (lon);

return latLon;

}

/*

* 检测value(slider改变得到的值),与当前value值得大小比较

* pow(x, y)表示x的y次方

* slider改变的值大于zoom值时,增加缩放比例

* slider改变的值小于zoom值时,减小缩放比例

* scale(s, s)将当前的视图装换为(s, s)

*/

void MapWidget::slotZoom (int value)

{

qreal s;

if(value > zoom)

{

s = pow(1.01, (value - zoom));

}

else

{

s = pow(1 / 1.01, (zoom - value));

}

scale(s, s);

zoom = value;

}

/*

* 绘制背景色

* 从左上方开始绘制

*/

void MapWidget::drawBackground (QPainter *painter, const QRectF &rect)

{

painter->drawPixmap (int(sceneRect ().left ()), int(sceneRect ().top ()), map);

}

/*

* 当鼠标按下移动时获取鼠标当前的位置信息(分别为x,y方向的值)

*/

void MapWidget::mouseMoveEvent (QMouseEvent *event)

{

//QGraphicsView坐标

QPoint viewPoint = event->pos ();

viewCoord->setText (QString::number (viewPoint.x ()) + " , " + QString::number (viewPoint.y ()));

//QGraphicsScene坐标

QPointF scenePoint = mapToScene (viewPoint);

sceneCoord->setText (QString::number (scenePoint.x ()) + " , " + QString::number (scenePoint.y ()));

//地图坐标经度纬度值

QPointF latLon = mapToMap (scenePoint);

mapCoord->setText (QString::number (latLon.x ()) + " , " + QString::number (latLon.y ()));

}

7.2.3 创建图元

通过实现一个在其中显示各种类型QGraphicsItem的窗口例子,介绍如何使用Qt预定义的各种标准的QGraphicsItem类型(如QGraphicsEllipseItem、

QGraphicsRectItem等),以及自定义的QGraphicsItem类型(如本例中不停闪烁的圆及来回移动的星星等)来创建图元。

效果如下

mainwindow.h

#ifndef MAINWINDOW_H

#define MAINWINDOW_H

#include

#include

#include

#include

#include

#include

#include

#include

class MainWindow : public QMainWindow

{

Q_OBJECT

public:

MainWindow(QWidget *parent = 0);

~MainWindow();

void initScene(); //初始化场景

void createActions(); //创建主窗体的所有动作

void createMenus(); //创建主窗体的菜单栏

public slots:

void slotNew(); //新建一个窗体

void slotClear(); //清除场景中的所有图元

void slotAddEllipseItem(); //在场景中加入一个椭圆形图元

void slotAddPolygonItem(); //在场景中加入一个多变形图元

void slotAddTextItem(); //在场景中加入一个文字图元

void slotAddRectItem(); //在场景中加入一个长方形图元

void slotAddAlphaItem(); //在场景中加入一个透明蝴蝶图元

void slotAddFlashItem(); //在场景中加入一个闪烁的图元

void slotAddAnimationItem();//在场景中加入一个动画图元

private:

QGraphicsScene *scene; //定义一个场景对象指针

QAction *newAct; //定义一个新建窗体动作指针

QAction *clearAct; //定义一个清除场景图元动作指针

QAction *exitAct; //定义一个退出窗体动作指针

QAction *addEllipseItemAct; //定义一个椭圆动作指针

QAction *addPolygonItemAct; //定义一个多边形动作指针

QAction *addTextItemAct; //定义一个文字动作指针

QAction *addRectItemAct; //定义一个长方形动作指针

QAction *addAlphaItemAct; //定义一个透明蝴蝶动作指针

QAction *addFlashItemAct; //定义一个闪烁的动作指针

QAction *addAnimationItemAct;//定义一个动画动作指针

};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"

#include "flashitem.h"

#include "startitem.h"

MainWindow::MainWindow(QWidget *parent)

: QMainWindow(parent)

{

createActions (); //创建主窗体的所有动作

createMenus (); //创建主窗体的所有菜单

scene = new QGraphicsScene;

scene->setSceneRect (-200, -200, 400, 400);

initScene (); //初始化场景

QGraphicsView *view = new QGraphicsView;

view->setScene (scene);

view->setMinimumSize (400, 400);

view->show ();

setCentralWidget (view); //将视图设置在主窗口的中央

resize (600, 500);

setWindowTitle ("Graphics Items");

}

MainWindow::~MainWindow()

{

}

void MainWindow::initScene ()

{

int i;

for(i = 0; i < 3; i++)

{

slotAddEllipseItem ();

}

for(i = 0; i < 3; i++)

{

slotAddPolygonItem ();

}

for(i = 0; i < 3; i++)

{

slotAddTextItem ();

}

for(i = 0; i < 3; i++)

{

slotAddAlphaItem ();

}

for(i = 0; i < 3; i++)

{

slotAddRectItem ();

}

for(i = 0; i < 3; i++)

{

slotAddFlashItem ();

}

for(i = 0; i < 3; i++)

{

slotAddAnimationItem ();

}

}

void MainWindow::createActions ()

{

newAct = new QAction(tr("新建"), this);

clearAct = new QAction(tr("清除"), this);

exitAct = new QAction(tr("退出"), this);

addEllipseItemAct = new QAction(tr("加入 椭圆"), this);

addPolygonItemAct = new QAction(tr("加入 多边形"), this);

addTextItemAct = new QAction(tr("加入 文字"), this);

addRectItemAct = new QAction(tr("加入 长方形"), this);

addAlphaItemAct = new QAction(tr("加入 透明蝴蝶"), this);

addFlashItemAct = new QAction(tr("加入 一个闪烁圆"), this);

addAnimationItemAct = new QAction(tr("加入 星星"), this);

connect (newAct, SIGNAL(triggered(bool)), this, SLOT(slotNew()));

connect (clearAct, SIGNAL(triggered(bool)), this, SLOT(slotClear()));

connect (exitAct, SIGNAL(triggered(bool)), this, SLOT(close()));

connect (addEllipseItemAct, SIGNAL(triggered(bool)), this, SLOT(slotAddEllipseItem()));

connect (addPolygonItemAct, SIGNAL(triggered(bool)), this, SLOT(slotAddPolygonItem()));

connect (addTextItemAct, SIGNAL(triggered(bool)), this, SLOT(slotAddRectItem()));

connect (addRectItemAct, SIGNAL(triggered(bool)), this, SLOT(slotAddRectItem()));

connect (addAlphaItemAct, SIGNAL(triggered(bool)), this, SLOT(slotAddAlphaItem()));

connect (addFlashItemAct, SIGNAL(triggered(bool)), this, SLOT(slotAddFlashItem()));

connect (addAnimationItemAct, SIGNAL(triggered(bool)), this, SLOT(slotAddAnimationItem()));

}

/*

* 创建主窗体的菜单栏

*/

void MainWindow::createMenus ()

{

QMenu *fileMenu = menuBar ()->addMenu (tr("文件"));

fileMenu->addAction(newAct);

fileMenu->addAction(clearAct);

fileMenu->addAction(exitAct);

fileMenu->addSeparator (); //添加一个分隔符

QMenu *itemsMenu = menuBar ()->addMenu (tr("元素"));

itemsMenu->addAction(addEllipseItemAct);

itemsMenu->addAction(addPolygonItemAct);

itemsMenu->addAction(addTextItemAct);

itemsMenu->addAction(addRectItemAct);

itemsMenu->addAction(addAlphaItemAct);

itemsMenu->addAction(addFlashItemAct);

itemsMenu->addAction(addAnimationItemAct);

}

/*

* 创建一个新的窗口场景

*/

void MainWindow::slotNew ()

{

slotClear();

initScene ();

MainWindow *newWindow = new MainWindow;

newWindow->show ();

}

/*

* 清空场景中的图元

*/

void MainWindow::slotClear ()

{

QList listItem = scene->items (); //获取当前场景中的图元

while(!listItem.isEmpty ())

{

scene->removeItem (listItem.at (0)); //从场景列表中移除当前图元

listItem.removeAt (0); //从listItem链表中移除当前图元

}

}

/*

* 在场景中添加一个椭圆形

*/

void MainWindow::slotAddEllipseItem ()

{

QGraphicsEllipseItem *item = new QGraphicsEllipseItem(QRectF(0, 0, 80, 80));

item->setPen(Qt::NoPen);

item->setBrush(QColor(qrand() % 256, qrand() % 256, qrand() % 256));

item->setFlag (QGraphicsItem::ItemIsMovable);

scene->addItem (item);

item->setPos(qrand() % ((int)scene->sceneRect ().width ()) - 200,

qrand() % ((int)scene->sceneRect ().height ()) - 200);

}

/*

* 在场景中添加一个多边形

*/

void MainWindow::slotAddPolygonItem ()

{

QVector<QPoint> v;

v << QPoint(30, -15) << QPoint(0, -30) << QPoint(-30, -15) << QPoint(-30, 15) << QPoint(0, 30) << QPoint(30, 15);

QGraphicsPolygonItem *item = new QGraphicsPolygonItem (QPolygonF(v));

item->setPen (Qt::NoPen);

item->setBrush (QColor(qrand () % 256, qrand() % 256, qrand() % 256));

item->setFlag (QGraphicsItem::ItemIsMovable);

scene->addItem (item);

item->setPos (qrand() % ((int)scene->sceneRect ().width ()) - 200,

qrand() % ((int)scene->sceneRect ().height ()) - 200);

}

/*

* 在场景中添加一个字体

*/

void MainWindow::slotAddTextItem ()

{

QFont font("Times", 20);

QGraphicsTextItem *item = new QGraphicsTextItem("Hello Qt!");

item->setFont (font);

item->setFlag (QGraphicsItem::ItemIsMovable);

item->setDefaultTextColor(QColor(qrand() % 256, qrand () % 256, qrand() % 256));

scene->addItem (item);

item->setPos (qrand() % ((int)scene->sceneRect ().width ()) - 200, qrand() % ((int)scene->height ()) - 200);

}

/*

* 在场景中添加一个长方形

*/

void MainWindow::slotAddRectItem ()

{

QGraphicsRectItem *item = new QGraphicsRectItem(QRectF(0, 0, 60, 60));

QPen pen; //定义一个画笔

pen.setWidth (3); //设置画笔的宽度

pen.setColor (QColor(qrand() % 256, qrand() % 256, qrand() % 256));

item->setPen (pen);

item->setBrush (QColor(qrand() % 256, qrand() % 256, qrand() % 256));

item->setFlag (QGraphicsItem::ItemIsMovable);

scene->addItem (item);

item->setPos (qrand () % ((int)scene->sceneRect ().width ()) - 200,

qrand () % ((int)scene->sceneRect ().height ()) - 200);

}

/*

* 添加一个图片

*/

void MainWindow::slotAddAlphaItem ()

{

QGraphicsPixmapItem *item = scene->addPixmap (QPixmap("image.png"));

item->setFlag (QGraphicsItem::ItemIsMovable);

item->setPos (qrand () % ((int)scene->sceneRect ().width ()) - 200,

qrand () % ((int)scene->sceneRect ().height ()) - 200);

}

/*

* 添加一个闪烁的椭圆

*/

void MainWindow::slotAddFlashItem ()

{

FlashItem *item = new FlashItem;

scene->addItem (item);

item->setPos (qrand () % ((int)scene->sceneRect ().width ()) - 200,

qrand () % ((int)scene->sceneRect ().height ()) - 200);

}

/*

* QTimeLine类为控制动画提供一个时间表

* setCurveShape() 设置曲线滑动的情况

* setLoopCount(int) 这个属性描述动画的循环计数,

* 默认情况下是1:表明动画应该只运行一次然后停止。

* 当int为0时动画将不会运行

* 当int为-1时动画一直循环直到停止

* QPointF(x, y)中的x(0, 200),y(0, 200)

*/

void MainWindow::slotAddAnimationItem ()

{

StartItem *item = new StartItem;

QGraphicsItemAnimation *anim = new QGraphicsItemAnimation;

anim->setItem (item);

QTimeLine *timeLine = new QTimeLine(4000);

timeLine->setCurveShape (QTimeLine::SineCurve);

timeLine->setLoopCount (0);

anim->setTimeLine (timeLine);

int y = (qrand() % 400) - 200;

for(int i = 0; i < 400; i++)

{

anim->setPosAt (i / 400.0, QPointF(i - 200, y));

}

timeLine->start ();

scene->addItem (item);

}

flashitem.h

#ifndef FLASHITEM_H

#define FLASHITEM_H

#include

#include

#include

class FlashItem : public QObject, public QGraphicsItem

{

Q_OBJECT

public:

explicit FlashItem(QObject *parent = 0);

QRectF boundingRect() const; //定义图元边界函数

void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);

void timerEvent (QTimerEvent *);

signals:

public slots:

private:

bool flash; //定义闪烁标志

QTimer *timer; //定义定时器指针对象

};

#endif // FLASHITEM_H

flashitem.cpp

#include "flashitem.h"

FlashItem::FlashItem(QObject *parent) : QObject(parent)

{

flash = true; //为颜色切换标识符赋初值

setFlag (ItemIsMovable); //设置图元属性,设置为鼠标可移动的属性

startTimer (1000); //启动一个定时器,以1s内为时间间隔

}

void FlashItem::paint (QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)

{

painter->setPen (Qt::NoPen); //设置闪烁的阴影区不绘制边界

painter->setBrush (Qt::darkGray); //设置闪烁的阴影区的画刷为深度

painter->drawEllipse (-7, -7, 40, 40);

painter->setPen (QPen(Qt::black, 0)); //闪烁区的椭圆边线颜色为黑色,线宽为0

painter->setBrush (flash ? (Qt::yellow) : (Qt::red));

painter->drawEllipse (-10, -10, 40, 40);//绘制和阴影区同样大小的椭圆,并且错开一定的距离实现立体的效果

}

/*

* 定以图元的边界函数boundingRect(),所有自定义图元均必须实现的函数

*/

QRectF FlashItem::boundingRect () const

{

qreal adjust = 2;

return QRectF(-10 - adjust, -10 - adjust, 43 + adjust, 43 + adjust);

}

void FlashItem::timerEvent (QTimerEvent *e)

{

flash = !flash;

update ();

}

startitem.h

#ifndef STRATITEM_H

#define STRATITEM_H

#include

#include

class StartItem : public QGraphicsItem

{

public:

StartItem();

QRectF boundingRect () const;

void paint (QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);

private:

QPixmap pix;

};

#endif // STRATITEM_H

startitem.cpp

#include "startitem.h"

StartItem::StartItem()

{

pix.load ("star.png");

}

QRectF StartItem::boundingRect () const

{

return QRectF(-pix.width () / 2, -pix.height () / 2, pix.width (), pix.height ());

}

void StartItem::paint (QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)

{

painter->drawPixmap (boundingRect ().topLeft (), pix);

}

7.2.4 图元的旋转、缩放、切变和位移

通过此实例介绍如何实现图元(QGraphicsItem)的旋转、缩放、切变及位移等各种变形操作

mainwidget.h

#ifndef MAINWIDGET_H

#define MAINWIDGET_H

#include

#include

#include

#include

#include "pixitem.h"

class MainWidget : public QWidget

{

Q_OBJECT

public:

MainWidget(QWidget *parent = 0);

~MainWidget();

void createControlFrame();

private:

int angle;

qreal scaleValue;

qreal shearValue;

qreal translateValue;

QGraphicsView *view;

QFrame *ctrlFrame;

PixItem *pixItem;

public slots:

void slotRotate(int);

void slotScale(int);

void slotShear(int);

void slotTranslate(int);

};

#endif // MAINWIDGET_H

mainwidget.cpp

#include "mainwidget.h"

#include

#include

#include

#include

/*

* setLayout()设置用户界面上的屏幕组件的格式布局

*/

MainWidget::MainWidget(QWidget *parent)

: QWidget(parent)

{

angle = 0;

scaleValue = 5;

shearValue = 5;

translateValue = 50;

QGraphicsScene *scene = new QGraphicsScene;

scene->setSceneRect (-200, -200, 400, 400); //限定新建QGraphicsScene对象的显示区域

QPixmap *pixmap = new QPixmap("image.png");

pixItem = new PixItem(pixmap);

scene->addItem (pixItem);

pixItem->setPos (0, 0);

view = new QGraphicsView; //新建一个试图对象

view->setScene (scene); //将试图对象与场景相连

view->setMinimumSize (400, 400); //设置视图的最小尺寸为(400,400)

ctrlFrame = new QFrame;

createControlFrame (); //新建主窗体右侧的控制面板区

//主窗口

QHBoxLayout *mainLayout = new QHBoxLayout;

mainLayout->setMargin (10);

mainLayout->setSpacing (20);

mainLayout->addWidget (view);

mainLayout->addWidget (ctrlFrame);

setLayout (mainLayout);

setWindowTitle ("Craphics Item TranSformation");

}

MainWidget::~MainWidget()

{

}

/*

* QGroupBox为一组箱型框架提供了一个标签

*/

void MainWidget::createControlFrame ()

{

//旋转控制

QSlider *rotateslider = new QSlider;

rotateslider->setOrientation (Qt::Horizontal); //设置rotateslider水平显示

rotateslider->setRange (0, 360); //设置旋转的角度(0, 360)

QHBoxLayout *rotateLayout = new QHBoxLayout;

rotateLayout->addWidget (rotateslider);

QGroupBox *rotateGroup = new QGroupBox(tr("Rotate"));

rotateGroup->setLayout (rotateLayout);

//缩放控制

QSlider *scaleslider = new QSlider;

scaleslider->setOrientation (Qt::Horizontal); //设置scaleslider水平显示

scaleslider->setRange (0, 2 * scaleValue * 2); //设置放大的倍数(0,10)

QHBoxLayout *scaleLayout = new QHBoxLayout;

scaleLayout->addWidget (scaleslider);

QGroupBox *scaleGroup = new QGroupBox(tr("Scale"));

scaleGroup->setLayout (scaleLayout);

//切边控制

QSlider *shearslider = new QSlider;

shearslider->setOrientation (Qt::Horizontal);

shearslider->setRange (0, 2 * shearValue);

QHBoxLayout *shearLayout = new QHBoxLayout;

shearLayout->addWidget (shearslider);

QGroupBox *shearGroup = new QGroupBox(tr("Shear"));

shearGroup->setLayout (shearLayout);

//位移控制

QSlider *translateslider = new QSlider;

translateslider->setOrientation (Qt::Horizontal);

translateslider->setRange (0, 2 * translateValue);

QHBoxLayout *translateLayout = new QHBoxLayout;

translateLayout->addWidget (translateslider);

QGroupBox *translateGroup = new QGroupBox(tr("Translate"));

translateGroup->setLayout (translateLayout);

connect (rotateslider, SIGNAL(valueChanged(int)), this, SLOT(slotRotate(int)));

connect (scaleslider, SIGNAL(valueChanged(int)), this, SLOT(slotScale(int)));

connect (shearslider, SIGNAL(valueChanged(int)), this, SLOT(slotShear(int)));

connect (translateslider, SIGNAL(valueChanged(int)), this, SLOT(slotTranslate(int)));

//控制面板布局

QVBoxLayout *frameLayout = new QVBoxLayout;

frameLayout->setMargin (10);

frameLayout->setSpacing (20);

frameLayout->addWidget (rotateGroup);

frameLayout->addWidget (scaleGroup);

frameLayout->addWidget (shearGroup);

frameLayout->addWidget (translateGroup);

ctrlFrame->setLayout (frameLayout);

}

/*

* value是传入的rotateslider->value ()的值(0,360)

*/

void MainWidget::slotRotate (int value)

{

view->rotate (value - angle);

angle = value;

}

/*

* value是传入的scaleslider->value()的值(0, 10)

*/

void MainWidget::slotScale (int value)

{

qreal s;

if(value > scaleValue)

{

s = pow(1.1, (value - scaleValue));

}

else

{

s = pow(1 / 1.1, (scaleValue - value));

}

view->scale (s, s);

scaleValue = value;

}

/*

* value是传入的shearslider->value()的值(0, 10)

*/

void MainWidget::slotShear (int value)

{

view->shear ((value - shearValue) / 10.0, 0);

shearValue = value;

}

void MainWidget::slotTranslate (int value)

{

view->translate (value - translateValue, value - translateValue);

translateValue = value;

}

pixitem.h

#ifndef PIXITEM_H

#define PIXITEM_H

#include

#include

#include

class PixItem : public QGraphicsItem

{

public:

PixItem(QPixmap *pixmap);

//定义图元边界函数

QRectF boundingRect() const;

void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);

private:

QPixmap pix; //作为图元显示的图片

};

#endif // PIXITEM_H

pixitem.cpp

#include "pixitem.h"

PixItem::PixItem(QPixmap *pixmap)

{

pix = *pixmap;

}

/*

* 图元边界函数

*/

QRectF PixItem::boundingRect() const

{

return QRectF(-2 - pix.width() / 2, -2 - pix.height() / 2,

pix.width() + 4, pix.height() + 4);

}

void PixItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)

{

painter->drawPixmap(-pix.width() / 2, -pix.height() / 2, pix);

}