I have a QWidget with a QLayout on which there is a QLabel.
I set a QPixmap on the label. Wherever the user clicks on the image, I want to draw a point. I defined mouseReleaseEvent (which works) and paintEvent (but no points are drawn). I've read all similar questions and none of the solutions worked for me. Any help? My relevant code:
class ImageScroller(QtWidgets.QWidget):
def __init__(self, img):
QtWidgets.QWidget.__init__(self)
main_layout = QtWidgets.QVBoxLayout()
self._image_label = QtWidgets.QLabel()
self._set_image(img)
main_layout.addWidget(self._image_label)
main_layout.addStretch()
self.setLayout(main_layout)
def _set_image(self, img):
img = qimage2ndarray.array2qimage(img)
qimg = QtGui.QPixmap.fromImage(img)
self._img_pixmap = QtGui.QPixmap(qimg)
self._image_label.show()
def paintEvent(self, paint_event):
painter = QtGui.QPainter(self)
painter.begin(self)
painter.setPen(QtGui.QPen(QtCore.Qt.red))
pen = QtGui.QPen()
pen.setWidth(20)
painter.setPen(pen)
painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
painter.drawPoint(300,300)
painter.drawLine(100, 100, 400, 400)
for pos in self.chosen_points:
painter.drawPoint(pos)
painter.end()
def mouseReleaseEvent(self, cursor_event):
self.chosen_points.append(QtGui.QCursor().pos())
self.update()
When you use QtGui.QCursor.pos() is getting the coordinates of the cursor with respect to the screen, but when you want to paint a widget you must be in the coordinates of the widget, for it the widget has the mapToGlobal() method:
self.mapFromGlobal(QtGui.QCursor.pos())
But in this case there is another solution, you must use the event that returns mouseReleaseEvent that has the information in the pos() method:
cursor_event.pos()
Another problem is that the label you created is above the widget so you do not see the points, the easiest thing is to draw the QPixmap directly with the drawPixmap() method.
Complete code:
from PyQt5 import QtWidgets, QtGui, QtCore
class ImageScroller(QtWidgets.QWidget):
def __init__(self):
self.chosen_points = []
QtWidgets.QWidget.__init__(self)
self._image = QtGui.QPixmap("image.png")
def paintEvent(self, paint_event):
painter = QtGui.QPainter(self)
painter.drawPixmap(self.rect(), self._image)
pen = QtGui.QPen()
pen.setWidth(20)
painter.setPen(pen)
painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
painter.drawPoint(300, 300)
painter.drawLine(100, 100, 400, 400)
for pos in self.chosen_points:
painter.drawPoint(pos)
def mouseReleaseEvent(self, cursor_event):
self.chosen_points.append(cursor_event.pos())
# self.chosen_points.append(self.mapFromGlobal(QtGui.QCursor.pos()))
self.update()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = ImageScroller()
w.resize(640, 480)
w.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_())
Basically I'm trying to draw a border around my frameless window. It's size is 550 and 407. I create my QPainter then my lines and in the end I'm trying to draw them.
def draw_border(self):
painter = QPainter()
painter.begin(self)
pen = QPen(QColor(255, 1, 1))
painter.setPen(pen)
left = QLine(0, 0, 0, 407)
bottom = QLine(0, 407, 550, 407)
right = QLine(550, 407, 550, 0)
painter.drawLine(left)
painter.drawLine(bottom)
painter.drawLine(right)
painter.end()
I expect to have three lines: left, right and bottom, but instead nothing happens.
I can not know where the error is because you do not provide an MCVE, so I will only propose my solution that is to reuse the rect() of the widget so the lines will adapt to the size of the window:
from PySide2 import QtGui, QtCore, QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint)
def paintEvent(self, event):
painter = QtGui.QPainter(self)
pen = QtGui.QPen(QtGui.QColor(255, 1, 1))
painter.setPen(pen)
width = pen.width()
rect = self.rect().adjusted(0, -width, -width, -width)
painter.drawRect(rect)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.resize(550, 407)
w.show()
sys.exit(app.exec_())
How would I apply a border radius, or otherwise achieve a rounded-corner effect, with a QMovie in PyQt5? It doesn't seem to react to QSS. Although I do not believe it to be relevant, here is my current code anyways, to give an idea what I have tried:
image = QLabel()
image.setObjectName("rant-image")
movie = QMovie("image_cache/" + img_name)
image.setMovie(movie)
movie.start()
with QSS:
QLabel#rant-image{
border-radius: 5px;
}
I have also tried painting the current pixmap each paintEvent by subclassing a QWidget, but nothing appears, and the pixmap has dimensions of 0:
invisible_pen = QPen()
invisible_pen.setWidth(0)
invisible_pen.setStyle(Qt.NoPen)
class RoundedMovie(QWidget):
def __init__(self, movie, parent=None):
QWidget.__init__(self, parent)
self.movie = movie
def setMovie(self, movie):
self.movie = movie
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing, True)
pixmap = self.movie.currentPixmap()
brush = QBrush(pixmap)
rect = QRect(0, 0, pixmap.width() - 10, pixmap.height() - 10)
painter.setPen(invisible_pen)
painter.setBrush(brush)
painter.drawRoundedRect(rect, 5, 5)
I also know the above implementation would not work because a paintEvent will not occur often enough to play the movie as expected
A possible solution is to implement a QProxyStyle:
from PyQt5 import QtCore, QtGui, QtWidgets
class RoundPixmapStyle(QtWidgets.QProxyStyle):
def __init__(self, radius=10, *args, **kwargs):
super(RoundPixmapStyle, self).__init__(*args, **kwargs)
self._radius = radius
def drawItemPixmap(self, painter, rectangle, alignment, pixmap):
painter.save()
pix = QtGui.QPixmap(pixmap.size())
pix.fill(QtCore.Qt.transparent)
p = QtGui.QPainter(pix)
p.setBrush(QtGui.QBrush(pixmap))
p.setPen(QtCore.Qt.NoPen)
p.drawRoundedRect(pixmap.rect(), self._radius, self._radius)
p.end()
super(RoundPixmapStyle, self).drawItemPixmap(painter, rectangle, alignment, pix)
painter.restore()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QLabel(alignment=QtCore.Qt.AlignCenter)
proxy_style = RoundPixmapStyle(radius=20, style=w.style())
w.setStyle(proxy_style)
movie = QtGui.QMovie("foo.gif")
w.setMovie(movie)
movie.start()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
I am having trouble maintaining previously drawn lines after I draw a new line. Right now if I click one button it will draw a line but once I click the second button a new line is drawn and the initial one is removed. I would like that both remain.
import sys
from PyQt5.QtWidgets import QMainWindow,QPushButton, QApplication
from PyQt5.QtCore import QSize, Qt, QLine, QPoint
from PyQt5.QtGui import QPainter, QPen
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setMinimumSize(QSize(300, 300))
pybutton = QPushButton('button', self)
pybutton.clicked.connect(self.draw_line)
pybutton.resize(100,100)
pybutton.move(0, 0)
pybutton2 = QPushButton('button2', self)
pybutton2.clicked.connect(self.draw_line)
pybutton2.resize(100,100)
pybutton2.move(200, 0)
self.line = QLine()
def draw_line(self):
button = self.sender()
x = int(button.x()) + int(button.width())/2
y = int(button.y())+100
self.line = QLine(x, y, x, y+100)
self.update()
def paintEvent(self,event):
QMainWindow.paintEvent(self, event)
if not self.line.isNull():
painter = QPainter(self)
pen = QPen(Qt.red, 3)
painter.setPen(pen)
painter.drawLine(self.line)
if __name__ == "__main__":
app = QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
sys.exit(app.exec_())
Short solution:
Store the QLine in a list and redraw:
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setMinimumSize(QSize(300, 300))
pybutton = QPushButton('button', self)
pybutton.clicked.connect(self.draw_line)
pybutton.resize(100,100)
pybutton.move(0, 0)
pybutton2 = QPushButton('button2', self)
pybutton2.clicked.connect(self.draw_line)
pybutton2.resize(100,100)
pybutton2.move(200, 0)
self.lines = []
def draw_line(self):
button = self.sender()
r = button.geometry()
p1 = QPoint(r.left() + r.width()/2, r.height())
p2 = p1+QPoint(0, 100)
line = QLine(p1, p2)
if line not in self.lines:
self.lines.append(line)
self.update()
def paintEvent(self,event):
QMainWindow.paintEvent(self, event)
painter = QPainter(self)
pen = QPen(Qt.red, 3)
painter.setPen(pen)
for line in self.lines:
painter.drawLine(line)
Long solution:
These kinds of questions have been asked countless times so I'm going to take the time to expand and give a general perspective of the problem so that I do not answer this type of question every time, so this question
It will be improved and updated.
paintEvent() is a method that handles Qt to perform the repainting of the GUI, this method redraws everything, therefore the drawing does not save memory, so you must store the instructions and make the drawing
with those instructions.
The paintEvent() method I recommend using to create custom widgets, not to make a GUI that performs the painting task as a main function, for this Qt offers class QGraphicsView, QGraphicsScene and QGraphicsItems.
The task of redrawing using the instructions of QPainter as drawLine(), fillRect(), etc consume resources, if you want to make a more efficient implementation it is appropriate to create a QPixmap that you must update whenever necessary, and repaint in the paintEvent() using the mentioned QPixmap:
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setMinimumSize(QSize(300, 300))
pybutton = QPushButton('button', self)
pybutton.clicked.connect(self.draw_line)
pybutton.resize(100,100)
pybutton.move(0, 0)
pybutton2 = QPushButton('button2', self)
pybutton2.clicked.connect(self.draw_line)
pybutton2.resize(100,100)
pybutton2.move(200, 0)
self.pixmap = QPixmap(self.size())
self.pixmap.fill(Qt.transparent)
def draw_line(self):
button = self.sender()
r = button.geometry()
p1 = QPoint(r.left() + r.width()/2, r.height())
p2 = p1+QPoint(0, 100)
line = QLine(p1, p2)
p = QPainter(self.pixmap)
pen = QPen(Qt.red, 3)
p.setPen(pen)
p.drawLine(line)
p.end()
self.update()
def paintEvent(self,event):
QMainWindow.paintEvent(self, event)
painter = QPainter(self)
painter.drawPixmap(QPoint(), self.pixmap)
def resizeEvent(self, event):
if self.width() > self.pixmap.width() or self.height() > self.pixmap.height():
pixmap = QPixmap(self.size())
pixmap.fill(Qt.transparent)
p = QPainter(pixmap)
p.drawPixmap(QPoint(), self.pixmap)
self.pixmap = pixmap
QMainWindow.resizeEvent(self, event)
I'm working on a touch screen app where gui space is very tight. I'd like to rotate a QLabel a bit so that it's either vertical, or offset at a diagonal a bit. Any suggestions? I couldn't find anything relevant on the QLabel interface.
Thanks so much!
QLabel does not do this. But you can easily create your own widget containing a bit of text:
class MyLabel(QtGui.QWidget):
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.setPen(QtCore.Qt.black)
painter.translate(20, 100)
painter.rotate(-90)
painter.drawText(0, 0, "hellos")
painter.end()
Another option is to draw a QGraphicsView, you then have the liberty to map real widgets (i.e. a QLabel) through any coordinate transformation.
I used this post to make another solution that I think is maybe better. Here it is:
class VerticalLabel(QLabel):
def __init__(self, *args):
QLabel.__init__(self, *args)
def paintEvent(self, event):
QLabel.paintEvent(self, event)
painter = QPainter (self)
painter.translate(0, self.height()-1)
painter.rotate(-90)
self.setGeometry(self.x(), self.y(), self.height(), self.width())
QLabel.render(self, painter)
def minimumSizeHint(self):
size = QLabel.minimumSizeHint(self)
return QSize(size.height(), size.width())
def sizeHint(self):
size = QLabel.sizeHint(self)
return QSize(size.height(), size.width())
Try this
class myStyle(QCommonStyle):
def __init__(self, angl=0, point=QPoint(0, 0)):
super(myStyle, self).__init__()
self.angl = angl
self.point = point
def drawItemText(self, painter, rect, flags, pal, enabled, text, textRole):
if not text:
return
savedPen = painter.pen()
if textRole != QPalette.NoRole:
painter.setPen(QPen(pal.brush(textRole), savedPen.widthF()))
if not enabled:
pen = painter.pen()
painter.setPen(pen)
painter.translate(self.point)
painter.rotate(self.angl)
painter.drawText(rect, flags, text)
painter.setPen(savedPen)
and
label = QLabel()
label.setStyle(myStyle(-45, QPoint(0, 100)))
Answer from #Controlix is a generic implementation, however, I was getting a "recursive painting call" warning. I was able to address that by combining approaches from #Controlix and #Ivo. Here is my implementation:
from PyQt5.Qt import QLabel
from PyQt5 import QtGui
class VerticalLabel(QLabel):
def __init__(self, *args):
QLabel.__init__(self, *args)
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.translate(0, self.height())
painter.rotate(-90)
painter.drawText(0, self.width()/2, self.text())
painter.end()
I took all of the above posts, and tested each one.
I believe this is the best combination of them all.
This centers the text horizontally and vertically, and sets size hints correctly.
from PyQt5 import QtGui
from PyQt5 import QtWidgets
from PyQt5 import QtCore
class VerticalLabel(QtWidgets.QLabel):
def __init__(self, *args):
QtWidgets.QLabel.__init__(self, *args)
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.translate(0, self.height())
painter.rotate(-90)
# calculate the size of the font
fm = QtGui.QFontMetrics(painter.font())
xoffset = int(fm.boundingRect(self.text()).width()/2)
yoffset = int(fm.boundingRect(self.text()).height()/2)
x = int(self.width()/2) + yoffset
y = int(self.height()/2) - xoffset
# because we rotated the label, x affects the vertical placement, and y affects the horizontal
painter.drawText(y, x, self.text())
painter.end()
def minimumSizeHint(self):
size = QtWidgets.QLabel.minimumSizeHint(self)
return QtCore.QSize(size.height(), size.width())
def sizeHint(self):
size = QtWidgets.QLabel.sizeHint(self)
return QtCore.QSize(size.height(), size.width())
class Example(QtWidgets.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
lbl1 = VerticalLabel('ABSOLUTE')
lbl1.setFont(QtGui.QFont('Arial', 20))
lbl1.setStyleSheet("QLabel { background-color : black; color : orange; }");
lbl2 = VerticalLabel('lbl 2')
lbl3 = VerticalLabel('lbl 3')
hBoxLayout = QtWidgets.QHBoxLayout()
hBoxLayout.addWidget(lbl1)
hBoxLayout.addWidget(lbl2)
hBoxLayout.addWidget(lbl3)
self.setLayout(hBoxLayout)
self.setGeometry(300, 300, 250, 150)
self.show()
def main():
app = QtWidgets.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()