4. 圆形储罐图元与液位动态显示

任务目标

在Qt图形视图框架中,实现图元(QGraphicsItem)的单击选择、框选多选、以及通过Ctrl/Shift键扩展选择,并在选中状态变化时实时更新状态栏信息。本案例将构建一个可独立运行的QGraphicsView应用,支持矩形、圆形、三角形三种图元的交互式选择操作。

环境依赖

仅需Qt 5.12+(含Widgets模块),使用QMake或CMake构建均可。核心依赖为QtWidgets与QtGui模块。

关键完整代码

// main.cpp
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QGraphicsEllipseItem>
#include <QGraphicsPolygonItem>
#include <QStatusBar>
#include <QMainWindow>
#include <QPolygonF>
#include <QPointF>

class SelectableItem : public QGraphicsItem {
public:
    SelectableItem(QGraphicsItem* parent = nullptr) : QGraphicsItem(parent) {
        setFlags(QGraphicsItem::ItemIsSelectable);
        setAcceptHoverEvents(true);
    }
    QRectF boundingRect() const override { return m_rect; }
    void paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) override {
        painter->setPen(QPen(Qt::black, 1.5));
        if (isSelected()) {
            painter->setBrush(QColor(100, 180, 255, 180));
            painter->drawRect(boundingRect().adjusted(-3, -3, 3, 3));
        } else {
            painter->setBrush(QColor(220, 220, 220));
            painter->drawRect(boundingRect());
        }
    }
protected:
    QRectF m_rect;
};

class RectItem : public SelectableItem {
public:
    RectItem(qreal x, qreal y) {
        m_rect = QRectF(x, y, 80, 50);
        setPos(x, y);
    }
};

class CircleItem : public SelectableItem {
public:
    CircleItem(qreal x, qreal y) {
        m_rect = QRectF(x, y, 60, 60);
        setPos(x, y);
    }
    void paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) override {
        painter->setPen(QPen(Qt::black, 1.5));
        if (isSelected()) {
            painter->setBrush(QColor(255, 170, 100, 180));
            painter->drawEllipse(m_rect.adjusted(-3, -3, 3, 3));
        } else {
            painter->setBrush(QColor(220, 220, 220));
            painter->drawEllipse(m_rect);
        }
    }
};

class TriangleItem : public SelectableItem {
public:
    TriangleItem(qreal x, qreal y) {
        m_rect = QRectF(x, y, 70, 60);
        setPos(x, y);
    }
    void paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) override {
        QPolygonF tri;
        tri << QPointF(m_rect.left(), m_rect.bottom())
           << QPointF(m_rect.center().x(), m_rect.top())
           << QPointF(m_rect.right(), m_rect.bottom());
        painter->setPen(QPen(Qt::black, 1.5));
        if (isSelected()) {
            painter->setBrush(QColor(150, 255, 150, 180));
            QPolygonF outer = tri;
            // 外扩选中框
            QPointF center = m_rect.center();
            for (auto& p : outer) {
                QPointF d = p - center;
                p += d * 0.1;
            }
            painter->drawPolygon(outer);
        } else {
            painter->setBrush(QColor(220, 220, 220));
            painter->drawPolygon(tri);
        }
    }
};

class MainWindow : public QMainWindow {
public:
    MainWindow() {
        QGraphicsScene* scene = new QGraphicsScene(this);
        scene->setSceneRect(0, 0, 500, 400);

        // 创建图元并添加到场景
        scene->addItem(new RectItem(30, 30));
        scene->addItem(new CircleItem(180, 50));
        scene->addItem(new TriangleItem(320, 30));
        scene->addItem(new RectItem(60, 180));
        scene->addItem(new CircleItem(250, 200));
        scene->addItem(new TriangleItem(380, 180));

        QGraphicsView* view = new QGraphicsView(scene, this);
        view->setRenderHint(QPainter::Antialiasing);
        view->setDragMode(QGraphicsView::RubberBandDrag); // 启用框选
        view->setSelectionMode(QGraphicsView::ExtendedSelection); // 支持Ctrl/Shift扩展
        setCentralWidget(view);

        statusBar()->showMessage("图元数量: 6 | 选中: 0");

        // 选中状态变化时更新状态栏
        connect(scene, &QGraphicsScene::selectionChanged, this, [this, scene]() {
            int count = scene->selectedItems().size();
            statusBar()->showMessage(QString("图元数量: %1 | 选中: %2")
                                     .arg(scene->items().size()).arg(count));
        });

        setWindowTitle("图元选择与多选 - 例4");
        resize(600, 450);
    }
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    MainWindow w;
    w.show();
    return app.exec();
}

运行与验证

构建运行:

qmake -project
qmake
make
./your_project_name   # Linux/macOS
# Windows下使用对应编译器运行生成的exe

若使用CMake,在CMakeLists.txt中链接Qt5::Widgets即可。

验证步骤与通过条件:

  1. 单击选择:鼠标左键单击任一图元,该图元出现高亮外框(颜色加深),状态栏显示“选中: 1”。
  2. 框选多选:在空白处按住左键拖拽,出现橡皮筋矩形框,松开后框内所有图元被选中,状态栏显示对应选中数量。
  3. 扩展选择:按住Ctrl键依次单击多个图元,可逐个增加/取消选中;按住Shift键单击可连续扩展选择。
  4. 取消选择:单击场景空白区域,所有图元取消选中,状态栏显示“选中: 0”。

以上操作均能实时反映在状态栏计数中,且选中图元视觉高亮明显,即判定案例通过。

关键实现说明

  • 图元通过设置ItemIsSelectable标志获得可选中能力,无需额外处理鼠标事件。
  • 视图的RubberBandDrag模式提供框选交互,ExtendedSelection模式启用Ctrl/Shift扩展语义。
  • 选中视觉反馈在paint()中通过isSelected()判断,绘制外扩高亮边框,不依赖系统默认虚线框。
  • 状态栏通过QGraphicsScene::selectionChanged信号实时更新,该信号在任意选中集合变化时触发。