In this image:
I would like to access the actual tabs, rather than the content, so I can set a QPropertyAnimation
on the actual tab when it is hovered on. I know how to get the hover event working, and I can get the tab index on the hover, I just can't access the actual tab when I hover on it. Is there a list of the tabs somewhere as an attribute of the QTabBar
or the QTabWidget
, or where can I find the tabs? Or do I have to subclass the addTab
function to create the tabs individually?
Extra Info
You cannot access "tabs", as they are not objects, but an abstract representation of the contents of the tab bar list.
The only way to customize their appearance is by subclassing QTabBar and overriding the paintEvent()
.
In order to add an over effect, you have to provide a unique animation for each tab, so you have to keep track of all tabs that are inserted or removed. The addTab
, insertTab
and removeTab
methods are not valid options, since they are not used by QTabWidget. It uses instead tabInserted()
and tabRemoved()
, so those are to be overridden too.
This could be a problem with stylesheets, though, especially if you want to set fonts or margins.
Luckily, we can use the qproperty-*
declaration with custom PyQt properties, and in the following example I'm using them for the tab colors.
class AnimatedTabBar(QtWidgets.QTabBar):
def __init__(self, *args):
super().__init__(*args)
palette = self.palette()
self._normalColor = palette.color(palette.Dark)
self._hoverColor = palette.color(palette.Mid)
self._selectedColor = palette.color(palette.Light)
self.animations = []
self.lastHoverTab = -1
@QtCore.pyqtProperty(QtGui.QColor)
def normalColor(self):
return self._normalColor
@normalColor.setter
def normalColor(self, color):
self._normalColor = color
for ani in self.animations:
ani.setEndValue(color)
@QtCore.pyqtProperty(QtGui.QColor)
def hoverColor(self):
return self._hoverColor
@hoverColor.setter
def hoverColor(self, color):
self._hoverColor = color
for ani in self.animations:
ani.setStartValue(color)
@QtCore.pyqtProperty(QtGui.QColor)
def selectedColor(self):
return self._selectedColor
@selectedColor.setter
def selectedColor(self, color):
self._selectedColor = color
self.update()
def tabInserted(self, index):
super().tabInserted(index)
ani = QtCore.QVariantAnimation()
ani.setStartValue(self.normalColor)
ani.setEndValue(self.hoverColor)
ani.setDuration(150)
ani.valueChanged.connect(self.update)
self.animations.insert(index, ani)
def tabRemoved(self, index):
super().tabRemoved(index)
ani = self.animations.pop(index)
ani.stop()
ani.deleteLater()
def event(self, event):
if event.type() == QtCore.QEvent.HoverMove:
tab = self.tabAt(event.pos())
if tab != self.lastHoverTab:
if self.lastHoverTab >= 0:
lastAni = self.animations[self.lastHoverTab]
lastAni.setDirection(lastAni.Backward)
lastAni.start()
if tab >= 0:
ani = self.animations[tab]
ani.setDirection(ani.Forward)
ani.start()
self.lastHoverTab = tab
elif event.type() == QtCore.QEvent.Leave:
if self.lastHoverTab >= 0:
lastAni = self.animations[self.lastHoverTab]
lastAni.setDirection(lastAni.Backward)
lastAni.start()
self.lastHoverTab = -1
return super().event(event)
def paintEvent(self, event):
selected = self.currentIndex()
qp = QtGui.QPainter(self)
qp.setRenderHints(qp.Antialiasing)
style = self.style()
fullTabRect = QtCore.QRect()
tabList = []
for i in range(self.count()):
tab = QtWidgets.QStyleOptionTab()
self.initStyleOption(tab, i)
tabRect = self.tabRect(i)
fullTabRect |= tabRect
if i == selected:
# make the selected tab slightly bigger, but ensure that it's
# still within the tab bar rectangle if it's the first or the last
tabRect.adjust(
-2 if i else 0, 0,
2 if i < self.count() - 1 else 0, 1)
pen = QtCore.Qt.lightGray
brush = self._selectedColor
else:
tabRect.adjust(1, 1, -1, 1)
pen = QtCore.Qt.NoPen
brush = self.animations[i].currentValue()
tabList.append((tab, tabRect, pen, brush))
# move the selected tab to the end, so that it can be painted "over"
if selected >= 0:
tabList.append(tabList.pop(selected))
# ensure that we don't paint over the tab base
margin = max(2, style.pixelMetric(style.PM_TabBarBaseHeight))
qp.setClipRect(fullTabRect.adjusted(0, 0, 0, -margin))
for tab, tabRect, pen, brush in tabList:
qp.setPen(pen)
qp.setBrush(brush)
qp.drawRoundedRect(tabRect, 4, 4)
style.drawControl(style.CE_TabBarTabLabel, tab, qp, self)
class Example(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QVBoxLayout(self)
self.tabWidget = QtWidgets.QTabWidget()
layout.addWidget(self.tabWidget)
self.tabBar = AnimatedTabBar(self.tabWidget)
self.tabWidget.setTabBar(self.tabBar)
self.tabWidget.addTab(QtWidgets.QCalendarWidget(), 'tab 1')
self.tabWidget.addTab(QtWidgets.QTableWidget(4, 8), 'tab 2')
self.tabWidget.addTab(QtWidgets.QGroupBox('Group'), 'tab 3')
self.tabWidget.addTab(QtWidgets.QGroupBox('Group'), 'tab 4')
self.setStyleSheet('''
QTabBar {
qproperty-hoverColor: rgb(128, 150, 140);
qproperty-normalColor: rgb(150, 198, 170);
qproperty-selectedColor: lightgreen;
}
''')
Some final notes: