• Qt5开发及实例V2.0-第七章-Qt图形视图框架


    第7章 Qt 5图形视图框架

    7.1 图形视图体系结构

    7.1.1 Graphics View的特点

    Graphics View框架结构的主要特点如下。
    (1)Graphics View框架结构中,系统可以利用Qt绘图系统的反锯齿、OpenGL工具来改善绘图性能。
    (2)Graphics View支持事件传播体系结构,可以使图元在场景(scene)中的交互能力提高1倍,图元能够处理键盘事件和鼠标事件。其中,鼠标事件包括鼠标按下、移动、释放和双击,还可以跟踪鼠标的移动。
    (3)在Graphics View框架中,通过二元空间划分树(Binary Space Partitioning,BSP)提供快速的图元查找,这样就能够实时地显示包含上百万个图元的大场景。

    7.1.2 Graphics View的三元素

    它们三者之间的关系如图7.1所示。
    在这里插入图片描述

    1.场景类:QGraphicsScene类
    场景类主要完成的工作包括提供对它包含的图元的操作接口和传递事件、管理各个图元的状态(如选择和焦点处理)、提供无变换的绘制功能(如打印)等。
    事件传播体系结构将场景事件发送给图元,同时也管理图元之间的事件传播。如果场景接收到了在某一点的鼠标单击事件,场景会将事件传给这一点的图元。
    管理各个图元的状态(如选择和焦点处理)。可以通过QGraphicsScene:: setSelectionArea()函数选择图元,选择区域可以是任意的形状,使用QPainterPath表示。若要得到当前选择的图元列表,则可以使用函数QGraphicsScene:: selectedItems()。可以通过QGraphicsScene:: setFocusItem()函数或QGraphicsScene:: setFocus()函数来设置图元的焦点,获得当前具有焦点的图元使用函数QGraphicsScene::focusItem()。

    2.视图类:QGraphicsView类
    QGraphicsView是可滚动的窗口部件,可以提供滚动条来浏览大的场景。如果需要使用OpenGL,则可以使用QGraphicsView::setViewport()将视图设置为QGLWidget。
    视图接收键盘和鼠标的输入事件,并将它们翻译为场景事件(将坐标转换为场景的坐标)。使用变换矩阵函数QGraphicsView::matrix()可以变换场景的坐标,实现场景缩放和旋转。QGraphicsView提供QGraphicsView::mapToScene()和QGraphicsView:: mapFromScene()用于与场景的坐标进行转换。

    3.图元类:QGraphicsItem类
    它是场景中各个图元的基类,在它的基础上可以继承出各种图元类,Qt已经预置的包括直线(QGraphicsLineItem)、椭圆(QGraphicsEllipseItem)、文本图元(QGraphicsTextItem)、矩形(QGraphicsRectItem)等。
    QGraphicsItem主要有以下功能。
     处理鼠标按下、移动、释放、双击、悬停、滚轮和右键菜单事件。
     处理键盘输入事件。
     处理拖曳事件。
     分组。
     碰撞检测。

    7.1.3 GraphicsView的坐标系统

    1.场景坐标
    场景坐标是所有图元的基础坐标系统。场景坐标系统描述了顶层的图元,每个图元都有场景坐标和相应的包容框。场景坐标的原点在场景中心,坐标原点是x轴正方向向右,y轴正方向向下。
    QGraphicsScene类的坐标系以中心为原点(0,0),如图7.2所示。
    在这里插入图片描述

    2.视图坐标
    视图坐标是窗口部件的坐标。视图坐标的单位是像素。QGraphicsView视图的左上角是(0,0),x轴正方向向右,y轴正方向向下。所有的鼠标事件最开始都是使用视图坐标。
    QGraphicsView类继承自QWidget类,因此它与其他的QWidget类一样,以窗口的左上角作为自己坐标系的原点,如图7.3所示。
    在这里插入图片描述

    3.图元坐标
    图元使用自己的本地坐标,这个坐标系统通常以图元中心为原点,这也是所有变换的原点。图元坐标方向是x轴正方向向右,y轴正方向向下。创建图元后,只需注意图元坐标就可以了,QGraphicsScene和QGraphicsView会完成所有的变换。
    QGraphicsItem类的坐标系,若在调用QGraphicsItem类的paint()函数重绘图元时,则以此坐标系为基准,如图7.4所示。
    在这里插入图片描述
    Graphics View框架提供了多种坐标变换函数,见表7.1。
    在这里插入图片描述

    7.2 【实例】:图形视图

    7.2.1 飞舞的蝴蝶

    【例】(难度中等)(CH701)设计界面,一个蝴蝶在屏幕上不停地上下飞舞。
    操作步骤如下。
    (1)新建Qt Widgets Application(详见1.3.1节),项目名为“Butterfly”,基类选择“QMainWindow”,类名命名默认为“MainWindow”,取消“创建界面”复选框的选中状态。单击“下一步”按钮,最后单击“完成”按钮,完成该项目工程的建立。
    (2)在“Butterfly”项目名上单击鼠标右键,在弹出的快捷菜单中选择“添加新文件…”菜单项,在弹出的对话框中选择“C++ Class”选项。单击“Choose…”按钮,弹出对话框,在“Base class”后面的下拉列表框中选择基类名“QObject”,在“Class name”后面的文本框中输入类的名称“Butterfly”。
    (3)单击“下一步”按钮,单击“完成”按钮,添加文件“butterfly.h”和“butterfly. cpp”。

    (4)Butterfly类继承自QObject类、QGraphicsItem类,在头文件“butterfly.h”中完成的代码具体内容。
    (5)在源文件“butterfly. cpp”中完成的代码具体内容如下:

    #include "butterfly.h"
    #include 
    const static double PI=3.1416;
    Butterfly::Butterfly(QObject *parent) : QObject(parent)
    {
        up = true;			//给标志蝴蝶翅膀位置的变量赋初值
        pix_up.load("up.png");		//调用QPixmap的load()函数加载所用到的图片
        pix_down.load("down.png");
        startTimer(100);		//启动定时器,并设置时间间隔为100毫秒
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    boundingRect()函数为图元限定区域范围。此范围是以图元自身的坐标系为基础设定的。具体实现代码内容如下:

    QRectF Butterfly::boundingRect() const
    {
        qreal adjust =2;
        return QRectF(-pix_up.width()/2-adjust,-pix_up.height()/2-adjust,
                      pix_up.width()+adjust*2,pix_up.height()+adjust*2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在重画函数paint()中,首先判断当前已显示的图片是pix_up还是pix_down。实现蝴蝶翅膀上下飞舞效果时,若当前显示的是pix_up图片,则重绘pix_down图片,反之亦然。具体实现代码内容如下:

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    定时器的timerEvent()函数实现蝴蝶的飞舞,具体实现代码内容如下:

    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+=(qrand()%10)/20.0;
        qreal dx=fabs(sin(angle*PI)*10.0);
        qreal dy=(qrand()%20)-10.0;
        setPos(mapToParent(dx,dy));		//(a)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    (6)完成了蝴蝶图元的实现后,在源文件“main.cpp”中将它加载到场景中,并关联一个视图,具体实现代码内容如下:

    #include 
    #include "butterfly.h"
    #include 
    int main(int argc,char* argv[])
    {
        QApplication a(argc,argv);
        QGraphicsScene *scene = new QGraphicsScene;
        scene->setSceneRect(QRectF(-200,-200,400,400));
        Butterfly *butterfly = new Butterfly;
        butterfly->setPos(-100,0);
        scene->addItem(butterfly);
        QGraphicsView *view = new QGraphicsView;
        view->setScene(scene);
        view->resize(400,400);
        view->show();
        return a.exec();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    (7)运行程序,将程序中用到的图片保存到该工程的D:\Qt\CH7\CH701\build-Butterfly-Desktop_Qt_5_8_0_MinGW_32bit-Debug文件夹中,运行结果如图7.5所示。
    在这里插入图片描述

    7.2.2 地图浏览器

    【例】(难度中等)(CH702)设计一个地图浏览器,包括地图的浏览、放大、缩小,以及显示各点的坐标等,如图7.6所示。
    在这里插入图片描述

    操作步骤如下。
    (1)新建Qt Widgets Application (详见1.3.1节),项目名称为“MapWidget”,基类选择“QMainWindow”,类名命名默认为“MainWindow”,取消“创建界面”复选框的选中状态。单击“下一步”按钮,最后单击“完成”按钮,完成该项目工程的建立。
    (2)在“MapWidget”项目名上单击鼠标右键,在弹出的快捷菜单中选择“添加新文件…”菜单项,在弹出的对话框中选择“C++ Class”选项。单击“Choose…”按钮,弹出对话框,在“Base class”后面的文本框中输入基类名“QGraphicsView”(手工添加),在“Class name”后面的文本框中输入类的名称“MapWidget”。
    (3)单击“下一步”按钮,单击“完成”按钮,添加文件“mapwidget.h”和文件“mapwidget.cpp”。
    (4)MapWidget类继承自QGraphicsView类,作为地图浏览器的主窗体。在头文件“mapwidget.h”中完成的代码。
    (5)在源文件“mapwidget.cpp”中完成的代码。
    (6)新建一个文本文件“maps.txt”,利用该文本文件描述与地图相关的信息,将该文件保存在该工程下的D:\Qt\CH7\CH702\ build-MapWidget-Desktop_Qt_5_ 4_0_ MinGW_32bit-Debug文件中,文件内容为:
    China.jpg 114.4665527 35.96022297 119.9597168 31.3911575
    (7)打开“mapwidget.cpp”文件,添加读取地图信息readMap()函数的具体实现代码如下:

    void MapWidget::readMap()            			//读取地图信息
    {
        QString mapName;
        QFile mapFile("maps.txt");			//(a)
        int ok = mapFile.open(QIODevice::ReadOnly);//以“只读”方式打开此文件
        if(ok)								//分别读取地图的名称和四个经纬度信息
        {
            QTextStream ts(&mapFile);
            if(!ts.atEnd())
            {
                ts>>mapName;
                ts>>x1>>y1>>x2>>y2;
            }
        }
        map.load(mapName);							//将地图读取至私有变量map中
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    根据缩放滑动条的当前值,确定缩放的比例,调用scale()函数实现地图缩放。完成地图缩放功能的slotZoom()函数的具体实现代码内容如下:

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    QGraphicsView类的drawBackground()函数中以地图图片重绘场景的背景来实现地图显示。具体实现代码如下:

    void MapWidget::drawBackground(QPainter *painter, const QRectF &rect)
    {
        painter->drawPixmap(int(sceneRect().left()),int(sceneRect(). top()), map);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    响应鼠标移动事件mouseMoveEvent()函数,完成某点在各层坐标中的映射及显示。具体实现代码如下:

    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()));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    完成从场景坐标至地图坐标的转换mapToMap()函数。具体实现代码如下:

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    (8)下面是文件“main.cpp”的具体代码:

    #include 
    #include "mapwidget.h"
    #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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    (9)将程序用到的图片保存到该工程的D:\Qt\CH7\CH702\build-MapWidget-Desktop_Qt_5_8_0_MinGW_32bit-Debug文件夹中,运行结果如图7.6所示。

    7.2.3 图元创建

    【例】(难度中等)(CH703)设计窗体,显示各种类型QGraphicsItem(包括不停闪烁的圆及来回移动的星星等),如图7.7所示。
    在这里插入图片描述

    实现步骤如下。
    (1)新建Qt Widgets Application(详见1.3.1节),项目名称为“GraphicsItem”,基类选择“QMainWindow”,类名命名默认为“MainWindow”,取消“创建界面”复选框的选中状态。单击“下一步”按钮,最后单击“完成”按钮,完成该项目工程的建立。
    (2)MainWindow类继承自QMainWindow作为主窗体,包含一个加入图元的各种操作的菜单栏,以及一个显示各种类型图元的QGraphicsView作为主窗体的centralWidget。“mainwindow.h”文件的具体代码实现内容。
    (3)“mainwindow.cpp”文件中的代码。
    (4)将程序中所用图片保存到该工程的D:\Qt\CH7\CH703\build-GraphicsItem-Desktop_Qt_5_8_0_MinGW_32bit-Debug文件夹下,此时运行效果如图7.8所示。
    在这里插入图片描述
    以上完成了主窗体的显示工作,下面介绍如何实现圆的闪烁功能。
    (1)在“GraphicsItem”项目名上单击鼠标右键,在弹出的快捷菜单中选择“添加新文件…”菜单项,在弹出的对话框中选择“C++ Class”选项。单击“Choose…”按钮,弹出对话框,在“Base class”后面的下拉列表框中选择基类名“QObject”,在“Class name”后面的文本框中输入类的名称“FlashItem”。
    (2)单击“下一步”按钮,单击“完成”按钮,添加文件“flashitem.h”和文件“flashitem.cpp”。

    (3)“flashitem.h”文件的具体代码如下:

    #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 *);
    private:
        bool flash;
        QTimer *timer;
    signals:
    public slots:
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    (4)“flashitem.cpp”文件的具体代码如下:

    #include "flashitem.h"
    FlashItem::FlashItem(QObject *parent) :  QObject(parent)
    {
        flash=true;			//为颜色切换标识赋初值
        setFlag(ItemIsMovable);	//(a)
        startTimer(1000);		//启动一个定时器,以1000毫秒为时间间隔
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    定义图元边界的函数boundingRect(),完成以图元坐标系为基础,增加两个像素点的冗余工作。具体实现代码如下:

    QRectF FlashItem::boundingRect() const
    {
        qreal adjust = 2;
        return QRectF(-10-adjust,-10-adjust,43+adjust,43+adjust);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    为自定义图元重绘的函数paint()具体实现代码如下:

    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::red):(Qt::yellow)); //(a)
        painter->drawEllipse(-10,-10,40,40);	      //(b)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    定时器响应函数timerEvent()完成颜色切换标识的反置,并在每次反置后调用update()函数重绘图元以实现闪烁的效果。具体实现代码如下:

    void FlashItem::timerEvent(QTimerEvent *)
    {
        flash=!flash;
        update();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (5)在“mainwindow.h”文件中添加代码如下:

    public slots:    
        void slotAddFlashItem();
    private:
        QAction *addFlashItemAct;
    
    • 1
    • 2
    • 3
    • 4

    (6)在“mainwindow.cpp”文件中添加代码如下:

    #include "flashitem.h"
    
    • 1

    其中,在createActions()函数中添加代码如下:

    addFlashItemAct = new QAction(tr("加入闪烁圆"),this);
    connect(addFlashItemAct,SIGNAL(triggered()),this,SLOT(slotAddFlashItem()));
    
    • 1
    • 2

    在createMenus()函数中添加代码如下:

    itemsMenu->addAction(addFlashItemAct);
    
    • 1

    在initScene()函数中添加代码如下:

    for(i=0;i<3;i++)      
            slotAddFlashItem();
    
    
    • 1
    • 2
    • 3

    函数slotAddFlashItem()具体实现代码如下:

    void MainWindow::slotAddFlashItem()  	//在场景中加入一个闪烁图元
    {
        FlashItem *item = new FlashItem;
        scene->addItem(item);
        item->setPos((qrand()%int(scene->sceneRect().width()))-200,
                     (qrand()%int(scene->sceneRect().height()))-200);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (7)运行效果如图7.9所示。
    在这里插入图片描述

    下面将接着完成实现星星移动的功能。
    (1)向项目中添加一个新的C++类,类名命名为“StartItem”,操作步骤同前。StartItem类继承自QGraphicsItem类,实际上是一个图片图元。
    “startitem.h”文件的具体代码如下:

    #include 
    #include 
    class StartItem : public QGraphicsItem
    {
    public:
        StartItem();
        QRectF boundingRect() const;
        void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
    private:
        QPixmap pix;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    (2)StartItem构造函数中仅完成读取图片信息的工作。
    “startitem.cpp”文件中的具体代码如下:

    #include "startitem.h"
    StartItem::StartItem()
    {
        pix.load("star.png");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    定义图元的边界函数boundingRect(),它是所有自定义图元均必须实现的函数,代码如下:

    QRectF StartItem::boundingRect() const
    {
        return QRectF(-pix.width()/2,-pix.height()/2,pix.width(),pix. height());
    }
    
    • 1
    • 2
    • 3
    • 4

    自定义图元重绘函数paint(),代码如下:

    void StartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget)
    {
        painter->drawPixmap(boundingRect().topLeft(),pix);
    }
    
    • 1
    • 2
    • 3
    • 4

    (3)在“mainwindow.h”文件中添加代码如下:

    public slots:    
        void slotAddAnimationItem();
    private:
        QAction *addAnimItemAct;
    
    • 1
    • 2
    • 3
    • 4

    (4)在“mainwindow.cpp”文件中添加代码如下:

    #include "startitem.h"
    #include 
    #include 
    
    • 1
    • 2
    • 3

    其中,在createActions()函数中添加代码如下:

    addAnimItemAct = new QAction(tr("加入 星星"),this);
    connect(addAnimItemAct,SIGNAL(triggered()),this,SLOT(slotAddAnimationItem()));
    
    • 1
    • 2

    在createMenus()函数中添加代码如下:
    itemsMenu->addAction(addAnimItemAct);
    在initScene()函数中添加代码如下:

    for(i=0;i<3;i++)      
        slotAddAnimationItem();
    
    
    • 1
    • 2
    • 3

    实现函数slotAddAnimationItem()的具体代码如下:

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    (5)最终运行结果如图7.7所示,图中的小星星会不停地左右移动。

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

    【例】(难度中等)(CH704)设计界面,实现蝴蝶各种变形。如图7.10所示。
    在这里插入图片描述
    (1)新建Qt Widgets Application(详见1.3.1节),项目名称为“ItemWidget”,基类选择“QWidget”,类名命名为“MainWidget”,取消“创建界面”复选框的选中状态。单击“下一步”按钮,最后单击“完成”按钮,完成该项目工程的建立。
    (2)MainWidget类继承自QWidget,作为主窗体类,用于对图元的显示,包含一个控制面板区及一个显示区。

    “mainwidget.h”文件中的代码如下:

    #include 
    #include 
    #include 
    #include 
    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;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    (3)“mainwidget.cpp”文件中的具体代码。
    右侧的控制面板区分为旋转控制区、缩放控制区、切变控制区及位移控制区,每个区均由包含一个QSlider对象的QGroupBox对象实现,具体实现代码。
    (4)运行效果如图7.11所示。
    在这里插入图片描述
    上面完成的是主窗体的功能,下面介绍用于变形显示的图元的制作。
    (1)在“ItemWidget”项目名上单击鼠标右键,在弹出的快捷菜单中选择“添加新文件…”菜单项,在弹出的对话框中选择“C++ Class”选项。单击“Choose…”按钮,弹出对话框,在“Base class”后面的文本框中输入基类名“QGraphicsItem”(手工添加),在“Class name”后面的文本框中输入类的名称“PixItem”。
    (2)单击“下一步”按钮,单击“完成”按钮,添加文件“pixitem.h”和文件“pixitem.cpp”。
    (3)自定义PixItem类继承自QGraphicsItem类。

    “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;     		//作为图元显示的图片
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    (4)PixItem的构造函数只是初始化了变量pix。“pixitem.cpp”文件中的具体内容如下:
    #include “pixitem.h”

    PixItem::PixItem(QPixmap *pixmap)
    {
        pix = *pixmap;
    }
    
    • 1
    • 2
    • 3
    • 4

    定义图元边界的函数boundingRect(),完成以图元坐标系为基础增加两个像素点的冗余的工作。具体实现代码如下:

    QRectF PixItem::boundingRect() const
    {
        return QRectF(-2-pix.width()/2,-2-pix.height()/2,pix.width()+4, pix. height()+4);
    }
    
    • 1
    • 2
    • 3
    • 4

    重画函数只需QPainter的drawPixmap()函数将图元图片绘出即可。具体代码如下:

    void PixItem::paint(QPainter *painter, const QStyleOptionGraphicsItem  *option,QWidget *widget)
    {
        painter->drawPixmap(-pix.width()/2,-pix.height()/2,pix);
    }
    
    • 1
    • 2
    • 3
    • 4

    (5)在“mainwidget.h”文件中添加代码如下:

    #include "pixitem.h"
    private:
    PixItem *pixItem;
    
    • 1
    • 2
    • 3

    (6)打开“mainwidget.cpp”文件,在语句scene->setSceneRect(-200,-200,400,400)与view = new QGraphicsView之间添加如下代码:

    QPixmap *pixmap = new  QPixmap("image.png");
    pixItem = new PixItem(pixmap);
    scene->addItem(pixItem);
    pixItem->setPos(0,0);
    
    • 1
    • 2
    • 3
    • 4

    (7)运行效果如图7.12所示。
    在这里插入图片描述
    上述内容只是完成了图元图片的加载显示。下面介绍实现图元的各种变形的实际功能。
    (1)在“mainwidget.h”文件中添加槽函数声明如下:

    public slots:
        void slotRotate(int);
        void slotScale(int);
        void slotShear(int);
        void slotTranslate(int);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (2)在“mainwidget.cpp”文件中添加头文件:

    #include 
    
    • 1

    其中,在createControlFrame()函数中的QVBoxLayout *frameLayout=new QVBoxLayout语句之前添加以下代码:

    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)));
    
    • 1
    • 2
    • 3
    • 4

    实现图元的旋转功能函数slotRotate(),主要是调用QGraphicsView类的rotate()函数实现的,它的参数为旋转角度的度数值,具体实现代码如下:

    void MainWidget::slotRotate(int value)
    {
        view->rotate(value-angle);
        angle = value;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    实现图元的缩放功能函数slotScale(),主要是调用QGraphicsView类的scale()函数实现的,它的参数为缩放的比例,具体实现代码如下:

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    实现图元的切变功能函数slotShear(),主要是调用QGraphicsView类的shear()函数实现的,它的参数为切变的比例,具体实现代码如下:

    void MainWidget::slotShear(int value)
    {
        view->shear((value-shearValue)/10.0,0);
        shearValue=value;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    实现图元的位移功能函数slotTranslate(),主要是调用QGraphicsView类的translate()函数实现的,它的参数为位移的大小,具体实现代码如下:

    void MainWidget::slotTranslate(int value)
    {
        view->translate(value-translateValue,value-translateValue);
        translateValue=value;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (3)最终运行结果如图7.10所示,读者可以试着拖曳滑块观看图形的各种变换效果。



    本章相关例程源码下载

    1.Qt5开发及实例_CH701.rar 下载

    Qt5开发及实例_CH701.rar

    2.Qt5开发及实例_CH702.rar 下载

    Qt5开发及实例_CH702.rar

    3.Qt5开发及实例_CH703.rar 下载

    Qt5开发及实例_CH703.rar

    4.Qt5开发及实例_CH704.rar 下载

    Qt5开发及实例_CH704.rar

  • 相关阅读:
    从阿里云容器攻防矩阵&API安全生命周期,看如何构建金融安全云原生平台
    android-smali记录
    依葫芦画瓢理解一个小型Go框架
    二、模型驱动测试设计
    ADG dataguard ALL_LOGFILES,ALL_ROLES
    如果面试官问你 JVM,额外回答“逃逸分析”技术会让你加分
    突发技术故障对工作进程的影响及其应对策略——以电脑硬盘损坏为例
    进阶JAVA篇- DateTimeFormatter 类与 Period 类、Duration类的常用API(八)
    [C语言]通过malloc分配一段连续内存存放类似二维数组
    CvT:微软提出结合CNN的ViT架构 | 2021 arxiv
  • 原文地址:https://blog.csdn.net/kingpower2018/article/details/133018608