Mechanics behind the synchronisation of QPainters - python

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_())

Related

Adding animation to QPushbutton enterEvent and exitEvent

I'm trying to add custom animation to QPushbutton without making a custom QPushbutton and overriding its enterEvent() and leaveEvent().
So far I've tried this,
#staticmethod
def addButtonHoverAnimation(button:QPushButton,currentPos:QPoint):
'''
Method to:
=> Add hover animation for provided button
'''
enterShift = QPropertyAnimation(button,b'pos',button)
exitShift = QPropertyAnimation(button,b'pos',button)
def enterEvent(e):
pos=button.pos()
enterShift.setStartValue(pos)
enterShift.setEndValue(QPoint(pos.x()+3,pos.y()+3))
enterShift.setDuration(100)
enterShift.start()
Effects.dropShadow(button,1,2)
def leaveEvent(e):
pos=button.pos()
exitShift.setStartValue(pos)
exitShift.setEndValue(QPoint(pos.x()-3,pos.y()-3))
exitShift.setDuration(100)
exitShift.start()
Effects.dropShadow(button)
button.enterEvent=enterEvent
button.leaveEvent=leaveEvent
But when I move the mouse very quickly in and out of the button before the animation finishes, The button starts to move wierdly towards the North-West direction.
Button Animation Using Dynamic Positions
I figured out this was due to the leaveEvent() being triggered before enterEvent() even finishes and also because the start and end values are dynamic. So, I tried providing currentPos as a static position and using it instead,
#staticmethod
def addButtonHoverAnimation(button:QPushButton,currentPos:QPoint):
'''
Method to:
=> Add hover animation for provided button
'''
enterShift = QPropertyAnimation(button,b'pos',button)
enterShift.setStartValue(currentPos)
enterShift.setEndValue(QPoint(currentPos.x()+3,currentPos.y()+3))
enterShift.setDuration(100)
exitShift = QPropertyAnimation(button,b'pos',button)
exitShift.setStartValue(QPoint(currentPos.x()-3,currentPos.y()-3))
exitShift.setEndValue(currentPos)
exitShift.setDuration(100)
def enterEvent(e):
button.setProperty(b'pos',exitShift.endValue())
enterShift.start()
Effects.dropShadow(button,1,2)
def leaveEvent(e):
exitShift.start()
Effects.dropShadow(button)
button.enterEvent=enterEvent
button.leaveEvent=leaveEvent
On running, as soon as the mouse enters the QPushbutton, it moves to the top-left of its parent widget and the animation starts working fine. I can't figure out why this is happening. But I was able to get that, it only happened when I used any static value in the animation.
Button Animation with Static Position:
Here is an example:
import sys
from PyQt5.QtCore import QEvent, QPoint, QObject, QPropertyAnimation
from PyQt5.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget
# This is the same method mentioned above
from styling import addButtonHoverAnimation
class Widget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
layout=QVBoxLayout()
button1 = QPushButton("Proceed1", self)
layout.addWidget(button1)
button2 = QPushButton("Proceed2", self)
layout.addWidget(button2)
self.setLayout(layout)
self.resize(640, 480)
addButtonHoverAnimation(button1)
addButtonHoverAnimation(button2)
def main():
app = QApplication(sys.argv)
view = Widget()
view.show()
ret = app.exec_()
sys.exit(ret)
if __name__ == "__main__":
main()
The problem is that probably when the state is changed from enter to leave (or vice versa) the previous animation still does not end so the position of the widget is not the initial or final position, so when starting the new animation there is a deviation that accumulates. One possible solution is to initialize the position and keep it as a reference.
On the other hand you should not do x.fooMethod = foo_callable since many can fail, in this case it is better to use an eventfilter.
import sys
from dataclasses import dataclass
from functools import cached_property
from PyQt5.QtCore import QEvent, QPoint, QObject, QPropertyAnimation
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget
#dataclass
class AnimationManager(QObject):
widget: QWidget
delta: QPoint = QPoint(3, 3)
duration: int = 100
def __post_init__(self):
super().__init__(self.widget)
self._start_value = QPoint()
self._end_value = QPoint()
self.widget.installEventFilter(self)
self.animation.setTargetObject(self.widget)
self.animation.setPropertyName(b"pos")
self.reset()
def reset(self):
self._start_value = self.widget.pos()
self._end_value = self._start_value + self.delta
self.animation.setDuration(self.duration)
#cached_property
def animation(self):
return QPropertyAnimation(self)
def eventFilter(self, obj, event):
if obj is self.widget:
if event.type() == QEvent.Enter:
self.start_enter_animation()
elif event.type() == QEvent.Leave:
self.start_leave_animation()
return super().eventFilter(obj, event)
def start_enter_animation(self):
self.animation.stop()
self.animation.setStartValue(self.widget.pos())
self.animation.setEndValue(self._end_value)
self.animation.start()
def start_leave_animation(self):
self.animation.stop()
self.animation.setStartValue(self.widget.pos())
self.animation.setEndValue(self._start_value)
self.animation.start()
class Widget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
button1 = QPushButton("Proceed1", self)
button1.move(100, 100)
button2 = QPushButton("Proceed2", self)
button2.move(200, 200)
self.resize(640, 480)
animation_manager1 = AnimationManager(widget=button1)
animation_manager2 = AnimationManager(widget=button2)
def main():
app = QApplication(sys.argv)
view = Widget()
view.show()
ret = app.exec_()
sys.exit(ret)
if __name__ == "__main__":
main()
184 / 5000
Resultados de traducción
If you are using a layout then you must reset the position since the layout does not apply the position change immediately but only when the parent widget applies the changes.
class Widget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
button1 = QPushButton("Proceed1")
button2 = QPushButton("Proceed2")
lay = QVBoxLayout(self)
lay.addWidget(button1)
lay.addWidget(button2)
self.resize(640, 480)
self.animation_manager1 = AnimationManager(widget=button1)
self.animation_manager2 = AnimationManager(widget=button2)
def resizeEvent(self, event):
super().resizeEvent(event)
self.animation_manager1.reset()
self.animation_manager2.reset()

update() does not trigger paintEvent()

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.

PyQt: How to prevent processing multiple resize events when maximizing a window?

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_())

Possible rendering issue with QScrollArea and QPainter

I'm trying to create a character map visualization tool with PyQt5. With fontTools library, I'm extracting the UNICODE code points supported in a given ttf file. Then using QPainter.drawText I'm drawing the glyphs on labels. The labels are stored in a QGridLayout and the layout is in a QScrollArea
Everything works fine, except when I try to scroll. The drawn images are overlapped whenever I try to scroll too fast. It looks like this.
The labels are redrawn properly the moment the window loses focus.
Here's an MWE of what I've so far.
import sys
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFontDatabase, QFont, QColor, QPainter
from fontTools.ttLib import TTFont
class App(QWidget):
def __init__(self):
super().__init__()
self.fileName = "mukti.ttf" #the ttf file is located in the same path as the script
self.initUI()
def initUI(self):
self.setWindowTitle("Glyph Viewer")
self.setFixedSize(640, 480)
self.move(100, 100)
vBox = QtWidgets.QVBoxLayout()
self.glyphView = GlyphView()
vBox.addWidget(self.glyphView)
self.setLayout(vBox)
self.showGlyphs()
self.show()
def showGlyphs(self):
#Using the fontTools libray, the unicode blocks are obtained from the ttf file
font = TTFont(self.fileName)
charMaps = list()
for cmap in font['cmap'].tables:
charMaps.append(cmap.cmap)
charMap = charMaps[0]
fontDB = QFontDatabase()
fontID = fontDB.addApplicationFont("mukti.ttf")
fonts = fontDB.applicationFontFamilies(fontID)
qFont = QFont(fonts[0])
qFont.setPointSize(28)
self.glyphView.populateGrid(charMap, qFont)
class GlyphView(QtWidgets.QScrollArea):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.setWidgetResizable(True)
def populateGrid(self, charMap, qFont):
glyphArea = QtWidgets.QWidget(self)
gridLayout = QtWidgets.QGridLayout()
glyphArea.setLayout(gridLayout)
row, col = 1, 1
for char in charMap:
uni = charMap[char]
gridLayout.addWidget(Glyph(qFont, chr(char)), row, col)
if not col % 4:
col = 1
row += 1
else:
col += 1
self.setWidget(glyphArea)
class Glyph(QtWidgets.QLabel):
def __init__(self, font, char):
super().__init__()
self.font = font
self.char = char
self.initUI()
def initUI(self):
self.setFixedSize(48, 48)
self.setToolTip(self.char)
def paintEvent(self, event):
qp = QPainter(self)
qp.setBrush(QColor(0,0,0))
qp.drawRect(0, 0, 48, 48)
qp.setFont(self.font)
qp.setPen(QColor(255, 255, 255))
qp.drawText(event.rect(), Qt.AlignCenter, self.char)
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
I'm not sure what is causing this. Any help is appreciated!
The paintEvent() method gives us an object that belongs to QPaintEvent, that object provides a QRect through the rect() method, that QRect is the area that is currently visible, and that information could be used to optimize the painting, for example let's say we have a widget that shows texts in several lines, if the text is large few lines will look so painting all is a waste of resources, so with a proper calculation using the aforementioned QRect we could get those lines and paint that part spending few resources. In this answer I show an example of the use of event.rect().
In your case there is no need to use event.rect() since you would be painting the text in a part of the widget widget. what you should use is self.rect():
def paintEvent(self, event):
qp = QPainter(self)
qp.setBrush(QColor(0,0,0))
qp.drawRect(0, 0, 48, 48)
qp.setFont(self.font())
qp.setPen(QColor(255, 255, 255))
# change event.rect() to self.rect()
qp.drawText(self.rect(), Qt.AlignCenter, self.text())
I also see unnecessary to overwrite paintEvent() method since you can point directly to the QLabel the font, the text and the alignment:
class Glyph(QtWidgets.QLabel):
def __init__(self, font, char):
super().__init__(font=font, text=char, alignment=Qt.AlignCenter, toolTip=char)

change label style temporary (imitate button)

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_())

Categories

Resources