网站开发环境安装程序,360如何做网站优化,网页版qq浏览器登录入口,wordpress wp_parse_args()文章目录 概述Qt 坐标系统图形视图的渲染过程Item图形项坐标系Scene场景坐标系View视图坐标系map坐标映射场景坐标转项坐标视图坐标转图形项坐标图形项之间的坐标转换 其他 概述
The Graphics View Coordinate System 图形视图坐标系统是Qt图形视图框架的重要组成部分#xf… 文章目录 概述Qt 坐标系统图形视图的渲染过程Item图形项坐标系Scene场景坐标系View视图坐标系map坐标映射场景坐标转项坐标视图坐标转图形项坐标图形项之间的坐标转换 其他 概述
The Graphics View Coordinate System 图形视图坐标系统是Qt图形视图框架的重要组成部分按照帮助文档中的划分其与 The Graphics View Architecture 图形视图架构是在一个层级上的主要包含图形项坐标系、场景坐标系、视图坐标系、坐标映射等内容。本文将结合帮助文档、Qt编程书籍、Qt示例程序等研习图形视图框架坐标系统的设计理念和使用方法。 补充20230501 编写HelpDoc的人大概率是极为熟悉该模块的开发者他们编写文档时的立意是全局化的很有可能在写第一个章节时潜移默化的渗透少许第二、甚至第三四五章节的知识点尽管他们可能已经在尽力避免类似事情的发生但那很难做到。所以有很多帮助内容初读时很有跳跃感比较吃力。此时先读个大概了解全局再回头细读可能是一种可行的方法。
转载请标明原文链接 https://blog.csdn.net/quguanxin/category_12597847.html
Qt 坐标系统
图形视图框架的坐标系统基于Qt大框架的坐标系统。逻辑坐标系的使用使绘制代码独立于绘画设备的分辨率和坐标系开发者可以在逻辑坐标系中编写绘制代码QPainter 会自动将其映射到实际的设备坐标系。 在Qt框架下QPainter类实际掌控着Qt坐标系统并且它与 QPaintDevice 和 QPaintEngine 类共同组成了Qt的绘制系统而且Qt绘制系统竟然还有一个名字Arthur 亚瑟这可能是一个项目代号具体不得而知。QPainter 用以执行绘制操作QPaintDevice 是一个二维绘制空间QPainter就是在该空间进行绘制操作QPaintEngine为painter在不同设备上的绘制操作提供了interface接口。 QPaintDevice类是所有支持绘制的类的基类它的绘画能力被 QWidget, QImage, QPixmap, QPicture, and QOpenGLPaintDevice 继承。一个Qt绘制设备的默认坐标系原点在Device左上角X轴向右增长Y轴向下增长。在基于像素的设备上以像素为默认长度单位在打印设备上默认单位是一个点1/72英寸。QPainter的逻辑坐标到QPaintDevice的物理坐标的映射由QPainter的变换矩阵、视口和窗口处理。 图形视图坐标系统以Qt坐标系统为基础不支持y轴向上增长的反向坐标系inverted Y-axis coordinate system。在传统的笛卡尔坐标系中坐标原点 (0,0) 位于左下角y轴向上增长也就是说 y 值越大坐标点在垂直方向上越高。而在Qt中采用Y轴向下增长其原因可能是在绘制图像时通常图像的第一行像素位于最上方向下递增这符合存储图像数据的方式如此更加自然。
图形视图的渲染过程 前边提到过图形视图使用Qt的坐标系统上述图片段落大意是将图形视图框架的渲染过程代入了 Qt 绘制系统。Qt图形视图框架下在渲染时图形视图的场景坐标对应于QPainter的逻辑坐标视图坐标等同于设备坐标。有关逻辑坐标和设备坐标之间关系的更多内容在Qt绘制系统的相关文章中讲述。在 ‘绘制’ 这个层次的概念上Qt图形视图框架并没有脱离QPainter机制而是在QPainter的基础上进行了扩展和封装如自动处理场景的渲染和重绘。
在图形视图中有三个有效effective的坐标系统图形项坐标系、场景坐标系和视图坐标系。为了简化实现Qt 图形视图提供了便利函数允许您在三个坐标系统之间进行映射。
Item图形项坐标系
Item居于live自己的本地坐标系中可以理解为Item在自己的本地坐标系中运作。图形项坐标系通常以项的中心点 (0,0) 为中心注意有的不是哦后文有提及这个点也是所有坐标变换的中心。图形项坐标系下的几何图元(Geometric primitives) 通常被称为点、Item线、Item矩形。 当你创建一个自定义项时你只需要关注Item本地坐标系即可场景和视图会帮你执行所有变换。如在 Diagram Scene Example 示例程序中创建基本流程图形状的代码是在Item本地坐标系中进行绘制的根本不用关心场景和视图坐标系的任何。
DiagramItem::DiagramItem(DiagramType diagramType, QMenu *contextMenu, QGraphicsItem *parent) : QGraphicsPolygonItem(parent) {...switch (myDiagramType) {...case Conditional: //流程图形状,判定框myPolygon QPointF(-100, 0) QPointF(0, 100) QPointF(100, 0) QPointF(0, -100) QPointF(-100, 0);break;case Step: //流程图形状,过程框myPolygon QPointF(-100, -100) QPointF(100, -100) QPointF(100, 100) QPointF(-100, 100) QPointF(-100, -100);break;...}setPolygon(myPolygon);...
}Qt 图形视图框架在尽力的使用Item本地坐标系坐标交互位置信息以方便开发者使用。例如当你 (潜台词在Item对象中) 接收到鼠标按下或拖拽进入事件时事件QGraphicsSceneMouseEvent对象携带的位置会以Item坐标给出(到图形项对象)。QGraphicsItem::contains() 虚函数用于判断某个点是否在物品内如果在则返回 true否则返回 false该函数的参数为Item坐标系下的坐标。类似地Item的边界矩形和形状也是以Item坐标表示的。下文以 mousePressEvent 鼠标事件为例观察该事件在视图、场景和图形项中的运行规律
//在图形项QGraphicsItem类中
virtual void QGraphicsItem::mousePressEvent(QGraphicsSceneMouseEvent *event);
//在场景QGraphicsScene类中
virtual void QGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event);
//是视图QGraphicsView类中
virtual void QGraphicsView::mousePressEvent(QMouseEvent *event);如上函数同为mousePressEvent原型QGraphicsView 使用QWidget传统的QMouseEvent 事件类型而 QGraphicsItem 和 QGraphicsScene 使用 QGraphicsSceneMouseEvent 事件类型后者可传递两种位置信息如下 相关示例代码放在了后续 ‘View视图坐标系’ 章节中。通过调试信息可确认在Item对象中接收到的mouseEvent事件通过 mouseEvent-pos 函数传递出来的是Item坐标系下的坐标。
父坐标 和 子坐标 同一个视觉位置点的父坐标和子坐标 不得不穿插对英语句子的理解只是个人理解原谅我英语没学好 At item’s position is the coordinate of the item’s center point in its parent’s coordinate system。 结合上图中QGraphicsItem的构造函数对上文的一个合理的释义可能是“(this这个) Item 的位置” 是指 (this) Item 的中心点在其父级(父Item) 坐标系中的坐标有时也简称为父坐标。从某种意义上来说若一个Item没有任何父(Item)那么场景将被当做是它的父(Item)顶层Item的位置以场景坐标来表示。整体上这一小段Doc目的是传递一个父坐标的概念即Item的pos函数位置是该Item原点在其父Item坐标系中的坐标。 理解上边这段话着实花费了我不少时间。如上EnglishChild是子图形项的意思parent是 this Item‘s parant 父项的意思复数形式的coordinates可能通常代表坐标系、单数coordinate可能通常代表坐标点。与前文那句英文类似这段话里似乎也藏了许多潜台词故此晦涩。本段开始就告诉我们子坐标系是相对于(be relative to)父坐标系的真不太好理解。接着文中提到如果子级Item未经过变换则潜台词在窗口中用户视觉上是同一个点如下图的红点子坐标与父级坐标之间的差距与the twoItems前面提到的Child和Parent在父坐标中的距离也即父Item和子Item中心点之间的距离相同。只能结合下文具体的例子继续理解 如果一个未经过变换的子Item恰好位于其父项的中心点上那么两个项的坐标系统将是相同的。然而如上图中红色矩形代表的子级Item其在父级Item坐标系下的位置是 (10, 0)而不是 (0,0) 原点。此时视觉红点其在子级Item坐标系下的坐标为0, 10同时它在父级坐标系下的坐标是10, 10。如此同样一个红点的子坐标0,10和父坐标10,10之间的差异便是10,0这个10,0也正好是子项和父项之间的距离两个矩形中心点的距离这个距离是10个单位长度。而文中 the difference between a child coordinate and a parent coordinate并没有特定的指代某点应该是指子Item上的任意的一个点即从视图上看到的任意一个点其子项坐标与父项坐标的差值是一样的都是子项和父项原点之间的距离。
由于项位置和项变换是与父项相对的子项的坐标系并不不会受到父项坐标系变换的影响尽管父项的变动隐式的转换了子项。还在上面的例子中即使父项被旋转和缩放子项坐标系下的0,10点依然将对应父项坐标系下的10,10点。然而相对于场景子项将跟随父项的变换和位置。如果父项被缩放2倍那么子项在场景中的坐标将变成20,0子项坐标系下的10,0在父项坐标系下对应的位置依然是20,0在场景坐标系中对应的点是40,0。
行文至此我们再倒回头来聊聊前面没理解的 “子坐标系是相对于父坐标系的”其含义是无论父级图形项如何缩放、旋转等变动子项在父项做标系下的位置不变子项坐标系下的点其对应的父项坐标系下的坐标也还是原来的值没有变化这就是所谓的相对性即子项任意可视点的子项坐标系下的坐标与对应的父项坐标系下的坐标都还是原来的坐标。与上述相对性对立的是子项相对于场景/绘制设备的绝对位置和变换则受父项变换的影响。可以不太精确的定义两个名词相对坐标系和绝对坐标系。这种父子层级结构让坐标变换更加清晰可控子项目可以独立于父项的变换来定义自身坐标,但最终的绝对变换位置仍由父项的属性确定。
除了少数例外( 如QGraphicsItem::pos() )之外,QGraphicsItem的函数都在项坐标系中运行,不受该项或其任何父项目的变换的影响。例如,一个项的边界矩形(即boundingRect)始终给出项坐标系下的坐标。
Scene场景坐标系
场景表示所有Item图形项的基础坐标系统。场景坐标系统描述的是所有顶层Item的位置也是所有从View视图到Scene场景的 QGraphicsSceneMouseEvent 场景型事件的基石。场景中的每个项除了具有Item本地位置和本地边界矩形之外还有一个场景位置和场景边界矩形 (QGraphicsItem::scenePos(), QGraphicsItem::sceneBoundingRect())。如下所示 场景位置描述了项在场景坐标系中的位置而项的场景边界矩形则构成了QGraphicsScene 确定场景中哪些区域已更改的基础。场景中的更改通过 QGraphicsScene::changed() 信号传播参数是一个场景矩形列表。通过上图中对 boundingRect() 和 sceneBoundingRect() 的对比可以直观的看出两者的区别本地边界矩形被用以QGraphicsView的重绘过程而场景边界矩形是混合了本地边界矩形和坐标系变换之后的场景坐标系下的矩形其主要用以探测出项在场景中的位置变动。
QRectF Arrow::boundingRect() const {qreal extra (pen().width() 20) / 2.0;return QRectF(line().p1(), QSizeF(line().p2().x() - line().p1().x(), line().p2().y() - line().p1().y())).normalized().adjusted(-extra, -extra, extra, extra);
}View视图坐标系
视图坐标系是窗口部件的坐标系。视图坐标系下的每个单元都对应一个像素。这个坐标系统的特殊之处在于它是相对于 widget部件 或 viewport视口的不受被观察场景的影响。QGraphicsView 的viewport 视口的左上角坐标永远是(0,0)右下角坐标永远是(视口宽度,视口高度)。
所有鼠标事件和拖放事件最初都是以视图坐标接收的你需要将这些坐标映射到场景中以便与Item项交互。也可以这么理解用户的键鼠事件等最早(originally)是由视图对象捕获的而场景对象可能是作为事件过滤器被安装到关联的视图对象上的为此我们进行了如下验证。我们扩展 Diagram Scene Example 示例程序添加从QGraphicsView派生的GraphicsView类并分别在DiagramItem、DiagramScene、DiagramView 中新实现或修改 mousePressEvent 虚函数
//派生图形项中对鼠标按压事件的处理
void DiagramItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {qDebug() \r\n--#-- DiagramItem::mousePressEvent --#--;qDebug() DiagramItem::pos: event-pos();qDebug() DiagramItem::scenePos: event-scenePos();//mustreturn QGraphicsPolygonItem::mousePressEvent(event);
}
//派生场景中对鼠标按压事件的处理
void DiagramScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) {qDebug() \r\n--#-- DiagramScene::mousePressEvent --#--;qDebug() DiagramScene::pos: mouseEvent-pos();qDebug() DiagramScene::scenePos: mouseEvent-scenePos();...
}
//派生视图中对鼠标按压事件的处理
void DiagramView::mousePressEvent(QMouseEvent *event) {qDebug() \r\n--#-- DiagramView::mousePressEvent --#--;qDebug() DiagramView::localPos: event-localPos();qDebug() DiagramView::windowPos: event-windowPos();qDebug() DiagramView::screenPos: event-screenPos();qDebug() DiagramView::pos: event-pos(); //localPosqDebug() DiagramView::globalPos: event-globalPos(); //screenPos//mustreturn QGraphicsView::mousePressEvent(event);
}/* //创建一个ShpeItem点击其靠近右下角的某个位置
--#-- DiagramView::mousePressEvent --#--
DiagramView::localPos: QPointF(355,265)
DiagramView::windowPos: QPointF(564,332)
DiagramView::screenPos: QPointF(664,432)
DiagramView::pos: QPoint(355,265) //localPos //以视图左上角为00
DiagramView::globalPos: QPoint(664,432) //screenPos--#-- DiagramScene::mousePressEvent --#--
DiagramScene::pos: QPointF(0,0) //只要点击位置在Item内则始终为00
DiagramScene::scenePos: QPointF(2573,2562)--#-- DiagramItem::mousePressEvent --#--
DiagramItem::pos: QPointF(75,78) //以Item中心位置为00原点
DiagramItem::scenePos: QPointF(2573,2562) //与在场景对象中的捕获坐标值是一致的
*/通过上运行结果我们验证了Qt鼠标事件在Qt图形视图框架下的传递顺序基本可推定视图对象会在最初捕获用户事件然后可能的转换过程方案有转为视图对象直接将 QMouseEvent 转为 QGraphicsSceneMouseEvent 事件场景对象是视图对象的事件过滤器图形项对象是场景的事件过滤器由场景和图形项对象各自分别转换但由于图形项没有从QObject继承即无法安装过滤器这种方案的可能性不大。针对上述问题后续将研究相关源码并在其他文章中继续讨论。
当使用未经过任何变换如缩放、旋转等的视图observing观察场景时场景上的unit单位长度对应屏幕上的一个像素。也即在没有应用任何变换的情况下场景坐标与视图坐标是相互对应的。 例如如果在场景坐标系中有两个点A(10,10) 和 B(20,10)那么它们在未经变换的视图中的像素距离就是10个像素。这种对应的关系使得最开始在场景上布局和定位图形项时很方便因为你可以直接以像素为单位来思考。若应用了任何变换比如缩放或旋转场景坐标与视图坐标之间就不再是简单的1-1对应关系了这时就需要使用Qt提供的坐标映射函数来在不同坐标系之间转换。
map坐标映射
通常在处理场景中的图形项时需要在场景、图形项、视图之间进行必要的坐标或任意矩形的映射。
例如当你在 QGraphicsView 的视口中单击鼠标时,可以通过调用 QGraphicsView::mapToScene() 将坐标映射到场景然后调用QGraphicsScene::itemAt() 来查询光标下的图形项对象。如果你想知道一个图形项在视图视口中的位置可以在图形项对象上调用QGraphicsItem::mapToScene()然后在视图上调用QGraphicsView::mapFromScene()。如果你想找出视口中椭圆内的所有图形项可以将椭圆对象的 QPainterPath 传递给 mapToScene() 映射到场景然后将映射后的路径传递给QGraphicsScene::items()。
在图形项和场景之间通过调用QGraphicsItem::mapToScene() and QGraphicsItem::mapFromScene() 函数来相互映射坐标和形状。在图形项与其父项之间通过调用QGraphicsItem::mapToParent() and QGraphicsItem::mapFromParent()来进行映射或者通过调用 QGraphicsItem::mapToItem() 和 QGraphicsItem::mapFromItem() 在同一个场景下不同图形项之间进行映射。所有的映射函数都支持点、矩形、多边形和路径以 mapToScene 为例在QGraphicsItem类(图左) 和 QGraphicsView类(图右)有以下6种重载 下文将对部分场景下的坐标转换过程通过示例程序进行说明。
场景坐标转项坐标
在整个Qt图形视图坐标系统中场景坐标处于至关重要的承上启下的位置。且通过上文可知场景类并没有直接提供坐标映射的接口这些映射接口仅存在于QGraphicsItem类 和 QGraphicsView类中。那么在场景对象的处理函数中如何进行坐标的相关运算呢 除了上述 itemAt 函数之外QGraphicsScene 类还提供了 6 个items(…) 的重载函数、selectedItems() 、views() 等函数获取目标图形项实例对象。一旦通过场景坐标获取到了图形项实例和视图实例便可以使用QGraphicsItem类 和 QGraphicsView类中的坐标映射接口。
在 Diagram Scene Example 示例程序场景类的实现中就使用了items函数的一个版本
void DiagramScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) {if (line ! 0 myMode InsertLine) {QListQGraphicsItem * startItems items(line-line().p1());if (startItems.count() startItems.first() line)startItems.removeFirst();QListQGraphicsItem * endItems items(line-line().p2());if (endItems.count() endItems.first() line)endItems.removeFirst();...
}在Diagram Scene Example 示例程序场景类的实现中添加如下itemAt函数的使用测试
void DiagramScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) {
...#if 1QTransform deviceTransform; //in paramQGraphicsItem *tempitem this-itemAt(mouseEvent-scenePos(), deviceTransform);QPointF ItemLocalPt tempitem-mapFromScene(mouseEvent-scenePos());qDebug() DiagramScene::ItemLocalPt: ItemLocalPt;#endif
...
}如上通过调用场景类的 itemAt 函数得到 QGraphicsItem 实例对象指针后便可使用 QGraphicsItem 的映射函数啦。
视图坐标转图形项坐标
如上视图中提供be available了与图形项类相同的映射函数用于在视图和场景之间进行映射。要从视图映射到图形项你首先需要将视图坐标映射到场景坐标系然后再从场景映射到图形项坐标系。我们扩展 Diagram Scene Example 示例程序
//从QGraphicsView派生DiagramView替代原view实例 /实现如下非功能的测试函数
void DiagramView::mousePressEvent(QMouseEvent *event) {//QPoint#pos()QPointF#localPos()qDebug() DiagramView# localPos: event-pos() windowPos: event-windowPos() screenPos: event-screenPos();
#if 1//在视图中得到鼠标位置的Item本地坐标QPointF itemLocalPos;//视图坐标转场景坐标 //event-pos()event-localPos()QPointF scenePtFromView mapToScene(event-pos());//获取鼠标事件发生的位置对应的QGraphicsItem实例QGraphicsItem *item this-scene()-itemAt(scenePtFromView, QTransform());if (item)itemLocalPos item-mapFromScene(scenePtFromView);qDebug() DiagramView# localpos: itemLocalPos scenePos: scenePtFromView;
#endif//mustreturn QGraphicsView::mousePressEvent(event);
}//在DiagramItem追加如下测试函数
void DiagramItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {qDebug() DiagramItem# localpos: event-pos() scenePos: event-scenePos();//mustreturn QGraphicsPolygonItem::mousePressEvent(event);
}/* 运行修改后的示例程序添加一个ShpeItem点击其中的位置运行结果如
DiagramView# localPos: QPoint(187,171) windowPos: QPointF(396,238) screenPos: QPointF(496,338)
DiagramView# localpos: QPointF(5,47) scenePos: QPointF(2405,2468)
DiagramItem# localpos: QPointF(5,47) scenePos: QPointF(2405,2468)
*/如上代码将View视图实例QMouseEvent事件中的本地坐标(以视图Widget左上角为原点)也即视图坐标系下的坐标通过 QGraphicsView::mapToScene 函数转换为场景坐标系下的坐标进而找到该场景坐标下的Item实例之后通过调用Item的mapFromScene函数将场景坐标映射到Item本地坐标系下。通过对比DiagramView对象和DiagramItem对象内的打印信息可验证转换效果。
在 Drill Down Example 这个示例程序中
void View::mouseReleaseEvent(QMouseEvent *event) {if (QGraphicsItem *item itemAt(event-pos())) {if (ImageItem *image qgraphicsitem_castImageItem *(item))showInformation(image);}QGraphicsView::mouseReleaseEvent(event);
}如上我们细看 QGraphicsView 类可知QGraphicsView 自身与 QGraphicsScene 类一样同样提供了 itemAt(…) 和 items(…) 函数。但是本质上还是要将视图坐标映射到场景坐标系然后再从场景映射到图形项坐标系。透过以下 QGraphicsView::items 函数的源码可以证明这一点
//
QGraphicsItem *QGraphicsView::itemAt(const QPoint pos) const {Q_D(const QGraphicsView);if (!d-scene) return 0;const QListQGraphicsItem * itemsAtPos items(pos);return itemsAtPos.isEmpty() ? 0 : itemsAtPos.first();
}
//
QListQGraphicsItem * QGraphicsView::items(const QPoint pos) const {Q_D(const QGraphicsView);if (!d-scene)return QListQGraphicsItem *();// ### Unify these two, and use the items(QPointF) version in// QGraphicsScene instead. The scene items function could use the viewport// transform to map the point to a rect/polygon.if ((d-identityMatrix || d-matrix.type() QTransform::TxScale)) {// Use the rect versionQTransform xinv viewportTransform().inverted();return d-scene-items(xinv.mapRect(QRectF(pos.x(), pos.y(), 1, 1)), Qt::IntersectsItemShape, Qt::DescendingOrder, viewportTransform());}// Use the polygon versionreturn d-scene-items(mapToScene(pos.x(), pos.y(), 1, 1), Qt::IntersectsItemShape, Qt::DescendingOrder, viewportTransform());
}图形项之间的坐标转换
以 Diagram Scene Example Arrow 类的 updatePosition 函数为例说明Item图形项之间的坐标映射过程。
void Arrow::updatePosition() {//将图形项myStartItem/myEndItem的原点转换为另一个Arrow图形项的坐标系下QLineF line(mapFromItem(myStartItem, 0, 0), mapFromItem(myEndItem, 0, 0));//绘制myStartItem/myEndItem中心点之间的线段setLine(line);
}如上为了绘制两个ShapeItem之间的连接线将myStartItem项的中心原点映射为线段的起点将myEndItem项的中心原点映射为线段的终点构建 QLineF 对象并更新 QGraphicsLineItem 类内部维护的线段成员变量。特别需要注意的是图形项QGraphicsLineItem的本地坐标系和场景坐标系是重合的因此this-mapFromItem(myStartItem, 0, 0) 与 myStartItem-mapToScene(QPointF(0,0)) 是相等的而 this-mapToScene(QPointF(0,0)) 和 this-scenePos() 等返回的都是0, 0场景坐标原点。
其他
前面讲到 itemAt 或 items 函数时它们都有一个QTransform 对象作为输入参数在 QGraphicsItem 的 mapFromScene、mapToScene等函数实现、QGraphicsItemPrivate的诸多实现中都用到了 QTransform 成员变量或函数参数。该类在 Qt 大框架的模块划分上属于 Qt GUI 模块隶属 Qt 绘制系统。 要更彻底掌握 Qt 绘制系统坐标系统 和 Qt 图形视图框架坐标系统的设计思路和使用方法或者仅是想稍加深入的阅读上述框架的源代码QTransform 是绕不开的知识点。它用于指定二维坐标系统变换提供了平移(translate)、缩放(scale)、剪切(shear)、旋转(rotate) 和投影(project) 等基本变换操作常被用于渲染图形时对坐标系统进行变换。QTransform是一个真正的3x3矩阵允许进行透视投影变换与QMatrix相比,它功能更加强大在Qt中QTransform被推荐用作变换类。限于文章篇幅此文不再对此展开。