Pyside坐标
基础
- 儿子的坐标是用父亲坐标作为原点
- 我.mapTo他(某个坐标) —— 把‘我’的坐标系转成 他的坐标系
- 我.mapFrom他(某个坐标) —— 把 他 的坐标转成‘我’的坐标
细节
调用者 | 函数 | 语义 | 输入坐标系 | 输出坐标系 |
---|---|---|---|---|
QGraphicsView | mapToScene(QPoint) |
视口→场景 | viewport | scene |
QGraphicsView | mapFromScene(QPoint) |
场景→视口 | scene | viewport |
QGraphicsItem | mapToScene(QPoint) |
item→scene | item-local | scene |
QGraphicsItem | mapFromScene(QPoint) |
场景→item | scene | item-local |
QGraphicsItem | mapToParent(QPoint) |
item→parent | item-local | parent |
QGraphicsItem | mapFromParent(QPoint) |
parent→item | parent | item-local |
QGraphicsItem | mapToItem(other, QPt) |
item→兄弟 | item-local | other-item-local |
QGraphicsItem | mapFromItem(other, QPt) |
兄弟→item | other-item-local | item-local |
源坐标系 | 目标坐标系 | 调用者 | 关键函数(重载有 QPoint/QRect/QPolygon/QPainterPath) |
---|---|---|---|
View 视口 | Scene 场景 | QGraphicsView | mapToScene() / mapFromScene() |
Scene 场景 | Item 项 | QGraphicsItem | mapFromScene() / mapToScene() |
Item 项 | Item 项 | QGraphicsItem | mapFromItem(item) / mapToItem(item) |
Item 项 | Parent 父 | QGraphicsItem | mapFromParent() / mapToParent() |
View 视口 | Item 项 | 组合 | 先 view.mapToScene() 再 item.mapFromScene() |
全局屏幕 | Widget | QWidget | mapFromGlobal() / mapToGlobal() (辅助) |
浮动
def keep_top_left():
top_left_scene = view.mapToScene(0, 0) # 核心代码: 视口左上角对应的 scene 坐标
float_rect.setPos(top_left_scene)
view.horizontalScrollBar().valueChanged.connect(keep_top_left)
view.verticalScrollBar().valueChanged.connect(keep_top_left)
# 窗口大小改变时也补一次
view.resizeEvent = lambda e: (keep_top_left(), QGraphicsView.resizeEvent(view, e))
-
paint 时瞬移
在paint()
里先setPos(view.mapToScene(0,0))
再画;最简单,但每帧都跑,性能一般。 -
scroll 信号里瞬移(上面代码)
监听scrollBar.valueChanged
/resizeEvent
,滚动或resize时把 item 钉回去;逻辑挪到场景外,paint 不动。 -
无视变换
给 item 加标志item.setFlag(QGraphicsItem.ItemIgnoresTransformations, True)
-
真正的“窗口层”——把 item 画到 viewport 上
重载QGraphicsView.drawForeground(QPainter, QRectF)
:Python
复制
def drawForeground(self, painter, rect): # 1. 存住 view 当前的那套变换(滚动、缩放、旋转、抗锯齿……) painter.save() # 2. 把 painter 还原成“裸”坐标系:原点就是 viewport 左上角,1 像素=1 单位 painter.resetTransform() # 3. 此时 painter 完全“无视” scene 的滚动/缩放, # 下面画的东西永远钉在窗口像素坐标系里 painter.fillRect(10, 10, 80, 40, Qt.red) # 4. 恢复 step 1 存的那套变换,免得后面 Qt 自己画网格/选择框时坐标系被搞乱 painter.restore()
它天生就“钉”在视口,场景怎么滚都不影响;缺点是不能用
QGraphicsItem
那一套选择、碰撞、信号,需要自己接管交互。
- 要参与 scene 的选中/碰撞吗?
- 要就选 1/2/3(最简单是 3),
- 不要就选 4,性能最好。
- 结论: 方案3简洁方便, 开发效率最高, 执行效率也很好, 完美方案, 但是, 还是要钉在位置上, 这个要点技巧, 很容易漏掉某些事件, 因此过滤器方案比较稳.
# 3. 钉住函数(这一行是关键)
def reanchor():
floater.setPos(view.mapToScene(0, 0))
# 4. 事件过滤器:任何视口变动都重新钉
class Hack(QObject):
def eventFilter(self, obj, ev):
# Paint/Resize/Wheel/Scroll 都会进来
if ev.type() in (QEvent.Paint, QEvent.Resize, QEvent.Wheel):
reanchor()
return False
view.viewport().installEventFilter(Hack(view))