K.M*_*ier 4 python pyqt python-3.x qtabwidget pyqt5
经过大量研究,我设法自定义了QTabWidgetPyQt5(Python 3.6)中的 ,以便我可以为任意选项卡分配不同的颜色:
是的,我知道可以使用 CSS 选择器操作某些选项卡,例如:
QTabBar::tab:selectedQTabBar::tab:hoverQTabBar::tab:selectedQTabBar::tab:!selected但是这些选择器都没有解决我遇到的实际问题。如果我想突出显示第二个选项卡 - 无论它是否被选中,悬停,...... - 这些 CSS 选择器都没有帮助我。
我现在将解释我是如何让它最终工作的。之后,我将展示计算密集型部分在哪里,以及为什么我不能把它弄出来。希望你能帮助我提高效率。
您可以在下面找到我的解决方案的源代码。要自己尝试一下,只需将代码复制粘贴到一个新文件(如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_())
''''''
Run Code Online (Sandbox Code Playgroud)
1. 动态样式表
我有一个用于 QTabWidget 的样式表和一个用于 QTabBar 的样式表。魔法在最后一个。选项卡的背景颜色(由 CSS-selector 表示QTabBar::tab)通常是 green #00ff00。但是当colorToggle属性打开时,颜色设置为 red #ff0000。
2. class MyTabBar
我子类QTabBar成一个新类MyTabBar。这样,我可以做两件事:
我添加了一个函数colorTab(index),以便外部代码可以调用它来为任意选项卡着色。
我覆盖了该paintEvent(event)功能,以便我可以在所选选项卡上应用颜色。
该colorTab(index)函数只需要一个索引并将其添加到列表中。就是这样。该列表将在被覆盖的paintEvent(event)函数中检查。
检查列表后,paintEvent(event)函数决定是否应该设置或清除属性"colorToggle":
self.setProperty("colorToggle", True)
Run Code Online (Sandbox Code Playgroud)
设置(或清除)此属性后,该paintEvent(event)函数继续绘制实际选项卡:
self.style().unpolish(self)
self.style().polish(self)
painter.drawControl(QStyle.CE_TabBarTabShape, opt)
painter.drawControl(QStyle.CE_TabBarTabLabel, opt)
Run Code Online (Sandbox Code Playgroud)
我注意到了这一点,
self.style().unpolish(self)并self.style().polish(self)消耗了大量的处理能力。但是删除它们会导致失败。我不知道任何(计算密集度较低的)替代方案。
3. 类 MyTabWidget
我也对这个QTabWidget类进行了子类化。在其构造函数中,我将默认值替换为QTabBar我自己的子类MyTabBar. 之后,我应用我的样式表。
4. 类 CustomMainWindow
我创建了一个主窗口(从 子类QMainWindow)来简单地测试新的 Tab Widget。这很简单。我实例化MyTabWidget()并在其中插入一些虚拟标签。
然后我给第二个上色(注意:标签计数从 0 开始)。
问题出在以下几行:
self.style().unpolish(self)
self.style().polish(self)
Run Code Online (Sandbox Code Playgroud)
在被覆盖的paintEvent(event)函数内部。它们需要一些执行时间,这是一个问题,因为paintEvent 函数被非常定期地调用。对于这个简单示例,我的处理器运行速度为 14%(我有一个 4Ghz 水冷 i7 处理器)。这种处理器负载简直是不能接受的。
我正在运行:
显然,小部件样式似乎很重要。在示例代码的最后几行,您可以看到:
QApplication.setStyle(QStyleFactory.create('Fusion'))
Run Code Online (Sandbox Code Playgroud)
该小部件样式应该始终如一——无论是在 Windows 还是 Linux 上。但同样 - 如果它适用于另一种非 Fusion 风格,请随时发布您的解决方案。
我被推荐看这里:Qt TabWidget Each tab Title Background Color
提出了一个解决方案:子类化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)
Run Code Online (Sandbox Code Playgroud)
现在我paintEvent(event)用这段代码替换我以前的函数。我运行文件......但所有标签都是绿色的:-(
一定是我做错了什么?
编辑:
显然,标签没有颜色,因为我是混合stylesheets与QPalette变化。有人建议我注释掉所有调用并重setStyleSheet(..)试。事实上,预期的标签获得了新的颜色。但是我失去了所有的风格......所以这对我没有帮助。
Musicamante 提出了一种基于QStyleOption辅助类的解决方案。请往下看,看看他的回答。我已将他的解决方案插入到我自己的示例代码中:
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_())
''''''
Run Code Online (Sandbox Code Playgroud)
结果非常接近预期的结果:
Musicamante 说:
这里唯一的问题是选项卡边框不使用样式表(我无法找到 QStyle 如何绘制它们),因此半径更小,笔宽更细。
非常感谢@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_())
Run Code Online (Sandbox Code Playgroud)
注意:此示例仅适用于 Fusion 风格。Breeze 不使用palette.Button而是使用palette.Window;这意味着您可能能够在其他样式中找到其他调色板角色组合,这可能会产生更符合您要求的结果。我不知道是否真的可以通过 QStyle 绘制选项卡边框;如果您绝对需要边框,另一种选择是自己绘制它们,从QStyle.subElementRect().
问题是,当使用 Qt 的样式表时,QStyle 函数的可选 widget参数非常重要,因为它们几乎完全依赖于小部件的样式表来绘制其形状和颜色(并计算其度量),而通常忽略调色板。
我想添加一个替代答案,一个实际上是一个小的“黑客”的解决方法,但最重要的是,通过完全按预期绘制选项卡栏来解决与选项卡边框的不一致。
此外,它似乎与风格无关:我已经尝试过 Breeze、Oxygen、Windows 和 Fusion 风格,它总是给出相同的预期结果。
诀窍是创建一个充当“代理”的“私有” QTabBar 小部件(没有父级,以确保它不会显示),并对其应用自定义样式表,该样式表具有默认背景设置;然后,如果要绘制的选项卡是“彩色”选项卡之一,则它使用该内部 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
Run Code Online (Sandbox Code Playgroud)
如您所见,结果几乎是完美的(除了标签栏和标签内容之间的小边距,这恐怕取决于样式和操作系统)。

| 归档时间: |
|
| 查看次数: |
3272 次 |
| 最近记录: |