在Qt中实现一维拖动操作

时间:2019-06-11 18:03:53

标签: python pyqt qgraphicsview qgraphicsscene qgraphicsitem

我希望只在特定方向(例如+/- 45度)上水平或垂直拖动QGraphicsItem,并且一旦拖动光标就能够“跳”到新方向距离当前最近的方向足够远。这将复制例如绘制直线并按住Ctrl(例如,this video)时会出现墨迹,但是我不确定如何实现。

我已经实现了一个拖动处理程序,该处理程序可以在移动项目时获取项目的新位置:

class Circle(QGraphicsEllipseItem):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Flags to allow dragging and tracking of dragging.
        self.setFlag(self.ItemSendsGeometryChanges)
        self.setFlag(self.ItemIsMovable)
        self.setFlag(self.ItemIsSelectable)

    def itemChange(self, change, value):
        if change == self.ItemPositionChange and self.isSelected():
            # do something...

        # Return the new position to parent to have this item move there.
        return super().itemChange(change, value)

由于通过此方法返回给父级的位置用于更新场景中项目的位置,因此我希望可以修改此QPointF以将其限制为一个轴,但是我不确定如何这样做的方式是,一旦将光标拖到足够远的位置,线就会“跳”到另一个方向。是否有针对此类行为的“标准算法”?还是一些内置的Qt代码可以为我做到这一点?

1 个答案:

答案 0 :(得分:2)

该问题已减少,可以计算点在该线上的投影(项目的位置)。按照this post中的说明进行一些数学运算。

让p1和p2分别为直线上的两个不同点,而p为该点,则算法为:

e1 = p2 - p1
e2 = p - p1
dp = e1 • e2 # dot product
l  = e1 • e1 # dot product
pp = p1 + dp * e1 / l

实施上述解决方案是:

import math
import random
from PyQt5 import QtCore, QtGui, QtWidgets


class Circle(QtWidgets.QGraphicsEllipseItem):
    def __init__(self, *args, **kwargs):
        self._line = QtCore.QLineF()
        super().__init__(*args, **kwargs)
        # Flags to allow dragging and tracking of dragging.
        self.setFlags(
            self.flags()
            | QtWidgets.QGraphicsItem.ItemSendsGeometryChanges
            | QtWidgets.QGraphicsItem.ItemIsMovable
            | QtWidgets.QGraphicsItem.ItemIsSelectable
        )

    @property
    def line(self):
        return self._line

    @line.setter
    def line(self, line):
        self._line = line

    def itemChange(self, change, value):
        if (
            change == QtWidgets.QGraphicsItem.ItemPositionChange
            and self.isSelected()
            and not self.line.isNull()
        ):
            # http://www.sunshine2k.de/coding/java/PointOnLine/PointOnLine.html
            p1 = self.line.p1()
            p2 = self.line.p2()
            e1 = p2 - p1
            e2 = value - p1
            dp = QtCore.QPointF.dotProduct(e1, e2)
            l = QtCore.QPointF.dotProduct(e1, e1)
            p = p1 + dp * e1 / l
            return p
        return super().itemChange(change, value)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)

    scene = QtWidgets.QGraphicsScene(QtCore.QRectF(-200, -200, 400, 400))
    view = QtWidgets.QGraphicsView(scene)

    points = (
        QtCore.QPointF(*random.sample(range(-150, 150), 2)) for _ in range(4)
    )
    angles = (math.pi / 4, math.pi / 3, math.pi / 5, math.pi / 2)

    for point, angle in zip(points, angles):
        item = Circle(QtCore.QRectF(-10, -10, 20, 20))
        item.setBrush(QtGui.QColor("salmon"))
        scene.addItem(item)
        item.setPos(point)
        end = 100 * QtCore.QPointF(math.cos(angle), math.sin(angle))
        line = QtCore.QLineF(QtCore.QPointF(), end)
        item.line = line.translated(item.pos())
        line_item = scene.addLine(item.line)
        line_item.setPen(QtGui.QPen(QtGui.QColor("green"), 4))

    view.resize(640, 480)
    view.show()

    sys.exit(app.exec_())