I am trying to draw an interactive zoom box on an image that will cause zoomed images to appear in other image windows. My issue is that nothing triggers the paintEvent(). It doesn't execute when the object is instantiated, and I can't trigger it manually using update() or repaint().
I put a print() statement inside to make sure paintEvent() is not being executed (it isn't). I know the code for drawing the rectangle on the image works because I tested it by executing it directly in the init() statement. I tried using update() (and forcing execution using QApplication.processEvent()) and repaint().
from math import floor, ceil
from PyQt5.QtWidgets import QLabel, QApplication, QHBoxLayout, QWidget, QGraphicsRectItem
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QPixmap, QColor, QPainter, QPolygon, QBrush, QPen
import sys
from RangeSlider import *
from StyleSheet import *
from LayoutDefine import *
class ZoomBox(QLabel):
def __init__(self, parent, zoomLevel = 100, penWidth = 3):
super(ZoomBox, self).__init__()
# zoomLevel specified in pct
if zoomLevel < 100:
self.zoom = 100
else:
self.zoom = zoomLevel
self.rectPen = QPen(QColor("#ff1f1f"))
self.rectPen.setWidth(penWidth)
self.left, self.top = 0, 0
self.parent = parent
self.DefSize()
self.update()
QApplication.processEvents()
def DefSize(self):
self.width = ceil(self.parent.width() / self.zoom * 100)
self.height = ceil(self.parent.height() / self.zoom * 100)
def paintEvent(self, event):
print("paintEvent")
self.DefSize()
img = QPixmap([path/to/image]).scaledToHeight(parent.height(), Qt.SmoothTransformation)
painter = QPainter(img)
painter.begin(self.parent)
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(self.rectPen)
painter.drawRect(self.left, self.top, self.width, self.height)
painter.end()
parent.setPixmap(img)
class VidWindow(QWidget):
def __init__(self):
super().__init__()
self.InitUI()
def InitUI(self):
foo = QLabel()
foo.setMaximumHeight(500)
foo.setPixmap(QPixmap([path/to/image]).scaledToHeight(500))
ZoomBox(foo)
bar = QHBoxLayout()
bar.addWidget(foo)
self.setLayout(bar)
self.showMaximized()
foo.update()
if __name__ == '__main__':
# init application
app = QApplication(sys.argv)
# instantiate main window
ex = VidWindow()
# end program handler
sys.exit(app.exec_())
The results are.. nothing. Nothing gets drawn and no error messages are output.
P.S. Apologies for all the imports, I wasn't confident about which ones I could delete, and I'm also allowing for the possibility that there might be a conflict.
musicamante, 3 hours ago (as a comment):
The reason for the zoomBox not being painted is that you're not
assigning it to any persistent object attribute, meaning that it is
deleted right after the InitUi returns. Moreover, it has no parent:
you're just setting the parent as a local attribute, while you should
possibly add that as an argument of the super init); if you don't do
that, you have to explicitly call show(). That said, the whole
procedure is a bit confused, you might think of clarifying your logic
some more.
Related
I'm studying QGraphicsView.
self._item = QGraphicsPixmapItem()
self._item.setFlags(QGraphicsPixmapItem.ItemIsFocusable | QGraphicsPixmapItem.ItemIsMovable)
self._scene = QGraphicsScene(self)
self.graphicsView_1.setScene(self._scene)
self._scene.addItem(self._item)
self._item.setPixmap(QPixmap("image_path"))
I know that the above sequence is the way to output pictures.
The second line "QGraphicsPixmapItem.ItemIsMovable" This is the mouse option.
I know that outputting an image in this way will move the picture with the mouse.
self._item = QGraphicsPixmapItem()
self._item.setFlags(QGraphicsPixmapItem.ItemIsFocusable | QGraphicsPixmapItem.ItemIsMovable)
self._scene = QGraphicsScene(self)
self.graphicsView_1.setScene(self._scene)
self.graphicsView_2.setScene(self._scene)
self._scene.addItem(self._item)
self._item.setPixmap(QPixmap("image_path"))
Then I made another QGraphicsView and applied the same scene as in 1.
In this case, if you click and drag the mouse in one QGraphicsView, both will be applied.
However, this has the disadvantage of having to watch the same scene.
I want to control multiple screens while outputting different pictures to QGraphicsView, but is there a way? Pictures are different, but zoom in, zoom out, pan, I want all QGraphicsviews to have the same control.
I want to control two different QGraphicsViews at the same time.
import sys
from PyQt5 import QtWidgets
from PyQt5.QtCore import QRectF
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap
from PyQt5.QtGui import QPainter
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import QGraphicsPixmapItem
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QGraphicsScene
from ui.preview_test import Ui_MainWindow
class mainwindow(QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.graphicsView_1.setCursor(Qt.OpenHandCursor)
self.graphicsView_1.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.graphicsView_2.setCursor(Qt.OpenHandCursor)
self.graphicsView_2.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.graphicsView_1.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.graphicsView_1.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.graphicsView_2.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.graphicsView_2.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.graphicsView_1.setRenderHints(QPainter.Antialiasing | QPainter.HighQualityAntialiasing |
QPainter.SmoothPixmapTransform)
self.graphicsView_2.setRenderHints(QPainter.Antialiasing | QPainter.HighQualityAntialiasing |
QPainter.SmoothPixmapTransform)
self.graphicsView_1.setBackgroundBrush(Qt.black)
self.graphicsView_2.setBackgroundBrush(Qt.black)
self._item = QGraphicsPixmapItem()
self._item.setFlags(QGraphicsPixmapItem.ItemIsFocusable |
QGraphicsPixmapItem.ItemIsMovable)
self._scene = QGraphicsScene(self)
self.graphicsView_1.setScene(self._scene)
self.graphicsView_2.setScene(self._scene)
self._scene.addItem(self._item)
self._delta = 0.1
self._item.setPixmap(QPixmap("C:/Users/wlxo0/Desktop/960x540 image.png"))
self.show()
def setBackground(self, color):
if isinstance(color, QColor):
self.graphicsView_1.setBackgroundBrush(color)
elif isinstance(color, (str, Qt.GlobalColor)):
color = QColor(color)
if color.isValid():
self.graphicsView_1.setBackgroundBrush(color)
def mouseMoveEvent(self, QMouseEvent):
self.mouse_x = QMouseEvent.x()
self.mouse_y = QMouseEvent.y()
print(f"X : {self.frame_2.x()} <= {self.mouse_x} <= {self.frame_2.x() + self.frame_2.width()-1}, Y : {self.frame_2.y()} <= {self.mouse_y} <= {self.frame_2.y() + self.frame_2.height()-1}")
def wheelEvent(self, event):
if event.angleDelta().y() > 0:
self.zoomIn()
else:
self.zoomOut()
def zoomIn(self):
self.zoom(1 + self._delta)
def zoomOut(self):
self.zoom(1 - self._delta)
def zoom(self, factor):
_factor = self.graphicsView_1.transform().scale(
factor, factor).mapRect(QRectF(0, 0, 1, 1)).width()
_factor2 = self.graphicsView_2.transform().scale(
factor, factor).mapRect(QRectF(0, 0, 1, 1)).width()
if _factor < 0.07 or _factor > 100:
return
if _factor2 < 0.07 or _factor2 > 100:
return
self.graphicsView_1.scale(factor, factor)
self.graphicsView_2.scale(factor, factor)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = mainwindow()
app.exec_()
Original_Code I was editing the code I found on github.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
self.horizontalLayout.setObjectName("horizontalLayout")
self.graphicsView_1 = QtWidgets.QGraphicsView(self.centralwidget)
self.graphicsView_1.setObjectName("graphicsView_1")
self.horizontalLayout.addWidget(self.graphicsView_1)
self.graphicsView_2 = QtWidgets.QGraphicsView(self.centralwidget)
self.graphicsView_2.setObjectName("graphicsView_2")
self.horizontalLayout.addWidget(self.graphicsView_2)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
The UI was created using Qt Designer.
Unfortunately, there's only a partial answer, as doing what you're trying to achieve requires a much complex implementation.
The first requirement is to use a unique scene for each view, because if you use the same scene you obviously have the same contents.
Then, you can use a QGraphicsObject as a "container" for the pixmap item. Basic QGraphicsItems don't inherit from QObject (a basic requirement for signals), and QGraphicsObject inherit from both QGraphicsItem and QObject.
By intercepting the ItemPositionHasChanged change, you can then emit the signal with the new position, and connect the two items in the two scenes with their setPos.
class PixmapItem(QtWidgets.QGraphicsObject):
moved = QtCore.pyqtSignal(QtCore.QPointF)
def __init__(self, parent=None):
super().__init__(parent)
self.pixmapItem = QtWidgets.QGraphicsPixmapItem(self)
self.setFlags(
self.ItemIsFocusable |
self.ItemIsMovable |
self.ItemSendsGeometryChanges)
def setPixmap(self, pixmap):
self.pixmapItem.setPixmap(pixmap)
self.pixmapItem.setPos(-self.pixmapItem.boundingRect().center())
def boundingRect(self):
return self.childrenBoundingRect()
def itemChange(self, change, value):
if change == self.ItemPositionHasChanged:
self.moved.emit(value)
return super().itemChange(change, value)
def paint(self, *args):
pass
class mainwindow(QMainWindow, Ui_MainWindow):
def __init__(self):
# ...
self.scene1 = QtWidgets.QGraphicsScene()
self.graphicsView_1.setScene(self.scene1)
self.item1 = PixmapItem()
self.scene1.addItem(self.item1)
self.scene2 = QtWidgets.QGraphicsScene()
self.graphicsView_2.setScene(self.scene2)
self.item2 = PixmapItem()
self.scene2.addItem(self.item2)
self.item1.setPixmap(QPixmap("image1.png"))
self.item2.setPixmap(QPixmap("image2.png"))
self.item1.moved.connect(self.item2.setPos)
self.item2.moved.connect(self.item1.setPos)
As said above, this will work, but won't be enough:
you're implementing the wheel event on the main window, which is problematic: first of all, you'd get a zoom from any place (except from widgets that handle wheel events and accept them without propagating it to the parent), then, as soon as the image is zoomed, the wheel will also scroll the contents, because hiding the scroll bars doesn't prevent their functionality;
hiding the scroll bars doesn't prevent the view to scroll the scene whenever required, which can be an issue when the image is bigger than the view and the visible scene rect requires adjustments after moving items;
the same issue is most important if the images have different sizes, which can result in flickering and other positioning issues everytime the window is resized;
There is no easy solution for this: providing such features requires most certainly to subclass the graphics view in order to provide custom behavior that properly works for each view and for the synchronization between them.
Other issues:
the whole mapRect computation is unnecessary: you can get the new factor just by doing self.graphicsView_1.transform().m11() * factor, which multiplies the current scaling by the given factor. See the QTransform docs;
scaling should always be consistent, and since scale() actually is applied to the current scale, the result is that you never get to the same values; try to add print(self.graphicsView_1.transform().m11()) at the end of zoom() and you'll see that after a few zoom in/out you can never get back to a precise 1.0;
use if not 0.07 <= _factor <= 100:;
I wanted to include a QToolbar in a Qwidget, but I found that I can only create a QToolbar in a QMainWindow. So, instead I want to create a Qlabel with an arrow icon in it. I downloaded an image with transparent background (I suppose). But, in the code, the image is not really transparent as I expected, it looks ugly. Is there any way to show only the arrow without the background. Below is a sample code
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class App(QMainWindow):
def __init__(self):
super().__init__()
self.title = 'test'
self.left = 0
self.top = 0
self.width = 300
self.height = 500
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.table_widget = MyTableWidget(self)
self.setCentralWidget(self.table_widget)
self.show()
class MyTableWidget(QWidget):
def __init__(self, parent):
super(QWidget, self).__init__(parent)
self.layout = QVBoxLayout(self)
# Create first tab
label3 = QLabel()
pixmap = QPixmap("index2.png")
smaller_pixmap = pixmap.scaled(32, 32, Qt.KeepAspectRatio, Qt.FastTransformation)
label3.setPixmap(smaller_pixmap)
label3.mouseReleaseEvent = self.on_click
self.layout.addWidget(label3)
self.setLayout(self.layout)
#pyqtSlot()
def on_click(self, event):
print('yes')
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
You are getting an "ugly" image because that image has lines that are partially transparent. I've increased the alpha threshold to better show them:
Those lines are part of the image and Qt cannot "guess" what portions of the image are "important" to you or not.
There's fundamentally no easy way to remove them by code, and even you'd succeed the result would be ugly anyway (some partial transparency is required around the border of the image to keep them smooth) and it wouldn't be worth the effort.
Just look for a different image, or edit it by clipping it to the arrow borders.
Context
I have a custom Widget which is supposed to make an animation of dots moving in order to make a kind of loading widget. To acheive that goal, I started using QPainter and QVariantAnimation objects, which seemed like a decent tools to do the job. The problem is that I think that the QPainters I initialise when drawing come in conflict with each other.
Technique
To acheive that, I initialize multiple QVariantAnimation, which signal .valueChanged() I connect to a function update(), which is supposed to launch the painEvent(), such as written in the docs
A paint event is a request to repaint all or part of a widget. It can happen for one of the following reasons:repaint() or update() was invoked,
the widget was obscured and has now been uncovered, or
many other reasons.
Since I start different animation at different times, I suppose that the update() is called many times, thus interfering with another QPainter already working. But, as I read in the docs,
When update() is called several times or the window system sends several paint events, Qt merges these events into one event with a larger region.
But it specifies nothing id the QPainter has the same region, which is why I suposse it crashes. It logs messages such as:
QBackingStore::endPaint() called with active painter on backingstore paint device
Minimal Working Example
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QDialog, QPushButton
from PyQt5.QtCore import Qt, pyqtSlot, QVariantAnimation, QVariant, QTimer
from PyQt5.QtGui import QColor, QPainter, QBrush
import time
class Dialog(QDialog):
def __init__(self, *args, **kwargs):
QDialog.__init__(self, *args, **kwargs)
self.resize(500, 500)
self.setLayout(QVBoxLayout())
self.button = QPushButton()
self.layout().addWidget(self.button)
self.paintWidget = PaintWidget()
self.layout().addWidget(self.paintWidget)
self.button.clicked.connect(self.paintWidget.startPainting)
self.button.clicked.connect(self.reverse)
def reverse(self):
if self.paintWidget.isMoving:
self.paintWidget.stopPainting()
class PaintWidget(QWidget):
def __init__(self):
super(PaintWidget, self).__init__()
self.dotRadius = 10
self.dotColor = QColor(255, 100, 100)
self.numberOfDots = 3
self.isMoving = False
self.animation = []
self.createAnimation()
self.dotPosition = [[0, 0], [0, 0], [0, 0]]
def startPainting(self):
for i in range(self.numberOfDots):
self.animation[i].start()
time.sleep(200)
self.isActive = True
def createAnimation(self):
for i in range(self.numberOfDots):
self.animation.append(QVariantAnimation(self, startValue=0, endValue=500, duration=3000))
self.animation[i].valueChanged.connect(self.updatePosition)
#pyqtSlot(QVariant)
def updatePosition(self, position):
self.dotPosition = [position, 0]
self.update()
def paintEvent(self, event):
painter = QPainter(self)
painter.fillRect(self.rect(), Qt.transparent)
painter.setRenderHint(QPainter.Antialiasing, True)
painter.setPen(Qt.NoPen)
for i in range(self.numberOfDots):
painter.save()
painter.translate(0, 0)
position = (self.dotPosition[i][0], self.dotPosition[i][1])
color = self.dotColor
painter.setBrush(QBrush(color, Qt.SolidPattern))
painter.drawEllipse(position[0], position[1], self.dotRadius, self.dotRadius)
painter.restore()
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
dial = Dialog()
dial.show()
sys.exit(app.exec_())
Results
I know that for now this code wouldn't work because I can't retreive which dot's animation got updated, but I believe the major problem here is the interference between the painters. Thus, could anyone tell me why this is hapenning and point me in a potential solution? Also, For knowing the dot that got updated and chose the good position, I'm really unsure of how to do this as well.
Your code has the following errors:
Never use time.sleep() in the main GUI thread since it blocks the event loop generating the freezing of the application.
the variable dotPosition that must store all the positions you are replacing it with only one position in the updatePosition method.
You should use QPoint if you are going to store a position instead of a list, use a list is not bad but using QPoint makes your code more readable.
Do not use painter.save() and painter.restore() unnecessarily, neither painter.translate().
Considering the above, the solution is as follows:
from functools import partial
from PyQt5 import QtCore, QtGui, QtWidgets
class Dialog(QtWidgets.QDialog):
def __init__(self, *args, **kwargs):
super(Dialog, self).__init__(*args, **kwargs)
self.resize(500, 500)
self.button = QtWidgets.QPushButton()
self.paintWidget = PaintWidget()
self.button.clicked.connect(self.paintWidget.startPainting)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.button)
lay.addWidget(self.paintWidget)
class PaintWidget(QtWidgets.QWidget):
def __init__(self):
super(PaintWidget, self).__init__()
self.dotRadius = 10
self.dotColor = QtGui.QColor(255, 100, 100)
self.animations = []
self.dotPosition = [
QtCore.QPoint(0, 0),
QtCore.QPoint(0, 0),
QtCore.QPoint(0, 0),
]
self.createAnimation()
def startPainting(self):
for i, animation in enumerate(self.animations):
QtCore.QTimer.singleShot(i * 200, animation.start)
def createAnimation(self):
for i, _ in enumerate(self.dotPosition):
wrapper = partial(self.updatePosition, i)
animation = QtCore.QVariantAnimation(
self,
startValue=0,
endValue=500,
duration=3000,
valueChanged=wrapper,
)
self.animations.append(animation)
#QtCore.pyqtSlot(int, QtCore.QVariant)
def updatePosition(self, i, position):
self.dotPosition[i] = QtCore.QPoint(position, 0)
self.update()
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.fillRect(self.rect(), QtCore.Qt.transparent)
painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(
QtGui.QBrush(self.dotColor, QtCore.Qt.SolidPattern)
)
for position in self.dotPosition:
painter.drawEllipse(position, self.dotRadius, self.dotRadius)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
dial = Dialog()
dial.show()
sys.exit(app.exec_())
I have a QMainWindow containing a child QWidget containing itself a QLabel.
When the window is maximized (e.g. by clicking the maximize icon on the window), the QLabel.resizeEvent() handler is called multiple times (supposedly to follow the progressive enlargement of the window until it takes the full desktop space).
The code in the event handler calls setPixmap() to scale the label pixmap. This is a relatively long operation which slows the process. Code for the label:
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QLabel, QFrame, QGridLayout
from PyQt5.QtGui import QImageReader, QPixmap
class DisplayArea(QLabel):
def __init__(self):
super().__init__()
self.pix_map = None
self.init_ui()
def init_ui(self):
self.setMinimumSize(1, 1)
self.setStyleSheet("border:1px solid black;")
def set_image(self, image):
self.pix_map = QPixmap.fromImage(image)
self.scale_image(self.size())
def scale_image(self, size):
if self.pix_map is None:
return
scaled = self.pix_map.scaled(size, Qt.KeepAspectRatio)
self.setPixmap(scaled)
def resizeEvent(self, e):
self.scale_image(e.size())
super().resizeEvent(e)
Is there a possibility to process the event only once, when the window has reached its final size?
The problem is that the resizeEvent is called many times in the time that the window is maximized, and that same number of times is what you call scale_image. One possible possible is not to update unless a period of time passes. In the following example only resizes for times greater than 100 ms (the time you must calibrate):
from PyQt5 import QtCore, QtGui, QtWidgets
class DisplayArea(QtWidgets.QLabel):
def __init__(self):
super().__init__()
self.pix_map = QtGui.QPixmap()
self._flag = False
self.init_ui()
def init_ui(self):
self.setMinimumSize(1, 1)
self.setStyleSheet("border:1px solid black;")
def set_image(self, image):
self.pix_map = QtGui.QPixmap.fromImage(image)
self.scale_image()
def scale_image(self):
if self.pix_map.isNull():
return
scaled = self.pix_map.scaled(self.size(), QtCore.Qt.KeepAspectRatio)
self.setPixmap(scaled)
def resizeEvent(self, e):
if not self._flag:
self._flag = True
self.scale_image()
QtCore.QTimer.singleShot(100, lambda: setattr(self, "_flag", False))
super().resizeEvent(e)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QMainWindow()
da = DisplayArea()
da.set_image(QtGui.QImage("logo.png"))
w.setCentralWidget(da)
w.show()
sys.exit(app.exec_())
I need to ask you guys for advice, as I'm running out of ideas. I'm working onclickable label. I have already done some "clickable label" class and handled mouseover event - which changes the label border and returns to the normal state when the mouse leaves.
Now I want it to have a custom glow effect on the label, but I want it to return to normal state after, let's say 0.5 s from a click.
I want my label with an image to imitate button. time.sleep does not work nice, especially with spamming clicks it freezes application main thread.
Hope I am not reinventing the wheel but as far as I know that's the way to go.
Heres the sample code, any answer is appreciated.
from PySide2.QtWidgets import QLabel, QSizePolicy, QGraphicsDropShadowEffect
from PySide2.QtGui import QPixmap
from PySide2.QtCore import (Signal, QEvent, QObject, QRect)
class ClickableLabel(QLabel):
def __init__(self, pic_path, width, height, border_color, click_function):
super(ClickableLabel, self).__init__()
# Setting the picture path and setting pixmap to label
self.pic_path = pic_path
self.pixmap = QPixmap(self.pic_path)
self.setPixmap(self.pixmap)
# Set the size
self.setFixedSize(width, height)
# Enable tracking and assign function
self.setMouseTracking(True)
self.click_function = click_function
# Set default
if border_color is None:
self.border_color = 'lightblue'
else:
self.border_color = border_color
def mouseMoveEvent(self, event):
# event.pos().x(), event.pos().y()
self.setStyleSheet("border: 1px solid " + str(self.border_color) + ";")
def leaveEvent(self, event):
# event.pos().x(), event.pos().y()
self.setStyleSheet("border: None")
def mousePressEvent(self, event):
self.click_function()
effect = QGraphicsDropShadowEffect(self)
effect.setOffset(0, 0)
effect.setBlurRadius(20)
effect.setColor(self.border_color)
self.setGraphicsEffect(effect)
If you want to run a task after a while you should not use time.sleep() because as it blocks the GUI causing it not to behave correctly, the best option is to use QTimer::singleShot().
On the other hand I see that you are passing a function so that it executes a task when it is clicked, that is not scalable, in Qt the correct thing is to create a signal and the advantage is that you can connect the same signal to several functions without coupling , that is, the one that emits the signal must not know in advance who will receive the signal.
I recommend taking values for defects for the arguments, I have taken the time to give an improvement to your code:
from PySide2 import QtCore, QtGui, QtWidgets
class ClickableLabel(QtWidgets.QLabel):
clicked = QtCore.Signal()
def __init__(self, pic_path="", width=80, height=30, border_color=QtGui.QColor("lightblue"), parent=None):
super(ClickableLabel, self).__init__(parent)
self.setPixmap(QtGui.QPixmap(pic_path))
self.setFixedSize(width, height)
self.setMouseTracking(True)
self.border_color = QtGui.QColor(border_color)
self.effect = QtWidgets.QGraphicsDropShadowEffect(self,
offset=QtCore.QPointF(0, 0),
blurRadius=20,
color=self.border_color)
self.setGraphicsEffect(self.effect)
self.disable_effect()
def mouseMoveEvent(self, event):
self.setStyleSheet("border: 1px solid {};".format(self.border_color.name()))
super(ClickableLabel, self).mouseMoveEvent(event)
def leaveEvent(self, event):
self.setStyleSheet("border: None")
super(ClickableLabel, self).leaveEvent(event)
def mousePressEvent(self, event):
self.clicked.emit()
self.effect.setEnabled(True)
QtCore.QTimer.singleShot(500, self.disable_effect)
super(ClickableLabel, self).mousePressEvent(event)
#QtCore.Slot()
def disable_effect(self):
self.effect.setEnabled(False)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
def on_click():
print("click")
w = ClickableLabel(pic_path="heart.png")
w.clicked.connect(on_click)
w.show()
sys.exit(app.exec_())