QT图形/视图架构详解(二)
上一章节示例程序仅演示了Graphics View 的基本结构和三个坐标系的概念,在本章程序中演示其更多的功能。
这个示例程序具有如下的功能:
● 可以创建矩形、椭圆、圆形、三角形、梯形、直线、文字等基本图形项。
● 每个图形项都可以被选择和拖动。
● 图形项或整个视图可以缩放和旋转。
● 图形项重叠时,可以调整前置或后置。
● 多个图形项可以组合,也可以解除组合。
● 可以删除选择的图形项。
● 鼠标在视图上移动时,会在状态栏显示视图坐标和场景坐标。
● 鼠标单击某个图形项时,会显示图形项的局部坐标,还会显示图形项的文字描述和编号。
● 双击某个图形项时,会根据图形项的类型调用颜色对话框或字体对话框,设置图形项的填充颜色、 线条颜色或文字的字体。
● 选中某个图形项时,可以进行按键操作,Delete 键删除图形项,PgUp 放大,PgDn 缩小,空格键旋转 90 度,上下左右光标键移动图形项。
● 鼠标右键长按拖拽场景,在视图小于场景的时候可以拖拽以显示感兴趣内容,而无需拖动滚动条
● 鼠标滚轮可以控制视图的缩放。
TGraphicsView类修改
首先重写TGraphicsView。修改实现的功能如下:
鼠标滚轮视图缩放
添加一个wheelEvent事件,用来获取鼠标滚轮量,正值表示滚轮远离,负值表示滚轮靠近,根据滚轮的正负值进行视图的缩放。
//添加对视图的缩放
void TGraphicsView::wheelEvent(QWheelEvent *event){
// 滚轮的滚动量
//在Qt 5中,delta() 函数用于获取滚轮的滚动量。但在Qt 6中,你应该使用 angleDelta() 函数来替代 delta() 函数。
QPoint scrollAmount = event->angleDelta();
// 正值表示滚轮远离使用者放大负值表示朝向使用者缩小
scrollAmount.y() > 0 ? ZoomIn() : ZoomOut();
}
//放大
void TGraphicsView::ZoomIn(){
Zoom(1.1);
}
//缩小
void TGraphicsView::ZoomOut(){
Zoom(0.9);
}
void TGraphicsView::Zoom(float scaleFactor){
// 防止过小或过大
qreal factor = transform().scale(scaleFactor, scaleFactor).mapRect(QRectF(0, 0, 1, 1)).width();
if (factor < 0.07 || factor > 100)
return;
scale(scaleFactor, scaleFactor);
}
Graphics View的transform函数可以对视图进行从常见的变换,包括平移、缩放、旋转等。setTransform方法可以设置视图的变换矩阵,也可以通过scale、rotate、translate等方法来实现对视图的变换。变换矩阵可以通过QTransform类来表示。QGraphicsView的变换可以对视图中的所有图形项生效,也可以对指定的图形项进行局部变换。
场景拖拽
当视图缩小比场景还小时,有些图形项会看不到,这时候可以通过鼠标右键来拖拽场景使其部分窗口显示在视图中。
设置一个变量m_isTranslate来跟踪鼠标右键状态,右键按下时设置为true,右键松开设置为false,然后在鼠标mouseMoveEvent事件中来拖拽场景。
void TGraphicsView::mousePressEvent(QMouseEvent *event)
{ //鼠标左键按下事件
if (event->button()==Qt::RightButton) //右键键长按用于拖拽场景
{
m_isTranslate = true; //按下标志
m_lastMousePos = event->pos();
this->setCursor(Qt::OpenHandCursor);
}
QGraphicsView::mousePressEvent(event);
}
//鼠标释放事件
void TGraphicsView::mouseReleaseEvent(QMouseEvent *event){
if (event->button() == Qt::RightButton){
m_isTranslate = false; //松开标志
this->setCursor(Qt::CrossCursor);
}
QGraphicsView::mouseReleaseEvent(event);
}
void TGraphicsView::mouseMoveEvent(QMouseEvent *event)
{ //鼠标移动事件
if(m_isTranslate)
{
//获取
QPointF mouseDelta = event->pos()-m_lastMousePos; //获取移动的向量,方向指的是newpoint
Translate(mouseDelta);
}
m_lastMousePos = event->pos();
}
void TGraphicsView::Translate(QPointF delta)
{
int w = viewport()->rect().width(); //获取视图宽度
int h = viewport()->rect().height(); //获取视图高度
// (w / 2,h / 2)即为视图的中心
QPoint newCenter(w / 2. - delta.x()+0.5, h / 2. - delta.y()+0.5); //获取中心的偏移量
centerOn(mapToScene(newCenter)); //反馈到场景当中,mapToScene将坐标转为场景坐标
}
代码中通过按下后当前点和上一次的点进行向量计算,得到拖拽的偏移量,然后转成中心点的偏移量,然后将中心坐标映射到场景中心,重新设置场景中心就相当于拖拽了场景。
完整的自定义Graphics View类代码如下所示:
/.h文件///
#ifndef TGRAPHICSVIEW_H
#define TGRAPHICSVIEW_H
#include <QObject>
#include <QGraphicsView>
class TGraphicsView : public QGraphicsView
{
Q_OBJECT
protected:
void mouseMoveEvent(QMouseEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseDoubleClickEvent(QMouseEvent *event);
void keyPressEvent(QKeyEvent *event);
void wheelEvent(QWheelEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
public:
TGraphicsView(QWidget *parent = nullptr);
void ZoomIn();
void ZoomOut();
void Zoom(float scaleFactor);
void Translate(QPointF delta);
signals:
void mouseMovePoint(QPoint point); //鼠标移动
void mouseClicked(QPoint point); //鼠标单击
void mouseDoubleClick(QPoint point); //双击事件
void keyPress(QKeyEvent *event); //按键事件
private:
bool m_isTranslate;
QPoint m_lastMousePos;
};
#endif // TGRAPHICSVIEW_H
//.cpp文件
#include "tgraphicsview.h"
#include <QMouseEvent>
#include <QPoint>
void TGraphicsView::mouseMoveEvent(QMouseEvent *event)
{ //鼠标移动事件
if(m_isTranslate)
{
//获取
QPointF mouseDelta = event->pos()-m_lastMousePos; //获取移动的向量,方向指的是newpoint
Translate(mouseDelta);
}
m_lastMousePos = event->pos();
QPoint point=event->pos(); //QGraphicsView的坐标
emit mouseMovePoint(point); //发射信号
QGraphicsView::mouseMoveEvent(event);
}
void TGraphicsView::mousePressEvent(QMouseEvent *event)
{ //鼠标左键按下事件
if (event->button()==Qt::RightButton) //右键键长按用于拖拽场景
{
m_isTranslate = true; //按下标志
m_lastMousePos = event->pos();
this->setCursor(Qt::OpenHandCursor);
}
if(event->button()==Qt::LeftButton){ //左键长按用于移动图形项目
QPoint point=event->pos(); //QGraphicsView的坐标
emit mouseClicked(point); //发射信号
}
QGraphicsView::mousePressEvent(event);
}
void TGraphicsView::mouseDoubleClickEvent(QMouseEvent *event)
{ //鼠标双击事件
if (event->button()==Qt::LeftButton)
{
QPoint point=event->pos(); //QGraphicsView的坐标
emit mouseDoubleClick(point); //发射信号
}
QGraphicsView::mouseDoubleClickEvent(event);
}
void TGraphicsView::keyPressEvent(QKeyEvent *event)
{ //按键事件
emit keyPress(event); //发射信号
QGraphicsView::keyPressEvent(event);
}
//鼠标释放事件
void TGraphicsView::mouseReleaseEvent(QMouseEvent *event){
if (event->button() == Qt::RightButton){
m_isTranslate = false; //松开标志
this->setCursor(Qt::CrossCursor);
}
QGraphicsView::mouseReleaseEvent(event);
}
//添加对视图的缩放
void TGraphicsView::wheelEvent(QWheelEvent *event){
// 滚轮的滚动量
//在Qt 5中,delta() 函数用于获取滚轮的滚动量。但在Qt 6中,你应该使用 angleDelta() 函数来替代 delta() 函数。
QPoint scrollAmount = event->angleDelta();
// 正值表示滚轮远离使用者放大负值表示朝向使用者缩小
scrollAmount.y() > 0 ? ZoomIn() : ZoomOut();
QGraphicsView::wheelEvent(event);
}
//放大
void TGraphicsView::ZoomIn(){
Zoom(1.1);
}
//缩小
void TGraphicsView::ZoomOut(){
Zoom(0.9);
}
void TGraphicsView::Zoom(float scaleFactor){
// 防止过小或过大
qreal factor = transform().scale(scaleFactor, scaleFactor).mapRect(QRectF(0, 0, 1, 1)).width();
if (factor < 0.07 || factor > 100)
return;
scale(scaleFactor, scaleFactor);
}
void TGraphicsView::Translate(QPointF delta)
{
int w = viewport()->rect().width(); //获取视图宽度
int h = viewport()->rect().height(); //获取视图高度
// (w / 2,h / 2)即为视图的中心
QPoint newCenter(w / 2. - delta.x()+0.5, h / 2. - delta.y()+0.5); //获取中心的偏移量
centerOn(mapToScene(newCenter)); //反馈到场景当中,mapToScene将坐标转为场景坐标
}
TGraphicsView::TGraphicsView(QWidget *parent):QGraphicsView(parent)
{
}
主界面窗口程序
主窗口类
头文件如下所示
///mainwindow.h///
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QGraphicsScene>
#include <QLabel>
#include <QRandomGenerator>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
private:
const quint32 boundValue=100; //随机数上限值
// QRandomGenerator* random; //随机数发生器
const int ItemId = 1; //绘图项自定义数据的key
const int ItemDesciption = 2; //绘图项自定义数据的key
int seqNum=0; //用于图形项的编号,每个图形项有一个编号
int backZ=0; //用于bring to front
int frontZ=0; //用于bring to back
QGraphicsScene *scene; //场景
QLabel *labViewCord; //用于状态栏
QLabel *labSceneCord;
QLabel *labItemCord;
QLabel *labItemInfo;
void setItemProperty(QGraphicsItem* item, QString desciption); //设置图形项的属性
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
//自定义槽函数
void do_mouseMovePoint(QPoint point); //鼠标移动
void do_mouseClicked(QPoint point); //鼠标单击
void do_mouseDoubleClick(QPoint point); //鼠标双击
void do_keyPress(QKeyEvent *event); //按键
void on_actItem_Rect_triggered();
void on_actItem_Ellipse_triggered();
void on_actItem_Polygon_triggered();
void on_actEdit_Delete_triggered();
void on_actZoomIn_triggered();
void on_actZoomOut_triggered();
void on_actRestore_triggered();
void on_actRotateLeft_triggered();
void on_actRotateRight_triggered();
void on_actEdit_Front_triggered();
void on_actEdit_Back_triggered();
void on_actItem_Line_triggered();
void on_actItem_Text_triggered();
void on_actGroup_triggered();
void on_actGroupBreak_triggered();
void on_actItem_Circle_triggered();
void on_actItem_Triangle_triggered();
// void on_actBackBrush_triggered();
void on_actHelp_triggered();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
● 常数 boundValue 用于设置随机数发生器产生随机数的数值上限值。
● 常数 ItemId 和 ItemDesciption 是用于设置图形项的自定义数据时用到的键。
● 变量 seqNum 用于给每个图形项编号,每个图形项有一个唯一编号。
● 变量 frontZ 用于设置图形项的叠放顺序,数值越大,越在前面显示。
● 变量 backZ 用于设置图形项的叠放顺序,数值越小,越在后面显示。
私有函数 setItemProperty()用于在场景图形项后,设置图形项的属性。
在主窗口构造函数中,为视图创建场景对象scene,并与界面上的组件关联,连接视图发送的鼠标和按键对应的响应槽函数。注意这些信号是视图中自动监听事件发生后传递给外界的,视图本生的事件函数处理的是对于视图自身的操作,而这些信号是发送给其他组件的,在这个例子中是mainwindow组件用于组件中的通信(这些信号也可以被视图自生使用,这里没有使用)
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
labViewCord=new QLabel("View 坐标:"); //创建状态栏上的标签
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);
scene=new QGraphicsScene(-300,-200,600,400); //创建QGraphicsScene
// scene->setBackgroundBrush(QBrush(Qt::white));
ui->view->setScene(scene); //scene与view关联
ui->view->setCursor(Qt::CrossCursor); //设置鼠标光标
// ui->view->setCursor(Qt::DragMoveCursor); //设置鼠标光标
ui->view->setMouseTracking(true); //设置鼠标跟踪
ui->view->setDragMode(QGraphicsView::RubberBandDrag); //设置拖动模式
this->setCentralWidget(ui->view);
connect(ui->view,&TGraphicsView::mouseMovePoint,this, &MainWindow::do_mouseMovePoint);
connect(ui->view,&TGraphicsView::mouseClicked,this, &MainWindow::do_mouseClicked);
connect(ui->view,&TGraphicsView::keyPress, this, &MainWindow::do_keyPress);
connect(ui->view,&TGraphicsView::mouseDoubleClick,this, &MainWindow::do_mouseDoubleClick);
// QObject::connect(ui->view,SIGNAL(mouseMovePoint(QPoint)), this, SLOT(do_mouseMovePoint(QPoint)));
// QObject::connect(ui->view,SIGNAL(mouseClicked(QPoint)),this, SLOT(do_mouseClicked(QPoint)));
// QObject::connect(ui->view,SIGNAL(mouseDoubleClick(QPoint)), this, SLOT(do_mouseDoubleClick(QPoint)));
// QObject::connect(ui->view,SIGNAL(keyPress(QKeyEvent*)),this, SLOT(do_keyPress(QKeyEvent*)));
}
鼠标与键盘信号槽函数
鼠标移动:鼠标在视图上移动时,在状态栏显示光标处的视图坐标和场景坐标。
鼠标左键单击:在视图上单击鼠标选中一个图形项时,程序会在状态栏上显示图形项的局部坐标,并提取其自定义信息并显示。获得的视图坐标 point 需要转换为场景中的坐标 pointScene,再利用 QGraphicsScene 的itemAt()函数获得光标处的图形项。利用 QGraphicsItem 的 mapFromScene()函数将 pointScene 转换为图形项的局部坐标 pointItem。用 data()函数提取图形项的自定义数据。
鼠标左键双击:当鼠标双击某个图形项时,当图形项是矩形、圆形、梯形等有填充色的对象时打开一个颜色选择对话框,设置其填充颜色;当图形项是直线时,设置其线条颜色;当图形项是文字时,打开一个字体对话框,设置其字体。
键盘按键操作:在选中一个图形项之后,我们可以通过键盘按键实现一些快捷操作,例如缩放、旋转、移动等。
这四个槽函数对应的定义如下所示:
//鼠标移动,point是视图的坐标,物理坐标
void MainWindow::do_mouseMovePoint(QPoint point)
{
labViewCord->setText(QString::asprintf("View 坐标:%d,%d", point.x(),point.y()));
QPointF pointScene=ui->view->mapToScene(point); //转换到Scene坐标
labSceneCord->setText(QString::asprintf("Scene 坐标:%.0f,%.0f", pointScene.x(),pointScene.y()));
}
//鼠标单击事件
void MainWindow::do_mouseClicked(QPoint point)
{
QPointF pointScene=ui->view->mapToScene(point); //转换到Scene坐标
QGraphicsItem *item=NULL;
item=scene->itemAt(pointScene,ui->view->transform()); //获取光标下的图形项
if (item != NULL) //有图形项
{
QPointF pointItem=item->mapFromScene(pointScene); //转换为图形项的局部坐标
labItemCord->setText(QString::asprintf("Item 坐标:%.0f,%.0f",pointItem.x(),pointItem.y()));
labItemInfo->setText(item->data(ItemDesciption).toString()+", ItemId="+item->data(ItemId).toString());
}
}
template<class T> void setBrushColor(T *item)
{
QColor color=item->brush().color();
color=QColorDialog::getColor(color,NULL,"选择填充颜色");
if (color.isValid())
item->setBrush(QBrush(color));
}
//鼠标双击事件,调用相应的对话框,设置填充颜色、线条颜色或字体
void MainWindow::do_mouseDoubleClick(QPoint point)
{
QPointF pointScene=ui->view->mapToScene(point); //转换到Scene坐标
QGraphicsItem *item=NULL;
item=scene->itemAt(pointScene,ui->view->transform()); //获取光标下的图形项
if (item == NULL)
return;
switch (item->type()) //图形项的类型
{
case QGraphicsRectItem::Type: //矩形框,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;
}
case QGraphicsLineItem::Type: //直线,设置线条颜色
{
QGraphicsLineItem *theItem=qgraphicsitem_cast<QGraphicsLineItem*>(item);
QPen pen=theItem->pen();
QColor color=theItem->pen().color();
color=QColorDialog::getColor(color,this,"选择线条颜色");
if (color.isValid())
{
pen.setColor(color);
theItem->setPen(pen);
}
break;
}
case QGraphicsTextItem::Type: //文字,设置字体
{
QGraphicsTextItem *theItem=qgraphicsitem_cast<QGraphicsTextItem*>(item);
QFont font=theItem->font();
bool ok=false;
font=QFontDialog::getFont(&ok,font,this,"设置字体");
if (ok)
theItem->setFont(font);
break;
}
}
}
//按键事件
void MainWindow::do_keyPress(QKeyEvent *event)
{
if (scene->selectedItems().count()!=1)
return; //没有选中的图形项,或选中的多于1个
QGraphicsItem *item=scene->selectedItems().at(0);
if (event->key()==Qt::Key_Delete) //删除
scene->removeItem(item);
else if (event->key()==Qt::Key_Space) //顺时针旋转90度
item->setRotation(90+item->rotation());
else if (event->key()==Qt::Key_PageUp) //放大
item->setScale(0.1+item->scale());
else if (event->key()==Qt::Key_PageDown)//缩小
item->setScale(-0.1+item->scale());
else if (event->key()==Qt::Key_Left) //左移
item->setX(-1+item->x());
else if (event->key()==Qt::Key_Right) //右移
item->setX(1+item->x());
else if (event->key()==Qt::Key_Up) //上移
item->setY(-1+item->y());
else if (event->key()==Qt::Key_Down) //下移
item->setY(1+item->y());
}
图形项的创建
setItemProperty()函数用于设置图形项的属性,如是否可移动,可选中,可获得焦点、叠放顺序、在场景中的位置,图形项编号、描述;最后再将该图形项添加到场景中(场景是一个容器);
void MainWindow::setItemProperty(QGraphicsItem *item,QString desciption)
{
item->setFlags(QGraphicsItem::ItemIsMovable //可移动
| QGraphicsItem::ItemIsSelectable //可选中
| QGraphicsItem::ItemIsFocusable); //可以获得焦点
item->setZValue(++frontZ); //叠放顺序号
quint32 v1=QRandomGenerator::global()->bounded(boundValue);
quint32 v2=QRandomGenerator::global()->bounded(boundValue);
item->setPos(v1,v2); //在场景中的位置
item->setData(ItemId,++seqNum); //图形项编号
item->setData(ItemDesciption,desciption); //图形项描述
scene->addItem(item); //添加到场景
scene->clearSelection();
item->setSelected(true);
}
setZValue()函数设置图形项的 Z 值,Z 值控制叠放顺序,当有多个图形项叠放在一起时,Z 值最大
的显示在最前面。这里意思是越新创建的叠放是在最前面。
setPos(x, y)函数设置图形项的位置,如果图形项有父容器项,坐标(x, y)是父容器的坐标,否则就是图形场景的坐标。
程序中使用了全局的随机数发生器 QRandomGenerator::global(),使用函数 bounded() 产生限制范围的随机数。将随机数生成的值作为图形项的放置位置。。
setData()函数用于设置图形项的自定义数据,这个函数的原型定义如下:
void QGraphicsItem::setData(int key, const QVariant &value)
参数 key 是数据名称,value 是具体的数据内容,可以是任何类型。key 和 value 是一个键值对使 用 setData()一次可以设置一个键值对,可以为一个图形项设置多个自定义键值对。
创建矩形
void MainWindow::on_actItem_Rect_triggered()
{ //添加一个矩形
//x,y 为左上角的图元局部坐标,图元中心点为0,0
QGraphicsRectItem *item=new QGraphicsRectItem(-50,-25,100,50);
item->setBrush(QBrush(Qt::yellow));
setItemProperty(item, "矩形");
}
创建椭圆
void MainWindow::on_actItem_Ellipse_triggered()
{ //添加一个椭圆
QGraphicsEllipseItem *item=new QGraphicsEllipseItem(-50,-30,100,60);
item->setBrush(QBrush(Qt::blue)); //填充颜色
setItemProperty(item, "椭圆");
}
创建梯形
void MainWindow::on_actItem_Polygon_triggered()
{ //添加一个梯形
QGraphicsPolygonItem *item=new QGraphicsPolygonItem;
QPolygonF points;
points.append(QPointF(-40,-40)); //添加顶点坐标
points.append(QPointF(40,-40));
points.append(QPointF(100,40));
points.append(QPointF(-100,40));
item->setPolygon(points); //创建多边形
item->setBrush(QBrush(Qt::green));
setItemProperty(item,"梯形");
}
创建直线
void MainWindow::on_actItem_Line_triggered()
{//添加直线
QGraphicsLineItem *item=new QGraphicsLineItem(-100,0,100,0);
QPen pen(Qt::red);
pen.setWidth(3);
item->setPen(pen);
setItemProperty(item,"直线");
}
创建文字
void MainWindow::on_actItem_Text_triggered()
{ //添加文字
QString str=QInputDialog::getText(this,"输入文字","请输入文字");
if (str.isEmpty())
return;
QGraphicsTextItem *item=new QGraphicsTextItem(str);
QFont font=this->font();
font.setPointSize(20);
font.setBold(true);
item->setFont(font); //设置字体
setItemProperty(item,"文字");
}
创建圆形
void MainWindow::on_actItem_Circle_triggered()
{ //添加圆形
QGraphicsEllipseItem *item=new QGraphicsEllipseItem(-50,-50,100,100);
item->setBrush(QBrush(Qt::cyan));
setItemProperty(item,"圆形");
}
创建三角形
void MainWindow::on_actItem_Triangle_triggered()
{ //添加三角形
QGraphicsPolygonItem *item=new QGraphicsPolygonItem;
QPolygonF points;
points.append(QPointF(0,-40)); //添加顶点坐标
points.append(QPointF(60,40));
points.append(QPointF(-60,40));
// points.append(QPointF(0,0)); //添加顶点坐标
// points.append(QPointF(50,0));
// points.append(QPointF(0,50));
item->setPolygon(points); //三角形就是一种多边形
item->setBrush(QBrush(Qt::magenta));
setItemProperty(item,"三角形");
}
图形项的操作
主窗口上水平工具栏上的一些按钮实现图形项的缩放、旋转、组合等操作。
缩放
图形项的缩放使用 QGraphicsItem 的 setScale()函数,参数大于 1 是放大,小于 1 是缩小。下面是放大和缩小按钮关联的槽函数代码:
void MainWindow::on_actZoomIn_triggered()
{ //放大
int cnt=scene->selectedItems().count(); //选中图形项的个数
if (cnt==1) //缩放单个图形项
{
QGraphicsItem *item;
item=scene->selectedItems().at(0);
item->setScale(0.1+item->scale());
}
else //缩放视图
ui->view->scale(1.1, 1.1);
}
void MainWindow::on_actZoomOut_triggered()
{//缩小
int cnt=scene->selectedItems().count(); //选中图形项的个数
if (cnt==1) //缩放单个图形项
{
QGraphicsItem *item;
item=scene->selectedItems().at(0);
item->setScale(item->scale()-0.1);
}
else //缩放视图
ui->view->scale(0.9,0.9);
}
QGraphicsScene 的 selectedItems()函数返回场景中选中的图形项的列表。如果只有一个图形项被选 中,就用 QGraphicsItem 的 setScale()函数对图形项进行缩放;如果选中的图形项个数大于 1个,或没有图形项被选中,就用 QGraphicsView 的 scale()函数对绘图视图进行缩放。
旋转
图形项的旋转使用 QGraphicsItem 的 setRotation()函数,参数为角度值,正值表示顺时针旋转,负值表示逆时针旋转。下面是左旋转和右旋转按钮关联的槽函数代码:
void MainWindow::on_actRotateRight_triggered()
{//顺时针旋转,右旋转
int cnt=scene->selectedItems().count();
if (cnt==1) //单个图形项旋转
{
QGraphicsItem* item=scene->selectedItems().at(0);
item->setRotation(+30+item->rotation());
}
else //视图旋转
ui->view->rotate(+30);
}
void MainWindow::on_actEdit_Front_triggered()
{ //bring to front,前置
int cnt=scene->selectedItems().count();
if (cnt>0)
{ //只处理选中的第1个图形项
QGraphicsItem* item=scene->selectedItems().at(0);
item->setZValue(++frontZ);
}
}
恢复坐标变换
缩放和旋转都是坐标变换,要取消所有变换恢复初始状态,调用 QGraphicsItem 或QGraphicsView 的 resetTransform()函数。下面是“恢复”按钮关联的槽函数代码:
void MainWindow::on_actRestore_triggered()
{//取消所有变换
int cnt=scene->selectedItems().count(); //选中图形项的个数
if (cnt==1) //针对单个图形项
{
QGraphicsItem* item=scene->selectedItems().at(0);
// item->resetTransform(); //不起作用
item->setRotation(0);
item->setScale(1.0);
}
else //针对视图
ui->view->resetTransform();
}
改变叠放顺序
void MainWindow::on_actEdit_Front_triggered()
{ //bring to front,前置
int cnt=scene->selectedItems().count();
if (cnt>0)
{ //只处理选中的第1个图形项
QGraphicsItem* item=scene->selectedItems().at(0);
item->setZValue(++frontZ);
}
}
void MainWindow::on_actEdit_Back_triggered()
{//bring to back,后置
int cnt=scene->selectedItems().count();
if (cnt>0)
{//只处理选中的第1个图形项
QGraphicsItem* item=scene->selectedItems().at(0);
item->setZValue(--backZ);
}
}
frontZ 和 backZ 是在 MainWindow 类中定义的私有变量,专门用于存储叠放次序的编号。frontZ只
增加,所以每增加一次都是最大值,设置该值的图形项就可以显示在最前面;backZ 只减少,所以每减小一次都是最小值,设置该值的图形项就可以显示在最后面。
图形项组合与拆解
可以将多个图形项组合为一个图形项,当做一个整体进行操作,如同 PowerPoint 软件里图形组合 功能一样。使用 QGraphicsItemGroup 类实现多个图形项的组合,QGraphicsItemGroup 是QGraphicsItem 的子类,所以,实质上也是一个图形项。
当有多个图形项被选择时,程序创建一个QGraphicsItemGroup类型的对象group,并添加到场景中,然后将选中的图形项逐一添加到 group 中。这样创建的 group 就是场景中的一个图形项,可以对其进行缩放、旋转等操作。
一个组合对象也可以被打散,使用 QGraphicsScene 的 destroyItemGroup()函数可以打散一个组合对象。这个函数打散组合,删除组合对象,但是不删除原来组合里的图形项。
组合与拆解的槽函数如下所示:
void MainWindow::on_actGroup_triggered()
{ //组合
int cnt=scene->selectedItems().count();
if (cnt>1)
{
QGraphicsItemGroup* group =new QGraphicsItemGroup; //创建组合
scene->addItem(group); //添加到场景中
for (int i=0;i<cnt;i++) //将选择的图形项添加到组合中
{
QGraphicsItem* item=scene->selectedItems().at(0);
item->setSelected(false); //取消选择
item->clearFocus(); //清除焦点状态
group->addToGroup(item); //添加到组合
}
setItemProperty(group, "组合"); //设置特性
}
}
void MainWindow::on_actGroupBreak_triggered()
{ //break group,打散组合
int cnt=scene->selectedItems().count();
if (cnt==1)
{
QGraphicsItemGroup *group;
group=(QGraphicsItemGroup*)scene->selectedItems().at(0);
scene->destroyItemGroup(group); //打散组合
}
}
图形项的删除
使用 QGraphicsScene 的 removeItem()函数从场景中移除某个图形项,函数如下:
void MainWindow::on_actEdit_Delete_triggered()
{ //删除所有选中的图形项
int cnt=scene->selectedItems().count();
for (int i=0;i<cnt;i++)
{
QGraphicsItem* item=scene->selectedItems().at(0);
scene->removeItem(item); //移除图形项
delete item; //删除对象,释放内存
}
}
注意此处scene->removeItem(item);只是从场景中移除了图形项,表示场景不再管理该图形项,但是其占据的内存依然存在,因此需要使用delete来彻底消除。添加到场景中的图形项最后无需手工删除,在场景被删除时,其中的图形项也会自动被删除。
额外技巧:在设计好一款软件后,需要为该软件写一个使用说明文档,可以通过点击界面的按钮来触发打开相应的文档。示例代码如下:
void MainWindow::on_actHelp_triggered()
{
QString helpFile=QCoreApplication::applicationDirPath()+"/说明文档.pdf";
QDesktopServices::openUrl(QUrl::fromLocalFile(helpFile));
}
参考
Qt 6 C++开发指南