强制 QTabBar 选项卡保持尽可能小并忽略 sizeHint
Force QTabBar tabs to stay as small as possible and ignore sizeHint
我正在尝试将 +
按钮添加到 QTabBar
。几年前有一个 great solution,有一个小问题,它不适用于 PySide2
。问题是由选项卡自动调整大小以填充 sizeHint
引起的,在这种情况下不需要,因为需要额外的 space。有没有办法可以禁用此行为?
我试过 QTabBar.setExpanding(False)
,但根据 this 的回答,属性 大部分被忽略了:
The bad news is that QTabWidget effectively ignores that property, because it always forces its tabs to be the minimum size (even if you set your own tab-bar).
不同之处在于 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_())
我认为您的问题可能有一个简单的解决方案(见下文)。在链接的部分解决方案计算“+”按钮的绝对定位的地方,Qt 的真正意图始终是让布局引擎做它的事情,而不是试图告诉它具体的大小和位置。 QTabWidget
基本上是 pre-built 布局和小部件的合并,有时您只需要跳过 pre-built 并构建自己的。
在 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_())
综上所述,我认为 pre-built QTabWidget.setCornerWidget
可能正是您要查找的内容(将 QPushButton 设置为 upper-right 小部件)。我写的例子应该更容易定制,但也更努力 re-implement 所有相同的功能。您将不得不 re-implement 一些信号逻辑来自行创建/删除/select/重新排列选项卡。我只演示了简单的实现,这可能不是对所有情况都是安全的。
以 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
,因为有些方法可能需要额外测试。
我正在尝试将 +
按钮添加到 QTabBar
。几年前有一个 great solution,有一个小问题,它不适用于 PySide2
。问题是由选项卡自动调整大小以填充 sizeHint
引起的,在这种情况下不需要,因为需要额外的 space。有没有办法可以禁用此行为?
我试过 QTabBar.setExpanding(False)
,但根据 this 的回答,属性 大部分被忽略了:
The bad news is that QTabWidget effectively ignores that property, because it always forces its tabs to be the minimum size (even if you set your own tab-bar).
不同之处在于 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_())
我认为您的问题可能有一个简单的解决方案(见下文)。在链接的部分解决方案计算“+”按钮的绝对定位的地方,Qt 的真正意图始终是让布局引擎做它的事情,而不是试图告诉它具体的大小和位置。 QTabWidget
基本上是 pre-built 布局和小部件的合并,有时您只需要跳过 pre-built 并构建自己的。
在 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_())
综上所述,我认为 pre-built QTabWidget.setCornerWidget
可能正是您要查找的内容(将 QPushButton 设置为 upper-right 小部件)。我写的例子应该更容易定制,但也更努力 re-implement 所有相同的功能。您将不得不 re-implement 一些信号逻辑来自行创建/删除/select/重新排列选项卡。我只演示了简单的实现,这可能不是对所有情况都是安全的。
以 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
,因为有些方法可能需要额外测试。