I am creating a desktop application using PyQt5 where the user will be able to draw rectangles.
User should be able to select the top-left corner of the rectangle with first mouse click and the bottom-right corner with second mouse click. A rectangle should appear in that location with the perimeter well defined. I created application but have a problem when I draw another rectangle previous rectangle vanishes. I am not able to draw multiple rectangles.
Please find the below code for reference
import sys
from PyQt5.QtWidgets import *
from PyQt5 import QtGui, QtCore
from PyQt5.QtGui import QPainter, QPen, QBrush
from PyQt5.QtCore import Qt
class Windo(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(150,250,500,500)
self.setWindowTitle("Ammyyy")
self.setWindowIcon(QtGui.QIcon('a.jpeg'))
self.begin = QtCore.QPoint()
self.end = QtCore.QPoint()
self.show()
def paintEvent(self,event):
qp = QPainter(self)
qp.begin(self)
qp.setPen(QPen(Qt.black, 6, Qt.SolidLine))
qp.drawRect(QtCore.QRect(self.begin, self.end))
qp.end()
def mousePressEvent(self, event):
self.begin = event.pos()
self.end = event.pos()
def mouseMoveEvent(self, event):
self.end = event.pos()
self.update()
def mouseReleaseEvent(self, event):
self.begin = event.pos()
self.end = event.pos()
app = QApplication(sys.argv)
win = Windo()
sys.exit(app.exec_())
If you want to draw n-rectangles then you must save that information in a list through QRect. On the other hand, the selection of 2 points does not imply that the QRect is valid, for example if the first point is on the right, the second point will not create a valid rectangle so that rectangle has to be normalized. Considering the above, the solution is:
import sys
from PyQt5.QtCore import Qt, QPoint, QRect
from PyQt5.QtGui import QPainter, QPen, QBrush, QIcon
from PyQt5.QtWidgets import QApplication, QWidget
class Window(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(150, 250, 500, 500)
self.setWindowTitle("Ammyyy")
self.setWindowIcon(QIcon("a.jpeg"))
self.begin = QPoint()
self.end = QPoint()
self.rectangles = []
def paintEvent(self, event):
qp = QPainter(self)
qp.setPen(QPen(Qt.black, 6, Qt.SolidLine))
for rectangle in self.rectangles:
qp.drawRect(rectangle)
if not self.begin.isNull() and not self.end.isNull():
qp.drawRect(QRect(self.begin, self.end).normalized())
def mousePressEvent(self, event):
self.begin = self.end = event.pos()
self.update()
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
self.end = event.pos()
self.update()
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
r = QRect(self.begin, self.end).normalized()
self.rectangles.append(r)
self.begin = self.end = QPoint()
self.update()
super().mouseReleaseEvent(event)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())
Related
I have created a square at random position in canvas, but I don't know how do I move it to somewhere else in canvas by dragging it to desired position, please suggest some edits or a new method to achieve the proposed task, I am learning while doing so.
P.S. Attached a screenshot of the output window.
import sys
from random import randint
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow,QPushButton,QWidget
from PyQt5 import QtGui
from PyQt5.QtCore import QRect,Qt
from PyQt5.QtGui import QPainter,QBrush, QPen
from PyQt5 import QtCore
class Window(QMainWindow):
def __init__(self):
super(Window,self).__init__()
title="TeeSquare"
left=500
top=200
width=500
height=400
iconName="square.jpg"
self.setWindowTitle(title)
self.setWindowIcon(QtGui.QIcon(iconName))
self.setGeometry(left, top, width, height)
self.should_paint_Rect = False
self.windowcomponents()
self.initUI()
self.show()
def initUI(self):
if self.should_paint_Rect:
self.label=QtWidgets.QLabel(self)
self.label.setText("circle")
def windowcomponents(self):
button=QPushButton("Add", self)
button.setGeometry(QRect(0, 0, 50, 28))
button.setIcon(QtGui.QIcon("Add.png"))
button.setToolTip("Create Square")
button.clicked.connect(self.paintRect)
def paintEvent(self, event):
super().paintEvent(event)
if self.should_paint_Rect:
painter = QtGui.QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(QPen(Qt.black, 5, Qt.SolidLine))
painter.drawRect(randint(0,500), randint(0,500), 100, 100)
self.initUI()
self.label.move(60,100)
def paintRect(self, painter):
self.should_paint_Rect = True
self.update()
app = QApplication(sys.argv)
Rect=Window()
Rect.show()
sys.exit(app.exec_())
The logic of creating a dynamic element is to indicate a set of specific characteristics that by modifying these, the element is modified.
In this case you could use the center of the square, the dimensions of the square, etc. and that data must be implemented through a data structure that can be created from scratch for example by creating a class that has the information of the rectangle, but in Qt it is not necessary to create that element since it already exists and is QRect.
Now that that element has been identified, you can create a QRect whose top-left is random when the button is pressed, and use that QRect to paint it.
For dragging the procedure is:
Get the mouse click position.
Verify that the click is inside the rectangle.
Calculate the position relative to the rectangle.
When moving the mouse, the position of the rectangle must be updated based on the position of the mouse press.
Considering all of the above, the solution is:
import random
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.rect = QtCore.QRect()
self.drag_position = QtCore.QPoint()
button = QtWidgets.QPushButton("Add", self)
button.clicked.connect(self.on_clicked)
self.resize(640, 480)
#QtCore.pyqtSlot()
def on_clicked(self):
if self.rect.isNull():
self.rect = QtCore.QRect(
QtCore.QPoint(*random.sample(range(200), 2)), QtCore.QSize(100, 100)
)
self.update()
def paintEvent(self, event):
super().paintEvent(event)
if not self.rect.isNull():
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(QtGui.QPen(QtCore.Qt.black, 5, QtCore.Qt.SolidLine))
painter.drawRect(self.rect)
def mousePressEvent(self, event):
if self.rect.contains(event.pos()):
self.drag_position = event.pos() - self.rect.topLeft()
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if not self.drag_position.isNull():
self.rect.moveTopLeft(event.pos() - self.drag_position)
self.update()
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.drag_position = QtCore.QPoint()
super().mouseReleaseEvent(event)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
Rect = Window()
Rect.show()
sys.exit(app.exec_())
I have created a circle at random place in canvas, but I am unable to move or edit its properties like its label just by clicking and dragging it.
I want to create a circle that is movable by dragging and its properties like label are editable at any time please suggest the edits or new approach to do it. I am a beginner Please help...
import sys
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow,QPushButton,QWidget
from PyQt5 import QtGui
from PyQt5.QtCore import QRect,Qt
from PyQt5.QtGui import QPainter,QBrush, QPen
from PyQt5 import QtCore
from random import randint
class Window(QMainWindow):
def __init__(self):
super(Window,self).__init__()
title="layout management"
left=500
top=200
width=500
height=400
iconName="Ash.jpg"
self.setWindowTitle(title)
self.setWindowIcon(QtGui.QIcon(iconName))
self.setGeometry(left, top, width, height)
self.should_paint_circle = False
self.windowcomponents()
self.initUI()
self.show()
def initUI(self):
if self.should_paint_circle:
self.label=QtWidgets.QLabel(self)
self.label.setText('<h2>circle<h2>')
def windowcomponents(self):
button=QPushButton("Add", self)
button.setGeometry(QRect(0, 0, 50, 28))
button.setIcon(QtGui.QIcon("addbutton.png"))
button.setToolTip("<h3>This is for creating random circles<h3>")
button.clicked.connect(self.paintcircle)
button=QPushButton("Generate Report", self)
button.setGeometry(QRect(49,0,150,28))
button.setIcon(QtGui.QIcon("generatereport.png"))
button.setToolTip("This is for generating pdf report of connection between two circles")
button=QPushButton("Save", self)
button.setGeometry(QRect(199,0,120,28))
button.setIcon(QtGui.QIcon("saveicon.png"))
button.setToolTip("This is for saving an image of canvas area")
def paintEvent(self, event):
super().paintEvent(event)
if self.should_paint_circle:
painter = QtGui.QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(QPen(Qt.black, 5, Qt.SolidLine))
painter.drawEllipse(randint(0,500), randint(0,500), 100, 100)
self.initUI()
self.label.move(60,100)
def paintcircle(self, painter):
self.should_paint_circle = True
self.update()
app = QApplication(sys.argv)
circle=Window()
circle.show()
sys.exit(app.exec_())
Image showing a circle at random position in window, its not draggable
The solution is similar, the code only changed in how to determine if the pressed point is inside the circle:
import random
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.rect = QtCore.QRect()
self.drag_position = QtCore.QPoint()
button = QtWidgets.QPushButton("Add", self)
button.clicked.connect(self.on_clicked)
self.resize(640, 480)
#QtCore.pyqtSlot()
def on_clicked(self):
if self.rect.isNull():
self.rect = QtCore.QRect(
QtCore.QPoint(*random.sample(range(200), 2)), QtCore.QSize(100, 100)
)
self.update()
def paintEvent(self, event):
super().paintEvent(event)
if not self.rect.isNull():
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(QtGui.QPen(QtCore.Qt.black, 5, QtCore.Qt.SolidLine))
painter.drawEllipse(self.rect)
def mousePressEvent(self, event):
if (
2 * QtGui.QVector2D(event.pos() - self.rect.center()).length()
< self.rect.width()
):
self.drag_position = event.pos() - self.rect.topLeft()
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if not self.drag_position.isNull():
self.rect.moveTopLeft(event.pos() - self.drag_position)
self.update()
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.drag_position = QtCore.QPoint()
super().mouseReleaseEvent(event)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
Rect = Window()
Rect.show()
sys.exit(app.exec_())
I want to draw polyline with mouse event. But I can't set endpoints by clicking, or choose pen type. I want to draw linear lines, but when i write this code it only shows dots instead of drawing a line. Here is my code:
import sys
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtGui import QPainter, QBrush, QColor, QPen, QPainterPath
from PyQt5.QtWidgets import QLabel, QGraphicsScene, QGraphicsView
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.begin = QtCore.QPoint()
self.end = QtCore.QPoint()
self.beginList = []
self.endList = []
self.initUI()
def initUI(self):
self.setGeometry(200, 200, 1000, 500)
self.label = QLabel(self)
self.label.resize(500, 40)
self.show()
def paintEvent(self, event):
qp = QPainter(self)
for i,j in zip(self.beginList, self.endList):
qp.drawLines(QtCore.QLineF(i,j))
def mouseMoveEvent(self, event):
self.begin = event.pos()
self.end = event.pos()
self.beginList.append(self.begin)
self.endList.append(self.end)
self.label.setText('Coordinates: ( %d : %d )' % (event.x(), event.y()))
self.update()
def mouseReleaseEvent(self, event):
self.begin = event.pos()
self.end = event.pos()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = MyWidget()
window.resize(800,600)
sys.exit(app.exec_())
If the OP code is analyzed, the starting point and the end point coincide, so when drawing a line between 2 points of the same location, only one point will be drawn. The logic is to join the point obtained in the i-th step with the (i+1)-th point.
To do the above the simplest thing is to use a QPainterPath:
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.paths = []
def initUI(self):
self.setGeometry(200, 200, 1000, 500)
self.label = QtWidgets.QLabel(self)
self.show()
def paintEvent(self, event):
qp = QtGui.QPainter(self)
for path in self.paths:
qp.drawPath(path)
def mousePressEvent(self, event):
path = QtGui.QPainterPath()
path.moveTo(event.pos())
self.paths.append(path)
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
self.paths[-1].lineTo(event.pos())
self.label.setText('Coordinates: ( %d : %d )' % (event.x(), event.y()))
self.label.adjustSize()
self.update()
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.paths[-1].lineTo(event.pos())
self.update()
super().mouseReleaseEvent(event)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = MyWidget()
window.resize(800, 600)
sys.exit(app.exec_())
Prologue: Both of the object represented below are on the same window.
I am having problem with the not updating
I am having a problem related to self.setGeometry(x,y,w,h) function. So what I want to achieve is multiple rect, rendered in parallel, which each has a line protruding out of their rectangle when clicked. (Creating a connector though that is not the point of this topic).
In this case, I have rect A and rect B rendering together. Rect A has a line protruding out to the position of the mouse.
(obj A) (obj B)
____ ____
| | | |
| \ | | |
---\ ----
\
(Mouse)
An example code of what am I trying to achieve.
# File: connectors.py
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QWidget
class Connector(QWidget):
def __init__(self, rect: List[int]):
super().__init__()
self.setGeometry(0, 0, 1920, 1080)
self.rect = rect
self.clicked = False
self.begin = QPoint(rect[0], rect[1])
self.end = QPoint(0,0)
def paintEvent(self, event):
qp = QPainter()
qp.begin(self)
qp.setPen(Qt.red)
qp.drawRect(*self.rect)
if self.clicked:
qp = QPainter()
qp.begin(self)
qp.setPen(Qt.red)
qp.drawLine(self.begin, self.end)
self.update()
def mousePressEvent(self, event):
if event.button() == 1:
self.clicked = True
self.end = event.pos()
def mouseMoveEvent(self, event):
if self.clicked:
self.end = event.pos()
def mouseReleaseEvent(self, event):
if event.button() == 1:
self.clicked = False
self.end = event.pos()
# File: main.py
scene = QGraphicsScene()
scene.addWidget( Connector((400, 400, 100, 100)) )
scene.addWidget( Connector((400, 600, 100, 100)) )
But what I am ending up is PyQt showing the top-most object onto the screen thus only showing one object, BUT I also tried to minimize the geometry leading the protruding line, when clicked, cutting off on the border.
Explanation:
In your case it has 2 widgets where you draw the rectangles where one is on top of another so you will only see one of them: the one above.
Solution:
Qt GraphicsView Framework (QGraphicsView, QGraphicsScene, QGraphicsXItem, etc.) works differently and painting is not used directly since they offer basic items that implement all the functionalities so in this case you must use QGraphicsRectItem with QGraphicsLineItem and modify it with the information of the QGraphicsView.
Considering the above, the solution is:
import sys
from PyQt5.QtCore import QRectF, Qt
from PyQt5.QtWidgets import (
QApplication,
QGraphicsLineItem,
QGraphicsRectItem,
QGraphicsScene,
QGraphicsView,
)
class RectItem(QGraphicsRectItem):
def __init__(self, rect, parent=None):
super().__init__(parent)
self.setRect(QRectF(*rect))
self.setPen(Qt.red)
self._line_item = QGraphicsLineItem(self)
self.line_item.setPen(Qt.red)
l = self.line_item.line()
l.setP1(self.rect().topLeft())
l.setP2(self.rect().topLeft())
self.line_item.setLine(l)
self.line_item.hide()
#property
def line_item(self):
return self._line_item
def move_line_to(self, sp):
lp = self.mapFromScene(sp)
l = self.line_item.line()
l.setP2(lp)
self.line_item.setLine(l)
class GraphicsView(QGraphicsView):
def __init__(self, parent=None):
super().__init__(parent)
self.setScene(QGraphicsScene())
self.scene().addItem(RectItem((400, 400, 100, 100)))
self.scene().addItem(RectItem((400, 600, 100, 100)))
def mousePressEvent(self, event):
vp = event.pos()
sp = self.mapToScene(vp)
self.move_lines(sp)
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
vp = event.pos()
sp = self.mapToScene(vp)
self.move_lines(sp)
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
for item in self.items():
if isinstance(item, RectItem):
item.line_item.hide()
super().mouseReleaseEvent(event)
def move_lines(self, sp):
for item in self.items():
if isinstance(item, RectItem):
item.line_item.show()
item.move_line_to(sp)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = GraphicsView()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
I'm new to PyQt5. I'm trying to draw on top of a loaded image lines that are tracing my mouse, i.e. pressing the mouse will result in drawing whatever your mouse moves to, while the mouse is still pressed, and stops when we release the mouse.
I saw This answer and it was very helpful, but I'm trying to do something a bit more complicated.
I think I'm messing up with the events.
What I wrote (which doesn't work):
import sys
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtWidgets import QMainWindow, QApplication, QLabel
from PyQt5.QtGui import QPixmap, QPainter, QPen
class Menu(QMainWindow):
def __init__(self):
super().__init__()
self.drawing = False
self.lastPoint = QPoint()
self.image = QPixmap("picture.png")
self.setGeometry(100, 100, 500, 300)
self.resize(self.image.width(), self.image.height())
self.label = QLabel(self)
self.show()
def paintEvent(self, event):
painter = QPainter(self)
painter.drawPixmap(self.rect(), self.image)
pen = QPen(Qt.red, 3)
painter.setPen(pen)
painter.drawLine(self.lastPoint, event.pos())
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.drawing = True
self.lastPoint = event.pos()
def mouseMoveEvent(self, event):
if event.buttons() and Qt.LeftButton and self.drawing:
painter = QPainter(self.image)
painter.setPen(QPen(Qt.red, 3, Qt.SolidLine))
self.lastPoint = event.pos()
self.update()
def mouseReleaseEvent(self, event):
if event.button == Qt.LeftButton:
self.drawing = False
if __name__ == '__main__':
app = QApplication(sys.argv)
mainMenu = Menu()
sys.exit(app.exec_())
You want to use drawLine in mouseMoveEvent. When you move your mouse, it will invoke mouseMoveEvent which will draw a line from the last position, and then it will invoke by itself the paintEvent.
Try this:
import sys
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtGui import QPixmap, QPainter, QPen
class Menu(QMainWindow):
def __init__(self):
super().__init__()
self.drawing = False
self.lastPoint = QPoint()
self.image = QPixmap("picture.png")
self.setGeometry(100, 100, 500, 300)
self.resize(self.image.width(), self.image.height())
self.show()
def paintEvent(self, event):
painter = QPainter(self)
painter.drawPixmap(self.rect(), self.image)
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.drawing = True
self.lastPoint = event.pos()
def mouseMoveEvent(self, event):
if event.buttons() and Qt.LeftButton and self.drawing:
painter = QPainter(self.image)
painter.setPen(QPen(Qt.red, 3, Qt.SolidLine))
painter.drawLine(self.lastPoint, event.pos())
self.lastPoint = event.pos()
self.update()
def mouseReleaseEvent(self, event):
if event.button == Qt.LeftButton:
self.drawing = False
if __name__ == '__main__':
app = QApplication(sys.argv)
mainMenu = Menu()
sys.exit(app.exec_())