You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PySide6自定义QTabBar绘制右侧文本后新标签文本不显示问题

问题:重写QTabBar的paintEvent后新标签文本空白

尝试将标签文本绘制在标签右侧以避免长名称被截断,但重写paintEvent后,新添加的标签仅显示空白,无法正常显示指定文本。

原代码

from PySide6 import QtWidgets
from PySide6 import QtCore
from PySide6.QtCore import Signal, QSize
from PySide6.QtWidgets import QLabel, QWidget, QVBoxLayout, QStyle, QStyleOptionTab, QStylePainter


class ShrinkTabBar(QtWidgets.QTabBar):
    _widthHint = -1
    _initialized = False
    _recursiveCheck = False

    addClicked = Signal()

    def __init__(self, parent):
        super(ShrinkTabBar, self).__init__(parent)
        self.setElideMode(QtCore.Qt.TextElideMode.ElideLeft)
        self.setExpanding(False)
        self.setMovable(True)
        self.addButton = QtWidgets.QToolButton(self.parent(), text='+')
        self.addButton.clicked.connect(self.addClicked)
        self._recursiveTimer = QtCore.QTimer(singleShot=True, timeout=self._unsetRecursiveCheck, interval=0)
        self._tabHint = QSize(0, 0)
        self._minimumHint = QSize(0, 0)

    def _unsetRecursiveCheck(self):
        self._recursiveCheck = False


    def _computeHints(self):
        if not self.count() or self._recursiveCheck:
            return
        self._recursiveCheck = True

        opt = QtWidgets.QStyleOptionTab()
        self.initStyleOption(opt, 0)
        width = self.style().pixelMetric(QtWidgets.QStyle.PixelMetric.PM_TabBarTabHSpace, opt, self)
        iconWidth = self.iconSize().width() + 2
        self._minimumWidth = width + iconWidth

        # default text widths are arbitrary
        fm = self.fontMetrics()
        self._minimumCloseWidth = self._minimumWidth + fm.horizontalAdvance('x' * 4) + iconWidth
        self._defaultWidth = width + fm.horizontalAdvance('x' * 26)
        self._defaultHeight = super().tabSizeHint(0).height()
        self._minimumHint = QtCore.QSize(self._minimumWidth, self._defaultHeight)
        self._defaultHint = self._tabHint = QtCore.QSize(self._defaultWidth, self._defaultHeight)

        self._initialized = True
        self._recursiveTimer.start()

    def _updateSize(self):
        if not self.count():
            return
        frameWidth = self.style().pixelMetric(
            QtWidgets.QStyle.PixelMetric.PM_DefaultFrameWidth, None, self.parent())
        buttonWidth = self.addButton.sizeHint().width()
        self._widthHint = (self.parent().width() - frameWidth - buttonWidth) // self.count()
        self._tabHint = QtCore.QSize(min(self._widthHint, self._defaultWidth), self._defaultHeight)
        # dirty trick to ensure that the layout is updated
        if not self._recursiveCheck:
            self._recursiveCheck = True
            self.setIconSize(self.iconSize())
            self._recursiveTimer.start()


    # BUG: is somewhere in here
    def paintEvent(self, event):
        painter = QStylePainter(self)
        option = QStyleOptionTab()

        for index in range(self.count()):
            self.initStyleOption(option, index)
            tabRect = self.tabRect(index)
            tabRect.moveLeft(10)
            painter.drawControl(QStyle.ControlElement.CE_TabBarTabShape, option)
            painter.drawText(tabRect,
                             QtCore.Qt.AlignmentFlag.AlignVCenter |
                             QtCore.Qt.TextFlag.TextDontClip,
                             self.tabText(index))


class ShrinkTabWidget(QtWidgets.QTabWidget):
    addClicked = Signal()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._tabBar = ShrinkTabBar(self)
        self.setTabBar(self._tabBar)
        self._tabBar.setTabsClosable(True)

        self._tabBar.addClicked.connect(self.get_page_choices)


    def get_page_choices(self):
        selection_page = QLabel("This worked")
        index = self.addTab(selection_page, 'New Tab')   # BUG: New Tab does not show up only a blank
        self._tabBar.setCurrentIndex(index)   # Set as current tab

问题分析与修复

问题根源

  1. 文本区域偏移错误:原代码中tabRect.moveLeft(10)将文本绘制区域向左偏移10像素,导致文本被移出标签的可见范围,所以显示空白。
  2. 未处理关闭按钮:开启setTabsClosable(True)后,原paintEvent没有绘制关闭按钮,也没有为关闭按钮预留空间,进一步挤压文本区域。
  3. 对齐方式不符合需求:仅设置AlignVCenter垂直居中,但没有设置水平靠右,无法实现“文本在标签右侧”的效果。

修复后的paintEvent代码

def paintEvent(self, event):
    painter = QStylePainter(self)
    option = QStyleOptionTab()

    for index in range(self.count()):
        self.initStyleOption(option, index)
        tabRect = self.tabRect(index)
        
        # 计算关闭按钮宽度(如果开启了可关闭)
        closeBtnWidth = 0
        if self.tabsClosable():
            closeBtnWidth = self.style().pixelMetric(QStyle.PM_TabBarCloseButtonWidth, option, self)
        
        # 为文本区域预留左侧空间:图标宽度 + 关闭按钮宽度 + 边距
        iconWidth = self.iconSize().width()
        textMargin = 8
        textRect = tabRect.adjusted(iconWidth + closeBtnWidth + textMargin, 0, -textMargin, 0)
        
        # 绘制标签形状(保持系统原生样式)
        painter.drawControl(QStyle.CE_TabBarTabShape, option)
        
        # 绘制文本:垂直居中 + 水平靠右 + 不截断
        painter.drawText(textRect,
                         QtCore.Qt.AlignVCenter | QtCore.Qt.AlignRight | QtCore.Qt.TextDontClip,
                         self.tabText(index))
        
        # 绘制关闭按钮(如果需要)
        if self.tabsClosable():
            # 调整关闭按钮的位置:左侧边距 + 垂直居中
            closeBtnRect = QtCore.QRect(
                tabRect.left() + textMargin,
                tabRect.top() + (tabRect.height() - closeBtnWidth) // 2,
                closeBtnWidth,
                closeBtnWidth
            )
            # 更新option的rect为关闭按钮区域,绘制按钮
            option.rect = closeBtnRect
            painter.drawControl(QStyle.CE_TabBarTabCloseButton, option)

额外调整

确保_computeHints方法中计算的最小宽度包含关闭按钮的空间,避免标签宽度不足:

def _computeHints(self):
    if not self.count() or self._recursiveCheck:
        return
    self._recursiveCheck = True

    opt = QtWidgets.QStyleOptionTab()
    self.initStyleOption(opt, 0)
    width = self.style().pixelMetric(QtWidgets.QStyle.PixelMetric.PM_TabBarTabHSpace, opt, self)
    iconWidth = self.iconSize().width() + 2
    self._minimumWidth = width + iconWidth

    # 增加关闭按钮宽度到最小宽度计算中
    closeBtnWidth = self.style().pixelMetric(QStyle.PM_TabBarCloseButtonWidth, opt, self) if self.tabsClosable() else 0
    
    fm = self.fontMetrics()
    self._minimumCloseWidth = self._minimumWidth + fm.horizontalAdvance('x' * 4) + iconWidth + closeBtnWidth
    self._defaultWidth = width + fm.horizontalAdvance('x' * 26) + closeBtnWidth
    self._defaultHeight = super().tabSizeHint(0).height()
    self._minimumHint = QtCore.QSize(self._minimumWidth + closeBtnWidth, self._defaultHeight)
    self._defaultHint = self._tabHint = QtCore.QSize(self._defaultWidth, self._defaultHeight)

    self._initialized = True
    self._recursiveTimer.start()

修改后,新添加的标签文本会正常显示在标签右侧,同时保留关闭按钮功能,不会出现文本截断或空白问题。

内容的提问来源于stack exchange,提问作者David Frick

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 18:24:52