前言
对于绘图QT提供了两个类,这里简单介绍他俩(QPainter 和GraphicsView)的区别,QPainter英译过来就是画笔的意思,而这个类就是在一个平面绘画,画出来的画面不能后期更改,一般使用的地方就是制作一些炫酷的控件后续不做大的更改;QGraphicsView一般是用来做图形管理他可以对单个图进行管理操作,例如移动、放大缩小、删除以及增加真的图像模型。
QGraphicsView一般由三个部分组成:
1、GraphicsView:看法;看;视野;(个人的)意见;见解;态度;(理解或思维的)方法;方式;视域;视线
2、QGraphicsScene:场景;场面;情景;镜头;(尤指不愉快事件发生的)地点,现场;事件;片段
3、QGraphicsItem :项目;一件商品(或物品);一则,一条(新闻)
从上面的英文翻译可以理解GraphicsView是一个放置我们制作的控件的层,scene是负责管理和操作我们的控件层,而item就是我们的控件实物层;
实验目的
记录GraphicsView的简单使用和对自己的理解进行梳理,也可能有理解的不对的地方欢迎指正,下面开始代码实验。
重构GraphicsView类
在实际使用中我们一般需要重构GraphicsView类的的几个信号提供给我们操作图层的信号。
//鼠标移动事件:用来获取实时获取我们的鼠标所在的坐标信息
void QWGraphicsView::mouseMoveEvent(QMouseEvent *event) { QPoint point=event->pos(); //QGraphicsView的坐标 emit mouseMovePoint(point); //发送信号 QGraphicsView::mouseMoveEvent(event); }
//鼠标左键按下事件:
void QWGraphicsView::mousePressEvent(QMouseEvent *event) { if (event->button()==Qt::LeftButton) { QPoint point=event->pos(); //QGraphicsView的坐标 emit mouseClicked(point); //发送信号 } QGraphicsView::mousePressEvent(event); }
//鼠标双击事件:双击我们的控件可以显示该控件的一些信息
void QWGraphicsView::mouseDoubleClickEvent(QMouseEvent *event) { if (event->button()==Qt::LeftButton) { QPoint point=event->pos(); //QGraphicsView的坐标 emit mouseDoubleClick(point); //发送信号 } QGraphicsView::mouseDoubleClickEvent(event); }
//按键事件:可以设置对图形操作的删除和移动
void QWGraphicsView::keyPressEvent(QKeyEvent *event) { emit keyPress(event); // 发送信号 QGraphicsView::keyPressEvent(event); }
// 当滚轮远离使用者时进行放大,当滚轮向使用者方向旋转时进行缩小
void QWGraphicsView::wheelEvent(QWheelEvent *event) { if(event->delta() > 0){ emit MyWheelEvent("add");// 发送向上滚动信号 }else{ emit MyWheelEvent("sub");// 发送向下滚动信号 } }
新建GraphicsView、QGraphicsScene层
但我们重构了上面的QGraphicsView类过后接下来我们开始我们的绘制工程制作;
首先我们拖一个GraphicsView控件到我们的画面当中去然后提升为我们继承的类;
然后开始定义我们需要用到的sence层和我们item需要存储数据的key,这里item存储数据用的应该是一个map类型,一个key对应一个数据,下面我定义了一个枚举分别对应存储ID的key和中文文字的数据key,然后还定义了两个变量一个用来存储当前item个数的ItemNum,我们在新建一个Item的时候可以将这个数值作为自加操作并赋给ID的key中作为保存,
enum{ ITEMID = 1, // IDkey ITEMDATA // dataKey }; QGraphicsScene *scene; // item管理图层 int seqNum=0; // ID个数 int layers; // 图层层数
然后我们在主页面设置view的属性和绑定我们的scene图层
scene=new QGraphicsScene(-300,-200,600,200); //创建QGraphicsScene ui->View->setScene(scene); //与view关联 ui->View->setCursor(Qt::CrossCursor); //设置鼠标 ui->View->setMouseTracking(true); //鼠標捕獲 ui->View->setDragMode(QGraphicsView::RubberBandDrag); // 設置拖動模式
到这里我们的图层就构建好了下面就开始绘制我们的第一个item图形
绘制Item图形控件
绘制圆形
// 可以通过调整这个属性得到不同的图形,例如椭圆 QGraphicsEllipseItem *item=new QGraphicsEllipseItem(-50,-50,100,60); item->setFlags(QGraphicsItem::ItemIsMovable /*该项支持使用鼠标进行交互式移动。通过单击项目然后拖动,项目将与鼠标光标一起移动。如果项目具有子项,则也会移动所有子项。如果项目是选定内容的一部分,则所有选定项目也将被移动。此功能是通过QGraphicsItem的鼠标事件处理程序的基本实现提供的。 */ | QGraphicsItem::ItemIsSelectable /*该项支持选择。启用此功能将启用setSelected()来切换项目的选择。它还允许在调用QGraphicscene::setSelectionArea()时,通过单击项目或使用QGraphicsView中的橡皮筋选择,自动选择项目。*/ | QGraphicsItem::ItemIsFocusable); /*该项支持键盘输入焦点(即,它是一个输入项)。启用此标志将允许项目接受焦点,这再次允许将关键事件传递到QGraphicsItem::keyPressEvent()和QGraphicsItem::keyReleaseEvent()。*/ // 更多的设置选项可以通过F1进去查看更多 item->setBrush(QBrush(Qt::cyan)); // 设置笔刷颜色 item->setPos(-50+(qrand() % 100),-50+(qrand() % 100)); // 这里设置item的放置位置这里随机放置到中心坐标点附近-50,50的坐标点 item->setZValue(++layers); item->setData(ITEMID,++seqNum); item->setData(ITEMDATA,"圆形"); scene->addItem(item); scene->clearSelection(); item->setSelected(true);
绘制矩形
QGraphicsRectItem *item=new QGraphicsRectItem(-50,-25,100,50); item->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsFocusable); item->setBrush(QBrush(Qt::yellow)); item->setPos(-50+(qrand() % 100),-50+(qrand() % 100)); item->setZValue(++layers); item->setData(ITEMID,++seqNum); item->setData(ITEMDATA,"矩形"); scene->addItem(item); scene->clearSelection(); item->setSelected(true);
绘制三角形
QGraphicsPolygonItem *item=new QGraphicsPolygonItem; QPolygonF points; points.append(QPointF(0,-40)); points.append(QPointF(60,40)); points.append(QPointF(-60,40)); item->setPolygon(points); item->setPos(-50+(qrand() % 100),-50+(qrand() % 100)); item->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsFocusable); item->setBrush(QBrush(Qt::magenta)); item->setZValue(++layers); item->setData(ITEMID,++seqNum); item->setData(ITEMDATA,"三角形"); scene->addItem(item); scene->clearSelection(); item->setSelected(true);
绘制文字
//添加文字 QString str=QInputDialog::getText(this,"输入文字","请输入文字"); if (str.isEmpty()) return; QGraphicsTextItem *item=new QGraphicsTextItem(str); QFont font=this->font(); font.setPointSize(20); font.setBold(true); item->setFont(font); item->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsFocusable); item->setPos(-50+(qrand() % 100),-50+(qrand() % 100)); item->setZValue(++layers); item->setData(ITEMID,++seqNum); item->setData(ITEMDATA,"文字"); scene->addItem(item); scene->clearSelection(); item->setSelected(true);
绘制直线
QGraphicsLineItem *item=new QGraphicsLineItem(-100,0,100,0); item->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsFocusable); QPen pen(Qt::red); pen.setWidth(3); item->setPen(pen); item->setZValue(++frontZ); item->setPos(-50+(qrand() % 100),-50+(qrand() % 100)); item->setZValue(++layers); item->setData(ITEMID,++seqNum); item->setData(ITEMDATA,"直线"); scene->addItem(item); scene->clearSelection(); item->setSelected(true);
学会了以上的几种绘图方式其实就已经可以做很多事情了下面扩展几种控件的操作
组合、打散、前置和后置
组合
具体的实现方法是引入一个QGraphicsItemGroup类将选中的控件添加到组中然后关闭每一个单独的item的选择、焦点、功能,然后将group加入到view中显示,而打散就是将group中的item全部取出来解除组合。
int cnt=scene->selectedItems().count(); if (cnt>1) { QGraphicsItemGroup* group =new QGraphicsItemGroup; //创建组合 scene->addItem(group); //组合添加到场景中 for (int i=0;i<cnt;i++) { QGraphicsItem* item=scene->selectedItems().at(0); item->setSelected(false); //清除选择虚线框 item->clearFocus(); group->addToGroup(item); //添加到组合 } group->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsFocusable); group->setZValue(++layers); scene->clearSelection(); group->setSelected(true); }
打散
int cnt=scene->selectedItems().count(); if (cnt==1) { QGraphicsItemGroup *group; group=(QGraphicsItemGroup*)scene->selectedItems().at(0); scene->destroyItemGroup(group); }
控件前置
这里我们只需要通过setZValue接口设置当前层数就可以实现效果。
int cnt=scene->selectedItems().count(); if (cnt>0) { QGraphicsItem* item=scene->selectedItems().at(0);//获取选中的第一个item item->setZValue(++layers); }
控件后置
int cnt=scene->selectedItems().count(); if (cnt>0) { QGraphicsItem* item=scene->selectedItems().at(0); item->setZValue(0); }
完成了这些功能过后我们把前面的鼠标、按键和滚轮时间结合起来
绑定功能事件
在这个连接信号中我设置了选中item按下delete删除选中的控件,需要注意的是删除多个控件的时候遍历控件的时候每次信号进来scene->selectedItems().count()函数中的数值都会发生改变所以每次删除对的时候我们都删除选中的低at(0)的控件;
按键上下左右这里制作了移动单个item控件还需要做更改,这里用到了 item的设置坐标接口setX和setY也就是设置item的坐标,这里利用item的xy坐标信息进行加减就可以实现坐标的移动移动的幅度大小根据自己需求设计即可;
对于空格这里用来设置item控件的角度,item同样提供了一个角度接口setRotation这里也是将获取item当前的角度值然后进行加减操作就可以实现角度的变化了。
/* 鍵盤按鍵監控 */ connect(ui->View,&QWGraphicsView::keyPress,this,[=](QKeyEvent *event){ do { QGraphicsItem *item; int scencSelectCount = scene->selectedItems().count(); qDebug() << scene->selectedItems().count() <<endl; if(Qt::Key_Delete == event->key() && scencSelectCount>=1){ for (int i = 0;i < scencSelectCount;i++) { item=scene->selectedItems().at(0);//每次都删除第一个 scene->removeItem(item); } break; } if (scencSelectCount!=1) break; //没有选中的绘图项,或选中的多于1个 item=scene->selectedItems().at(0); if(Qt::Key_Left == event->key()){ item->setX(-1+item->x()); //左移 break; } if(Qt::Key_Right == event->key()){ item->setX(1+item->x());//右移 break; } if(Qt::Key_Up == event->key()){ item->setY(-1+item->y());//上移 break; } if(Qt::Key_Down == event->key()){ item->setY(1+item->y());//下移 break; } if(Qt::Key_Space == event->key()){ item->setRotation(90+item->rotation());//旋转90度 break; } } while (false); });
这里监控鼠标的滚动,但我们选择的控件只有一个的时候(用selectedItems().count()判断)判断鼠标滚轮方向然后利用item提供的scale参数也就是大小然后按照百分比的方式去叠加来实现item的放大和缩小。
/* 鼠标滚轮事件 */ connect(ui->View,&QWGraphicsView::MyWheelEvent,this,[=](QString str){ if (scene->selectedItems().count()!=1) return; //没有选中的绘图项,或选中的多于1个 QGraphicsItem *item=scene->selectedItems().at(0); if (str == "add"){ item->setScale(0.1+item->scale());//放大 }else{ item->setScale(-0.1+item->scale());//缩小 } });
这里监控鼠标的移动事件,这个信号发送过来的数据是一个QPoint 他带的坐标是指的是我们view层的坐标信息他是以左上方为中心点,而我们的scene的坐标是已中心为坐标轴中心
蓝色的交叉是view的0,0,红色的交叉点是sence的0,0;
/* 鼠標移動事件 用於獲取坐標 */ connect(ui->View,&QWGraphicsView::mouseMovePoint,this,[=](QPoint point){ QPointF pointScene=ui->View->mapToScene(point); //转换到Scene坐标 ui->label->setText(QString::asprintf("View 坐标:%d,%d Scene 坐标:%.0f,%.0f", point.x(),point.y(),pointScene.x(),pointScene.y())); });
但鼠标左键按下的时候传来一个坐标位置,然后我们将这个坐标位置转换为sence坐标,然后再sence图层去寻找这个坐标有没有item控件,这里新建了一个空的item用来接受这个查找的返回结果,如果返回不为空我们可以显示这个item的坐标信息以及图标类型,还有我们给这个item设置的data属性,这个也是根据需求来指定,如果选择的位置为空我们可以做一个标志如果这里按下了绘画按钮,这里可以进行一个item的绘画操作。
/* 鼠標左擊事件 */ connect(ui->View,&QWGraphicsView::mouseClicked,this,[=](QPoint point){ QPointF pointScene=ui->View->mapToScene(point); //转换到Scene坐标 QGraphicsItem *item=NULL; item=scene->itemAt(pointScene,ui->View->transform()); //获取光标下的绘图项 if (item != NULL) //有绘图项 { QPointF pointItem=item->mapFromScene(pointScene); //转换为绘图项的局部坐标 ui->label_2->setText(QString::asprintf("Item坐標:%.0f,%.0f 選中圖標ID:%1 圖標類型:%2", pointItem.x(),pointItem.y()) .arg(item->data(ItemId).toString()) .arg(item->data(ItemDesciption).toString())); }else if(yuanFlag){ //判断是否有绘画标志位 QGraphicsEllipseItem *item=new QGraphicsEllipseItem(-50,-50,100,100); item->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsFocusable); item->setBrush(QBrush(Qt::cyan)); item->setPos((pointScene.x()+10),(pointScene.y()+10)); item->setData(ITEAID,++seqNum); item->setData(ITENDATA,"圆形"); scene->addItem(item); scene->clearSelection(); item->setSelected(true); yuanFlag = false; } });
双击信号这里暂时没有写有需求的功能
/* 鼠標雙擊事件 */ connect(ui->View,&QWGraphicsView::mouseDoubleClick,this,[=](){ });
总结
这篇文章也只是简单的介绍了GraphicsView的基本操作,熟练掌握这个类的使用还可以做很多好玩有趣的程序,在这个互联网如此发达的时代好好利用网络上的知识来丰富自己,也让自己所踩的坑通过分享让大家一起进步。
编程这回事,乃漫长之事,我不能用冰山一角衡量冰山之高,不能用燕雀心境描绘鸿鹄之志。只希望洁身自好,努力追求,愿为燕雀找寻星空。
参考博文:
程序例子 提取码:lloh
GraphicsViewDemo 阿里云个人代码demo