QTabWidget 访问实际选项卡(不是内容小部件)

Posted

技术标签:

【中文标题】QTabWidget 访问实际选项卡(不是内容小部件)【英文标题】:QTabWidget Access Actual Tab (not the content widget) 【发布时间】:2020-03-01 15:23:12 【问题描述】:

在这张图片中:

我想访问实际的选项卡,而不是内容,所以我可以在实际选项卡上设置QPropertyAnimation,当它悬停在上面时。我知道如何使悬停事件起作用,并且可以在悬停时获取选项卡索引,只是在悬停时无法访问实际的选项卡。某处是否有标签列表作为QTabBarQTabWidget 的属性,或者我在哪里可以找到这些标签?还是我必须继承 addTab 函数才能单独创建选项卡?

额外信息

使用 PyQt5.14.1 Windows 10 Python 3.8.0

【问题讨论】:

选项卡不是小部件,也不是实际对象。你能明确说明你想用它做什么吗? 您能否更好地向我解释您想要做什么,询问其基本目标,而不是如何实施没有人保证可行的可能解决方案。我想你有一个XY problem @musicamante 当我将鼠标悬停在一个选项卡上时,我希望颜色随着动画而改变,所以我不能使用样式表。我得到了悬停事件上选项卡所在的索引(子分类和创建);我只是无法获取选项卡的实际对象,因此我可以对其颜色的变化进行动画处理。 我在分析QTabBar.cpp 时遇到了QTabBar.tabList(...),但也许这是私人的所以无法访问? (我几乎没有 C++ 编程知识,我使用 Python)。如果它不是私有的,那么我如何从QTabBar 访问它? @musicamante 那么标签是什么? 【参考方案1】:

您无法访问“标签”,因为它们不是对象,而是标签栏列表内容的抽象表示。

自定义其外观的唯一方法是继承 QTabBar 并覆盖 paintEvent()

为了添加过度效果,您必须为每个选项卡提供独特的动画,因此您必须跟踪所有插入或删除的选项卡。 addTabinsertTabremoveTab 方法不是有效选项,因为 QTabWidget 不使用它们。它使用 tabInserted()tabRemoved() 代替,因此它们也将被覆盖。

不过,这可能是样式表的问题,尤其是在您想设置字体或边距时。 幸运的是,我们可以将 qproperty-* 声明与自定义 PyQt 属性一起使用,在下面的示例中,我将它们用于选项卡颜色。

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;
            
        ''')

一些最后的笔记:

我只实现了顶部标签栏方向,如果你想在其他方向使用标签,你需要改变边距和矩形调整; 请记住,使用样式表会破坏箭头按钮的外观;(当标签超出标签栏的宽度时),您需要仔细设置它们 可移动(可拖动)标签的绘制已损坏; 现在我真的不知道如何解决这个问题;

【讨论】:

谢谢,这正是我所需要的 一个问题:边框半径是怎么画的?哪几行代码可以做到这一点?谢谢 @SamG101 我在pen = ... 行中设置了该值,然后在使用qp.setPen() 时应用该值。为简单起见,我只为选定的选项卡使用了边框,而其他选项卡没有(QtCore.Qt.NoPen),但您也可以按照与selectedColor 相同的概念设置该属性,可能使用类似@ 987654337@ 和 normalBorder. 啊抱歉,我的意思是创建的选项卡边框的曲率,而不是颜色。 @SamG101 那是drawRoundedRect()。如果你想了解更多关于 Qt 绘画的知识,可以阅读关于QPainter 的文档。

以上是关于QTabWidget 访问实际选项卡(不是内容小部件)的主要内容,如果未能解决你的问题,请参考以下文章

QTabWidget 在选项卡上设置掩码(不是选项卡内容)

访问 QTabWidget 中选项卡的小部件

QTabWidget 选项卡在其中一个选项卡中不显示任何内容

如何访问 QTabWidget 不同选项卡中的小部件?

将选项卡从 QTabWidget 调整为内容 [重复]

如何识别 QTabWidget 中当前选项卡何时发生变化?