Issue
I want to show an image when hovering on a button.
But, the pyqt5 tooltip reference only contains text.
How can I do this? I want to do dynamically as for loop element like below.
I need to complete the # code
def createButtons(self):
for d_name in dic:
btn = QPushButton(d_name, self)
btn.clicked.connect(lambda state, x=d_name: self.btn_clicked(x))
# btn.addTooltipImage(d_name)
self.button_map[btn.text()] = btn
Solution
Qt's tooltip support rich text formatting (only a basic subset of HTML), so the <img>
tag is available:
self.button.setToolTip('<img src="icon.svg">')
Remember that if you are using a local file path, it has to be absolute or relative to the path of the file that loads it.
The alternative is to use Qt's resource system: you can create a resource file in Designer, then build it using pyrcc myresource.qrc -o myresource.py
, import it with import myresource
and load the images using the colon prefix path:
self.button.setToolTip('<img src=":/images/icon.svg">')
Animated tooltips (GIF)
Tooltips, like any other widget that is based on QTextDocument, don't support animations. The only solution is to create a custom widget that behaves like a tooltip.
In order to achieve that, the most logical approach is to subclass QLabel, which supports the QMovie class that provides support for animated images.
Note that this is not easy: while tooltips might seem very simple objects, their behavior follows many aspects that the user gives for granted. To mimic that behavior, a subclass has to be carefully tailored in the same way.
class ToolTipAnimation(QtWidgets.QLabel):
def __init__(self, parent, file, width=None, height=None):
super().__init__(parent, flags=QtCore.Qt.ToolTip)
self.setMouseTracking(True)
# image loading doesn't happen immediately, as it could require some time;
# we store the information for later use
self._file = file
self._width = width
self._height = height
self._shown = False
# a timer that prevents the enterEvent to hide the tip immediately
self.showTimer = QtCore.QTimer(interval=100, singleShot=True)
# install an event filter for the application, so that we can be notified
# whenever the user performs any action
QtWidgets.QApplication.instance().installEventFilter(self)
def load(self):
movie = QtGui.QMovie(self._file)
if self._width and not self._height:
self._height = self._width
if self._width and self._height:
size = QtCore.QSize(self._width, self._height)
movie.setScaledSize(size)
else:
size = QtCore.QSize()
for f in range(movie.frameCount()):
movie.jumpToFrame(f)
size = size.expandedTo(movie.currentImage().size())
self.setFixedSize(size)
self.setMovie(movie)
self._shown = True
def show(self, pos=None):
if not self._shown:
self.load()
if pos is None:
pos = QtGui.QCursor.pos()
# ensure that the tooltip is always shown within the screen geometry
for screen in QtWidgets.QApplication.screens():
if pos in screen.availableGeometry():
screen = screen.availableGeometry()
# add an offset so that the mouse cursor doesn't hide the tip
pos += QtCore.QPoint(2, 16)
if pos.x() < screen.x():
pos.setX(screen.x())
elif pos.x() + self.width() > screen.right():
pos.setX(screen.right() - self.width())
if pos.y() < screen.y():
pos.setY(screen.y())
elif pos.y() + self.height() > screen.bottom():
pos.setY(screen.bottom() - self.height())
break
self.move(pos)
super().show()
self.movie().start()
def maybeHide(self):
# if for some reason the tooltip is shown where the mouse is, we should
# not hide it if it's still within the parent's rectangle
if self.parent() is not None:
parentPos = self.parent().mapToGlobal(QtCore.QPoint())
rect = QtCore.QRect(parentPos, self.parent().size())
if QtGui.QCursor.pos() in rect:
return
self.hide()
def eventFilter(self, source, event):
# hide the tip for any user interaction
if event.type() in (QtCore.QEvent.KeyPress, QtCore.QEvent.KeyRelease,
QtCore.QEvent.WindowActivate, QtCore.QEvent.WindowDeactivate,
QtCore.QEvent.FocusIn, QtCore.QEvent.FocusOut,
QtCore.QEvent.Leave, QtCore.QEvent.Close,
QtCore.QEvent.MouseButtonPress, QtCore.QEvent.MouseButtonRelease,
QtCore.QEvent.MouseButtonDblClick, QtCore.QEvent.Wheel):
self.hide()
return False
def mouseMoveEvent(self, event):
QtCore.QTimer.singleShot(100, self.hide)
def enterEvent(self, event):
# hide the tooltip when mouse enters, but not immediately, otherwise it
# will be shown right after from the parent widget
if not self.showTimer.isActive():
QtCore.QTimer.singleShot(100, self.hide)
def showEvent(self, event):
self.showTimer.start()
def hideEvent(self, event):
self.movie().stop()
class ButtonIcon(QtWidgets.QPushButton):
toolTipAnimation = None
formats = tuple(str(fmt, 'utf8') for fmt in QtGui.QMovie.supportedFormats())
def setToolTipImage(self, image, width=None, height=None):
if not image or self.toolTipAnimation:
self.toolTipAnimation.hide()
self.toolTipAnimation.deleteLater()
self.toolTipAnimation = None
self.setToolTip('')
if not image:
return
if image.endswith(self.formats):
self.toolTipAnimation = ToolTipAnimation(self, image, width, height)
else:
if width and not height:
height = width
if width and height:
self.setToolTip(
'<img src="{}" width="{}" height="{}">'.format(
image, width, height))
else:
self.setToolTip('<img src="{}">'.format(image))
def event(self, event):
if (event.type() == QtCore.QEvent.ToolTip and self.toolTipAnimation and
not self.toolTipAnimation.isVisible()):
self.toolTipAnimation.show(event.globalPos())
return True
elif event.type() == QtCore.QEvent.Leave and self.toolTipAnimation:
self.toolTipAnimation.maybeHide()
return super().event(event)
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QHBoxLayout(self)
buttonFixed = ButtonIcon('fixed image')
buttonFixed.setToolTipImage('icon.svg')
layout.addWidget(buttonFixed)
buttonAnimated = ButtonIcon('animated gif')
# the size can be set explicitly (if height is not provided, it will
# be the same as the width)
buttonAnimated.setToolTipImage('animated.gif', 200)
layout.addWidget(buttonAnimated)
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.