强制 QTabBar 选项卡保持尽可能小并忽略 sizeHint

问题描述 投票:0回答:3

我正在尝试将

+
按钮添加到
QTabBar
中。几年前有一个很棒的解决方案,但有一个小问题,它不适用于
PySide2
。该问题是由选项卡自动调整大小以填充
sizeHint
引起的,在本例中不需要这样做,因为需要额外的空间。有什么方法可以禁用此行为吗?

我已经尝试过

QTabBar.setExpanding(False)
,但根据this答案,该属性大多被忽略:

坏消息是 QTabWidget 实际上忽略了该属性,因为它总是强制其选项卡为最小尺寸(即使您设置了自己的选项卡栏)。

差异在于

PySide2
,它强制选项卡为首选尺寸,我希望使用最小尺寸的旧行为。

编辑: 代码最少的示例。

sizeHint
宽度将选项卡拉伸至整个宽度,而在较旧的 Qt 版本中则不会这样做。我无法真正覆盖
tabSizeHint
,因为我不知道原始选项卡大小应该是多少。

import sys
from PySide2 import QtCore, QtWidgets

class TabBar(QtWidgets.QTabBar):
    def sizeHint(self):
        return QtCore.QSize(100000, super().sizeHint().height())

class Test(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super(Test, self).__init__(parent)

        layout = QtWidgets.QVBoxLayout()
        self.setLayout(layout)

        tabWidget = QtWidgets.QTabWidget()
        tabWidget.setTabBar(TabBar())
        layout.addWidget(tabWidget)

        tabWidget.addTab(QtWidgets.QWidget(), 'this shouldnt be stretched')

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    test = Test()
    test.show()
    sys.exit(app.exec_())
python pyside2 qtabbar
3个回答
1
投票

我认为您的问题可能有一个简单的解决方案(见下文)。在链接的部分解决方案计算“+”按钮的绝对定位的情况下,Qt 的真正意图始终是让布局引擎完成它的工作,而不是试图告诉它具体的大小和位置。

QTabWidget
基本上是布局和小部件的预构建合并,有时您只需跳过预构建并构建自己的。

在 TabBar 上构建带有额外内容的自定义 TabWidget 的示例:

import sys
from PySide2 import QtWidgets
from random import randint
    
class TabWidget(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        #layout for entire widget
        vbox = QtWidgets.QVBoxLayout(self)
        
        #top bar:
        hbox = QtWidgets.QHBoxLayout()
        vbox.addLayout(hbox)
        
        self.tab_bar = QtWidgets.QTabBar()
        self.tab_bar.setMovable(True)
        hbox.addWidget(self.tab_bar)
        
        spacer = QtWidgets.QSpacerItem(0,0,QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
        hbox.addSpacerItem(spacer)
        
        add_tab = QtWidgets.QPushButton('+')
        hbox.addWidget(add_tab)
        
        #tab content area:
        self.widget_stack = QtWidgets.QStackedLayout()
        vbox.addLayout(self.widget_stack)
        self.widgets = {}

        #connect events
        add_tab.clicked.connect(self.add_tab)
        self.tab_bar.currentChanged.connect(self.currentChanged)
        
    def add_tab(self):
        tab_text = 'tab' + str(randint(0,100))
        tab_index = self.tab_bar.addTab(tab_text)
        widget = QtWidgets.QLabel(tab_text)
        self.tab_bar.setTabData(tab_index, widget)
        
        self.widget_stack.addWidget(widget)
        
        self.tab_bar.setCurrentIndex(tab_index)

        
    def currentChanged(self, i):
        if i >= 0:
            self.widget_stack.setCurrentWidget(self.tab_bar.tabData(i))
        


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    test = TabWidget()
    test.show()
    sys.exit(app.exec_())

总而言之,我认为预构建的

QTabWidget.setCornerWidget
可能正是您正在寻找的(将 QPushButton 设置为右上角的小部件)。我编写的示例应该更容易定制,但也需要付出更多努力来重新实现所有相同的功能。您将必须重新实现一些信号逻辑来自行创建/删除/选择/重新排列选项卡。我只演示了简单的实现,这可能无法适应所有情况。


1
投票

以 Aaron 的代码为基础,我成功实现了使用现有脚本所需的所有功能:

from PySide2 import QtCore, QtWidgets

class TabBar(QtWidgets.QTabBar):
    def minimumSizeHint(self):
        """Allow the tab bar to shrink as much as needed."""
        minimumSizeHint = super(TabBar, self).minimumSizeHint()
        return QtCore.QSize(0, minimumSizeHint.height())

class TabWidgetPlus(QtWidgets.QWidget):

    tabOpenRequested = QtCore.Signal()
    tabCountChanged = QtCore.Signal(int)

    def __init__(self, parent=None):
        self._addingTab = False
        super(TabWidgetPlus, self).__init__(parent=parent)

        # Main layout
        layout = QtWidgets.QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)

        # Bar layout
        self._tabBarLayout = QtWidgets.QHBoxLayout()
        self._tabBarLayout.setContentsMargins(0, 0, 0, 0)
        self._tabBarLayout.setSpacing(0)
        layout.addLayout(self._tabBarLayout)

        self._tabBar = TabBar()
        self._tabBarLayout.addWidget(self._tabBar)
        for method in (
                'isMovable', 'setMovable',
                'tabsClosable', 'setTabsClosable',
                'tabIcon', 'setTabIcon',
                'tabText', 'setTabText',
                'currentIndex', 'setCurrentIndex',
                'currentChanged', 'tabCloseRequested',
            ):
            setattr(self, method, getattr(self._tabBar, method))

        self._plusButton = QtWidgets.QPushButton('+')
        self._tabBarLayout.addWidget(self._plusButton)  # TODO: Find location to insert
        self._plusButton.setFixedWidth(20)

        self._tabBarLayout.addStretch()

        # Content area
        self._contentArea = QtWidgets.QStackedLayout()
        layout.addLayout(self._contentArea)

        # Signals
        self.currentChanged.connect(self._currentChanged)
        self._plusButton.clicked.connect(self.tabOpenRequested.emit)

        # Final setup
        self.installEventFilter(self)

    @QtCore.Slot(int)
    def _currentChanged(self, i):
        """Update the widget."""
        if i >= 0 and not self._addingTab:
            self._contentArea.setCurrentWidget(self.tabBar().tabData(i))

    def eventFilter(self, obj, event):
        """Intercept events until the correct height is set."""
        if event.type() == QtCore.QEvent.Show:
            self.plusButton().setFixedHeight(self._tabBar.geometry().height())
            self.removeEventFilter(self)
        return False

    def tabBarLayout(self):
        return self._tabBarLayout

    def tabBar(self):
        return self._tabBar

    def plusButton(self):
        return self._plusButton

    def tabAt(self, point):
        """Get the tab at a given point.
        This takes any layout margins into account.
        """
        offset = self.layout().contentsMargins().top() + self.tabBarLayout().contentsMargins().top()
        return self.tabBar().tabAt(point - QtCore.QPoint(0, offset))

    def addTab(self, widget, name=''):
        """Add a new tab.

        Returns:
            Tab index as an int.
        """
        self._addingTab = True
        tabBar = self.tabBar()
        try:
            index = tabBar.addTab(name)
            tabBar.setTabData(index, widget)
            self._contentArea.addWidget(widget)

        finally:
            self._addingTab = False
        return index

    def insertTab(self, index, widget, name=''):
        """Inserts a new tab.
        If index is out of range, a new tab is appended.

        Returns:
            Tab index as an int.
        """
        self._addingTab = True
        tabBar = self.tabBar()
        try:
            index = tabBar.insertTab(index, name)
            tabBar.setTabData(index, widget)
            self._contentArea.insertWidget(index, widget)

        finally:
            self._addingTab = False
        return index

    def removeTab(self, index):
        """Remove a tab."""
        tabBar = self.tabBar()
        self._contentArea.removeWidget(tabBar.tabData(index))
        tabBar.removeTab(index)


if __name__ == '__main__':
    import sys
    import random

    app = QtWidgets.QApplication(sys.argv)
    test = TabWidgetPlus()

    test.addTab(QtWidgets.QPushButton(), 'yeah')
    test.insertTab(0, QtWidgets.QCheckBox(), 'what')
    test.insertTab(1, QtWidgets.QRadioButton(), 'no')
    test.removeTab(1)
    test.setMovable(True)
    test.setTabsClosable(True)

    def tabTest():
        name = 'Tab ' + str(random.randint(0, 100))
        index = test.addTab(QtWidgets.QLabel(name), name)
        test.setCurrentIndex(index)
    test.tabOpenRequested.connect(tabTest)
    test.tabCloseRequested.connect(lambda index: test.removeTab(index))

    test.show()
    sys.exit(app.exec_())

唯一的区别是,如果您使用

tabWidget.tabBar().tabAt(point)
,则不再保证它是正确的,因为它不考虑任何边距。我将边距设置为
0
,因此这应该不是问题,但我还在
TabWidgetPlus.tabAt
中包含了这些更正。

我只从

QTabBar
复制了一些方法到
QTabWidget
,因为有些方法可能需要额外的测试。


0
投票

在任何地方都找不到该问题的正确答案,但在实验过程中得出了这样的结论:所有缩小选项卡的尝试都将失败,因为尚未找到设置每个选项卡的 SizeHint 的方法。

但是 QTabWidget 默认情况下会这样做,所以..为什么不使用它并添加一些代码来处理“加选项卡”?

简化示例:

import sys
from PySide6.QtWidgets import QApplication, QTabWidget, QLabel, QInputDialog


class Tabs(QTabWidget):
    def __init__(self):
        super().__init__()
        self.prev_tab_index = 0
        self.tab_change_ignore = False

        # Add at least one normal tab
        self.tab_all = QLabel('All')
        self.addTab(self.tab_all, 'All')

        # Access tabs directly, and add 'plus tab' without its widget
        tabs = self.tabBar()
        tabs.addTab('+')
        tabs.currentChanged.connect(self.tab_change)

    def tab_change(self, i):
        # Ignore internal manipulations
        if self.tab_change_ignore:
            return

        # Handle 'plus tab'
        if self.count() - i == 1:
            self.tab_change_ignore = True
            if name := QInputDialog.getText(self, 'Adding tab', 'Enter tab name:')[0]:
                self.insertTab(i, QLabel(name), name)
                self.setCurrentIndex(i)
            else:
                self.setCurrentIndex(self.prev_tab_index)
            self.tab_change_ignore = False
            
        # Handle other tabs
        else:
            self.setCurrentIndex(i)
            self.prev_tab_index = i


if __name__ == '__main__':
    app = QApplication(sys.argv)
    test = Tabs()
    test.show()
    sys.exit(app.exec())

prev_tab_index
tab_change_ignore
也用于我的项目中的可关闭选项卡,我不重写其逻辑。但一切似乎都按预期进行。

此外,对于提供的逻辑 - 必须至少添加一个选项卡,“加选项卡”除外!

© www.soinside.com 2019 - 2024. All rights reserved.