网上有很多关于这三个的资料,这方面的功能不做过多的描述,仅仅将在做小Demo过程中遇到的问题做下记录,下面是Demo的界面图:

Demo的初衷是不想每次使用QGraphicsView和QGraphicsScene都重写,可以直接拿来使用,后来慢慢加了一些图形项,因为其实是为了熟悉QGpaphicsXX,所以目前只加了3种(正矩形,旋转矩形、圆),刚好这个Demo也练习了一种设计模式——工厂模式,现在就讲些步骤及遇到的问题(只贴关键的代码,整体代码看最后下载链接):
一、以鼠标为中心进行缩放
QGraphicsView和QGraphicsScene(后面简称View和Scene)都是要重写的,这一部分我主要放在View中实现,主要重写滚轮事件 wheelEvent(QWheelEvent * event),代码(部分)如下:
void MyGraphicsView::wheelEvent(QWheelEvent * event)
{
if (this->scene()->items().size() == 0)
return;
QPointF curPoint = event->pos();
QPointF scenePos = this->mapToScene(QPoint(curPoint.x(), curPoint.y()));
qreal viewWidth = viewport()->width();
qreal viewHeight = viewport()->height();
qreal hScale = curPoint.x() / viewWidth;
qreal vScale = curPoint.y() / viewHeight;
int wheelDeltaValue = event->delta();
// 当前放缩倍数;
qreal scaleFactor = this->matrix().m11();
if ((scaleFactor < 0.4 && wheelDeltaValue<0)||(scaleFactor>50 &&wheelDeltaValue>0))
return;
wheelDeltaValue > 0 ? scale(1.2, 1.2) : scale(1.0/1.2, 1.0/1.2);
// 将scene坐标转换为放大缩小后的坐标;
QPointF viewPoint = this->matrix().map(scenePos);
this->horizontalScrollBar()->setValue(int(viewPoint.x() - viewWidth * hScale ));
this->verticalScrollBar()->setValue(int(viewPoint.y() - viewHeight * vScale ));
update();
}
这段代码其实是通过控制ScrollBar来达到目的,但是其实可以不需要这么做,只需要在构造函数中设置属性即可,构造函数如下:
MyGraphicsView::MyGraphicsView(QWidget *parent)
: QGraphicsView(parent)
{
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // 去掉滚动条
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
setRenderHints(QPainter::Antialiasing);
/*以鼠标中心进行缩放*/
setMouseTracking(true);
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
setResizeAnchor(QGraphicsView::AnchorUnderMouse);
/********************/
}
构造函数中的后三句才是重点。之所以还留着滚轮事件中ScrollBar->setValue()是发现在放大过程中放大到一定程度,加载进去的图片就“消失”。
注:在Scene中要设置SceneRect的大小,尽量大,具体看后面附带的代码下载链接;
二、绘制不随窗口变化大小的背景
Demo带有棋盘格的背景,实现是重写View的drawBackground(QPainter * painter, const QRectF & rect);
void MyGraphicsView::drawBackground(QPainter * painter, const QRectF & rect)
{
//绘制背景
int wid = this->geometry().width();
int hei = this->geometry().height();
m_ptCenter = this->mapToScene(wid / 2, hei / 2);
QPixmap pix(wid, hei);
QPainter pter(&pix);
QColor clr_white(Qt::white);
QColor clr_gray(240, 240, 240, 240);
int spacing = 15;
QColor useColor;
for (int i = 0; i <= floor(wid / spacing); i++)
{
for (int j = 0; j <= floor(hei/ spacing); j++)
{
useColor = ((i + j) % 2 == 0 ? clr_white : clr_gray);
pter.fillRect(i*spacing, j*spacing, spacing, spacing, useColor);
}
}
painter->drawImage(rect, pix.toImage());
}
三、加载自定义图像Item
自定义图像item其实就是继承QGraphicsItem,由于代码里加了很多图形item,只显示部分;
QRectF MyGraphicsImageItem::boundingRect() const
{
return QRectF(-m_Pixmap.width() / 2, -m_Pixmap.height() / 2, m_Pixmap.width(), m_Pixmap.height());
}
void MyGraphicsImageItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
QPen pen = painter->pen();
pen.setWidth(0);
pen.setStyle(Qt::SolidLine);
pen.setColor(QColor(0, 255, 255, 100));
painter->setPen(pen);
painter->setBrush(QColor(0, 0, 255, 100));
painter->drawPixmap(-m_Pixmap.width() / 2, -m_Pixmap.height() / 2, m_Pixmap);
}
四、在图像Item上绘制图形item
1、图形边缘控制点
边缘控制点其实也是自定义的Item,定义为MyCornerItem,该Item不随视图的缩放而缩放,需要更新移动后item的位置:
#pragma once
#ifndef MY_CORNER_ITEM_H
#define MY_CORNER_ITEM_H
#include <QAbstractGraphicsShapeItem>
#include <QGraphicsSceneHoverEvent>
#include <QPainter>
#include <QCursor>
#include <QGraphicsItem>
enum CustomType {
TypeImage = 65536 + 1,
TypeCorner = 65536 + 2,
TypeRect,
TypeRotateRect,
TypeCircle
};
constexpr int CORNER_SIZE = 3;
enum SelectionHandleState
{
SelectionHandleOff,
SelectionHandleInactive,
SelectionHandleActive
};
enum CornerDirction //鼠标经过时的形状
{
None = 0,
LeftTop,
Top,
RightTop,
Right,
RightBottom,
Bottom,
LeftBottom,
Left,
Rotation, //旋转
};
class MyCornerItem : public QAbstractGraphicsShapeItem
{
/*Q_OBJECT*/
public:
explicit MyCornerItem(QGraphicsItem *parent, QPointF point, CornerDirction dir, bool control = false);
virtual ~MyCornerItem() {};
bool hitTest(const QPointF &point);
CornerDirction getDir() const { return m_Dir; }
void setState(SelectionHandleState state);
void move(QPointF point);
void cornerTranslate(QPointF point);
inline void setSelectState(bool bState) { m_bSelect = bState; }
inline bool getSelectState() { return m_bSelect; }
inline QPointF getPointCenter() { return m_point; }
virtual int type() const { return TypeCorner; }
private:
QBrush m_brush;
QPointF m_point;
CornerDirction m_Dir;
SelectionHandleState m_State;
QCursor m_RotateCursor; //旋转图标
double m_scaleFactor;
bool m_bSelect; //对由多个点组成的图形,点击被选中的标志
QPointF m_ptPress, m_ptMove;
protected:
virtual QRectF boundingRect() const override;
virtual void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) Q_DECL_OVERRIDE;
virtual void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
};
#endif
#include "MyCornerItem.h"
#include <QDebug>
MyCornerItem::MyCornerItem(QGraphicsItem * parent, QPointF point, CornerDirction dir, bool control)
:QAbstractGraphicsShapeItem(parent)
, m_point(point)
,m_Dir(dir)
{
setAcceptHoverEvents(true);
QPixmap pixRotate = QPixmap("Resources\\icon\\rotate.png");
m_RotateCursor = QCursor(pixRotate);
m_scaleFactor = 1;
m_bSelect = false;
m_brush = QBrush(Qt::white);
}
void MyCornerItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option); //这个宏是用来把不用到的参数注掉的功能
Q_UNUSED(widget);
m_scaleFactor = painter->matrix().m11();
painter->save();
QPen pen;
pen.setWidth(0);
pen.setStyle(Qt::SolidLine);
painter->setPen(pen);
painter->setBrush(m_brush);
painter->setRenderHint(QPainter::Antialiasing, false); //不开反走样
painter->drawEllipse(m_point, CORNER_SIZE/ m_scaleFactor, CORNER_SIZE/ m_scaleFactor);
painter->restore();
}
bool MyCornerItem::hitTest(const QPointF & point)
{
//QPointF pt = mapFromScene(point);
return boundingRect().contains(point);
}
void MyCornerItem::setState(SelectionHandleState state)
{
if (state == m_State)
return;
switch (state) {
case SelectionHandleOff:
hide();
break;
case SelectionHandleInactive:
case SelectionHandleActive:
show();
break;
}
m_State = state;
}
void MyCornerItem::move(QPointF point)
{
if (point == m_point)
return;
//moveBy(0, 0);
m_point = point;
update();
}
void MyCornerItem::cornerTranslate(QPointF point)
{
QPointF local = point + boundingRect().topLeft();
QRectF delta = QRectF(local, boundingRect().size());
prepareGeometryChange();
m_point = delta.center();
update();
}
QRectF MyCornerItem::boundingRect() const
{
double centerX = m_point.x() - CORNER_SIZE * 2 / m_scaleFactor / 2;
double centerY = m_point.y() - CORNER_SIZE * 2 / m_scaleFactor / 2;
return QRectF(centerX, centerY, CORNER_SIZE * 2 / m_scaleFactor, CORNER_SIZE * 2 / m_scaleFactor);
}
void MyCornerItem::mousePressEvent(QGraphicsSceneMouseEvent * event)
{
if (event->button() == Qt::LeftButton)
{
m_ptPress = event->scenePos();
m_ptMove = event->scenePos();
m_bSelect = true;
}
}
void MyCornerItem::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
{
if (event->buttons()&Qt::LeftButton)
{
m_ptMove = event->scenePos();
QPointF pt = mapFromScene(m_ptMove) - mapFromScene(m_ptPress);
m_ptPress = event->scenePos();
cornerTranslate(pt);
}
QGraphicsItem::mouseMoveEvent(event);
}
void MyCornerItem::mouseReleaseEvent(QGraphicsSceneMouseEvent * event)
{
/*if (event->button() == Qt::LeftButton)
{
m_ptMove = event->scenePos();
QPointF pt = mapFromScene(m_ptMove) - mapFromScene(m_ptPress);
m_ptPress = event->scenePos();
cornerTranslate(pt);
}
QGraphicsItem::mouseReleaseEvent(event);*/
}
void MyCornerItem::hoverEnterEvent(QGraphicsSceneHoverEvent * event)
{
switch (m_Dir)
{
case LeftTop:
case RightBottom:
setCursor(Qt::SizeFDiagCursor);
break;
case LeftBottom:
case RightTop:
setCursor(Qt::SizeBDiagCursor);
break;
case Top:
case Bottom:
setCursor(Qt::SizeVerCursor);
break;
case Left:
case Right:
setCursor(Qt::SizeHorCursor);
break;
case Rotation:
setCursor(m_RotateCursor);
break;
default:
setCursor(Qt::ArrowCursor);
break;
}
m_brush = QBrush(Qt::green);
update();
QGraphicsItem::hoverEnterEvent(event);
}
void MyCornerItem::hoverLeaveEvent(QGraphicsSceneHoverEvent * event)
{
m_bSelect = false;
m_brush = QBrush(Qt::white);
update();
QGraphicsItem::hoverLeaveEvent(event);
}
2、图形基类及工厂
图形基类继承QGraphicsItem,以组合的方式声明边缘点QList<MyCornerItem*> m_HandlesList,主要实现item移动,左键双击改变z值,右双击可以改变颜色
#ifndef MY_CUSTOMDRAWITEM_H
#define MY_CUSTOMDRAWITEM_H
#include <QGraphicsItem>
#include "MyCornerItem.h"
#include <QPainterPath>
#include <QColorDialog>
#include <QDebug>
/* 需要使用的定义及相关函数 */
constexpr double PI = 3.1415926;
template <typename T> //T 为QPointF 或QPoint
double Distance(T p1, T p2)
{
return std::sqrt(std::pow(p1.x() - p2.x(), 2) + std::pow(p1.y() - p2.y(), 2));
}
class QGraphicsItem;
/**图形基类**/
class CustomItemBase : public QGraphicsItem
{
public:
CustomItemBase(QGraphicsItem *parent = nullptr)
{
setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemSendsGeometryChanges);
this->setAcceptHoverEvents(true);
m_bUseDoubleClick = true;
m_brush = QBrush(QColor(0, 0, 255, 100));
}
virtual ~CustomItemBase() {};
void SetState(SelectionHandleState state);
bool isCornerSelected(int &index);
void itemAtImage2Move(QPointF point)
{
move(point);
}
virtual int type() const = 0;
virtual void move(QPointF point) = 0; //平移
virtual void updateShape() = 0; //拖动corner后要更新形状
virtual void updateConnerPos() = 0; //移动后更新所有corner的位置
virtual QPainterPath shape() const = 0;
protected:
virtual QRectF boundingRect() const = 0;
virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value);
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) = 0;
virtual void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
public:
QRectF m_rect; //外接矩形
QList<MyCornerItem*> m_HandlesList;
QPointF m_ptPress,m_ptMove;
QBrush m_brush;
bool m_bUseDoubleClick;
};
class CustomItemBaseFactory
{
public:
virtual ~CustomItemBaseFactory() {}
virtual CustomItemBase* CreateCustomItem(QVector<QPointF> vecPoint) = 0;
virtual void DrawOnPaint(QPainter* painter, QVector<QPointF> vecPoint) = 0;
virtual int NeedClickCount()
{ return --m_nClickCnt; } //图形需要几步完成
int m_nClickCnt;
inline int GetNeedCnt() { return m_nNeedCnt; }
protected:
int m_nNeedCnt; //不变
};
#include "MyCustomDrawItem.h"
#include <QGraphicsScene>
#include <QDebug>
/************************************************************************/
/* 图形基类相关函数 */
/* 左双击改变Z值,右双击改变颜色 */
/************************************************************************/
void CustomItemBase::SetState(SelectionHandleState state)
{
for (auto item: m_HandlesList)
item->setState(state);
}
bool CustomItemBase::isCornerSelected(int & index)
{
bool bSelect = false;
index = 0;
for (auto item : m_HandlesList)
{
if (item->getSelectState())
{
bSelect = true;
break;
}
index++;
}
return bSelect;
}
QVariant CustomItemBase::itemChange(GraphicsItemChange change, const QVariant & value)
{
if (change == QGraphicsItem::ItemSelectedHasChanged)
{
qDebug() << "Item Selected:" << value.toString();
SetState(value.toBool() ? SelectionHandleActive : SelectionHandleOff);
}
else if (change == QGraphicsItem::ItemRotationHasChanged)
{
qDebug() << "Item Rotation Changed:" << value.toString();
}
else if (change == QGraphicsItem::ItemTransformOriginPointHasChanged)
{
qDebug() << "ItemTransformOriginPointHasChanged:" << value.toPointF();
}
return QGraphicsItem::itemChange(change, value);
}
void CustomItemBase::mousePressEvent(QGraphicsSceneMouseEvent * event)
{
if (event->button() == Qt::LeftButton)
{
m_ptPress = event->scenePos();
m_ptMove = event->scenePos();
for (auto corner:m_HandlesList)
{
corner->setZValue(this->zValue());
}
}
QGraphicsItem::mousePressEvent(event);
}
void CustomItemBase::mouseMoveEvent(QGraphicsSceneMouseEvent * event)
{
if (event->buttons() & Qt::LeftButton)
{
int index = 0;
if (!isCornerSelected(index))
{
m_ptMove = event->scenePos();
QPointF pt = mapFromScene(m_ptMove) - mapFromScene(m_ptPress);
m_ptPress = event->scenePos();
move(pt);
}
update();
}
}
void CustomItemBase::mouseReleaseEvent(QGraphicsSceneMouseEvent * event)
{
if (event->button() == Qt::LeftButton)
{
for (auto item : m_HandlesList)
{
item->setSelectState(false);
}
QGraphicsItem::mouseReleaseEvent(event);
}
}
void CustomItemBase::mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event)
{
if (event->button() == Qt::RightButton)
{
if (m_bUseDoubleClick)
{
QColorDialog dlg;
if (dlg.exec() == QDialog::Accepted)
{
QColor color = dlg.selectedColor();
color.setAlpha(100);
m_brush = QBrush(color);
}
}
}
}
至于为什么还要加个“工厂”,主要就是要加入一个图形,如果以switch的方式分别new出相关的类,这样不便于后续添加其他图形,使用工厂模式就解决了该问题。图形有自己的工厂,使用的时候只要传入相应的工厂指针就好了【不知道该模式的话自行补】。下面仅展示正矩形和圆的实现:
/**************矩形******************/
class CustomRectItem :public CustomItemBase
{
public:
CustomRectItem(QRectF &rect, QGraphicsItem *parent = nullptr);
virtual int type() const { return TypeRect; }
virtual void move(QPointF point);
virtual void updateShape(); //一个corner改变则进行一次更新
virtual void updateConnerPos();
virtual QPainterPath shape() const;
protected:
virtual QRectF boundingRect() const;
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
};
class CustomRectItemFactory : public CustomItemBaseFactory
{
public:
CustomRectItemFactory()
{
m_nClickCnt = 1;
m_nNeedCnt = 1;
}
virtual CustomItemBase* CreateCustomItem(QVector<QPointF> vecPoint)
{
return new CustomRectItem(QRectF(vecPoint.at(0), vecPoint.at(1)));
}
virtual void DrawOnPaint(QPainter* painter, QVector<QPointF> vecPoint)
{
painter->drawRect(QRectF(vecPoint.at(0), vecPoint.at(1)));
}
};
/**************圆**************/
class CustomCircleItem :public CustomItemBase
{
public:
CustomCircleItem(QPointF center, double radius, QGraphicsItem *parent = nullptr);
virtual int type() const { return TypeCircle; }
virtual void move(QPointF point); //平移
virtual void updateShape();
virtual void updateConnerPos(); //移动后更新所有corner的位置
virtual QPainterPath shape() const;
protected:
virtual QRectF boundingRect() const;
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
};
class CustomCircleItemFactory : public CustomItemBaseFactory
{
public:
CustomCircleItemFactory()
{
m_nClickCnt = 1;
m_nNeedCnt = 1;
}
virtual CustomItemBase* CreateCustomItem(QVector<QPointF> vecPoint)
{
double dRadius = Distance(vecPoint.at(0), vecPoint.at(1));
return new CustomCircleItem(vecPoint.at(0), dRadius);
}
virtual void DrawOnPaint(QPainter* painter, QVector<QPointF> vecPoint)
{
double radius = Distance(vecPoint.at(0), vecPoint.at(1));
painter->drawEllipse(vecPoint.at(0), radius, radius);
}
};
/************************************************************************/
/* 图形子类函数实现 */
/* 1、 正矩形 */
/************************************************************************/
CustomRectItem::CustomRectItem(QRectF & rect, QGraphicsItem * parent)
{
m_rect = rect;
m_HandlesList.clear();
const int num = 8;
QPointF point[num] = { QPointF((rect.left() + rect.right()) / 2,rect.bottom()),QPointF(rect.left(),rect.bottom()),
QPointF(rect.left(),(rect.top() + rect.bottom()) / 2) ,QPointF(rect.left(),rect.top()),
QPointF((rect.left() + rect.right()) / 2,rect.top()),QPointF(rect.right(),rect.top()),
QPointF(rect.right(),(rect.top() + rect.bottom()) / 2),QPointF(rect.right(),rect.bottom())};
CornerDirction dir[num] = { Bottom,LeftBottom ,Left,LeftTop ,Top , RightTop, Right,RightBottom };
for (int i = 0; i < num; i++)
{
MyCornerItem* corner = new MyCornerItem(this, point[i], dir[i]);
m_HandlesList.push_back(corner);
}
}
void CustomRectItem::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
QPen pen = painter->pen();
pen.setWidth(0);
pen.setColor(QColor(0, 255, 255, 100));
painter->setPen(pen);
painter->setBrush(m_brush);
updateShape();
painter->drawRect(m_rect);
}
void CustomRectItem::updateShape()
{
int index = 0;
if (isCornerSelected(index))
{
QRectF rect = m_rect;
CornerDirction dir = m_HandlesList[index]->getDir();
switch (dir)
{
case Top:
rect.setTop(m_HandlesList[index]->getPointCenter().y());
break;
case Right:
rect.setRight(m_HandlesList[index]->getPointCenter().x());
break;
case Bottom:
rect.setBottom(m_HandlesList[index]->getPointCenter().y());
break;
case Left:
rect.setLeft(m_HandlesList[index]->getPointCenter().x());
break;
case LeftTop:
rect.setTopLeft(m_HandlesList[index]->getPointCenter());
break;
case LeftBottom:
rect.setBottomLeft(m_HandlesList[index]->getPointCenter());
break;
case RightTop:
rect.setTopRight(m_HandlesList[index]->getPointCenter());
break;
case RightBottom:
rect.setBottomRight(m_HandlesList[index]->getPointCenter());
break;
default:
break;
}
prepareGeometryChange();
m_rect = rect;
updateConnerPos();
}
}
QPainterPath CustomRectItem::shape() const
{
QPainterPath path;
path.addRect(boundingRect());
return path;
}
void CustomRectItem::move(QPointF point)
{
QPointF local = point + m_rect.topLeft();
QRectF delta = QRectF(local, m_rect.size());
prepareGeometryChange();
m_rect = delta;
updateConnerPos();
}
void CustomRectItem::updateConnerPos()
{
const QRectF rect = m_rect;
for (auto item : m_HandlesList)
{
CornerDirction dir = item->getDir();
switch (dir)
{
case Top:
item->move((rect.topLeft()+rect.topRight())/2);
break;
case Right:
item->move((rect.topRight()+ rect.bottomRight())/2);
break;
case Bottom:
item->move((rect.bottomLeft()+ rect.bottomRight())/2);
break;
case Left:
item->move((rect.topLeft()+ rect.bottomLeft())/2);
break;
case LeftTop:
item->move(rect.topLeft());
break;
case LeftBottom:
item->move(rect.bottomLeft());
break;
case RightTop:
item->move(rect.topRight());
break;
case RightBottom:
item->move(rect.bottomRight());
break;
}
}
}
QRectF CustomRectItem::boundingRect() const
{
return m_rect;
}
圆形实现也类似,只要理解了正矩形,圆形也一样。
因为所有的图形类都是在图像上画的,所以在图像Item中声明CustomItemBaseFactory* m_itemFactory; 使用的话就简单贴下代码 [具体请看下载链接的代码]:
m_rectFactory = new CustomRectItemFactory;
m_Scene->m_ImageItem->m_itemFactory = m_rectFactory;
总结
因为只是练习的demo,所以在完成正矩形,旋转矩形和圆形这三个图像就不在继续了,这里只是做个Mark,后面想继续在继续完善。
下载链接地址:
QtAppTest220104.zip-C/C++文档类资源-优快云下载
现在已经更新到这样的形式了,虽然感觉写复杂了,有部分还能继续优化,但就先这样吧,以后有心思再改了



本文档记录了一个Qt图形视图框架的Demo实现,包括以鼠标为中心的缩放、固定大小的背景绘制以及自定义图像项的操作。Demo旨在避免重复编写QGraphicsView和QGraphicsScene,同时添加了正矩形、旋转矩形和圆形图形项,使用工厂模式进行扩展。在图形项上,实现了边缘控制点,这些点不随视图缩放而缩放,并能响应鼠标事件进行图形编辑。此外,详细展示了各个关键代码段的实现细节,如滚轮事件处理、背景绘制、图像Item和自定义图形Item的绘制与交互等。
493

被折叠的 条评论
为什么被折叠?



