45、QGraphicsScene 与 QGraphicsView 框架---------绘图
QGraphicsScene 与 QGraphicsView 框架QGraphicsScene 和 QGraphicsView 提供了一个高级的二维图形框架适用于创建复杂的图形界面、动画以及交互式应用。它基于场景-视图模式允许在场景中添加和管理大量的图形项然后通过视图进行显示和交互。基础概念●QGraphicsScene管理和组织所有的图形项QGraphicsItem。●QGraphicsView显示 QGraphicsScene并提供视图变换缩放、旋转等和用户交互功能。●QGraphicsItem场景中的基本图形单元可以是预定义的形状或自定义的图形。打个比方QGraphicsItem 相当于是舞台上的人QGraphicsScene 相当于是舞台QGraphicsView 相当于我们看待舞台的视角QGraphicsSceneQGraphicsScene 是一个用于管理和存储图形项QGraphicsItem的场景。它负责处理图形项的添加、删除、更新和渲染。主要功能●管理图形项: 可以添加、删除和管理多个图形项。●事件处理: 处理与图形项相关的事件如鼠标点击、键盘输入等。●坐标系统: 提供一个坐标系统可以在其中定位和操作图形项。常用方法●addItem(QGraphicsItem *item): 向场景中添加图形项。●removeItem(QGraphicsItem *item): 从场景中移除图形项。●clear(): 清空场景中的所有图形项。●items(): 返回场景中的所有图形项。QGraphicsViewQGraphicsView 是一个用于显示 QGraphicsScene 的窗口部件。它负责将场景的内容渲染到屏幕上并提供用户交互。主要功能●可视化图形场景: 显示 QGraphicsScene 中的内容。●视图变换: 支持缩放、平移等视图变换操作。●事件转发: 将用户输入事件如鼠标和键盘事件转发给场景中的图形项。常用方法●setScene(QGraphicsScene *scene): 设置要显示的场景。●fitInView(): 调整视图以适应场景的内容。●scale(): 缩放视图。●setRenderHint(): 设置渲染提示以提高绘制质量。添加与管理图形项QGraphicsScene提供了多种添加图形项的方法如addRect、addEllipse、addText等也支持自定义图形项。示例1创建简单的图形场景实现下图所示我们在场景中添加几个item然后实现拖动和选中它们的效果。我们声明一个类GraphicsSceneWidget使其继承自QGraphicsView#include QGraphicsView #include QWidget class GraphicsSceneWidget: public QGraphicsView { Q_OBJECT public: GraphicsSceneWidget(QWidget* parent nullptr); ~GraphicsSceneWidget(); };我们可以在GraphicsSceneWidget构造函数内部创建QGraphicsScene向QGraphicsScene添加一些图形最后将QGraphicsScene设置到视图里GraphicsSceneWidget::GraphicsSceneWidget(QWidget *parent) { //创建场景 QGraphicsScene *scene new QGraphicsScene(this); //设置场景区域 //scene-setSceneRect(0,0,600,600); scene-setSceneRect(-1000, -1000, 2000, 2000); // 添加矩形 QGraphicsRectItem *rect scene-addRect(50, 50, 100, 100, QPen(Qt::black), QBrush(Qt::yellow)); //设置可移动可选中 rect-setFlag(QGraphicsItem::ItemIsMovable ,true); rect-setFlag(QGraphicsItem::ItemIsSelectable ,true); // 添加椭圆 QGraphicsEllipseItem *ellipse scene-addEllipse(200, 50, 100, 100, QPen(Qt::blue), QBrush(Qt::green)); //设置可移动可选中 ellipse-setFlag(QGraphicsItem::ItemIsMovable ,true); ellipse-setFlag(QGraphicsItem::ItemIsSelectable ,true); // 添加文本 QGraphicsTextItem *text scene-addText(Hello, QGraphicsScene!); text-setFont(QFont(Arial, 16)); text-setDefaultTextColor(Qt::red); text-setPos(350, 50); //设置可移动可选中 text-setFlag(QGraphicsItem::ItemIsMovable ,true); text-setFlag(QGraphicsItem::ItemIsSelectable ,true); // 设置场景 setScene(scene); // 设置视图属性 setRenderHint(QPainter::Antialiasing); // 设置拖拽模式 setDragMode(QGraphicsView::ScrollHandDrag); }关于拖拽模式NoDrag: 无拖动操作适用于默认或无交互状态。ScrollHandDrag: 用于滚动视图允许用户通过拖动来平移内容。RubberBandDrag: 用于选择多个对象或区域用户通过拖动来绘制选择框。接下来我们在主函数创建视图并且展示int main(int argc, char *argv[]) { QApplication a(argc, argv); //创建视图 GraphicsSceneWidget w; //展示视图 w.show(); return a.exec(); }示例2调整视图我们可以通过调整视图实现不同方式查看场景包括旋转缩放平移等如下图所示我们在MainWindow.ui中为CentralWidget添加一个水平布局然后将CentralWidget调整为垂直布局那么水平布局就会填满整个CentralWidget。左边放入QGraphicsView右边放入一个widgetwidget宽度限制120。接下来在右侧widget中加入几个按钮并将右侧widget设置为垂直布局。ui布局如下属性结构如下我们之前自定义了一个视图是GraphicsSceneWidgetGraphicsSceneWidget继承自QGraphicsView.那么我们可以将QGraphicsView提升为我们自定义的GraphicsSceneWidget类型右键属性图中的graphicsView点击提升为弹出窗口后添加提升类的名称为GraphicsSceneWidget勾选全局包含。然后点击添加再点击提升。可以看到属性图中graphicsView提升为GraphicsSceneWidget类型了。接下来分别实现几个槽函数//实现放大 void MainWindow::on_zoomIn_clicked() { //视图放大1.5被 ui-graphicsView-scale(1.5,1.5); } //实现缩小 void MainWindow::on_zoomOut_clicked() { //视图缩小0.5 ui-graphicsView-scale(0.5,0.5); } //顺时针旋转 void MainWindow::on_clockwise_clicked() { //顺时针旋转15度 ui-graphicsView-rotate(15); } //逆时针旋转 void MainWindow::on_counterClockwise_clicked() { //逆时针旋转15度 ui-graphicsView-rotate(-15); } //左平移 void MainWindow::on_moveleft_clicked() { // 将可视区域的矩形范围的中心点的坐标转换到场景中得出在场景中的坐标 QPointF currentCenter ui-graphicsView-mapToScene( ui-graphicsView-viewport()-rect().center()); // 视口左移 ui-graphicsView-centerOn(currentCenter.x() - 100, currentCenter.y()); } //右平移 void MainWindow::on_moveRight_clicked() { // 将可视区域的矩形范围的中心点的坐标转换到场景中得出在场景中的坐标 QPointF currentCenter ui-graphicsView-mapToScene( ui-graphicsView-viewport()-rect().center()); // 视口右移 ui-graphicsView-centerOn(currentCenter.x() 100, currentCenter.y()); }这样我们点击对应的按钮就可以实现视图的缩放旋转平移了。示例3自定义图形(拓展内容)我们可以自定义图形比如实现如下图的五角星的绘制创建好项目CustomItem之后我们添加一个类StarItem继承自QGraphicsItemclass StarItem : public QGraphicsItem { public: StarItem(QGraphicsItem *parent nullptr); // 重写边界函数 QRectF boundingRect() const override; // 重写绘制函数 void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; protected: // 重写鼠标事件处理 void mousePressEvent(QGraphicsSceneMouseEvent *event) override; void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; private: QPainterPath starPath; };我们编程中采用的cos和sin等正余弦函数的参数和数学中不同编程中正余弦参数为弧度比如180°角度的弧度就是M_PI就是我们常说的3.1415926.使用M_PI要包含头文件#include qmath.h那么360°对应的弧度就是M_PI * 360/180 2 *M_PI30°的余弦函数计算公式为cos(M_PI*30/180)一个五角星有五个角所以角与角之间应该是360/572°72°对应的弧度就是M_PI*72/180,我们用deg存储假设我们将五角星的原点设置为(0,0)那么它上面的点我们定义为第一个点也就是(0,-R)接下来求顺时针的第二个点已知第一个点和第二个点之间的角度为72°(两条红线夹角)我们通过计算72°对应的弧度应为deg M_PI*72/180我们还知道第二个点到圆心的长度为R那么可以计算出绿色线的长度为R*sin(deg)黑色的线的长度为R*cos(deg)所以第二个点的坐标为(R*sin(deg), -R*cos(deg)),因为坐标原点在五角星中心所以第二个点的纵坐标为-R*cos(deg)依次类推可以推出第三个点的坐标为(R*sin(2*deg),-R*cose(2*deg)), 我们推出五个点的坐标然后将要绘制的路线提前存起来路线就是先将画笔移动到第一个点的位置接下来从第一个点向第三个点连线红色的线表示绘制的路径从第三个点向第五个点连线从第五个点向第二个点连线从第二个点向第四个点连线从第四个点回到原点StarItem::StarItem(QGraphicsItem *parent) : QGraphicsItem(parent) { double R 100; double deg M_PI*72/180; QListQPoint points { QPoint(0,-R), QPoint(R*sin(deg),-R*cos(deg)), QPoint(R*sin(2*deg),-R*cos(2*deg)), QPoint(R*sin(3*deg),-R*cos(3*deg)), QPoint(R*sin(4*deg),-R*cos(4*deg)), }; //移动到第一个点 starPath.moveTo(points[0]); //从第一个点向第三个点连线 starPath.lineTo(points[2]); //从第三个点向第五个点连线 starPath.lineTo(points[4]); //从第五个点向第二个点连线 starPath.lineTo(points[1]); //从第二个点向第四个点连线 starPath.lineTo(points[3]); //从第四个点回到原点 starPath.closeSubpath(); // 设置图形项在场景的位置 setPos(300, 200); // 启用图形项的选中状态 setFlag(QGraphicsItem::ItemIsSelectable, true); setFlag(QGraphicsItem::ItemIsMovable, true); }接下来实现item所在的碰撞区域每一个item都有一个矩形区域表示它的边界我们重写这个函数QRectF StarItem::boundingRect() const { // 增加边界以避免裁剪 return starPath.boundingRect().adjusted(-5, -5, 5, 5); }starPath.boundingRect()starPath 是一个 QPainterPath 对象它表示一个绘图路径可能包含多个形状、线条和曲线。boundingRect() 是 QPainterPath 的一个成员函数它返回一个 QRectF 对象表示该路径的边界矩形。这个矩形是一个最小的矩形完全包围了路径中的所有形状。adjusted(left, top, right, bottom)adjusted() 是 QRectF 的一个成员函数用于调整矩形的边界。它接受四个参数分别表示left: 向左调整的距离top: 向上调整的距离right: 向右调整的距离bottom: 向下调整的距离我们可以重写item的paintEvent函数达到自定义绘制item的效果//重回item样式 void StarItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); // 设置画笔和刷子 //选中状态更改画笔颜色 if (isSelected()) { painter-setPen(QPen(Qt::red, 2)); } else { painter-setPen(QPen(Qt::cyan, 2)); } painter-setBrush(QBrush(Qt::yellow)); // 绘制星形路径 painter-drawPath(starPath); }我们同样可以重写鼠标点击和释放的功能这里没有做特殊处理只是调用了基类的处理void StarItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { // 点击时改变颜色 Q_UNUSED(event); // 可以在这里添加更多自定义的交互逻辑 QGraphicsItem::mousePressEvent(event); } void StarItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { Q_UNUSED(event); QGraphicsItem::mouseReleaseEvent(event); }接下来在main函数中加载这个itemint main(int argc, char *argv[]) { QApplication a(argc, argv); //创建场景 QGraphicsScene *scene new QGraphicsScene(); //设置场景大小 scene-setSceneRect(0, 0, 600, 400); // 添加自定义星形图形项 StarItem *star new StarItem(); scene-addItem(star); // 创建视图 QGraphicsView *view new QGraphicsView(scene); view-setRenderHint(QPainter::Antialiasing); view-setWindowTitle(自定义图形项示例); view-resize(800, 600); view-show(); return a.exec(); }