2

I'm making an project that will use an arrow in a field as shown bellow.

enter image description here

I want the object to move as it receives some data for movement - such as coordinates from a satellite.

Questions are:

  • How do I create the object arrow?
  • What libraries should I use? should I use the QGraphicsScene Class to move it?.

I've never worked exactly with this type of Qt object. I'd started coding in Python with PyQt5, but with the lack of examples using this library in this language - I started studying C++ to understand Qt5 library better.

jupiterbjy
  • 2,882
  • 1
  • 10
  • 28
  • 1
    It can certainly be done in PyQt, but your question is a bit on the edge of the "opinion based" (which usually results in a closed question here on StackOverflow). Depending on the needs you might choose a QWidget subclass that implements custom painting, or a QGraphicsView that uses graphics items as an abstraction layer. I suggest you to do some research about both of them, play around with some self-made tests, and learn their corresponding capabilities, so that you can decide on your own. – musicamante Aug 29 '20 at 01:52
  • thank u, for making my question better – Iaggo Capitanio Aug 30 '20 at 10:07

1 Answers1

3

The advantage of Qt is that it allows you to create visual elements using low-level tools (like QPainter) to high-level tools (like QGraphicsItem, QML items). In this case, the easiest thing is to use QGraphicsItem based on a QGraphicsPathItem and override the paint method for custom painting. For the case of movement you just have to use the setPos() method, but to obtain a smooth movement then a QVariantAnimation must be used.

import random

from PyQt5 import QtCore, QtGui, QtWidgets


class ArrowItem(QtWidgets.QGraphicsPathItem):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._length = -1
        self._points = QtCore.QPointF(), QtCore.QPointF(), QtCore.QPointF()

        self.length = 40.0

    @property
    def length(self):
        return self._length

    @length.setter
    def length(self, l):
        self._length = l

        pos_top = QtCore.QPointF(0, l * 4 / 5)
        pos_left = QtCore.QPointF(-l * 3 / 5, -l / 5)
        pos_right = QtCore.QPointF(
            l * 3 / 5,
            -l / 5,
        )

        path = QtGui.QPainterPath()
        path.moveTo(pos_top)
        path.lineTo(pos_right)
        path.lineTo(pos_left)

        self.setPath(path)

        self._points = pos_top, pos_left, pos_right

    def paint(self, painter, option, widget):

        pos_top, pos_left, pos_right = self._points

        left_color = QtGui.QColor("#cc0000")
        right_color = QtGui.QColor("#ff0000")
        bottom_color = QtGui.QColor("#661900")

        path_left = QtGui.QPainterPath()
        path_left.lineTo(pos_top)
        path_left.lineTo(pos_left)

        path_right = QtGui.QPainterPath()
        path_right.lineTo(pos_top)
        path_right.lineTo(pos_right)

        path_bottom = QtGui.QPainterPath()
        path_bottom.lineTo(pos_left)
        path_bottom.lineTo(pos_right)

        painter.setPen(QtGui.QColor("black"))
        painter.setBrush(left_color)
        painter.drawPath(path_left)
        painter.setBrush(right_color)
        painter.drawPath(path_right)
        painter.setBrush(bottom_color)
        painter.drawPath(path_bottom)

    def moveTo(self, next_position, duration=100):
        self._animation = QtCore.QVariantAnimation()
        self._animation.setStartValue(self.pos())
        self._animation.setEndValue(next_position)
        self._animation.setDuration(duration)
        self._animation.start(QtCore.QAbstractAnimation.DeleteWhenStopped)
        self._animation.valueChanged.connect(self.setPos)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.scene = QtWidgets.QGraphicsScene()
        view = QtWidgets.QGraphicsView()
        view.setRenderHints(QtGui.QPainter.Antialiasing)
        view.setScene(self.scene)
        view.scale(1, -1)
        button = QtWidgets.QPushButton("Click me")

        central_widget = QtWidgets.QWidget()
        lay = QtWidgets.QVBoxLayout(central_widget)
        lay.addWidget(view)
        lay.addWidget(button)
        self.setCentralWidget(central_widget)
        self.resize(640, 480)

        self.arrow_item = ArrowItem()
        self.scene.addItem(self.arrow_item)

        button.clicked.connect(self.handle_clicked)

    def handle_clicked(self):
        x, y = random.sample(range(300), 2)
        print("next position:", x, y)
        self.arrow_item.moveTo(QtCore.QPointF(x, y))


if __name__ == "__main__":
    app = QtWidgets.QApplication([])

    w = MainWindow()
    w.show()

    app.exec_()

Note: PyQt5 (and also PySide2) offer many examples written in python that are the translation of the examples in C++ so I recommend you download the source code(PyQt5 and PySide2) and study them. I also have a repo with translations of some examples.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241