这个小demo基本把View,Scene,Item都涉及了,记录一下
首先是ui文件
ui还是比较简单的,一个MainWindow,几个action,一个GraphicsView。就是工具栏有两个,右击mainwindow添加工具栏然后把它移动到左侧就可以实现竖着的工具栏了 。
看一下运行效果图
由于QGraphicsView本身没有MouseMoveEvent之类的信号,所以必须写一个QGraphicsView的派生类然后实现这些事件,这里就新建一个类名叫TGraphicsView,继承GraphicsView,然后我们override这几个虚函数
virtual void mousePressEvent(QMouseEvent *event) override;//鼠标点击
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;//鼠标双击
virtual void mouseMoveEvent(QMouseEvent *event) override;//鼠标移动
virtual void keyPressEvent(QKeyEvent *event) override;//键盘摁下
由于我们这个类也不是说是很完美的通用类,一些操作的槽函数可以在MainWindow里实现,我们的TGraphicsView就负责发送信号就行了。所以我们再定义几个信号,触发上面的Event时发送信号,让MainWindow接受然后再实现;
我们定义下面四个信号,分别对应上面四个Event
signals:
void mouseMousePoint(QPoint point);//mouseMoveEvent
void mouseClicked(QPoint point);//mousePressEvent
void mouseDoubleClicked(QPoint point);//mouseDoubleClickEvent
void keyPress(QKeyEvent *event);//keyPressEvent
我们event的函数里发信号就行,看看四个event函数吧
void TGraphicsView::mousePressEvent(QMouseEvent *event)
{
if(event->button()==Qt::LeftButton){//如果是左键点击
emit mouseClicked(event->pos());//发送信号,通过信号把点击的坐标传出去
}
QGraphicsView::mousePressEvent(event);
}
void TGraphicsView::mouseDoubleClickEvent(QMouseEvent *event)
{
if(event->button()==Qt::LeftButton){
emit mouseDoubleClicked(event->pos());
}
QGraphicsView::mouseDoubleClickEvent(event);
}
void TGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
emit mouseMousePoint(event->pos());//通过信号把鼠标位置传出去
QGraphicsView::mouseMoveEvent(event);
}
void TGraphicsView::keyPressEvent(QKeyEvent *event)
{
emit keyPress(event);//这里直接把键盘点击的事件传出去
QGraphicsView::keyPressEvent(event);
}
以上,就完成了TGraphicsView的代码,但是ui里一开始放的是GraphicsView,所以我们右键ui里的GraphicsView,找到提升,然后输入TGraphicsView,就可以把我们ui里的View升级成TGraphicsView了。
然后思考一下MainWindow的头文件里需要什么东西,需要发出来的信号对应的槽函数,需要action对应的槽函数
看一下运行示例,状态栏里有几个label,前三个是坐标,最后一个是图形的信息,也在头文件里准备一些东西来实现状态栏
//自定义的槽函数,对应view的信号
void do_mouseMovePoint(QPoint point);
void do_mouseClicked(QPoint point);
void do_mouseDoubleClicked(QPoint point);
void do_keyPress(QKeyEvent *event);
QLabel *labViewCord,*labSceneCord,*labItemCord,*labItemInfo;
//info包括id和des
const int ItemId=1;//这个是key 对应的value 是seqnum
const int ItemDesscription=2; // string
int seqNum=0;//图形项的编号
const quint32 boundValue=100;//随机数范围
void setItemProperty(QGraphicsItem *item,QString description);//生成图形的公共部分
int frontZ=0,backZ=0;
我们每生成一个Item,都用setData来设置它的信息,这些信息会显示到状态栏里
先看MainWindow的初始化
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->setCentralWidget(ui->graphicsView);//把View变成中心组件
QGraphicsScene *scene=new QGraphicsScene(-300,-200,600,400);//创建场景
ui->graphicsView->setScene(scene);//View设置场景
ui->graphicsView->setCursor(Qt::CrossCursor);//在View里的鼠标会变成十字
ui->graphicsView->setMouseTracking(true);//允许追踪鼠标,这样那些Event才有用
ui->graphicsView->setDragMode(QGraphicsView::RubberBandDrag);
labViewCord=new QLabel("view 坐标:");//初始化状态栏的label
labViewCord->setMinimumWidth(150);
ui->statusbar->addWidget(labViewCord);//加到状态栏里
labSceneCord=new QLabel("Scene 坐标:");
labSceneCord->setMinimumWidth(150);
ui->statusbar->addWidget(labSceneCord);
labItemCord=new QLabel("Item 坐标:");
labItemCord->setMinimumWidth(150);
ui->statusbar->addWidget(labItemCord);
labItemInfo=new QLabel("ItemInfo:");
labItemInfo->setMinimumWidth(200);
ui->statusbar->addWidget(labItemInfo);
//关联信号和槽
connect(ui->graphicsView,&TGraphicsView::keyPress,this,&MainWindow::do_keyPress);
connect(ui->graphicsView,&TGraphicsView::mouseMousePoint,this,&MainWindow::do_mouseMovePoint);
connect(ui->graphicsView,&TGraphicsView::mouseClicked,this,&MainWindow::do_mouseClicked);
connect(ui->graphicsView,&TGraphicsView::mouseDoubleClicked,this,&MainWindow::do_mouseDoubleClicked);
}
QGraphicsScene::QGraphicsScene(qreal x, qreal y, qreal width, qreal height, QObject *parent = nullptr)
Constructs a QGraphicsScene object, using the rectangle specified by (x, y), and the given width and height for its scene rectangle. The parent parameter is passed to QObject's constructor.这是QGraphicsScene的初始化函数,x,y是QGraphicsScene左上角的坐标,可以自己设置,如果
width
是x
的两倍,且height
是y
的两倍,那么中心点的坐标就是(0,0),这里就是这么初始化的
这是鼠标的所有形状,凡是QWidget子类都可以设置鼠标形状,这样鼠标在移动到自己组件位置时就会改变形状
本代码中用的是CrossCursor
QGraphicsView::DragMode
是QGraphicsView
类中定义的枚举,表示视图中的拖动模式。这个枚举定义了三种不同的拖动模式:
QGraphicsView::NoDrag
: 表示没有启用拖动。在这种模式下,视图不会响应拖动手势,即无法通过鼠标拖动来平移场景。
QGraphicsView::ScrollHandDrag
: 表示启用滚动手势拖动。在这种模式下,当用户按住鼠标右键并移动时,视图会跟随鼠标移动而平移场景,实现类似于拖动手势的效果。
QGraphicsView::RubberBandDrag
: 表示启用橡皮筋选择拖动。在这种模式下,用户可以通过在视图上按住鼠标左键并拖动来创建一个矩形区域,该区域内的图形项将被选择。区别总结如下:
NoDrag
: 视图不响应拖动手势,不能通过鼠标拖动来平移场景。
ScrollHandDrag
: 视图启用了滚动手势拖动,用户按住鼠标右键并移动时,视图平移场景。
RubberBandDrag
: 视图启用了橡皮筋选择拖动,用户可以通过在视图上按住鼠标左键并拖动来创建一个选择区域。Chatgpt言
首先看setItemProperty,这是创建Item后的共同操作
void MainWindow::setItemProperty(QGraphicsItem *item, QString description)
{
item->setFlags(QGraphicsItem::ItemIsMovable|QGraphicsItem::ItemIsFocusable|QGraphicsItem::ItemIsSelectable);
item->setZValue(++frontZ);//用来解决叠放顺序,后生成的会放到前面
qint32 v1=QRandomGenerator::global()->bounded(boundValue);
qint32 v2=QRandomGenerator::global()->bounded(boundValue);//随机坐标
if(v1%2==0) v1=-v1;
if(v2%2==0) v2=-v2;//随机在任意一个象限
item->setPos(v1,v2);
item->setData(ItemId,++seqNum);//设置序号的Data
item->setData(ItemDesscription,description);//设置图像类型的Data
ui->graphicsView->scene()->addItem(item);//Item加入场景
ui->graphicsView->scene()->clearSelection();//场景清空选择
item->setSelected(true);
}
QGraphicsItem::GraphicsItemFlag
是QGraphicsItem
类中定义的枚举,表示图形项的一些状态和属性。以下是一些常见的GraphicsItemFlag
枚举值及其解释:
ItemIsMovable
: 表示图形项可以移动。如果设置了这个标志,用户可以通过鼠标拖动图形项来移动它。
ItemIsSelectable
: 表示图形项可以被选择。如果设置了这个标志,用户可以通过点击图形项来选择它。被选择的图形项通常会呈现不同的外观,以示区分。
ItemIsFocusable
: 表示图形项可以获得焦点。如果设置了这个标志,图形项可以接收键盘焦点,从而可以处理键盘事件。
ItemClipsToShape
: 表示图形项将被裁剪到其形状的边界。如果设置了这个标志,图形项将被裁剪,不会显示在其形状之外的区域。
ItemIgnoresTransformations
: 表示图形项忽略父项的变换。如果设置了这个标志,图形项将不受父项的变换影响,即其坐标不随父项的变换而变化。
ItemSendsGeometryChanges
: 表示图形项在其几何属性发生变化时发送itemChange()
事件。如果设置了这个标志,当图形项的位置或形状发生变化时,将触发itemChange()
事件。Chatgpt言
然后就是左侧的action的槽函数了,都是大同小异我就放一起了
void MainWindow::on_actionrect_triggered()
{
QGraphicsRectItem *item=new QGraphicsRectItem(-50,-25,100,50);//前两个参数的坐标没用,到时候还是会随机生成坐标
item->setBrush(QBrush(Qt::yellow));//设置填充色
setItemProperty(item,"矩形");
}
void MainWindow::on_actionellipse_triggered()
{
QGraphicsEllipseItem *item=new QGraphicsEllipseItem(-50,-25,100,50);
item->setBrush(QBrush(Qt::blue));
setItemProperty(item,"椭圆");
}
void MainWindow::on_actionround_triggered()
{
QGraphicsEllipseItem *item=new QGraphicsEllipseItem(-50,-50,100,100);
item->setBrush(QBrush(Qt::cyan));
setItemProperty(item,"圆形");
}
void MainWindow::on_actiontangle_triggered()
{
QPolygonF points;
points.append(QPointF(0,-40));
points.append(QPointF(60,40));
points.append(QPointF(-60,40));
QGraphicsPolygonItem *item=new QGraphicsPolygonItem(points);
item->setBrush(QBrush(Qt::magenta));
setItemProperty(item,"三角形");
}
void MainWindow::on_actiontrapezoid_triggered()
{
QPolygonF points;
points.append(QPointF(-40,-40));
points.append(QPointF(40,-40));
points.append(QPointF(100,40));
points.append(QPointF(-100,40));
QGraphicsPolygonItem *item=new QGraphicsPolygonItem(points);
item->setBrush(QBrush(Qt::green));
setItemProperty(item,"梯形");
}
void MainWindow::on_actionline_triggered()
{
QGraphicsLineItem *item=new QGraphicsLineItem(-100,0,100,0);
QPen pen(Qt::red);//线要用pen来设置
pen.setWidth(3);
item->setPen(pen);
setItemProperty(item,"直线");
}
void MainWindow::on_actiontext_triggered()
{
QString str=QInputDialog::getText(this,"输入文字","输入文字");//会弹出对话框来让你输入文字
if(str.isEmpty()) return;
QGraphicsTextItem *item=new QGraphicsTextItem(str);
QFont font=this->font();//用font设置文字
font.setPointSize(20);
font.setBold(true);
item->setFont(font);
setItemProperty(item,"文字");
}
遇到比较少的是QPolygonF,QPolygonF
是 Qt 中用于表示浮点数坐标的多边形的类。画一个多边形就是往里面加点,然后用它初始化一个QGraphicsPolygonItem
接下来是几个处理信号的槽函数
void MainWindow::do_mouseMovePoint(QPoint point)
{
labViewCord->setText(QString::asprintf("view 坐标:%d,%d",point.x(),point.y()));
QPointF pointScene=ui->graphicsView->mapToScene(point);//通过View的坐标得到Scene的坐标
labSceneCord->setText(QString::asprintf("Scene 坐标:%.0f %.0f",pointScene.x(),pointScene.y()));
}
void MainWindow::do_mouseClicked(QPoint point)
{
QPointF pointScene=ui->graphicsView->mapToScene(point);
QGraphicsItem *item=ui->graphicsView->scene()->itemAt(pointScene,ui->graphicsView->transform());//根据Scene的坐标得到对应位置的item
if(item==nullptr) return;
QPointF pointItem=item->mapFromScene(pointScene);//通过Scene的坐标得到相对Item的坐标
labItemCord->setText(QString::asprintf("Item 坐标:%.0f %.0f",pointItem.x(),pointItem.y()));
labItemInfo->setText(item->data(ItemDesscription).toString()+",ItemId="+item->data(ItemId).toString());
}
void MainWindow::do_mouseDoubleClicked(QPoint point)
{
QPointF pointScene=ui->graphicsView->mapToScene(point);
QGraphicsItem *item=ui->graphicsView->scene()->itemAt(pointScene,ui->graphicsView->transform());
if(item==nullptr) return;
//双击选择颜色
switch (item->type()) {
case QGraphicsRectItem::Type:{
QGraphicsRectItem *theItem=qgraphicsitem_cast<QGraphicsRectItem *>(item);
setBrushColor(theItem);
break;}
case QGraphicsEllipseItem::Type:{
QGraphicsEllipseItem *theItem=qgraphicsitem_cast<QGraphicsEllipseItem *>(item);
setBrushColor(theItem);
break;}
case QGraphicsPolygonItem::Type:{
QGraphicsPolygonItem *theItem=qgraphicsitem_cast<QGraphicsPolygonItem *>(item);
setBrushColor(theItem);
break;}
default:
break;
}
}
void MainWindow::do_keyPress(QKeyEvent *event)
{
if(ui->graphicsView->scene()->selectedItems().count()!=1)//通过这个可以一次性获得很多个被选择的Item的列表,现在只有选一个有效
return;
QGraphicsItem *item=ui->graphicsView->scene()->selectedItems().at(0);
if(event->key()==Qt::Key_Delete)
ui->graphicsView->scene()->removeItem(item);
else if(event->key()==Qt::Key_Space)//旋转
item->setRotation(item->rotation()+90);
else if(event->key()==Qt::Key_Up)
item->setY(item->y()-1);
else if(event->key()==Qt::Key_Down)
item->setY(item->y()+1);
else if(event->key()==Qt::Key_Left)
item->setX(item->x()-1);
else if(event->key()==Qt::Key_Right)
item->setX(item->x()+1);
else if(event->key()==Qt::Key_PageUp)//放大
item->setScale(item->scale()+0.1);
else if(event->key()==Qt::Key_PageDown)//放大
item->setScale(item->scale()-0.1);
}
template<class T>
void MainWindow::setBrushColor(T *item)
{
QColor color=item->brush().color();
color=QColorDialog::getColor(color,nullptr,"选择颜色");
item->setBrush(QBrush(color));
}
我之前会想
QGraphicsRectItem *theItem=qgraphicsitem_cast<QGraphicsRectItem *>(item);
setBrushColor(theItem);
好像有点多此一举,为什么要转化,后来才知道,QGraphicsItem根本没有brush的接口,所以得转化到具体类型。
最后是上面工具栏的action
void MainWindow::on_actionbig_triggered()//放大
{
int cnt=ui->graphicsView->scene()->selectedItems().count();
if(cnt==1){
QGraphicsItem *item=ui->graphicsView->scene()->selectedItems()[0];
item->setScale(item->scale()+0.1);
}
else{
ui->graphicsView->scale(1.1,1.1);
}
}
void MainWindow::on_actionsmall_triggered()//缩小
{
int cnt=ui->graphicsView->scene()->selectedItems().count();
if(cnt==1){
QGraphicsItem *item=ui->graphicsView->scene()->selectedItems()[0];
item->setScale(item->scale()-0.1);
}
else{
ui->graphicsView->scale(0.9,0.9);
}
}
void MainWindow::on_actionundo_triggered()
{
int cnt=ui->graphicsView->scene()->selectedItems().count();
if(cnt==1){
QGraphicsItem *item=ui->graphicsView->scene()->selectedItems()[0];
item->setScale(1.0);
item->setRotation(0);
}
else{
ui->graphicsView->resetTransform();
}
}
void MainWindow::on_actionleftrotate_triggered()
{
int cnt=ui->graphicsView->scene()->selectedItems().count();
if(cnt==1){
QGraphicsItem *item=ui->graphicsView->scene()->selectedItems()[0];
item->setRotation(item->rotation()-30);
}
else{
ui->graphicsView->rotate(-30);
}
}
void MainWindow::on_actionrightrotate_triggered()
{
int cnt=ui->graphicsView->scene()->selectedItems().count();
if(cnt==1){
QGraphicsItem *item=ui->graphicsView->scene()->selectedItems()[0];
item->setRotation(item->rotation()+30);
}
else{
ui->graphicsView->rotate(+30);
}
}
void MainWindow::on_actionfront_triggered()
{
for(auto &item:ui->graphicsView->scene()->selectedItems()){
item->setZValue(++frontZ);
}
}
void MainWindow::on_actionback_triggered()
{
for(auto &item:ui->graphicsView->scene()->selectedItems()){
item->setZValue(--backZ);
}
}
void MainWindow::on_actiongroup_triggered()
{
int cnt=ui->graphicsView->scene()->selectedItems().count();
if(cnt==0) return;
QGraphicsItemGroup *group=new QGraphicsItemGroup();
for(auto &item:ui->graphicsView->scene()->selectedItems()){
item->setSelected(false);
item->clearFocus();
group->addToGroup(item);
}
setItemProperty(group,"组合");
}
void MainWindow::on_actionungroup_triggered()
{
for(auto &item:ui->graphicsView->scene()->selectedItems()){
QGraphicsItemGroup *group=dynamic_cast<QGraphicsItemGroup *>(item);
if(group)
ui->graphicsView->scene()->destroyItemGroup(group);//拆散ItemGroup
}
}
void MainWindow::on_actiondelete_triggered()
{
for(auto &item:ui->graphicsView->scene()->selectedItems()){
ui->graphicsView->scene()->removeItem(item);//remove不会删除内存
delete item;
}
}
至此所有内容都完成了,上方的action基本没啥可讲的就略过了