主要问题:该QGraphicsView.mapToScene方法根据是否显示GUI返回不同的答案.为什么,我能绕过它吗?
上下文是我正在尝试编写单元测试,但我不想实际显示测试工具.
下面的小例子说明了这种行为.我使用一个子类视图,通过调用打印场景坐标中的鼠标点击事件位置,左下角的原点(垂直方向为-1)mapToScene.但是,mapToScene在显示对话框之前,不会返回我期望的内容.如果我在底部运行主要部分,我得到以下输出:
Size is (150, 200)
Putting in (50, 125) - This point should return (50.0, 75.0)
Before show(): PyQt5.QtCore.QPointF(84.0, -20.0)
After show() : PyQt5.QtCore.QPointF(50.0, 75.0)
Run Code Online (Sandbox Code Playgroud)
在此之前show(),x中有34个像素和y中有105个一致的偏移(并且在y中,偏移量反向移动,就像没有应用比例一样).那些偏移似乎相当随机,我不知道它们来自哪里.
这是示例代码:
import numpy as np
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QPointF, QPoint
from PyQt5.QtWidgets import (QDialog, QGraphicsView, QGraphicsScene,
QVBoxLayout, QPushButton, QApplication,
QSizePolicy)
from PyQt5.QtGui import QPixmap, QImage
class MyView(QGraphicsView):
"""View subclass that emits mouse events in the scene coordinates."""
mousedown = pyqtSignal(QPointF)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setSizePolicy(QSizePolicy.Fixed,
QSizePolicy.Fixed)
# This is the key thing I need
self.scale(1, -1)
def mousePressEvent(self, event):
return self.mousedown.emit(self.mapToScene(event.pos()))
class SimplePicker(QDialog):
def __init__(self, data, parent=None):
super().__init__(parent=parent)
# Get a grayscale image
bdata = ((data - data.min()) / (data.max() - data.min()) * 255).astype(np.uint8)
wid, hgt = bdata.shape
img = QImage(bdata.T.copy(), wid, hgt, wid,
QImage.Format_Indexed8)
# Construct a scene with pixmap
self.scene = QGraphicsScene(0, 0, wid, hgt, self)
self.scene.setSceneRect(0, 0, wid, hgt)
self.px = self.scene.addPixmap(QPixmap.fromImage(img))
# Construct the view and connect mouse clicks
self.view = MyView(self.scene, self)
self.view.mousedown.connect(self.mouse_click)
# End button
self.doneb = QPushButton('Done', self)
self.doneb.clicked.connect(self.accept)
# Layout
layout = QVBoxLayout(self)
layout.addWidget(self.view)
layout.addWidget(self.doneb)
@pyqtSlot(QPointF)
def mouse_click(self, xy):
print((xy.x(), xy.y()))
if __name__ == "__main__":
# Fake data
x, y = np.mgrid[0:4*np.pi:150j, 0:4*np.pi:200j]
z = np.sin(x) * np.sin(y)
qapp = QApplication.instance()
if qapp is None:
qapp = QApplication(['python'])
pick = SimplePicker(z)
print("Size is (150, 200)")
print("Putting in (50, 125) - This point should return (50.0, 75.0)")
p0 = QPoint(50, 125)
print("Before show():", pick.view.mapToScene(p0))
pick.show()
print("After show() :", pick.view.mapToScene(p0))
qapp.exec_()
Run Code Online (Sandbox Code Playgroud)
这个例子在Windows上的PyQt5中,但是Linux上的PyQt4做同样的事情.
在深入研究 C++ Qt 源代码后,这是mapToSceneQPoint 的 Qt 定义:
QPointF QGraphicsView::mapToScene(const QPoint &point) const
{
Q_D(const QGraphicsView);
QPointF p = point;
p.rx() += d->horizontalScroll();
p.ry() += d->verticalScroll();
return d->identityMatrix ? p : d->matrix.inverted().map(p);
}
Run Code Online (Sandbox Code Playgroud)
最重要的是p.rx() += d->horizontalScroll();垂直滚动。QGraphicsView 始终包含滚动条,即使它们始终关闭或不显示。在显示小部件之前观察到的偏移量来自初始化时水平和垂直滚动条的值,必须修改该偏移量以在显示小部件并计算布局时匹配视图/视口。为了mapToScene正确操作,必须设置滚动条以匹配场景/视图。
如果我将以下几行放在示例中的调用之前mapToScene,那么我将获得适当的转换结果,而无需显示小部件。
pick.view.horizontalScrollBar().setRange(0, 150)
pick.view.verticalScrollBar().setRange(-200, 0)
pick.view.horizontalScrollBar().setValue(0)
pick.view.verticalScrollBar().setValue(-200)
Run Code Online (Sandbox Code Playgroud)
为了更普遍地执行此操作,您可以从视图中提取一些相关的转换。
# Use the size hint to get shape info
wid, hgt = (pick.view.sizeHint().width()-2,
pick.view.sizeHint().height()-2) # -2 removes padding ... maybe?
# Get the opposing corners through the view transformation
px = pick.view.transform().map(QPoint(wid, 0))
py = pick.view.transform().map(QPoint(0, hgt))
# Set the scroll bars accordingly
pick.view.horizontalScrollBar().setRange(px.y(), px.x())
pick.view.verticalScrollBar().setRange(py.y(), py.x())
pick.view.horizontalScrollBar().setValue(px.y())
pick.view.verticalScrollBar().setValue(py.y())
Run Code Online (Sandbox Code Playgroud)
这是一个黑客式且丑陋的解决方案,因此虽然它确实有效,但可能有一种更优雅的方法来处理这个问题。
| 归档时间: |
|
| 查看次数: |
822 次 |
| 最近记录: |