基于GraphicsView的绘图程序[Qt]

这个小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左上角的坐标,可以自己设置,如果widthx 的两倍,且 heighty 的两倍,那么中心点的坐标就是(0,0),这里就是这么初始化的

这是鼠标的所有形状,凡是QWidget子类都可以设置鼠标形状,这样鼠标在移动到自己组件位置时就会改变形状

本代码中用的是CrossCursor

QGraphicsView::DragModeQGraphicsView 类中定义的枚举,表示视图中的拖动模式。这个枚举定义了三种不同的拖动模式:

  1. QGraphicsView::NoDrag: 表示没有启用拖动。在这种模式下,视图不会响应拖动手势,即无法通过鼠标拖动来平移场景。

  2. QGraphicsView::ScrollHandDrag: 表示启用滚动手势拖动。在这种模式下,当用户按住鼠标右键并移动时,视图会跟随鼠标移动而平移场景,实现类似于拖动手势的效果。

  3. 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::GraphicsItemFlagQGraphicsItem 类中定义的枚举,表示图形项的一些状态和属性。以下是一些常见的 GraphicsItemFlag 枚举值及其解释:

  1. ItemIsMovable: 表示图形项可以移动。如果设置了这个标志,用户可以通过鼠标拖动图形项来移动它。

  2. ItemIsSelectable: 表示图形项可以被选择。如果设置了这个标志,用户可以通过点击图形项来选择它。被选择的图形项通常会呈现不同的外观,以示区分。

  3. ItemIsFocusable: 表示图形项可以获得焦点。如果设置了这个标志,图形项可以接收键盘焦点,从而可以处理键盘事件。

  4. ItemClipsToShape: 表示图形项将被裁剪到其形状的边界。如果设置了这个标志,图形项将被裁剪,不会显示在其形状之外的区域。

  5. ItemIgnoresTransformations: 表示图形项忽略父项的变换。如果设置了这个标志,图形项将不受父项的变换影响,即其坐标不随父项的变换而变化。

  6. 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基本没啥可讲的就略过了

  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值