I am new to QtWidgets and trying to build an app in QtWidgets and Python (3.x). the end goal of the app is to show images and a
superposed cursor (to be exact, a "plus" sign of 2cm) that can be moved along the image reacting to mouse events. I concentrate now first on this cursor. So far, I read examples on how to do it on matplotlib. however, i have trouble to understand how to integrate matplotlib on my code.
Also, is matplotlib the easiest way to do it on this code. or there might be a better way to do it.
any hint would be helpful
thank you in advance.
here is my desired output and the code of my app
import sys
from PySide2 import QtWidgets
from vispy import scene
from PySide2.QtCore import QMetaObject
from PySide2.QtWidgets import *
class SimpleItem(QtWidgets.QGraphicsItem):
def __init__(self):
QtWidgets.QGraphicsItem.__init__(self)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
def boundingRect(self):
penWidth = 1.0
return QRectF(-10 - penWidth / 2, -10 - penWidth / 2,
20 + penWidth, 20 + penWidth)
def paint(self, painter, option, widget):
rect = self.boundingRect()
painter.drawRect(rect)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
if not MainWindow.objectName():
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
self.groupBox = QGroupBox(self.centralwidget)
self.groupBox.setObjectName("groupBox")
self.gridLayout.addWidget(self.groupBox, 0, 0, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
QMetaObject.connectSlotsByName(MainWindow)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# OpenGL drawing surface
self.canvas = scene.SceneCanvas(keys='interactive')
self.canvas.create_native()
self.canvas.native.setParent(self)
self.view = self.canvas.central_widget.add_view()
self.view.bgcolor = '#ffffff' # set the canva to a white background
scene2 = QGraphicsScene()
item = SimpleItem()
scene2.addItem(item)
self.setWindowTitle('MyApp')
def main():
import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID('my_gui')
app = QtWidgets.QApplication([])
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
edit: I added a class (here it is a rectangle just as an example) to illustrate my problem. i have trouble integrating that snippet of the code (with SimpleItem) to OpenGL canvas
You can use the QApplication.setOverrideCursor method to assign a .png image file as your cursor when it appears inside of the Qt program.
Here is an example that is mostly based on the code in your question. And below is a gif that demonstrates the example. And the last image is the image I used in the code as cursor.png
Hope this helps
import sys
from PySide2.QtCore import *
from PySide2.QtWidgets import *
from PySide2.QtGui import *
class SimpleItem(QtWidgets.QGraphicsItem):
def __init__(self):
QtWidgets.QGraphicsItem.__init__(self)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
self._brush = QBrush(Qt.black)
def boundingRect(self):
penWidth = 1.0
return QRectF(-50 - penWidth / 2, -50 - penWidth / 2,
50 + penWidth, 50 + penWidth)
def paint(self, painter, option, widget):
rect = self.boundingRect()
painter.drawRect(rect)
painter.fillRect(rect, self._brush)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.resize(800, 600)
self.scene = QGraphicsScene()
self.canvas = scene.SceneCanvas(keys='interactive')
self.view = QGraphicsView(self.scene)
item = SimpleItem()
self.scene.addItem(item)
self.setCentralWidget(self.view)
self.setWindowTitle('MyApp')
def main():
import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID('my_gui')
app = QtWidgets.QApplication([])
app.setOverrideCursor(QCursor(QPixmap('cursor.png')))
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Related
I want to move the following red cross in the canvas with the mouse events. it should only move when we click on it and drag it with the move. it should stop moving when we release the mouse.
I do get the events of the mouse. but I don't know how i can detect that I clicked on the object to make it move.
also for example i can't set self.plot1.pos to change its position. we don't have access to that attribute.
does anybody have an idea?
I am using python 3.5 and OpenGL Canvas with vispy and a QtWidgets window.
import sys
from PySide2 import QtWidgets
from vispy import scene
from PySide2.QtCore import QMetaObject
from PySide2.QtWidgets import *
import numpy as np
class my_canvas(scene.SceneCanvas):
def __init__(self):
super().__init__(keys="interactive")
self.unfreeze()
self.view = self.central_widget.add_view()
self.view.bgcolor = '#ffffff' # set the canva to a white background
window_size_0 = 800, 400
window_center = window_size_0[0] / 2, window_size_0[1] / 2
crosshair_max_length = 50
data_1 = np.random.normal(size=(2, 2))
data_1[0] = window_center[0] - crosshair_max_length, window_center[1]
data_1[1] = window_center[0] + crosshair_max_length, window_center[1]
data_2 = np.random.normal(size=(2, 2))
data_2[0] = window_center[0], window_center[1] - crosshair_max_length
data_2[1] = window_center[0], window_center[1] + crosshair_max_length
self.plot1 = scene.Line(data_1, parent=self.view.scene, color="r")
self.plot2 = scene.Line(data_2, parent=self.view.scene, color="r")
self.selected_object = None
self.freeze()
def on_mouse_press(self, event):
if event.button == 1:
print("pressed left")
if event.button == 2:
print("pressed right")
def on_mouse_move(self, event):
pass
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
if not MainWindow.objectName():
MainWindow.setObjectName("MainWindow")
MainWindow.resize(748, 537)
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
self.groupBox = QGroupBox(self.centralwidget)
self.groupBox.setObjectName("groupBox")
self.gridLayout.addWidget(self.groupBox, 0, 0, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
QMetaObject.connectSlotsByName(MainWindow)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# OpenGL drawing surface
self.canvas = my_canvas()
self.canvas.create_native()
self.canvas.native.setParent(self)
self.setWindowTitle('MyApp')
def main():
import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID('my_gui')
app = QtWidgets.QApplication([])
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
i found the solution for the ones who are interested.
self.plot1.set_data(pos=...)
with this method we can move it easily
I would like to continuously rotate a QPainter pixmap every tick based from a QTimer - in this example a clock arm. I can rotate the clock arm, however I dont have the skills to make the rotation dynamic. Here is the clock I would like to make and below is my sample code. Let me know if you can help me on the way, thanks!
import sys
import random
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.label = QtWidgets.QLabel()
self.setCentralWidget(self.label)
self.Clock_pixmap = QtGui.QPixmap("clock.png")
self.Arm_pixmap = QtGui.QPixmap("clockarm.png")
self.painter = QtGui.QPainter(self.Clock_pixmap)
self.painter.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform)
self.painter.drawPixmap(QtCore.QPoint(), self.Arm_pixmap)
self.painter.end()
self.label.setPixmap(self.Clock_pixmap.scaled(self.label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
self.label.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.label.setMinimumSize(150, 150)
self.w1 = self.Arm_pixmap.width()/2
self.h1 = self.Arm_pixmap.height()/2
self.rotationData = random.sample(range(100), 100)
timer = QtCore.QTimer(self, timeout=self.rotateArm, interval=100)
timer.start()
self.n=0
def rotateArm(self):
self.n+=1
self.painter.translate(self.w1,self.h1)
self.painter.rotate(self.rotationData[self.n])
self.painter.translate(-self.w1,-self.h1)
self.update()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
You are painting on a QPainter where you indicated that it was finished painting since you used the end() method. So it is not necessary to make a class attribute to QPainter but only a local variable. Considering the above, the solution is:
import sys
import random
from PyQt5 import QtCore, QtGui, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self._angle = 0
self.label = QtWidgets.QLabel()
self.setCentralWidget(self.label)
self.clock_pixmap = QtGui.QPixmap("clock.png")
self.arm_pixmap = QtGui.QPixmap("clockarm.png")
rotation_data = random.sample(range(100), 100)
self.data_iter = iter(rotation_data)
timer = QtCore.QTimer(self, timeout=self.rotate_arm, interval=100)
timer.start()
def rotate_arm(self):
try:
angle = next(self.data_iter)
except StopIteration:
pass
else:
self.draw(angle)
def draw(self, angle):
pixmap = self.clock_pixmap.copy()
painter = QtGui.QPainter(pixmap)
painter.setRenderHints(
QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform
)
painter.translate(pixmap.rect().center())
painter.rotate(angle)
painter.translate(-pixmap.rect().center())
painter.drawPixmap(QtCore.QPoint(), self.arm_pixmap)
painter.end()
self.label.setPixmap(pixmap)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
I want to set the background for text, which means that I want to set the color of the rectangle contains the text. I have tested QPainter.setBackground, but it do not work. This is my code:
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class MyLabel(QLabel):
def __init__(self):
super(MyLabel, self).__init__()
self.setMinimumHeight(200)
self.setMinimumWidth(200)
def paintEvent(self, QPaintEvent):
super(MyLabel, self).paintEvent(QPaintEvent)
pos = QPoint(50, 50)
painter = QPainter(self)
brush = QBrush()
brush.setColor(QColor(255,0,0))
painter.setBackgroundMode(Qt.OpaqueMode)
painter.setBackground(brush)
painter.drawText(pos, 'hello,world')
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
layout = QHBoxLayout(self)
self.label = MyLabel()
layout.addWidget(self.label)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
What I want is:
Thanks for any help.
It is not necessary to implement a personalized QLabel, it is enough to set the background color through Qt Style Sheet, also do not use a layout if you want to establish a certain position
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.label = QtWidgets.QLabel("hello,world", self)
self.label.adjustSize()
self.label.setStyleSheet(
"background-color: {};".format(QtGui.QColor(255, 0, 0).name())
)
self.label.move(QtCore.QPoint(50, 50))
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
Trying to animate a line growing from nothing to a (0,0) to (200, 200) line with PyQt5 and using QPropertyAnimation. I already read a lot of documentation about Qt and tried several samples, but I just cannot get this to work. This is the code I have now:
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QWidget, QLabel
from PyQt5.QtGui import QPainter, QPixmap, QPainterPath
from PyQt5.QtCore import QObject, QPointF, QPropertyAnimation, pyqtProperty
from PyQt5.QtCore import QLineF
import sys
class Sample(QWidget):
l1 = QLineF(QPointF(), QPointF())
def __init__(self):
super().__init__()
self.initView()
self.initAnimation()
def initView(self):
self.show()
def initAnimation(self):
self.anim = QPropertyAnimation(self.l1, b'geometry')
self.anim.setDuration(7000)
self.anim.setStartValue(QPointF(0, 0))
self.anim.setEndValue(QPointF(200, 200))
self.anim.start()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Sample()
sys.exit(app.exec_())
Not posting all previous attemps, each one fails with a different error. I got a fade out animation on a widget to work, and a picture following a path, but I can seem to make a simple line drawing work. I was hoping to achieve something like this:
Codepen example
Qt documentation is huge and it seems there are several ways to achieve this, painter and timer, animation, variant animation, but I am not very familiar with C++ and the translation to Python is not always easy. Also samples are not that easy to find.
Am I missing something obvious?
Thanks!
For the record, this is what I achieved so far but as soon as I un-comment the QPropertyAnimation creation, app does not launch. Anyway I was still far from the result in the accepted answer.
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class MyLine(QGraphicsLineItem, QObject):
def __init__(self):
super().__init__()
def _set_start(self, point):
self.setLine(point.x(), point.y(), self.line().p2().x(), self.line().p2().y())
def _set_end(self, point):
self.setLine(self.line().p1().x(), self.line().p1().y(), point.x(), point.y())
start = pyqtProperty(QPointF, fset=_set_start)
end = pyqtProperty(QPointF, fset=_set_end)
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
hbox = QHBoxLayout(self)
self.button = QPushButton("Start", self)
self.button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
hbox.addWidget(self.button)
hbox.addSpacing(40)
self.line = MyLine()
self.line.setLine(0, 0, 10, 10)
scene = QGraphicsScene()
scene.addItem(self.line)
view = QGraphicsView(scene)
hbox.addWidget(view)
self.anim = QPropertyAnimation(self.line, b"end") # crash without error here
# self.anim.setDuration(2500)
# self.anim.setLoopCount(1)
# self.anim.setStartValue(QPointF(10, 10))
# self.anim.setEndValue(QPointF(200, 200))
# self.button.clicked.connect(self.anim.start)
self.setGeometry(300, 300, 380, 250)
self.setWindowTitle('Color anim')
self.show()
if __name__ == "__main__":
app = QApplication([])
ex = Example()
ex.show()
app.exec_()
You have to use QGraphicsView, QGraphicsScene with QGraphicsLineItem as I show below:
from PyQt5 import QtCore, QtGui, QtWidgets
class LineAnimation(QtCore.QObject):
def __init__(self, parent=None):
super(LineAnimation, self).__init__(parent)
self.m_line = QtCore.QLineF()
self.m_item = QtWidgets.QGraphicsLineItem()
self.m_item.setLine(self.m_line)
self.m_item.setPen(
QtGui.QPen(
QtGui.QColor("salmon"),
10,
QtCore.Qt.SolidLine,
QtCore.Qt.SquareCap,
QtCore.Qt.RoundJoin,
)
)
self.m_animation = QtCore.QPropertyAnimation(
self,
b"p2",
parent=self,
startValue=QtCore.QPointF(0, 0),
endValue=QtCore.QPointF(200, 200),
duration=5 * 1000,
)
self.m_animation.start()
def p1(self):
return self.m_line.p1()
def setP1(self, p1):
self.m_line.setP1(p1)
self.m_item.setLine(self.m_line)
def p2(self):
return self.m_line.p2()
def setP2(self, p2):
self.m_line.setP2(p2)
self.m_item.setLine(self.m_line)
p1 = QtCore.pyqtProperty(QtCore.QPointF, fget=p1, fset=setP1)
p2 = QtCore.pyqtProperty(QtCore.QPointF, fget=p2, fset=setP2)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
scene = QtWidgets.QGraphicsScene(self)
view = QtWidgets.QGraphicsView(
scene, alignment=QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop
)
self.setCentralWidget(view)
line_animation = LineAnimation(self)
scene.addItem(line_animation.m_item)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
I'm trying to overlay 2 pixmaps and convert them into a single pixmap in a QGraphics scene. Both pixmaps are transparent at certain locations. I want to combine the maps using the 'SourceOver' blend type listed here: I have a simple toy example below to illustrate my issue where I have created two dummy transparent pixmaps, one green and one blue. In reality, these maps are loaded from images and painted over, but this example reproduces the problem. Based on this How to add an image on the top of another image?, the approach I tried (4 lines commented out) was to create a QPainter with one of the pixmaps and then draw the other pixmap on top of it, however that crashes the program. Any ideas on how to fix this? I eventually want to be able to save the combined pixmap.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QPixmap, QPainter, QPen, QBrush, QPainterPath
from PyQt5.QtCore import (QLineF, QPointF, QRectF, Qt)
class Viewer(QtWidgets.QGraphicsView):
def __init__(self, parent):
super(Viewer, self).__init__(parent)
self._scene = QtWidgets.QGraphicsScene(self)
self.photo = QtWidgets.QGraphicsPixmapItem()
self.label = QtWidgets.QGraphicsPixmapItem()
self._scene.addItem(self.photo)
self._scene.addItem(self.label)
self.setScene(self._scene)
def overlayMaps(self):
blue = QtGui.QPixmap(600, 600)
blue.fill(QtGui.QColor(0,0,255,0))
p = QPainter(blue)
self.pen = QPen()
self.pen.setColor(QtGui.QColor(0,0,255,255))
self.pen.setWidth(10)
p.setPen(self.pen)
p.drawLine(0,0,600,600)
green = QtGui.QPixmap(600, 600)
green.fill(QtGui.QColor(0,255,0,0))
p = QPainter(green)
self.pen = QPen()
self.pen.setColor(QtGui.QColor(0,255,0,255))
self.pen.setWidth(10)
p.setPen(self.pen)
p.drawLine(600,0,0,600)
self.photo.setPixmap(blue)
self.label.setPixmap(green)
resultPixmap = QtGui.QPixmap(self.photo.pixmap().width(), self.photo.pixmap().height())
# resultPainter = QtGui.QPainter(resultPixmap)
# resultPainter.setCompositionMode(QtGui.QPainter.CompositionMode_SourceOver)
# resultPainter.drawPixmap(300,300, self.photo.pixmap())
# resultPainter.drawPixmap(300,300, self.label.pixmap())
def saveOverlayMap(self):
pass
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.viewer = Viewer(self)
self.viewer.overlayMaps()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 600, 600)
window.show()
sys.exit(app.exec_())
I have implemented a function that performs the action of joining depending on the mode, for a better appreciation I have moved the items.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
def join_pixmap(p1, p2, mode=QtGui.QPainter.CompositionMode_SourceOver):
s = p1.size().expandedTo(p2.size())
result = QtGui.QPixmap(s)
result.fill(QtCore.Qt.transparent)
painter = QtGui.QPainter(result)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.drawPixmap(QtCore.QPoint(), p1)
painter.setCompositionMode(mode)
painter.drawPixmap(result.rect(), p2, p2.rect())
painter.end()
return result
class Viewer(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(Viewer, self).__init__(parent)
self._scene = QtWidgets.QGraphicsScene(self)
self.setScene(self._scene)
blue = QtGui.QPixmap(100, 100)
blue.fill(QtCore.Qt.transparent)
p = QtGui.QPainter(blue)
pen = QtGui.QPen(QtGui.QBrush(QtGui.QColor(0,0,255)), 10)
p.setPen(pen)
p.drawLine(0, 0, 100, 100)
p.end()
self.photo = self._scene.addPixmap(blue)
green = QtGui.QPixmap(100, 100)
green.fill(QtCore.Qt.transparent)
p = QtGui.QPainter(green)
pen = QtGui.QPen(QtGui.QBrush(QtGui.QColor(0, 255, 0, 255)), 10)
p.setPen(pen)
p.drawLine(100, 0, 0, 100)
p.end()
self.label = self._scene.addPixmap(green)
self.label.setPos(200, 0)
self.overlayMaps()
def overlayMaps(self):
p1 = QtGui.QPixmap(self.photo.pixmap())
p2 = QtGui.QPixmap(self.label.pixmap())
result_pixmap = join_pixmap(self.photo.pixmap(), self.label.pixmap())
self.result_item = self._scene.addPixmap(result_pixmap)
self.result_item.setPos(100, 200)
result_pixmap.save("result_pixmap.png")
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = Viewer()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
result_pixmap.png