1. 资源介绍
功能:使用qt在画板上绘制各种形状,并保持绘制内容到xml文件中。
项目源码:https://github.com/egan2015/qdraw
软件界面:
1.1 支持shape
6种
1.2 支持的功能
6种,分别是对绘制的图形进行撤销undo,重做redo,裁剪,复制,粘贴,删除功能。
2. 总体类图关系
总体分割3个独立块。
2.1 绘制类
DrawTool
基类定义了基本的绘图和交互行为,而各子类分别实现了不同的编辑和绘制功能。这种设计方式具有良好的扩展性,可以轻松添加新的工具类以支持更多类型的图形操作。但是具体的绘制操作是放在各自的GraphicsItem,后面会介绍。
很奇怪,为何父类和子类都没有继承QObject,难道是不使用qt的信号槽机制?
注解:懂得可以不看,跳过。
这里以void PolygonTool::mouseReleaseEvent函数为例,其内部有一句
emit scene->itemAdded( item );
在 C++ 的 Qt 框架中,要使用信号和槽机制的确需要类继承自
QObject
并使用Q_OBJECT
宏。然而,PolygonTool
类本身并没有使用Q_OBJECT
或继承自QObject
,却调用了emit
关键字发送信号。这可能导致一些困惑。信号定义在其他类中:
itemAdded
是在scene
对象中定义的信号。PolygonTool
没有继承自QObject
,它仍然可以通过持有的scene
对象(必须是QObject
的子类)来发射信号。这是 Qt 框架设计的一部分,允许灵活的信号和槽连接,即使在复杂的类继承结构中。
2.2 GraphicsItem类
3. 绘制类成员介绍
3.1 DrawTool基类
其他四个类SelectTool, RotationTool, RectTool, PolygonTool都是继承此基类。 DrawTool
是一个基类,用于为不同的绘图工具提供公共功能和接口。它的主要作用是定义和实现一些基础的绘图操作,以及管理和查找不同的绘图工具实例。
//drawtool.h
enum DrawShape
{
selection, // 选择模式:用于选择和操作现有的图形项。
rotation, // 旋转模式:用于对图形项进行旋转操作。
line, // 线段:用于绘制直线。
rectangle, // 矩形:用于绘制矩形。
roundrect, // 圆角矩形:用于绘制带有圆角的矩形。
ellipse, // 椭圆:用于绘制椭圆形状。
bezier, // 贝塞尔曲线:用于绘制贝塞尔曲线,用于平滑和复杂的曲线形状。
polygon, // 多边形:用于绘制闭合的多边形。
polyline, // 折线:用于绘制由多个线段组成的折线。
};
// 基类
class DrawTool
{
public:
DrawTool( DrawShape shape ); // 构造函数,初始化绘制工具,参数为绘制形状
virtual void mousePressEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ; // 鼠标按下事件,虚函数
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene );
virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event ,DrawScene *scene );
DrawShape m_drawShape; // 当前绘制工具的形状
bool m_hoverSizer; // 是否悬停在调整大小的手柄上
static DrawTool * findTool( DrawShape drawShape ); // 根据形状查找相应的绘制工具
static QList<DrawTool*> c_tools; // 静态成员,保存所有绘制工具的列表
static QPointF c_down; // 静态成员,保存鼠标按下时的位置
static quint32 c_nDownFlags; // 静态成员,保存按下时的标志
static QPointF c_last; // 静态成员,保存最后一次鼠标事件的位置
static DrawShape c_drawShape; // 静态成员,当前选中的绘制形状
};
// drawtool.cpp
// 初始化全部的静态成员变量,为啥不在构造函数内部初始化:
// 语法规则, 静态成员变量的初始化必须在类定义的外部进行
// 因为是静态的,静态成员属于类而不是对象,避免重复初始化,静态成员变量在程序加载时就会被分配内存并初始化,而构造函数是在对象被创建时才会调用。
QList<DrawTool*> DrawTool::c_tools; // 初始化静态成员,绘制工具列表
QPointF DrawTool::c_down; // 初始化静态成员,鼠标按下位置
QPointF DrawTool::c_last; // 初始化静态成员,最后一次鼠标位置
quint32 DrawTool::c_nDownFlags; // 初始化静态成员,鼠标按下标志
DrawShape DrawTool::c_drawShape = selection; // 初始化静态成员,当前绘制形状为 selection
// DrawTool 构造函数,初始化绘制形状并将工具加入工具列表
DrawTool::DrawTool(DrawShape shape)
{
m_drawShape = shape ; // 设置绘制形状
m_hoverSizer = false; // 初始化悬停调整大小状态
c_tools.push_back(this); // 将当前工具加入工具列表
}
// 鼠标按下事件处理
void DrawTool::mousePressEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
c_down = event->scenePos(); // 保存鼠标 按下时的位置
c_last = event->scenePos(); // 初始化最后位置为 按下时位置
}
// 鼠标移动事件处理
void DrawTool::mouseMoveEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
c_last = event->scenePos(); // 更新最后一次鼠标事件的位置
}
// 鼠标释放事件处理
void DrawTool::mouseReleaseEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
if (event->scenePos() == c_down ) // 如果鼠标释放位置和按下位置相同
c_drawShape = selection; // 设置当前绘制形状为 selection
setCursor(scene,Qt::ArrowCursor); // 重置光标为箭头样式
}
// 鼠标双击事件处理,当前为空实现
void DrawTool::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
}
// 根据形状查找相应的绘制工具
DrawTool *DrawTool::findTool(DrawShape drawShape)
{
QList<DrawTool*>::const_iterator iter = c_tools.constBegin(); // 获取工具列表的常量迭代器
for ( ; iter != c_tools.constEnd() ; ++iter ){
if ((*iter)->m_drawShape == drawShape ) // 如果找到形状匹配的工具
return (*iter); // 返回该工具
}
return 0; // 如果没有匹配的工具,返回 nullptr
}
3.2 SelectTool选中类
SelectTool
类专注于处理绘图场景中的选择操作,包括选择、移动、缩放和编辑图形项。它通过重写基类 DrawTool
的虚拟函数,提供了具体的选择操作实现。SelectTool
管理图形项的选择状态、虚线框的显示以及选择模式的切换,为用户提供了直观的操作反馈和功能。
class SelectTool : public DrawTool
{
public:
SelectTool();
virtual void mousePressEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene );
QPointF initialPositions; // 记录初始位置
QPointF opposite_; // 对应点,用于大小调整操作
QGraphicsPathItem * dashRect; // 虚线框,用于选择时的视觉反馈
GraphicsItemGroup * selLayer; // 选择的图层
};
// 构造函数,初始化成员变量
SelectTool::SelectTool()
:DrawTool(selection) // 初始化基类 DrawTool,模式为 selection (选择),这样继承过来的变量得到了初始化。
{
dashRect = 0; // 初始化虚线框指针为空
selLayer = 0; // 初始化选择层指针为空
opposite_ = QPointF(); // 初始化对点为空,即与当前拖动的控制点相对的点。
}
// 选中时会有三种操作:size(调整大小),editor(编辑),move(移动)
void SelectTool::mousePressEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
DrawTool::mousePressEvent(event,scene); // 调用基类的鼠标按下事件处理
if ( event->button() != Qt::LeftButton ) return; // 如果不是左键按下,则不处理
if (!m_hoverSizer)
scene->mouseEvent(event); // 如果没有悬停在调整器上,则将事件传递给场景
nDragHandle = Handle_None; // 初始化拖拽句柄为无
selectMode = none; // 初始化选择模式为无
QList<QGraphicsItem *> items = scene->selectedItems(); // 获取当前选中的图形项,是通过鼠标点击、框选选中的。
AbstractShape *item = 0; // 初始化选中的形状指针为空
if ( items.count() == 1 ) // 如果只选中了一个图形项
item = qgraphicsitem_cast<AbstractShape*>(items.first()); // 尝试将其转换为 AbstractShape 类型
if ( item != 0 ){ // 如果选中的图形项不为空
nDragHandle = item->collidesWithHandle(event->scenePos()); // 检查是否与句柄碰撞(鼠标在形状的边缘位置),从而判断当前模式。
if ( nDragHandle != Handle_None && nDragHandle <= Left ) // 如果拖拽句柄<=Left,在左边就说明有碰撞
selectMode = size; // 设置选择模式为大小调整,其中size是枚举SelectMode成员
else if ( nDragHandle > Left ) // 如果拖拽句柄>Left, 设置选择模式为编辑
selectMode = editor;
else
selectMode = move; // 否则光标就是没有碰撞,而是在形状内部,设置为移动模式
if ( nDragHandle!= Handle_None && nDragHandle <= Left ){ // 如果是大小调整模式
opposite_ = item->opposite(nDragHandle); // 获取相对点,即与当前拖动的控制点相对的点。
if( opposite_.x() == 0 )
opposite_.setX(1); // 防止出现零值,设置为 1
if (opposite_.y() == 0 )
opposite_.setY(1);
}
setCursor(scene,Qt::ClosedHandCursor); // 设置光标为闭合手形状
}else if ( items.count() > 1 )
selectMode = move; // 如果选中了多个图形项,则只能是移动模式
if( selectMode == none ){ // 如果没有设置模式
selectMode = netSelect; // 设置为网络选择模式
if ( scene->view() ){
QGraphicsView * view = scene->view();
view->setDragMode(QGraphicsView::RubberBandDrag); // 设置视图为橡皮筋拖动模式
}
#if 0
if ( selLayer ){ // 取消注释以清除选择图层
scene->destroyGroup(selLayer);
selLayer = 0;
}
#endif
}
if ( selectMode == move && items.count() == 1 ){ // 如果是移动模式且只有一个选中项
if (dashRect ){
scene->removeItem(dashRect); // 如果已有虚线框,移除
delete dashRect; // 删除虚线框
dashRect = 0; // 重置虚线框指针
}
dashRect = new QGraphicsPathItem(item->shape()); // 创建一个新的虚线框
dashRect->setPen(Qt::DashLine); // 设置为虚线
dashRect->setPos(item->pos()); // 设置位置
dashRect->setTransformOriginPoint(item->transformOriginPoint()); // 设置变换原点
dashRect->setTransform(item->transform()); // 应用图形项的变换
dashRect->setRotation(item->rotation()); // 设置旋转
dashRect->setScale(item->scale()); // 设置缩放
dashRect->setZValue(item->zValue()); // 设置 Z 值
scene->addItem(dashRect);// 将虚线框添加到场景
initialPositions = item->pos(); // 记录初始位置
}
}
// 处理鼠标移动事件
void SelectTool::mouseMoveEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
DrawTool::mouseMoveEvent(event,scene); // 调用基类的鼠标移动事件处理
QList<QGraphicsItem *> items = scene->selectedItems(); // 获取当前选中的图形项
AbstractShape * item = 0; // 初始化选中的形状指针为空
if ( items.count() == 1 ){ // 如果只选中了一个图形项
item = qgraphicsitem_cast<AbstractShape*>(items.first()); // 尝试将其转换为 AbstractShape 类型
if ( item != 0 ){ // 如果选中的图形项不为空
if ( nDragHandle != Handle_None && selectMode == size ){ // 如果是大小调整模式
if (opposite_.isNull()){ // 如果对点为空
opposite_ = item->opposite(nDragHandle); // 获取对点,即与当前拖动的控制点相对的点。
if( opposite_.x() == 0 )
opposite_.setX(1); // 防止零值,设置为 1
if (opposite_.y() == 0 )
opposite_.setY(1);
}
/*
mapFromScene 的作用是将场景坐标(Scene Coordinates)转换为图形项自身的局部坐标系(Item Coordinates)。
将全局的场景坐标 c_last 转换为 item 自身的局部坐标系中的一个点。这是因为 c_last 是在场景中的一个点,
但我们在操作图形项时,通常需要知道该点在图形项自身坐标系中的位置。
按下c_down之后,移动c_last鼠标就是拉伸
按下的坐标是initial_delta,移动后的坐标是new_delta
在图形项的缩放过程中,我们通常是以某个固定点(对点 opposite_)为基准,
然后根据鼠标的位置(c_last 和 c_down)计算拉伸或缩放的比例。
这样就能准确地控制图形的大小变化,使得缩放操作相对直观且符合用户的期望。
*/
QPointF new_delta = item->mapFromScene(c_last) - opposite_; // 计算新的相对距离: c_last鼠标移动的坐标 - 边界
QPointF initial_delta = item->mapFromScene(c_down) - opposite_; // 计算初始相对距离: c_down鼠标按下的坐标 - 边界
double sx = new_delta.x() / initial_delta.x(); // 计算 X 方向的缩放比例
double sy = new_delta.y() / initial_delta.y(); // 计算 Y 方向的缩放比例
/*
nDragHandle: 一个图形项会有多个控制手柄(如顶点、边缘中点等),每个手柄对应不同的缩放或调整操作。
sx sy: 表示 X,Y 方向上的缩放比例。
opposite_: 表示固定不动的对点坐标(相对于拖动手柄)。在缩放或拉伸过程中,这个点保持不变,是所有变换操作的基准点。
*/
item->stretch(nDragHandle, sx , sy , opposite_); // 执行图形项的拉伸操作
emit scene->itemResize(item,nDragHandle,QPointF(sx,sy)); // 发送图形项大小调整的信号
// qDebug()<<"scale:"<<nDragHandle<< item->mapToScene(opposite_)<< sx << " 锛? << sy
// << new_delta << item->mapFromScene(c_last)
// << initial_delta << item->mapFromScene(c_down) << item->boundingRect();
} else if ( nDragHandle > Left && selectMode == editor ){ // 如果是编辑模式
// 确定控制点:通过 nDragHandle 确定用户正在操作的控制点。
// 更新位置:根据当前鼠标的位置 c_last,将控制点移动到新的位置。
// 修改图形形状:调整图形项的形状以反映控制点的移动。这可能包括更新曲线的曲率、多边形的形状等。
item->control(nDragHandle,c_last); // 更新图形项的控制点位置。控制点是用于定义图形形状的关键点
emit scene->itemControl(item,nDragHandle,c_last,c_down); // 发送图形项控制的信号
}
else if(nDragHandle == Handle_None ){ // 如果没有拖拽句柄
int handle = item->collidesWithHandle(event->scenePos()); // 检查是否与句柄碰撞
if ( handle != Handle_None){
setCursor(scene,Qt::OpenHandCursor); // 如果碰撞,设置光标为打开的手形状
m_hoverSizer = true; // 设置悬停调整标志
}else{
setCursor(scene,Qt::ArrowCursor); // 否则设置光标为箭头形状
m_hoverSizer = false; // 取消悬停调整标志
}
}
}
}
if ( selectMode == move ){ // 如果是移动模式
setCursor(scene,Qt::ClosedHandCursor); // 设置光标为闭合的手形状
if ( dashRect ){
dashRect->setPos(initialPositions + c_last - c_down); // 更新虚线框的位置
}
}
if ( selectMode != size && items.count() > 1) // 如果不是大小调整模式且选中多个项
{
scene->mouseEvent(event); // 将事件传递给场景
}
}
void SelectTool::mouseReleaseEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
DrawTool::mouseReleaseEvent(event,scene);
if ( event->button() != Qt::LeftButton ) return;
// 获取当前场景中所有被选中的图形项。
QList<QGraphicsItem *> items = scene->selectedItems();
// 如果只选中了一个图形项,进行以下处理。
if ( items.count() == 1 ){
AbstractShape * item = qgraphicsitem_cast<AbstractShape*>(items.first());
// 如果转换成功且当前选择模式为移动模式,并且鼠标释放的位置与按下的位置不同。
if ( item != 0 && selectMode == move && c_last != c_down ){
// 将图形项的位置设置为初始位置加上鼠标移动的偏移量。
item->setPos(initialPositions + c_last - c_down);
// 触发场景的 itemMoved 信号,通知图形项已被移动,并传递移动的偏移量。
emit scene->itemMoved(item , c_last - c_down );
}
// 如果图形项存在,并且选择模式为尺寸调整或编辑模式,并且鼠标释放的位置与按下的位置不同。
else if ( item !=0 && (selectMode == size || selectMode ==editor) && c_last != c_down ){
// 更新图形项的坐标,通常是为了确保调整后的形状参数被正确应用。
item->updateCoordinate();
}
}
// 如果选中了多个图形项,且选择模式为移动模式,并且鼠标释放的位置与按下的位置不同。
else if ( items.count() > 1 && selectMode == move && c_last != c_down ){
// 触发场景的 itemMoved 信号,传递 NULL 代表多个图形项的移动操作,并传递移动的偏移量。
emit scene->itemMoved(NULL , c_last - c_down );
}
// 如果当前选择模式为网格选择模式(框选),进行以下处理。
if (selectMode == netSelect ){
// 检查场景是否有视图,如果有视图,则设置视图的拖拽模式为无拖拽。
if ( scene->view() ){
QGraphicsView * view = scene->view();
view->setDragMode(QGraphicsView::NoDrag);
}
// 如果代码块开启,检查选中的图形项数量是否大于 1,并将其创建为一个组。
#if 0
if ( scene->selectedItems().count() > 1 ){
selLayer = scene->createGroup(scene->selectedItems());
selLayer->setSelected(true);
}
#endif
}
// 后面是清空成员变量。
if (dashRect ){
// 从场景中移除虚线矩形,并删除该对象释放内存。
scene->removeItem(dashRect);
delete dashRect;
dashRect = 0;
}
// 重置选择模式为无,表示没有正在进行的选择或编辑操作。
selectMode = none;
// 重置拖拽句柄为无,表示没有正在进行的拖拽操作。
nDragHandle = Handle_None;
// 重置悬停尺寸标志为 false,表示当前鼠标没有悬停在可调整大小的区域。
m_hoverSizer = false;
// 重置对点坐标为 (0, 0),用于下一次的尺寸调整操作。
opposite_ = QPointF();
// 将鼠标事件传递给场景进行处理,确保场景接收到完整的鼠标事件流程。
scene->mouseEvent(event);
}
3.3 RotationTool旋转类
RotationTool
类专注于处理图形项的旋转操作。它通过重写基类 DrawTool
的虚拟函数,提供了具体的旋转实现。RotationTool
管理图形项的旋转状态、旋转角度的计算以及旋转视觉反馈的显示,为用户提供了直观的旋转操作体验。通过管理 lastAngle
和 dashRect
,RotationTool
能够准确地计算和显示旋转效果,并在鼠标操作时给予用户实时反馈。
class RotationTool : public DrawTool
{
public:
RotationTool();
virtual void mousePressEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene );
qreal lastAngle; // 记录上一次计算的旋转角度
QGraphicsPathItem * dashRect; // 用于显示虚线矩形的指针
};
// 构造函数,初始化旋转工具
RotationTool::RotationTool()
:DrawTool(rotation) // 实例化基类的成员变量
{
lastAngle = 0; // 初始化上一次角度为0
dashRect = 0; // 初始化虚线矩形指针为空
}
void RotationTool::mousePressEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
// 调用基类 DrawTool 的 mousePressEvent 方法
DrawTool::mousePressEvent(event,scene);
if ( event->button() != Qt::LeftButton ) return;
// 如果没有在调整大小的状态,则将事件传递给场景
if (!m_hoverSizer)
scene->mouseEvent(event);
// 获取当前场景中所有被选中的图形项
QList<QGraphicsItem *> items = scene->selectedItems();
// 如果选中了一个图形项
if ( items.count() == 1 ){
AbstractShape * item = qgraphicsitem_cast<AbstractShape*>(items.first());
if ( item != 0 ){
// 检测鼠标点击的位置是否在图形项的控制句柄上,即边界顶点等位置
nDragHandle = item->collidesWithHandle(event->scenePos());
// 如果点击的是控制句柄
if ( nDragHandle !=Handle_None)
{
// 获取图形项的中心点相对于场景的坐标
QPointF origin = item->mapToScene(item->boundingRect().center());
// 计算鼠标当前位置相对于中心点的 Y 和 X 方向的长度
qreal len_y = c_last.y() - origin.y();
qreal len_x = c_last.x() - origin.x();
// 使用反正切函数计算鼠标位置与中心点连线的角度,并将其转换为度数
qreal angle = atan2(len_y,len_x)*180/PI;
// 当前点和item中心点坐标(都是场景坐标)形成的角度
lastAngle = angle; // 记录当前角度为 lastAngle
selectMode = rotate; // 设置选择模式为旋转
// 如果 dashRect 已存在,移除并删除它
if (dashRect ){
scene->removeItem(dashRect);
delete dashRect;
dashRect = 0;
}
// 创建一个新的虚线矩形项,以形状项的轮廓作为路径
dashRect = new QGraphicsPathItem(item->shape());
dashRect->setPen(Qt::DashLine); // 设置虚线笔刷
dashRect->setPos(item->pos());
dashRect->setTransformOriginPoint(item->transformOriginPoint()); // 设置变换的原点
dashRect->setTransform(item->transform()); // 设置变换矩阵
dashRect->setRotation(item->rotation()); // 设置旋转角度
dashRect->setScale(item->scale()); // 设置缩放比例
dashRect->setZValue(item->zValue()); // 设置 Z 轴位置
scene->addItem(dashRect); // 将虚线矩形添加到场景中
setCursor(scene,QCursor((QPixmap(":/icons/rotate.png")))); // 设置旋转光标
}
else{
// 如果没有点击控制句柄,则切换到选择工具,并处理按下事件
c_drawShape = selection;
selectTool.mousePressEvent(event,scene);
}
}
}
}
void RotationTool::mouseMoveEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
// 调用基类 DrawTool 的 mouseMoveEvent 方法
DrawTool::mouseMoveEvent(event,scene);
// 获取当前场景中所有被选中的图形项
QList<QGraphicsItem *> items = scene->selectedItems();
// 如果选中了一个图形项
if ( items.count() == 1 ){
AbstractShape * item = qgraphicsitem_cast<AbstractShape*>(items.first());
// 如果转换成功且在拖拽句柄且选择模式为旋转模式
if ( item != 0 && nDragHandle !=Handle_None && selectMode == rotate ){
// 获取图形项的中心点相对于场景的坐标
QPointF origin = item->mapToScene(item->boundingRect().center());
// 计算鼠标当前位置相对于中心点的 Y 和 X 方向的长度
qreal len_y = c_last.y() - origin.y();
qreal len_x = c_last.x() - origin.x();
// 使用反正切函数计算鼠标位置与中心点连线的角度,并将其转换为度数
qreal angle = atan2(len_y,len_x)*180/PI;
// 计算新的旋转角度
// angle - lastAngle: 计算自上次记录角度以来的角度变化量。这表示用户在旋转操作期间,鼠标移动所导致的角度变化。
// item->rotation(): 获取图形项当前的旋转角度,自带的角度。
angle = item->rotation() + int(angle - lastAngle) ;
// 将角度限制在 -360 到 360 度之间
if ( angle > 360 )
angle -= 360;
if ( angle < -360 )
angle+=360;
// 如果虚线矩形存在,更新其旋转角度
if ( dashRect ){
//dashRect->setTransform(QTransform::fromTranslate(15,15),true);
//dashRect->setTransform(QTransform().rotate(angle));
//dashRect->setTransform(QTransform::fromTranslate(-15,-15),true);
dashRect->setRotation( angle );
}
// 设置旋转光标
setCursor(scene,QCursor((QPixmap(":/icons/rotate.png"))));
}
else if ( item )
{
// 检查鼠标当前位置是否在控制句柄上
int handle = item->collidesWithHandle(event->scenePos());
// 如果在控制句柄上,设置旋转光标,并标记悬停状态
if ( handle != Handle_None){
setCursor(scene,QCursor((QPixmap(":/icons/rotate.png"))));
m_hoverSizer = true;
}else{
// 如果不在控制句柄上,设置为普通箭头光标,并重置悬停状态
setCursor(scene,Qt::ArrowCursor);
m_hoverSizer = false;
}
}
}
// 将事件传递给场景
scene->mouseEvent(event);
}
void RotationTool::mouseReleaseEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
// 调用基类 DrawTool 的 mouseReleaseEvent 方法
DrawTool::mouseReleaseEvent(event,scene);
// 如果释放的不是左键,直接返回
if ( event->button() != Qt::LeftButton ) return;
QList<QGraphicsItem *> items = scene->selectedItems();
if ( items.count() == 1 ){
AbstractShape * item = qgraphicsitem_cast<AbstractShape*>(items.first());
// 如果转换成功且在拖拽句柄且选择模式为旋转模式
if ( item != 0 && nDragHandle !=Handle_None && selectMode == rotate ){
// 获取图形项的中心点相对于场景的坐标
QPointF origin = item->mapToScene(item->boundingRect().center());
// 计算鼠标当前位置相对于中心点的偏移量
QPointF delta = c_last - origin ;
// 计算鼠标当前位置相对于中心点的 Y 和 X 方向的长度
qreal len_y = c_last.y() - origin.y();
qreal len_x = c_last.x() - origin.x();
// 使用反正切函数计算鼠标位置与中心点连线的角度,并将其转换为度数
qreal angle = atan2(len_y,len_x)*180/PI,oldAngle = item->rotation();
// 计算最终的旋转角度
angle = item->rotation() + int(angle - lastAngle) ;
// 将角度限制在 -360 到 360 度之间
if ( angle > 360 )
angle -= 360;
if ( angle < -360 )
angle+=360;
// 设置图形项的旋转角度
item->setRotation( angle );
// 触发场景的 itemRotate 信号,通知图形项已被旋转,并传递旧的角度
emit scene->itemRotate(item , oldAngle);
qDebug()<<"rotate:"<<angle<<item->boundingRect();
}
}
setCursor(scene,Qt::ArrowCursor);
// 清空成员变量。
selectMode = none;
nDragHandle = Handle_None;
lastAngle = 0;
m_hoverSizer = false;
if (dashRect ){
scene->removeItem(dashRect);
delete dashRect;
dashRect = 0;
}
// 将事件传递给场景
scene->mouseEvent(event);
}
3.4 RectTool
这里RectTool类支持绘制rectangle矩形、roundrect圆角矩形、ellipse椭圆,但是具体的绘制操作是由其成员变量item自己负责。
class RectTool : public DrawTool
{
public:
RectTool(DrawShape drawShape); // 构造函数,用于初始化工具的形状类型
virtual void mousePressEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene );
GraphicsItem * item; // 当前创建的图形项
};
RectTool::RectTool(DrawShape drawShape)
:DrawTool(drawShape) // 调用基类构造函数初始化工具类型
{
item = 0; // 初始化图形项为nullptr
}
void RectTool::mousePressEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
if ( event->button() != Qt::LeftButton ) return;
// 清除场景中所有的选中项
scene->clearSelection();
// 调用基类的鼠标按下事件处理
DrawTool::mousePressEvent(event,scene);
// 根据工具类型创建不同的图形项
switch ( c_drawShape ){
case rectangle: // 矩形
item = new GraphicsRectItem(QRect(1,1,1,1)); // 创建一个初始大小为1x1的矩形项
break;
case roundrect: // 圆角矩形
item = new GraphicsRectItem(QRect(1,1,1,1),true); // 创建一个初始大小为1x1的圆角矩形项
break;
case ellipse: // 椭圆
item = new GraphicsEllipseItem(QRect(1,1,1,1)); // 创建一个初始大小为1x1的椭圆项
break;
}
if ( item == 0) return; // 如果图形项创建失败,则返回
// 将点击起始点位置向右下偏移2像素:
// 在图形项的初始创建时(例如,矩形或圆角矩形),其尺寸通常从一个很小的区域开始。偏移量确保图形项在可视区域内有足够的空间来展示
c_down+=QPoint(2,2);
item->setPos(event->scenePos()); // 设置图形项的位置为鼠标点击的位置
scene->addItem(item); // 将图形项添加到场景中
item->setSelected(true); // 选中图形项
selectMode = size; // 设置选择模式为调整大小
nDragHandle = RightBottom; // 设置拖拽手柄为右下角
}
void RectTool::mouseMoveEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
setCursor(scene,Qt::CrossCursor); // 设置光标为十字形,以指示绘图模式
selectTool.mouseMoveEvent(event,scene); // 调用选择工具的鼠标移动事件处理
}
void RectTool::mouseReleaseEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
selectTool.mouseReleaseEvent(event,scene); // 调用选择工具的鼠标释放事件处理
// 如果鼠标释放位置与初始点击位置相同,则删除创建的图形项
if ( event->scenePos() == (c_down-QPoint(2,2))){ // 前面c_down鼠标点击的位置已经偏移2像素,所以这里要补偿回来。
if ( item != 0){
item->setSelected(false); // 取消选中图形项
scene->removeItem(item); // 从场景中移除图形项
delete item ; // 删除图形项对象
item = 0; // 将图形项指针设置为nullptr
}
qDebug()<<"RectTool removeItem:";
}
// 发射信号,表示图形项已添加到场景中
else if( item ){
emit scene->itemAdded( item );
}
// 将工具形状重置为选择模式
c_drawShape = selection;
}
3.5 PolygonTool
PolygonTool
是一个用于绘制多边形、折线、线段或贝塞尔曲线的工具类,如下所示,继承自 DrawTool
。它提供了在场景中通过鼠标交互来创建和编辑这些图形的功能。
class PolygonTool : public DrawTool
{
public:
PolygonTool(DrawShape shape );
virtual void mousePressEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene );
virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event ,DrawScene *scene );
GraphicsPolygonItem * item; // 当前绘制的图形项,可能是多边形、贝塞尔曲线、多线段或直线
int m_nPoints; // 当前图形的点的数量
QPointF initialPositions; // 初始位置,用于记录第一个点的位置
};
PolygonTool::PolygonTool(DrawShape shape)
:DrawTool(shape)
{
item = NULL; // 初始化 item 为 NULL
m_nPoints = 0; // 初始化点的数量为 0
}
// 当首次按下时,会被添加两次,一次是item->addPoint(c_down); item->addPoint(c_down+QPoint(1,0));
// 为了初始化一个起始线段或者提供初步的形状轮廓,使得在接下来的鼠标移动事件中能够立即看到一个图形的初步形态。
void PolygonTool::mousePressEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
// 调用基类的鼠标按下事件处理
DrawTool::mousePressEvent(event,scene);
if ( event->button() != Qt::LeftButton ) return;
// 如果当前没有正在绘制的图形项,即刚开始绘制
if ( item == NULL ){
// 根据绘制的形状类型创建相应的图形项
if ( c_drawShape == polygon ){
item = new GraphicsPolygonItem(NULL); // 创建一个多边形项
}else if (c_drawShape == bezier ){
item = new GraphicsBezier(); // 创建一个贝塞尔曲线项
}else if ( c_drawShape == polyline ){
item = new GraphicsBezier(false); // 创建一个折线
}else if ( c_drawShape == line ){
item = new GraphicsLineItem(0); // 创建一个直线项
}
item->setPos(event->scenePos()); // 设置图形项的位置为鼠标按下的位置
scene->addItem(item); // 将图形项添加到场景中
initialPositions = c_down; // 记录起始位置
item->addPoint(c_down); // 添加第一个点,因为是刚开始绘制
item->setSelected(true); // 设置图形项为选中状态
m_nPoints++;
}else if ( c_down == c_last ){ // 如果按下的点与上一个点重合
/*
if ( item != NULL )
{
scene->removeItem(item); // 从场景中移除当前图形项
delete item;
item = NULL ;
c_drawShape = selection;
selectMode = none;
return ;
}
*/
}
item->addPoint(c_down+QPoint(1,0)); // 添加一个点,位置偏移一点以避免重合
m_nPoints++; // 点的数量加 1
selectMode = size ; // 设置选择模式为 size,用于调整大小
nDragHandle = item->handleCount(); // 设置拖动手柄为当前图形项的手柄数量
// 意味着将拖动手柄的索引设置为最后一个添加的手柄,即当前鼠标点击的位置。这有助于后续在鼠标移动事件中准确控制拖动的手柄。
}
void PolygonTool::mouseMoveEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
DrawTool::mouseMoveEvent(event,scene); // 调用基类的鼠标移动事件处理
setCursor(scene,Qt::CrossCursor); // 设置光标为十字形
// selectTool.mouseMoveEvent(event,scene); // 选择工具移动事件处理
if ( item != 0 ){ // 如果图形项存在
// 如果有有效的手柄并且选择模式是调整大小
if ( nDragHandle != Handle_None && selectMode == size ){
// 控制图形项的控制点位置
item->control(nDragHandle,c_last);
}
}
}
// 直线是点击开始坐标,和最后单击一个点坐标即可。其他形状需要双击才结束,所以放到另一个函数处理。
void PolygonTool::mouseReleaseEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
// 调用基类的鼠标释放事件处理
DrawTool::mousePressEvent(event,scene);
// 绘制的形状类型如果只直线,需要特殊处理下
if ( c_drawShape == line ){
item->endPoint(event->scenePos()); // 设置直线的结束点
item->updateCoordinate(); // 更新图形项的坐标,确之前的变化得到应用
emit scene->itemAdded( item ); // 触发图形项添加信号
// 清空成员变量
item = NULL; // 将 item 设置为 NULL
selectMode = none; // 设置选择模式为 none
c_drawShape = selection; // 设置绘制模式为选择模式
m_nPoints = 0; // 重置点的数量
}
}
void PolygonTool::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
{
// 调用基类的鼠标双击事件处理
DrawTool::mouseDoubleClickEvent(event,scene);
// 设置图形项的结束点
item->endPoint(event->scenePos());
// 更新图形项的坐标
item->updateCoordinate();
// 触发图形项添加信号
emit scene->itemAdded( item );
// 清空成员变量
item = NULL;
selectMode = none;
c_drawShape = selection;
m_nPoints = 0;
}
4. GraphicsItem类
AbstractShapeType是模板基类,提供基本的绘制属性和方法。 如果AbstractShapeType模板,拥有两个直接子类GraphicsItemGroup和GraphicsItem。
4.1 AbstractShapeType类
AbstractShapeType
是一个模板类,提供了一些绘图的基本属性和方法。它通过继承自 BaseType
(默认为 QGraphicsItem
)来扩展图形项的功能,主要用于定义形状的通用行为和属性。
// AbstractShapeType模板类:定义了一些绘图形状的基本属性和方法。
template < typename BaseType = QGraphicsItem >
class AbstractShapeType : public BaseType // 这么写,继承也是一个动态的,通过传参指定。
{
public:
// 构造函数,初始化基类,并设置笔刷和笔的初始状态
explicit AbstractShapeType(QGraphicsItem * parent = 0 )
:BaseType(parent)
{
// 设置画笔为无笔触(透明)
m_pen=QPen(Qt::NoPen);
// 设置画刷为随机颜色的画刷
m_brush= QBrush(QColor(rand() % 32 * 8, rand() % 32 * 8, rand() % 32 * 8));
// 初始化宽度和高度为0
m_width = m_height = 0;
}
virtual ~AbstractShapeType(){}
// 返回图形的显示名称
virtual QString displayName () const { return QString("AbstractType");}
// 处理控制点的操作,默认实现为空
virtual void control(int dir, const QPointF & delta ){ Q_UNUSED(dir);Q_UNUSED(delta);}
// 处理图形的拉伸操作,默认实现为空
virtual void stretch( int , double , double , const QPointF & ) {}
// 返回图形的边界矩形
virtual QRectF rect() const { return m_localRect; }
// 更新图形的坐标系,默认实现为空
virtual void updateCoordinate () {}
// 移动图形,默认实现为空
virtual void move( const QPointF & point ){Q_UNUSED(point);}
// 复制图形项,默认返回NULL
virtual QGraphicsItem * duplicate() const { return NULL;}
// 返回控制点的数量
virtual int handleCount() const { return m_handles.size();}
// 从XML加载图形,必须由派生类实现
virtual bool loadFromXml(QXmlStreamReader * xml ) = 0;
// 保存图形到XML,必须由派生类实现
virtual bool saveToXml( QXmlStreamWriter * xml ) = 0 ;
// 检查指定点是否与控制点碰撞,返回碰撞的控制点方向
int collidesWithHandle( const QPointF & point ) const
{
// 获取处理手柄的逆向迭代器的结束位置。m_handles:是一个存储图形项控制手柄的容器
const Handles::const_reverse_iterator hend = m_handles.rend();
// 从末尾开始遍历所有处理手柄
for (Handles::const_reverse_iterator it = m_handles.rbegin(); it != hend; ++it)
{
// 将点从场景坐标系转换为处理手柄的局部坐标系
QPointF pt = (*it)->mapFromScene(point);
// 检查点是否在处理手柄的区域内
if ((*it)->contains(pt) ){
return (*it)->dir(); // 返回处理手柄的方向
}
}
return Handle_None; // 未碰撞,返回无控制点
}
// 返回指定控制点的位置
virtual QPointF handlePos( int handle ) const
{
// 从末尾开始遍历所有处理手柄
const Handles::const_reverse_iterator hend = m_handles.rend();
for (Handles::const_reverse_iterator it = m_handles.rbegin(); it != hend; ++it)
{
if ((*it)->dir() == handle ){
return (*it)->pos();
}
}
// 如果找不到处理手柄,返回默认的 QPointF()
return QPointF();
}
// 根据缩放方向交换控制点
int swapHandle(int handle, const QPointF& scale ) const
{
int dir = Handle_None;
if ( scale.x() < 0 || scale.y() < 0 ){
// 根据当前处理手柄和比例因子的方向,计算交换后的处理手柄方向
switch (handle) {
case RightTop:
if ( scale.x() < 0 && scale.y() < 0 )
dir = LeftBottom;
else if ( scale.x() > 0 && scale.y() < 0 )
dir = RightBottom;
else
dir = LeftTop;
break;
case RightBottom:
if ( scale.x() < 0 && scale.y() < 0 )
dir = LeftTop;
else if ( scale.x() > 0 && scale.y() < 0 )
dir = RightTop;
else
dir = LeftBottom;
break;
case LeftBottom:
if ( scale.x() < 0 && scale.y() < 0 )
dir = RightTop;
else if ( scale.x() > 0 && scale.y() < 0 )
dir = LeftTop;
else
dir = RightBottom;
break;
case LeftTop:
if ( scale.x() < 0 && scale.y() < 0 )
dir = RightBottom;
else if ( scale.x() > 0 && scale.y() < 0 )
dir = LeftBottom;
else
dir = RightTop;
break;
case Right:
if (scale.x() < 0 )
dir = Left;
break;
case Left:
if (scale.x() < 0 )
dir = Right;
break;
case Top:
if (scale.y()<0)
dir = Bottom;
break;
case Bottom:
if (scale.y()<0)
dir = Top;
break;
}
}
return dir;
}
// 返回指定控制点的相对位置
virtual QPointF opposite( int handle ) {
QPointF pt;
switch (handle) {
case Right:
pt = m_handles.at(Left-1)->pos();
break;
case RightTop:
pt = m_handles[LeftBottom-1]->pos();
break;
case RightBottom:
pt = m_handles[LeftTop-1]->pos();
break;
case LeftBottom:
pt = m_handles[RightTop-1]->pos();
break;
case Bottom:
pt = m_handles[Top-1]->pos();
break;
case LeftTop:
pt = m_handles[RightBottom-1]->pos();
break;
case Left:
pt = m_handles[Right-1]->pos();
break;
case Top:
pt = m_handles[Bottom-1]->pos();
break;
}
return pt;
}
// 返回笔刷颜色
QColor brushColor() const {return m_brush.color();}
// 获取画刷
QBrush brush() const {return m_brush;}
// 获取画笔
QPen pen() const {return m_pen;}
// 获取画笔的颜色
QColor penColor() const {return m_pen.color();}
// 设置画笔
void setPen(const QPen & pen ) { m_pen = pen;}
// 设置画刷
void setBrush( const QBrush & brush ) { m_brush = brush ; }
// 设置画刷的颜色
void setBrushColor( const QColor & color ) { m_brush.setColor(color);}
// 获取宽度
qreal width() const { return m_width ; }
// 设置宽度,并更新坐标
void setWidth( qreal width )
{
m_width = width ;
updateCoordinate();
}
// 获取高度
qreal height() const {return m_height;}
// 设置高度,并更新坐标
void setHeight ( qreal height )
{
m_height = height ;
updateCoordinate();
}
protected:
// 更新处理手柄(虚函数,子类可以重写)
virtual void updatehandles(){}
// 设置处理手柄的状态
void setState(SelectionHandleState st)
{
// 遍历所有处理手柄并设置其状态
const Handles::iterator hend = m_handles.end();
for (Handles::iterator it = m_handles.begin(); it != hend; ++it)
(*it)->setState(st);
}
// 画刷(用于填充形状)
QBrush m_brush;
// 画笔(用于边框)
QPen m_pen ;
// 处理手柄的容器
typedef std::vector<SizeHandleRect*> Handles;
Handles m_handles;
// 本地矩形(形状的边界)
QRectF m_localRect;
// 形状的宽度和高度
qreal m_width;
qreal m_height;
};
typedef AbstractShapeType< QGraphicsItem > AbstractShape;
4.2 GraphicsItem类
可以看到GrapicsItems继承了AbstractShapeType<QGraphicsItem>,而AbstractShapeType是继承了GrapicsItems。这里设计有些问题,搞得复杂了,建议稍微看看就得了。
注意:这里好像是相互继承的结构,c++不支持相互继承。举个简单例子:
// 不支持直接相互继承
class A : public B{}
class B : public A> {}
// 支持间接的相互继承,但不建议这么做
template < typename BaseType = QGraphicsItem >
class A : public BaseType {}
class B : public A<B> {}
递归继承:B继承了A<B>,而A<B>又继承了B,C++ 不支持真正的“相互继承”,即两个类互相直接继承彼此。模板类继承的情况虽然复杂,但并不等于相互继承。
在这种情况下,模板类和继承机制结合使用时可能产生复杂的类型关系,建议在设计时避免这种复杂的继承结构,以减少潜在的问题和困惑。
class GraphicsItem : public QObject,
public AbstractShapeType<QGraphicsItem>
{
Q_OBJECT
// Q_PROPERTY 自动生成对属性的访问函数,使得属性可以方便地通过 Qt 的信号槽机制、Qt Designer 或其他方式进行访问。
Q_PROPERTY(QColor pen READ penColor WRITE setPen ) // 声明pen和setPen方法,是读还是写操作。
Q_PROPERTY(QColor brush READ brushColor WRITE setBrushColor )
Q_PROPERTY(qreal width READ width WRITE setWidth )
Q_PROPERTY(qreal height READ height WRITE setHeight )
Q_PROPERTY(QPointF position READ pos WRITE setPos )
public:
GraphicsItem(QGraphicsItem * parent );
enum {Type = UserType+1}; // 定义一个唯一的类型标识符
int type() const { return Type; } // 返回类型标识符
virtual QPixmap image() ; // 声明一个虚拟函数用于获取图像
signals:
void selectedChange(QGraphicsItem *item); // 声明一个信号,当选择状态改变时发射
protected:
QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value); // 处理图形项的变化
void updatehandles(); // 更新控制柄的位置
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event); // 处理上下文菜单事件
bool readBaseAttributes(QXmlStreamReader * xml ); // 从 XML 读取基本属性
bool writeBaseAttributes( QXmlStreamWriter * xml ); // 将基本属性写入 XML
};
GraphicsItem::GraphicsItem(QGraphicsItem *parent)
:AbstractShapeType<QGraphicsItem>(parent)
{
/* // 设置图形项的效果
QGraphicsDropShadowEffect *effect = new QGraphicsDropShadowEffect;
effect->setBlurRadius(4);
setGraphicsEffect(effect);
*/
// 初始化控制柄
m_handles.reserve(Left); // 预留 m_handles 容器的空间,以提高性能并避免不必要的内存重新分配。
for (int i = LeftTop; i <= Left; ++i) { // 遍历每个控制柄方向
SizeHandleRect *shr = new SizeHandleRect(this,i); // 创建一个控制柄,都是矩形的边界框
m_handles.push_back(shr);
}
// 设置图形项的标志
setFlag(QGraphicsItem::ItemIsMovable, true); // 允许移动
setFlag(QGraphicsItem::ItemIsSelectable, true); // 允许选择
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); // 当几何形状发生变化时发送通知
this->setAcceptHoverEvents(true); // 接受悬停事件
}
QPixmap GraphicsItem::image() {
QPixmap pixmap(64, 64); // 创建一个 64x64 的图像
pixmap.fill(Qt::transparent); // 填充透明背景
//设置绘制属性
QPainter painter(&pixmap); // 创建绘图对象
setPen(QPen(Qt::black)); // 设置画笔为黑色
setBrush(Qt::white); // 设置画刷为白色
QStyleOptionGraphicsItem *styleOption = new QStyleOptionGraphicsItem; // 创建绘图选项
//painter.translate(m_localRect.center().x(),m_localRect.center().y()); // 移动绘图位置(被注释掉了)
// 绘制图形: 实现不在代码中提供,因此图像的具体样式取决于 paint 函数如何绘制图形。
paint(&painter,styleOption);
delete styleOption; // 删除绘图选项
return pixmap; // 返回图像
}
void GraphicsItem::updatehandles()
{
const QRectF &geom = this->boundingRect(); // 获取图形项的边界矩形
// 获取控制柄列表的末尾
const Handles::iterator hend = m_handles.end();
for (Handles::iterator it = m_handles.begin(); it != hend; ++it) {
SizeHandleRect *hndl = *it;
// 根据控制柄的方向更新位置
switch (hndl->dir()) { // hndl是哪个方向
case LeftTop: // 左上角
hndl->move(geom.x() , geom.y() ); // move 方法将手柄的左上角移动到 (x, y) 坐标。
break;
case Top: // 是上边
hndl->move(geom.x() + geom.width() / 2 , geom.y() ); // 将手柄移动到图形项的上边中点位置。
break;
case RightTop:
hndl->move(geom.x() + geom.width() , geom.y() );
break;
case Right:
hndl->move(geom.x() + geom.width() , geom.y() + geom.height() / 2 );
break;
case RightBottom:
hndl->move(geom.x() + geom.width() , geom.y() + geom.height() );
break;
case Bottom:
hndl->move(geom.x() + geom.width() / 2 , geom.y() + geom.height() );
break;
case LeftBottom:
hndl->move(geom.x(), geom.y() + geom.height());
break;
case Left:
hndl->move(geom.x(), geom.y() + geom.height() / 2);
break;
default:
break;
}
}
}
void GraphicsItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
Q_UNUSED(event); // 忽略事件
}
// 从xml读取属性值,赋值给对象实例
bool GraphicsItem::readBaseAttributes(QXmlStreamReader *xml)
{
qreal x = xml->attributes().value(tr("x")).toDouble();
qreal y = xml->attributes().value(tr("y")).toDouble();
m_width = xml->attributes().value("width").toDouble();
m_height = xml->attributes().value("height").toDouble();
setZValue(xml->attributes().value("z").toDouble());
setRotation(xml->attributes().value("rotate").toDouble());
setPos(x,y);
return true;
}
// 从对象实例中,写入属性值到xml文件中
bool GraphicsItem::writeBaseAttributes(QXmlStreamWriter *xml)
{
xml->writeAttribute(tr("rotate"),QString("%1").arg(rotation())); // "rotate",表示 XML 属性的名称。%1 是一个占位符
xml->writeAttribute(tr("x"),QString("%1").arg(pos().x()));
xml->writeAttribute(tr("y"),QString("%1").arg(pos().y()));
xml->writeAttribute(tr("z"),QString("%1").arg(zValue()));
xml->writeAttribute(tr("width"),QString("%1").arg(m_width));
xml->writeAttribute(tr("height"),QString("%1").arg(m_height));
return true;
}
QVariant GraphicsItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
// 如果选择状态发生改变
if ( change == QGraphicsItem::ItemSelectedHasChanged ) {
// 如果不是一个组
QGraphicsItemGroup *g = dynamic_cast<QGraphicsItemGroup*>(parentItem());
if (!g)
// 设置控制柄的状态:SelectionHandleOff, SelectionHandleInactive, SelectionHandleActive
setState(value.toBool() ? SelectionHandleActive : SelectionHandleOff);
// 如果是组,取消选择状态
else{
setSelected(false);
return QVariant::fromValue<bool>(false);
}
}
/*
else if (change == ItemPositionChange && scene()) {
// value is the new position.
QPointF newPos = value.toPointF();
QRectF rect = scene()->sceneRect();
if (!rect.contains(newPos)) {
// Keep the item inside the scene rect.
newPos.setX(qMin(rect.right()-boundingRect().width()/2, qMax(newPos.x(), rect.left()+boundingRect().width()/2)));
newPos.setY(qMin(rect.bottom()-boundingRect().height()/2, qMax(newPos.y(), rect.top()+boundingRect().height()/2)));
return newPos;
}
}
*/
return QGraphicsItem::itemChange(change, value); // 调用基类的 itemChange 方法
}
待续。。。