Всплывающая подсказка Qt поддерживает форматирование расширенного текста (только базовое c подмножество HTML), поэтому доступен тег <img>
:
self.button.setToolTip('<img src="icon.svg">')
Помните, что если вы используете локальный путь к файлу, он должен быть абсолютным или относительным к пути файла, который его загружает.
Альтернативой является использование системы ресурсов Qt : вы можете создать файл ресурсов в Designer, затем создайте его, используя pyrcc myresource.qrc -o myresource.py
, импортируйте его с import myresource
и загрузите изображения, используя путь префикса двоеточия:
self.button.setToolTip('<img src=":/images/icon.svg">')
Анимированные подсказки (GIF)
Подсказки Как и любой другой виджет, основанный на QTextDocument , анимация не поддерживается. Единственное решение - создать собственный виджет, который ведет себя как всплывающая подсказка.
Для этого наиболее логичным подходом является создание подкласса QLabel, который поддерживает класс QMov ie. это обеспечивает поддержку анимированных изображений.
Обратите внимание, что это нелегко: хотя всплывающие подсказки могут показаться очень простыми объектами, их поведение следует многим аспектам, которые пользователь считает само собой разумеющимся. Чтобы имитировать c такое поведение, подкласс должен быть тщательно подобран таким же образом.
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)