Qt图形视图框架入门:坐标系统与鼠标事件处理详解

最近我在学习Qt的图形视图框架时,发现坐标系统和鼠标事件处理是初学者最容易混淆的部分。通过实践,我总结了一些经验,希望能帮助到同样在学习这个框架的朋友们。

一、图形视图框架的三个核心概念

在Qt的图形视图框架中,主要有三个核心类需要我们理解:

  • QGraphicsView(视图):显示场景的窗口,用户可以与之交互
  • QGraphicsScene(场景):管理所有图形项的容器
  • QGraphicsItem(图形项):场景中的具体图形元素

想象一下,这就像一个舞台剧:

  • Scene(场景) 是整个舞台
  • Item(图形项) 是舞台上的演员和道具
  • View(视图) 是观众看到的镜头

二、理解三个坐标系系统

这是最让我困惑的部分,但理解后就会发现很清晰:

1. 视图坐标系(View Coordinates)

以视图窗口的左上角为原点(0,0),向右为x正方向,向下为y正方向,单位是像素。

cpp 复制代码
// 在鼠标移动事件中,我们获取的就是视图坐标
void MyGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
    QPoint point = event->pos();  // 这是视图坐标
    emit mouseMove(point);
}

2. 场景坐标系(Scene Coordinates)

场景有自己的坐标系,我们可以自由定义原点和范围。

cpp 复制代码
// 创建场景时指定坐标系范围
scene = new QGraphicsScene(
    -200, -100,    // 场景左上角在场景坐标系中的位置
    400, 200);     // 场景的宽度和高度

3. 图形项坐标系(Item Coordinates)

每个图形项都有自己的局部坐标系,以图形项自身的左上角为原点。

cpp 复制代码
// 创建矩形图形项,参数是在图形项自身坐标系中的位置和大小
QGraphicsRectItem *item2 = new QGraphicsRectItem(0,0,200,100);

三、坐标转换的实用方法

在实际开发中,我经常需要在不同坐标系间转换:

cpp 复制代码
// 视图坐标 → 场景坐标
QPointF pointScene = ui->graphicsView->mapToScene(point);

// 场景坐标 → 图形项坐标  
QPointF pointItem = item->mapFromScene(pointScene);

// 场景坐标 → 视图坐标
QPoint pointView = ui->graphicsView->mapFromScene(pointScene);

四、自定义视图类处理鼠标事件

我创建了一个自定义视图类来更好地处理鼠标事件:

头文件:mygraphicsview.h

cpp 复制代码
#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H

#include <QGraphicsView>
#include <QMouseEvent>

class MyGraphicsView : public QGraphicsView
{
    Q_OBJECT
public:
    explicit MyGraphicsView(QWidget *parent = nullptr);

signals:
    // 自定义信号,传递鼠标位置(视图坐标)
    void mousePress(QPoint point);
    void mouseMove(QPoint point);

protected:
    // 重写鼠标事件处理函数
    void mousePressEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
};

#endif // MYGRAPHICSVIEW_H

实现文件:mygraphicsview.cpp

cpp 复制代码
#include "mygraphicsview.h"
#include <QMouseEvent>

MyGraphicsView::MyGraphicsView(QWidget *parent) : QGraphicsView(parent)
{
    // 启用鼠标跟踪,这样不按鼠标也能触发移动事件
    setMouseTracking(true);
}

void MyGraphicsView::mousePressEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton) {
        QPoint point = event->pos();  // 获取鼠标在视图中的位置
        emit mousePress(point);       // 发射自定义信号
    }
    
    // 调用基类处理,确保不影响原有功能
    QGraphicsView::mousePressEvent(event);
}

void MyGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
    QPoint point = event->pos();     // 获取当前鼠标位置
    emit mouseMove(point);           // 发射移动信号
    
    QGraphicsView::mouseMoveEvent(event);
}

五、主窗口中的场景搭建和事件处理

在主窗口中,我创建了场景并添加了图形项:

cpp 复制代码
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 创建场景
    scene = new QGraphicsScene(-200, -100, 400, 200);
    ui->graphicsView->setScene(scene);

    // 创建底层蓝色边框矩形(作为背景参考)
    QGraphicsRectItem *item1 = new QGraphicsRectItem(-200, -100, 400, 200);
    item1->setPos(0,0);  // 设置图形项在场景中的位置
    
    QPen pen;
    pen.setWidth(2);
    pen.setColor(Qt::blue);
    item1->setPen(pen);
    scene->addItem(item1);

    // 创建可移动的红色矩形
    QGraphicsRectItem *item2 = new QGraphicsRectItem(0,0,200,100);
    item2->setPos(-200,-100);  // 在场景中的位置
    item2->setBrush(QBrush(Qt::red));
    
    // 设置图形项属性:可选择、可聚焦、可移动
    item2->setFlags(QGraphicsItem::ItemIsSelectable |
                    QGraphicsItem::ItemIsFocusable |
                    QGraphicsItem::ItemIsMovable);
    scene->addItem(item2);

    // 创建绿色矩形作为红色矩形的子项
    QGraphicsRectItem *item3 = new QGraphicsRectItem(0,0,100,50);
    item3->setBrush(QBrush(Qt::green));
    item3->setPos(100,0);  // 相对于父项的位置
    item3->setParentItem(item2);  // 设置为item2的子项

    // 连接信号槽
    connect(ui->graphicsView, &MyGraphicsView::mousePress, 
            this, &MainWindow::on_graphicsView_mousePress);
    connect(ui->graphicsView, &MyGraphicsView::mouseMove, 
            this, &MainWindow::on_graphicsView_mouseMove);
}

六、鼠标事件的具体处理

当鼠标在视图上移动或点击时,我需要实时显示不同坐标系下的位置:

cpp 复制代码
void MainWindow::on_graphicsView_mouseMove(QPoint point)
{
    // 显示视图坐标
    ui->labelView->setText(QString::asprintf("视图坐标: %d, %d", 
                                             point.x(), point.y()));

    // 转换为场景坐标并显示
    QPointF pointScene = ui->graphicsView->mapToScene(point);
    ui->labelScene->setText(QString::asprintf("场景坐标: %.0f, %.0f", 
                                              pointScene.x(), pointScene.y()));
}

void MainWindow::on_graphicsView_mousePress(QPoint point)
{
    // 视图坐标转场景坐标
    QPointF pointScene = ui->graphicsView->mapToScene(point);

    // 在场景坐标位置查找图形项
    QGraphicsItem *item = scene->itemAt(pointScene, 
                                       ui->graphicsView->transform());

    if (item) {
        // 场景坐标转图形项局部坐标
        QPointF pointItem = item->mapFromScene(pointScene);
        ui->labelItem->setText(QString::asprintf("Item坐标: %.0f, %.0f",
                                                 pointItem.x(), pointItem.y()));
    }
}

运行结果:

七、我遇到的重点难点解析

1. setPos() 与图形项构造函数参数的区别

  • 构造函数中的坐标是图形项自身坐标系中的位置和大小
  • setPos()设置的是图形项原点场景坐标系中的位置

2. 图形项的父子关系

子项会跟随父项移动,其坐标是相对于父项的。这在我创建item3作为item2的子项时体现得很明显。

3. 鼠标跟踪的重要性

setMouseTracking(true)让我不用按住鼠标也能获得移动事件,这在需要实时显示坐标时非常有用。

八、总结

通过这个练习,我深刻理解了:

  1. 三个坐标系要分清:视图、场景、图形项各有各的坐标系
  2. 坐标转换要熟练:mapToScene()、mapFromScene()等方法是桥梁
  3. 事件处理要完整:重写事件方法时记得调用父类实现
  4. 图形项层次要明确:父子关系会影响坐标计算

这个框架虽然概念较多,但一旦理解了坐标系统的原理,使用起来就会得心应手。希望我的学习笔记对大家有所帮助!

相关推荐
CSCN新手听安2 小时前
【Qt】Qt窗口(八)QFontDialog字体对话框,QInputDialog输入对话框的使用,小结
开发语言·c++·qt
tumu_C2 小时前
用std::function减缓C++模板代码膨胀和编译压力的一个场景
开发语言·c++
Hical613 小时前
C++17 实战心得:那些真正改变我写代码方式的特性
c++
Hical614 小时前
实测:C++20 协程 vs Go Gin vs Rust Actix,谁的 Web 性能更强?
c++
草莓熊Lotso4 小时前
《告别 “会用不会讲”:C++ string 底层原理拆解 + 手撕实现,面试 / 开发都适用》
开发语言·c++·面试
会编程的土豆4 小时前
【数据结构与算法】空间复杂度从入门到面试:不仅会算,还要会解释
数据结构·c++·算法·面试·职场和发展
张槊哲4 小时前
C++ 进阶指南:如何丝滑地理解与实践多线程与多进程
开发语言·c++·算法
雪度娃娃4 小时前
Effective Modern C++——型别推导
开发语言·c++
Hello eveybody5 小时前
介绍一下背包DP(C++)
开发语言·c++·动态规划·dp·背包dp
charlie1145141915 小时前
AwesomeQt:最小的Qt6系列迷你版本教程发布!
linux·c++·qt·c