将QSS字符串转换为字典(在下面的代码中使用
MyQSS.str_to_mapping
),并修改字典。
将修改后的字典转换回字符串(在下面的代码中使用
MyQSS.mapping_to_str
)。
使用新字符串调用
setStyleSheet
(在下面的代码中使用
MyQSS.set_value
)。
import re
import sys
from typing import Dict, Union
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSize
from PyQt5.QtWidgets import (
QMainWindow,
QPushButton,
QWidget,
)
QSSMapping = Dict[str, Union[str, Dict[str, str]]]
class MyQSS:
@classmethod
def mapping_to_str(cls, mapping: QSSMapping) -> str:
qss_str = ""
for selector, declaration in mapping.items():
if isinstance(declaration, str):
if not declaration.endswith(";"):
declaration = f"{declaration};"
qss_str += f"{selector}: {declaration}\n"
else:
qss_str += f"{selector} {{\n"
for property, value in declaration.items():
if not value.endswith(";"):
value = f"{value};"
qss_str += f"{property}: {value}\n"
qss_str += "}\n"
return qss_str
@classmethod
def str_to_mapping(cls, qss_str: str) -> QSSMapping:
"""
This func requires that in the qss_str
(1) Every value ends with ";"
e.g., "font-size: 11pt" ✗
e.g., "font-size: 11pt;" ✓
(2) The trailing ";" be attached to the value and separated
from the subsequent token
e.g., "font-size: 11pt ;" ✗
e.g., "font-size: 11pt;" ✓
e.g., "font-size: 11pt;max-width: 75px;" ✗
e.g., "font-size: 11pt; max-width: 75px;" ✓
(3) The trailing ":" of a property be attached to the lhs property and
separated from the rhs value
e.g., "font-size :11pt;" ✗
e.g., "font-size : 11pt;" ✗
e.g., "font-size: 11pt;" ✓
This func allows that in the qss_str
(1) Values have whitespaces
e.g., "font-family: Noto Sans;" ✓
(2) Selectors have whitespaces
e.g., "QDialog QPushButton { color: black; }" ✓
e.g., "QDialog > QPushButton { color: black; }" ✓
(3) "{" and "}" are not separated from others
e.g., "QDialog QPushButton{color: black;}" ✓
Later value for the same property will override the previous one
The returned dict will always has a trailing ";" across its values
"""
selector_declaration_mapping = {}
cur_selector = None
cur_property = None
qss_str = qss_str.replace("{", " { ").replace("}", " } ")
token_gen = iter(qss_str.split())
while (token := next(token_gen, None)) is not None:
if token == "{":
pass
elif token == "}":
cur_selector = None
elif token.endswith(":"):
cur_property = token.rstrip(":")
elif cur_property is not None:
if cur_selector is None:
value_prev = selector_declaration_mapping.get(cur_property, "")
value = f"{value_prev} {token}" if not value_prev.endswith(";") else token
selector_declaration_mapping[cur_property] = value.lstrip()
else:
if cur_selector not in selector_declaration_mapping:
selector_declaration_mapping[cur_selector] = {}
value_prev = selector_declaration_mapping[cur_selector].get(cur_property, "")
value = f"{value_prev} {token}" if not value_prev.endswith(";") else token
selector_declaration_mapping[cur_selector][cur_property] = value.lstrip()
if token.endswith(";"):
cur_property = None
else:
cur_selector = f"{cur_selector} {token}" if cur_selector is not None else token
return selector_declaration_mapping
@classmethod
def set_value(cls, widget: QWidget, selector: str, property: str, value: str) -> None:
qss_str = widget.styleSheet()
selector_declaration_mapping: QSSMapping = cls.str_to_mapping(qss_str)
property_value_mapping = selector_declaration_mapping.get(selector, {})
if isinstance(property_value_mapping, str):
raise ValueError(f"{selector} is not a valid selector")
property_value_mapping[property] = value
selector_declaration_mapping[selector] = property_value_mapping
widget.setStyleSheet(cls.mapping_to_str(selector_declaration_mapping))
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setMinimumSize(QSize(400, 400))
color = QPushButton("color", self)
color.clicked.connect(self.color)
color.resize(100, 115)
color.move(0, 100)
buttons = ["button1", "button2", "button3"]
self.pybutton = {}
x = 0
t = 0
for i in buttons:
t = t + 100
x += 1
self.pybutton[str(x)] = QPushButton(i, self)
self.pybutton[str(x)].setObjectName("btn" + str(x))
self.pybutton[str(x)].resize(100, 100)
self.pybutton[str(x)].move(400 - int(t), 100)
self.pybutton[str(x)].setStyleSheet(
"QPushButton {max-width: 75px; text-align: center; padding-left: 20px; max-height: 60px; font-size: 20px;}"
)
self.statusBar()
def status(self):
sender = self.sender()
print("PyQt5 button click")
self.statusBar().showMessage(sender.text() + " was pressed")
def color(self):
for i in self.pybutton:
self.pybutton[str(i)].objectName()
if self.pybutton[str(i)].objectName() == "btn1":
MyQSS.set_value(self.pybutton[str(i)], "*", "background-color", "green")
else:
MyQSS.set_value(self.pybutton[str(i)], "*", "background-color", "red")
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
sys.exit(app.exec_())