为单个选项卡赋予颜色会消耗过多的处理能力
Giving a color to single tab consumes too much processing power
经过大量研究,我设法在 PyQt5 (Python 3.6) 中自定义 QTabWidget
,这样我就可以为任意选项卡分配不同的颜色:
是的,我知道可以使用 CSS-selectors 操作某些选项卡,例如:
QTabBar::tab:selected
QTabBar::tab:hover
QTabBar::tab:selected
QTabBar::tab:!selected
但是 none 这些选择器解决了我遇到的实际问题。如果我想突出显示第二个选项卡——无论它是否被选中、悬停……——这些都没有帮助我。CSS-selectors。
我现在将解释我是如何让它最终起作用的。之后,我将展示 computation-intensive 部分在哪里,以及为什么我不能把它弄出来。希望大家能帮我提高效率。
密码
您可以在下面找到我的解决方案的源代码。要自己尝试,只需 copy-paste 将代码放入一个新文件(如 tab_test.py
)并 运行 即可。在代码下方您可以找到更多解释。
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
#########################################################
# STYLESHEET FOR QTABWIDGET #
#########################################################
def get_QTabWidget_style():
styleStr = str("""
QTabWidget::pane {
border-width: 2px;
border-style: solid;
border-color: #0000ff;
border-radius: 6px;
}
QTabWidget::tab-bar {
left: 5px;
}
""")
return styleStr
#########################################################
# STYLESHEET FOR QTABBAR #
#########################################################
def get_QTabBar_style():
styleStr = str("""
QTabBar {
background: #00ffffff;
color: #ff000000;
font-family: Courier;
font-size: 12pt;
}
QTabBar::tab {
background: #00ff00;
color: #000000;
border-width: 2px;
border-style: solid;
border-color: #0000ff;
border-bottom-color: #00ffffff;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
min-height: 40px;
padding: 2px;
}
QTabBar::tab:selected {
border-color: #0000ff;
border-bottom-color: #00ffffff;
}
QTabBar::tab:!selected {
margin-top: 2px;
}
QTabBar[colorToggle=true]::tab {
background: #ff0000;
}
""")
return styleStr
#########################################################
# SUBCLASS QTABBAR #
#########################################################
class MyTabBar(QTabBar):
def __init__(self, *args, **kwargs):
super(MyTabBar, self).__init__(*args, **kwargs)
self.__coloredTabs = []
self.setProperty("colorToggle", False)
def colorTab(self, index):
if (index >= self.count()) or (index < 0) or (index in self.__coloredTabs):
return
self.__coloredTabs.append(index)
self.update()
def uncolorTab(self, index):
if index in self.__coloredTabs:
self.__coloredTabs.remove(index)
self.update()
def paintEvent(self, event):
painter = QStylePainter(self)
opt = QStyleOptionTab()
painter.save()
for i in range(self.count()):
self.initStyleOption(opt, i)
if i in self.__coloredTabs:
self.setProperty("colorToggle", True)
self.style().unpolish(self)
self.style().polish(self)
painter.drawControl(QStyle.CE_TabBarTabShape, opt)
painter.drawControl(QStyle.CE_TabBarTabLabel, opt)
else:
self.setProperty("colorToggle", False)
self.style().unpolish(self)
self.style().polish(self)
painter.drawControl(QStyle.CE_TabBarTabShape, opt)
painter.drawControl(QStyle.CE_TabBarTabLabel, opt)
painter.restore()
#########################################################
# SUBCLASS QTABWIDGET #
#########################################################
class MyTabWidget(QTabWidget):
def __init__(self, *args, **kwargs):
super(MyTabWidget, self).__init__(*args, **kwargs)
self.myTabBar = MyTabBar()
self.setTabBar(self.myTabBar)
self.setTabsClosable(True)
self.setStyleSheet(get_QTabWidget_style())
self.tabBar().setStyleSheet(get_QTabBar_style())
def colorTab(self, index):
self.myTabBar.colorTab(index)
def uncolorTab(self, index):
self.myTabBar.uncolorTab(index)
'''=========================================================='''
'''| CUSTOM MAIN WINDOW |'''
'''=========================================================='''
class CustomMainWindow(QMainWindow):
def __init__(self):
super(CustomMainWindow, self).__init__()
# -------------------------------- #
# Window setup #
# -------------------------------- #
# 1. Define the geometry of the main window
# ------------------------------------------
self.setGeometry(100, 100, 800, 800)
self.setWindowTitle("Custom TabBar test")
# 2. Create frame and layout
# ---------------------------
self.__frm = QFrame(self)
self.__frm.setStyleSheet("QWidget { background-color: #efefef }")
self.__lyt = QVBoxLayout()
self.__frm.setLayout(self.__lyt)
self.setCentralWidget(self.__frm)
# 3. Insert the TabMaster
# ------------------------
self.__tabMaster = MyTabWidget()
self.__lyt.addWidget(self.__tabMaster)
# 4. Add some dummy tabs
# -----------------------
self.__tabMaster.addTab(QFrame(), "first")
self.__tabMaster.addTab(QFrame(), "second")
self.__tabMaster.addTab(QFrame(), "third")
self.__tabMaster.addTab(QFrame(), "fourth")
# 5. Color a specific tab
# ------------------------
self.__tabMaster.colorTab(1)
# 6. Show window
# ---------------
self.show()
''''''
'''=== end Class ==='''
if __name__ == '__main__':
app = QApplication(sys.argv)
QApplication.setStyle(QStyleFactory.create('Fusion'))
myGUI = CustomMainWindow()
sys.exit(app.exec_())
''''''
代码解释
1.动态样式表
我有一个用于 QTabWidget 的样式表和一个用于 QTabBar 的样式表。魔术在最后一个。选项卡的背景颜色(由 CSS-selector QTabBar::tab
表示)通常为绿色 #00ff00
。但是当 colorToggle
属性 打开时,颜色设置为红色 #ff0000
.
2。 classMyTabBar
我把classQTabBar
分成一个新的classMyTabBar
。这样,我可以做两件事:
我添加了一个函数 colorTab(index)
这样外部代码就可以调用它来为任意选项卡着色。
我重写了 paintEvent(event)
函数,这样我就可以在选定的选项卡上应用颜色。
colorTab(index)
函数只是获取一个索引并将其添加到列表中。就是这样。该列表将在覆盖的 paintEvent(event)
函数中进行检查。
检查列表后,paintEvent(event)
函数决定是否设置或清除 属性 "colorToggle"
:
self.setProperty("colorToggle", True)
设置(或清除)此 属性 后,paintEvent(event)
函数继续绘制实际选项卡:
self.style().unpolish(self)
self.style().polish(self)
painter.drawControl(QStyle.CE_TabBarTabShape, opt)
painter.drawControl(QStyle.CE_TabBarTabLabel, opt)
I have noticed that self.style().unpolish(self)
and self.style().polish(self)
consume a lot of processing power. But deleting them results in failure. I don't know any (less computational-intensive) alternative.
3。 classMyTabWidget
我还订阅了 class QTabWidget
class。在它的构造函数中,我将默认的 QTabBar
替换为我自己的 subclassed MyTabBar
。之后,我应用我的样式表。
4。 class自定义主窗口
我创建了一个主 window(从 QMainWindow
编辑的子class)来简单地测试新的选项卡小部件。那很简单。我实例化 MyTabWidget()
并在其中插入一些虚拟标签。
然后我给第二个涂上颜色(注意:标签计数从 0 开始)。
问题解释
问题都在这几行:
self.style().unpolish(self)
self.style().polish(self)
在重写的 paintEvent(event)
函数中。它们需要一些执行时间,这是一个问题,因为 paintEvent 函数被非常频繁地调用。对于这个简单的示例,我的处理器 运行s 为 14%(我有一个 4Ghz 水冷 i7 处理器)。这样的处理器负载简直让人无法接受。
platform/environment
我运行正在:
- Python 3.6.3
- PyQt5
- Windows 10(但如果您的解决方案适用于 Linux,请随时 post)
显然 widget-style 似乎很重要。在示例代码的最后几行,您可以看到:
QApplication.setStyle(QStyleFactory.create('Fusion'))
widget-style 应该始终如一 - 在 Windows 和 Linux 上都是一样的。但同样 - 如果您的解决方案适用于另一种 non-Fusion 风格,请随意 post。
第一个提出的解决方案
有人推荐我看这里:
提出解决方案:Subclass QTabBar
覆盖paintEvent(event)
函数。这与我上面已有的解决方案非常相似,但 paintEvent(event)
函数中的代码不同。所以我试一试。
首先,我将给定的 C++ 代码翻译成 Python:
def paintEvent(self, event):
painter = QStylePainter(self)
opt = QStyleOptionTab()
for i in range(self.count()):
self.initStyleOption(opt, i)
if i in self.__coloredTabs:
opt.palette.setColor(QPalette.Button, QColor("#ff0000"))
painter.drawControl(QStyle.CE_TabBarTabShape, opt)
painter.drawControl(QStyle.CE_TabBarTabLabel, opt)
现在我用这段代码替换了之前的 paintEvent(event)
函数。我 运行 文件...但是所有选项卡都是绿色的:-(
一定是我做错了什么?
编辑:
显然该选项卡没有颜色,因为我将 stylesheets
与 QPalette
更改混合在一起。有人建议我注释掉对 setStyleSheet(..)
的所有调用,然后重试。事实上,预期的选项卡获得了新颜色。但是我失去了我所有的风格......所以这对我没有真正的帮助。
第二个提议的解决方案
Musicamante 提出了一个基于 QStyleOption
助手 classes 的解决方案。请往下看他的回答。我已经将他的解决方案插入到我自己的示例代码中:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
#########################################################
# STYLESHEET FOR QTABWIDGET #
#########################################################
def get_QTabWidget_style():
styleStr = str("""
QTabWidget::pane {
border-width: 2px;
border-style: solid;
border-color: #0000ff;
border-radius: 6px;
}
QTabWidget::tab-bar {
left: 5px;
}
""")
return styleStr
#########################################################
# STYLESHEET FOR QTABBAR #
#########################################################
def get_QTabBar_style():
styleStr = str("""
QTabBar {
background: #00ffffff;
color: #ff000000;
font-family: Courier;
font-size: 12pt;
}
QTabBar::tab {
background: #00ff00;
color: #000000;
border-width: 2px;
border-style: solid;
border-color: #0000ff;
border-bottom-color: #00ffffff;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
min-height: 40px;
padding: 2px 12px;
}
QTabBar::tab:selected {
border-color: #0000ff;
border-bottom-color: #00ffffff;
}
QTabBar::tab:!selected {
margin-top: 2px;
}
QTabBar[colorToggle=true]::tab {
background: #ff0000;
}
""")
return styleStr
#########################################################
# SUBCLASS QTABBAR #
#########################################################
class MyTabBar(QTabBar):
def __init__(self, parent):
QTabBar.__init__(self, parent)
self.colorIndexes = parent.colorIndexes
def paintEvent(self, event):
qp = QPainter(self)
qp.setRenderHints(qp.Antialiasing)
option = QStyleOptionTab()
option.features |= option.HasFrame
palette = option.palette
for index in range(self.count()):
self.initStyleOption(option, index)
palette.setColor(palette.Button, self.colorIndexes.get(index, QColor(Qt.green)))
palette.setColor(palette.Window, QColor(Qt.blue))
option.palette = palette
self.style().drawControl(QStyle.CE_TabBarTab, option, qp)
#########################################################
# SUBCLASS QTABWIDGET #
#########################################################
class MyTabWidget(QTabWidget):
def __init__(self):
QTabWidget.__init__(self)
self.colorIndexes = {
1: QColor(Qt.red),
3: QColor(Qt.blue),
}
self.setTabBar(MyTabBar(self))
self.tabBar().setStyleSheet(get_QTabBar_style())
self.setStyleSheet(get_QTabWidget_style())
self.setTabsClosable(True)
'''=========================================================='''
'''| CUSTOM MAIN WINDOW |'''
'''=========================================================='''
class CustomMainWindow(QMainWindow):
def __init__(self):
super(CustomMainWindow, self).__init__()
# -------------------------------- #
# Window setup #
# -------------------------------- #
# 1. Define the geometry of the main window
# ------------------------------------------
self.setGeometry(100, 100, 800, 800)
self.setWindowTitle("Custom TabBar test")
# 2. Create frame and layout
# ---------------------------
self.__frm = QFrame(self)
self.__frm.setStyleSheet("QWidget { background-color: #efefef }")
self.__lyt = QVBoxLayout()
self.__frm.setLayout(self.__lyt)
self.setCentralWidget(self.__frm)
# 3. Insert the TabMaster
# ------------------------
self.__tabMaster = MyTabWidget()
self.__lyt.addWidget(self.__tabMaster)
# 4. Add some dummy tabs
# -----------------------
self.__tabMaster.addTab(QFrame(), "first")
self.__tabMaster.addTab(QFrame(), "second")
self.__tabMaster.addTab(QFrame(), "third")
self.__tabMaster.addTab(QFrame(), "fourth")
# 5. Show window
# ---------------
self.show()
''''''
'''=== end Class ==='''
if __name__ == '__main__':
app = QApplication(sys.argv)
QApplication.setStyle(QStyleFactory.create('Fusion'))
myGUI = CustomMainWindow()
sys.exit(app.exec_())
''''''
结果非常接近预期结果:
Musicamante 说:
The only issue here is that the tab border does not use stylesheets (I wasn't able to find how QStyle draws them), so the radius is smaller and the pen width is thinner.
非常感谢@musicamante!仍然存在一个问题(边界),但结果是我们最接近解决方案的结果。
编辑:在我使用 QStyle 获得大量经验之后,由于最近发布的另一个问题,我突然想起了这个问题,并意识到为什么 问题中的链接不起作用,我的也不起作用(出于相同的原因,但使用不同的实现)。向下滚动以获取替代解决方案。
第一个(已接受)答案
几周前我偶然发现了类似的问题,然后我研究了一下 QStyle 的工作原理。
这个概念是让 Qt 绘制整个小部件,但是使用 QStyleOption 助手 类(几乎每种小部件都有一个)。
这是一个简单的示例(我更新了代码),使用了您使用的部分样式表。
这里唯一的问题是标签边框没有正确使用样式表(我没能找到QStyle如何绘制它们),所以半径更小,笔宽更细。
我测试了它并且它可以在不消耗资源的情况下工作。希望对你有帮助。
class TabBar(QtWidgets.QTabBar):
def __init__(self, parent):
QtWidgets.QTabBar.__init__(self, parent)
self.colorIndexes = parent.colorIndexes
self.setStyleSheet('''
QTabBar {
font-family: Courier;
font-size: 12pt;
}
QTabBar::tab {
min-height: 40px;
padding: 2px 8px;
}
''')
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.setRenderHints(qp.Antialiasing)
option = QtWidgets.QStyleOptionTab()
option.features |= option.HasFrame
palette = option.palette
for index in range(self.count()):
self.initStyleOption(option, index)
palette.setColor(palette.Button, self.colorIndexes.get(index, QtGui.QColor(QtCore.Qt.green)))
palette.setColor(palette.Window, QtGui.QColor(QtCore.Qt.blue))
option.palette = palette
self.style().drawControl(QtWidgets.QStyle.CE_TabBarTab, option, qp)
class TabWidget(QtWidgets.QTabWidget):
def __init__(self):
QtWidgets.QTabWidget.__init__(self)
self.setStyleSheet('''
QTabWidget::pane {
border: 2px solid blue;
border-radius: 6px;
}
QTabWidget::tab-bar {
left: 5px;
}
''')
self.colorIndexes = {
1: QtGui.QColor(QtCore.Qt.red),
3: QtGui.QColor(QtCore.Qt.blue),
}
self.setTabBar(TabBar(self))
for i in range(5):
w = QtWidgets.QWidget()
self.addTab(w, 'tab {}'.format(i))
app = QtWidgets.QApplication(sys.argv)
QtWidgets.QApplication.setStyle('Fusion')
w = TabWidget()
w.show()
sys.exit(app.exec_())
注意:此示例仅适用于 Fusion 样式。 Breeze 不使用 palette.Button
,而是使用 palette.Window
;这意味着您可以在其他样式中找到其他调色板角色组合,这可能会产生更符合您要求的结果。
我不知道是否真的可以通过QStyle绘制标签边框;如果你绝对需要边框,另一种方法是自己绘制它们,从 QStyle.subElementRect()
中获取各种内容大小。
备用(更新和改进)解决方案
问题是,当使用 Qt 的样式表时,QStyle 函数的 optional widget
参数实际上是 很重要,因为它们几乎完全依赖小部件的样式表来绘制其形状和颜色(并计算其指标),而通常会忽略调色板。
我想添加一个备用答案,一个实际上很小的解决方法 "hack",但最重要的是,它通过完全按照预期绘制标签栏来解决与标签边框不一致的问题。
此外,它似乎与风格无关:我已经尝试使用 Breeze、Oxygen、Windows 和 Fusion 风格,它总是给出相同的预期结果。
诀窍是创建一个 "private" QTabBar 小部件(没有父级,以确保它不会显示)充当 "proxy",并对其应用自定义样式表,它有一个默认的背景集;然后,如果要绘制的选项卡是 "colored" 选项卡之一,它使用 that 内部 QTabBar 小部件作为 drawControl
函数的参数。我已经创建了一个示例,可以用不同的颜色为每个选项卡着色,但是如果您不需要那么复杂,显然您可以只使用一个。
这里的重要区别是我们使用的是普通的 QPainter 而不是 QStylePainter,它的函数不允许我们将另一个小部件设置为参数。
def get_QTabBar_style(background='#00ff00'):
styleStr = str('''
QTabBar {{
background: #00ffffff;
color: #ff000000;
font-family: Courier;
font-size: 12pt;
}}
QTabBar::tab {{
background: {};
color: #000000;
border-width: 2px;
border-style: solid;
border-color: #0000ff;
border-bottom-color: #00ffffff;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
min-height: 40px;
padding: 2px;
}}
QTabBar::tab:selected {{
border-color: #0000ff;
border-bottom-color: #00ffffff;
}}
QTabBar::tab:!selected {{
margin-top: 2px;
}}
'''.format(background))
return styleStr
class MyTabBar(QtWidgets.QTabBar):
def __init__(self, parent):
QtWidgets.QTabBar.__init__(self, parent)
self.setStyleSheet(get_QTabBar_style())
self.__coloredTabs = {}
def colorTab(self, index, color='#ff0000'):
if not 0 <= index < self.count():
return
proxy = self.__coloredTabs.get(index)
if not proxy:
proxy = self.__coloredTabs[index] = QtWidgets.QTabBar()
proxy.setStyleSheet(get_QTabBar_style(color))
self.update()
def uncolorTab(self, index):
try:
self.__coloredTabs.pop(index)
self.update()
except:
return
def paintEvent(self, event):
painter = QtGui.QPainter(self)
opt = QtWidgets.QStyleOptionTab()
for i in range(self.count()):
self.initStyleOption(opt, i)
self.style().drawControl(
QtWidgets.QStyle.CE_TabBarTabShape, opt, painter,
self.__coloredTabs.get(i, self))
self.style().drawControl(
QtWidgets.QStyle.CE_TabBarTabLabel, opt, painter, self)
class MyTabWidget(QtWidgets.QTabWidget):
def __init__(self):
QtWidgets.QTabWidget.__init__(self)
self.setStyleSheet(get_QTabWidget_style())
tabBar = MyTabBar(self)
self.setTabBar(tabBar)
self.colorTab = tabBar.colorTab
self.uncolorTab = tabBar.uncolorTab
如您所见,结果几乎是完美的(除了选项卡栏和选项卡内容之间的小边距,这恐怕是样式和 OS 相关)。
经过大量研究,我设法在 PyQt5 (Python 3.6) 中自定义 QTabWidget
,这样我就可以为任意选项卡分配不同的颜色:
是的,我知道可以使用 CSS-selectors 操作某些选项卡,例如:
QTabBar::tab:selected
QTabBar::tab:hover
QTabBar::tab:selected
QTabBar::tab:!selected
但是 none 这些选择器解决了我遇到的实际问题。如果我想突出显示第二个选项卡——无论它是否被选中、悬停……——这些都没有帮助我。CSS-selectors。
我现在将解释我是如何让它最终起作用的。之后,我将展示 computation-intensive 部分在哪里,以及为什么我不能把它弄出来。希望大家能帮我提高效率。
密码
您可以在下面找到我的解决方案的源代码。要自己尝试,只需 copy-paste 将代码放入一个新文件(如 tab_test.py
)并 运行 即可。在代码下方您可以找到更多解释。
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
#########################################################
# STYLESHEET FOR QTABWIDGET #
#########################################################
def get_QTabWidget_style():
styleStr = str("""
QTabWidget::pane {
border-width: 2px;
border-style: solid;
border-color: #0000ff;
border-radius: 6px;
}
QTabWidget::tab-bar {
left: 5px;
}
""")
return styleStr
#########################################################
# STYLESHEET FOR QTABBAR #
#########################################################
def get_QTabBar_style():
styleStr = str("""
QTabBar {
background: #00ffffff;
color: #ff000000;
font-family: Courier;
font-size: 12pt;
}
QTabBar::tab {
background: #00ff00;
color: #000000;
border-width: 2px;
border-style: solid;
border-color: #0000ff;
border-bottom-color: #00ffffff;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
min-height: 40px;
padding: 2px;
}
QTabBar::tab:selected {
border-color: #0000ff;
border-bottom-color: #00ffffff;
}
QTabBar::tab:!selected {
margin-top: 2px;
}
QTabBar[colorToggle=true]::tab {
background: #ff0000;
}
""")
return styleStr
#########################################################
# SUBCLASS QTABBAR #
#########################################################
class MyTabBar(QTabBar):
def __init__(self, *args, **kwargs):
super(MyTabBar, self).__init__(*args, **kwargs)
self.__coloredTabs = []
self.setProperty("colorToggle", False)
def colorTab(self, index):
if (index >= self.count()) or (index < 0) or (index in self.__coloredTabs):
return
self.__coloredTabs.append(index)
self.update()
def uncolorTab(self, index):
if index in self.__coloredTabs:
self.__coloredTabs.remove(index)
self.update()
def paintEvent(self, event):
painter = QStylePainter(self)
opt = QStyleOptionTab()
painter.save()
for i in range(self.count()):
self.initStyleOption(opt, i)
if i in self.__coloredTabs:
self.setProperty("colorToggle", True)
self.style().unpolish(self)
self.style().polish(self)
painter.drawControl(QStyle.CE_TabBarTabShape, opt)
painter.drawControl(QStyle.CE_TabBarTabLabel, opt)
else:
self.setProperty("colorToggle", False)
self.style().unpolish(self)
self.style().polish(self)
painter.drawControl(QStyle.CE_TabBarTabShape, opt)
painter.drawControl(QStyle.CE_TabBarTabLabel, opt)
painter.restore()
#########################################################
# SUBCLASS QTABWIDGET #
#########################################################
class MyTabWidget(QTabWidget):
def __init__(self, *args, **kwargs):
super(MyTabWidget, self).__init__(*args, **kwargs)
self.myTabBar = MyTabBar()
self.setTabBar(self.myTabBar)
self.setTabsClosable(True)
self.setStyleSheet(get_QTabWidget_style())
self.tabBar().setStyleSheet(get_QTabBar_style())
def colorTab(self, index):
self.myTabBar.colorTab(index)
def uncolorTab(self, index):
self.myTabBar.uncolorTab(index)
'''=========================================================='''
'''| CUSTOM MAIN WINDOW |'''
'''=========================================================='''
class CustomMainWindow(QMainWindow):
def __init__(self):
super(CustomMainWindow, self).__init__()
# -------------------------------- #
# Window setup #
# -------------------------------- #
# 1. Define the geometry of the main window
# ------------------------------------------
self.setGeometry(100, 100, 800, 800)
self.setWindowTitle("Custom TabBar test")
# 2. Create frame and layout
# ---------------------------
self.__frm = QFrame(self)
self.__frm.setStyleSheet("QWidget { background-color: #efefef }")
self.__lyt = QVBoxLayout()
self.__frm.setLayout(self.__lyt)
self.setCentralWidget(self.__frm)
# 3. Insert the TabMaster
# ------------------------
self.__tabMaster = MyTabWidget()
self.__lyt.addWidget(self.__tabMaster)
# 4. Add some dummy tabs
# -----------------------
self.__tabMaster.addTab(QFrame(), "first")
self.__tabMaster.addTab(QFrame(), "second")
self.__tabMaster.addTab(QFrame(), "third")
self.__tabMaster.addTab(QFrame(), "fourth")
# 5. Color a specific tab
# ------------------------
self.__tabMaster.colorTab(1)
# 6. Show window
# ---------------
self.show()
''''''
'''=== end Class ==='''
if __name__ == '__main__':
app = QApplication(sys.argv)
QApplication.setStyle(QStyleFactory.create('Fusion'))
myGUI = CustomMainWindow()
sys.exit(app.exec_())
''''''
代码解释
1.动态样式表
我有一个用于 QTabWidget 的样式表和一个用于 QTabBar 的样式表。魔术在最后一个。选项卡的背景颜色(由 CSS-selector QTabBar::tab
表示)通常为绿色 #00ff00
。但是当 colorToggle
属性 打开时,颜色设置为红色 #ff0000
.
2。 classMyTabBar
我把classQTabBar
分成一个新的classMyTabBar
。这样,我可以做两件事:
我添加了一个函数
colorTab(index)
这样外部代码就可以调用它来为任意选项卡着色。我重写了
paintEvent(event)
函数,这样我就可以在选定的选项卡上应用颜色。
colorTab(index)
函数只是获取一个索引并将其添加到列表中。就是这样。该列表将在覆盖的 paintEvent(event)
函数中进行检查。
检查列表后,paintEvent(event)
函数决定是否设置或清除 属性 "colorToggle"
:
self.setProperty("colorToggle", True)
设置(或清除)此 属性 后,paintEvent(event)
函数继续绘制实际选项卡:
self.style().unpolish(self)
self.style().polish(self)
painter.drawControl(QStyle.CE_TabBarTabShape, opt)
painter.drawControl(QStyle.CE_TabBarTabLabel, opt)
I have noticed that
self.style().unpolish(self)
andself.style().polish(self)
consume a lot of processing power. But deleting them results in failure. I don't know any (less computational-intensive) alternative.
3。 classMyTabWidget
我还订阅了 class QTabWidget
class。在它的构造函数中,我将默认的 QTabBar
替换为我自己的 subclassed MyTabBar
。之后,我应用我的样式表。
4。 class自定义主窗口
我创建了一个主 window(从 QMainWindow
编辑的子class)来简单地测试新的选项卡小部件。那很简单。我实例化 MyTabWidget()
并在其中插入一些虚拟标签。
然后我给第二个涂上颜色(注意:标签计数从 0 开始)。
问题解释
问题都在这几行:
self.style().unpolish(self)
self.style().polish(self)
在重写的 paintEvent(event)
函数中。它们需要一些执行时间,这是一个问题,因为 paintEvent 函数被非常频繁地调用。对于这个简单的示例,我的处理器 运行s 为 14%(我有一个 4Ghz 水冷 i7 处理器)。这样的处理器负载简直让人无法接受。
platform/environment
我运行正在:
- Python 3.6.3
- PyQt5
- Windows 10(但如果您的解决方案适用于 Linux,请随时 post)
显然 widget-style 似乎很重要。在示例代码的最后几行,您可以看到:
QApplication.setStyle(QStyleFactory.create('Fusion'))
widget-style 应该始终如一 - 在 Windows 和 Linux 上都是一样的。但同样 - 如果您的解决方案适用于另一种 non-Fusion 风格,请随意 post。
第一个提出的解决方案
有人推荐我看这里:
提出解决方案:Subclass QTabBar
覆盖paintEvent(event)
函数。这与我上面已有的解决方案非常相似,但 paintEvent(event)
函数中的代码不同。所以我试一试。
首先,我将给定的 C++ 代码翻译成 Python:
def paintEvent(self, event):
painter = QStylePainter(self)
opt = QStyleOptionTab()
for i in range(self.count()):
self.initStyleOption(opt, i)
if i in self.__coloredTabs:
opt.palette.setColor(QPalette.Button, QColor("#ff0000"))
painter.drawControl(QStyle.CE_TabBarTabShape, opt)
painter.drawControl(QStyle.CE_TabBarTabLabel, opt)
现在我用这段代码替换了之前的 paintEvent(event)
函数。我 运行 文件...但是所有选项卡都是绿色的:-(
一定是我做错了什么?
编辑:
显然该选项卡没有颜色,因为我将 stylesheets
与 QPalette
更改混合在一起。有人建议我注释掉对 setStyleSheet(..)
的所有调用,然后重试。事实上,预期的选项卡获得了新颜色。但是我失去了我所有的风格......所以这对我没有真正的帮助。
第二个提议的解决方案
Musicamante 提出了一个基于 QStyleOption
助手 classes 的解决方案。请往下看他的回答。我已经将他的解决方案插入到我自己的示例代码中:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
#########################################################
# STYLESHEET FOR QTABWIDGET #
#########################################################
def get_QTabWidget_style():
styleStr = str("""
QTabWidget::pane {
border-width: 2px;
border-style: solid;
border-color: #0000ff;
border-radius: 6px;
}
QTabWidget::tab-bar {
left: 5px;
}
""")
return styleStr
#########################################################
# STYLESHEET FOR QTABBAR #
#########################################################
def get_QTabBar_style():
styleStr = str("""
QTabBar {
background: #00ffffff;
color: #ff000000;
font-family: Courier;
font-size: 12pt;
}
QTabBar::tab {
background: #00ff00;
color: #000000;
border-width: 2px;
border-style: solid;
border-color: #0000ff;
border-bottom-color: #00ffffff;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
min-height: 40px;
padding: 2px 12px;
}
QTabBar::tab:selected {
border-color: #0000ff;
border-bottom-color: #00ffffff;
}
QTabBar::tab:!selected {
margin-top: 2px;
}
QTabBar[colorToggle=true]::tab {
background: #ff0000;
}
""")
return styleStr
#########################################################
# SUBCLASS QTABBAR #
#########################################################
class MyTabBar(QTabBar):
def __init__(self, parent):
QTabBar.__init__(self, parent)
self.colorIndexes = parent.colorIndexes
def paintEvent(self, event):
qp = QPainter(self)
qp.setRenderHints(qp.Antialiasing)
option = QStyleOptionTab()
option.features |= option.HasFrame
palette = option.palette
for index in range(self.count()):
self.initStyleOption(option, index)
palette.setColor(palette.Button, self.colorIndexes.get(index, QColor(Qt.green)))
palette.setColor(palette.Window, QColor(Qt.blue))
option.palette = palette
self.style().drawControl(QStyle.CE_TabBarTab, option, qp)
#########################################################
# SUBCLASS QTABWIDGET #
#########################################################
class MyTabWidget(QTabWidget):
def __init__(self):
QTabWidget.__init__(self)
self.colorIndexes = {
1: QColor(Qt.red),
3: QColor(Qt.blue),
}
self.setTabBar(MyTabBar(self))
self.tabBar().setStyleSheet(get_QTabBar_style())
self.setStyleSheet(get_QTabWidget_style())
self.setTabsClosable(True)
'''=========================================================='''
'''| CUSTOM MAIN WINDOW |'''
'''=========================================================='''
class CustomMainWindow(QMainWindow):
def __init__(self):
super(CustomMainWindow, self).__init__()
# -------------------------------- #
# Window setup #
# -------------------------------- #
# 1. Define the geometry of the main window
# ------------------------------------------
self.setGeometry(100, 100, 800, 800)
self.setWindowTitle("Custom TabBar test")
# 2. Create frame and layout
# ---------------------------
self.__frm = QFrame(self)
self.__frm.setStyleSheet("QWidget { background-color: #efefef }")
self.__lyt = QVBoxLayout()
self.__frm.setLayout(self.__lyt)
self.setCentralWidget(self.__frm)
# 3. Insert the TabMaster
# ------------------------
self.__tabMaster = MyTabWidget()
self.__lyt.addWidget(self.__tabMaster)
# 4. Add some dummy tabs
# -----------------------
self.__tabMaster.addTab(QFrame(), "first")
self.__tabMaster.addTab(QFrame(), "second")
self.__tabMaster.addTab(QFrame(), "third")
self.__tabMaster.addTab(QFrame(), "fourth")
# 5. Show window
# ---------------
self.show()
''''''
'''=== end Class ==='''
if __name__ == '__main__':
app = QApplication(sys.argv)
QApplication.setStyle(QStyleFactory.create('Fusion'))
myGUI = CustomMainWindow()
sys.exit(app.exec_())
''''''
结果非常接近预期结果:
Musicamante 说:
The only issue here is that the tab border does not use stylesheets (I wasn't able to find how QStyle draws them), so the radius is smaller and the pen width is thinner.
非常感谢@musicamante!仍然存在一个问题(边界),但结果是我们最接近解决方案的结果。
编辑:在我使用 QStyle 获得大量经验之后,由于最近发布的另一个问题,我突然想起了这个问题,并意识到为什么
第一个(已接受)答案
几周前我偶然发现了类似的问题,然后我研究了一下 QStyle 的工作原理。 这个概念是让 Qt 绘制整个小部件,但是使用 QStyleOption 助手 类(几乎每种小部件都有一个)。
这是一个简单的示例(我更新了代码),使用了您使用的部分样式表。 这里唯一的问题是标签边框没有正确使用样式表(我没能找到QStyle如何绘制它们),所以半径更小,笔宽更细。
我测试了它并且它可以在不消耗资源的情况下工作。希望对你有帮助。
class TabBar(QtWidgets.QTabBar):
def __init__(self, parent):
QtWidgets.QTabBar.__init__(self, parent)
self.colorIndexes = parent.colorIndexes
self.setStyleSheet('''
QTabBar {
font-family: Courier;
font-size: 12pt;
}
QTabBar::tab {
min-height: 40px;
padding: 2px 8px;
}
''')
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.setRenderHints(qp.Antialiasing)
option = QtWidgets.QStyleOptionTab()
option.features |= option.HasFrame
palette = option.palette
for index in range(self.count()):
self.initStyleOption(option, index)
palette.setColor(palette.Button, self.colorIndexes.get(index, QtGui.QColor(QtCore.Qt.green)))
palette.setColor(palette.Window, QtGui.QColor(QtCore.Qt.blue))
option.palette = palette
self.style().drawControl(QtWidgets.QStyle.CE_TabBarTab, option, qp)
class TabWidget(QtWidgets.QTabWidget):
def __init__(self):
QtWidgets.QTabWidget.__init__(self)
self.setStyleSheet('''
QTabWidget::pane {
border: 2px solid blue;
border-radius: 6px;
}
QTabWidget::tab-bar {
left: 5px;
}
''')
self.colorIndexes = {
1: QtGui.QColor(QtCore.Qt.red),
3: QtGui.QColor(QtCore.Qt.blue),
}
self.setTabBar(TabBar(self))
for i in range(5):
w = QtWidgets.QWidget()
self.addTab(w, 'tab {}'.format(i))
app = QtWidgets.QApplication(sys.argv)
QtWidgets.QApplication.setStyle('Fusion')
w = TabWidget()
w.show()
sys.exit(app.exec_())
注意:此示例仅适用于 Fusion 样式。 Breeze 不使用 palette.Button
,而是使用 palette.Window
;这意味着您可以在其他样式中找到其他调色板角色组合,这可能会产生更符合您要求的结果。
我不知道是否真的可以通过QStyle绘制标签边框;如果你绝对需要边框,另一种方法是自己绘制它们,从 QStyle.subElementRect()
中获取各种内容大小。
备用(更新和改进)解决方案
问题是,当使用 Qt 的样式表时,QStyle 函数的 optional widget
参数实际上是 很重要,因为它们几乎完全依赖小部件的样式表来绘制其形状和颜色(并计算其指标),而通常会忽略调色板。
我想添加一个备用答案,一个实际上很小的解决方法 "hack",但最重要的是,它通过完全按照预期绘制标签栏来解决与标签边框不一致的问题。
此外,它似乎与风格无关:我已经尝试使用 Breeze、Oxygen、Windows 和 Fusion 风格,它总是给出相同的预期结果。
诀窍是创建一个 "private" QTabBar 小部件(没有父级,以确保它不会显示)充当 "proxy",并对其应用自定义样式表,它有一个默认的背景集;然后,如果要绘制的选项卡是 "colored" 选项卡之一,它使用 that 内部 QTabBar 小部件作为 drawControl
函数的参数。我已经创建了一个示例,可以用不同的颜色为每个选项卡着色,但是如果您不需要那么复杂,显然您可以只使用一个。
这里的重要区别是我们使用的是普通的 QPainter 而不是 QStylePainter,它的函数不允许我们将另一个小部件设置为参数。
def get_QTabBar_style(background='#00ff00'):
styleStr = str('''
QTabBar {{
background: #00ffffff;
color: #ff000000;
font-family: Courier;
font-size: 12pt;
}}
QTabBar::tab {{
background: {};
color: #000000;
border-width: 2px;
border-style: solid;
border-color: #0000ff;
border-bottom-color: #00ffffff;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
min-height: 40px;
padding: 2px;
}}
QTabBar::tab:selected {{
border-color: #0000ff;
border-bottom-color: #00ffffff;
}}
QTabBar::tab:!selected {{
margin-top: 2px;
}}
'''.format(background))
return styleStr
class MyTabBar(QtWidgets.QTabBar):
def __init__(self, parent):
QtWidgets.QTabBar.__init__(self, parent)
self.setStyleSheet(get_QTabBar_style())
self.__coloredTabs = {}
def colorTab(self, index, color='#ff0000'):
if not 0 <= index < self.count():
return
proxy = self.__coloredTabs.get(index)
if not proxy:
proxy = self.__coloredTabs[index] = QtWidgets.QTabBar()
proxy.setStyleSheet(get_QTabBar_style(color))
self.update()
def uncolorTab(self, index):
try:
self.__coloredTabs.pop(index)
self.update()
except:
return
def paintEvent(self, event):
painter = QtGui.QPainter(self)
opt = QtWidgets.QStyleOptionTab()
for i in range(self.count()):
self.initStyleOption(opt, i)
self.style().drawControl(
QtWidgets.QStyle.CE_TabBarTabShape, opt, painter,
self.__coloredTabs.get(i, self))
self.style().drawControl(
QtWidgets.QStyle.CE_TabBarTabLabel, opt, painter, self)
class MyTabWidget(QtWidgets.QTabWidget):
def __init__(self):
QtWidgets.QTabWidget.__init__(self)
self.setStyleSheet(get_QTabWidget_style())
tabBar = MyTabBar(self)
self.setTabBar(tabBar)
self.colorTab = tabBar.colorTab
self.uncolorTab = tabBar.uncolorTab
如您所见,结果几乎是完美的(除了选项卡栏和选项卡内容之间的小边距,这恐怕是样式和 OS 相关)。