如何在 PyQt QFrame 中制作有角度的箭头型边框?在我的代码中,我有两个 QLabel 和各自的框架。我的目标是在每个 QFrame 的右侧制作一个箭头形状的边框。为了获得清晰的想法,请附上示例图片。
import sys
from PyQt5.QtWidgets import *
class Angle_Border(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Angle Border")
self.lbl1 = QLabel("Python")
self.lbl2 = QLabel("PyQt")
self.frame1 = QFrame()
self.frame1.setProperty("type","1")
self.frame1.setFixedSize(200,50)
self.frame1.setStyleSheet("background-color:red;color:white;"
"font-family:Trebuchet MS;font-size: 15pt;text-align: center;"
"border-top-right-radius:25px solid ; border-bottom-right-radius:25px solid ;")
self.frame2 = QFrame()
self.frame2.setFixedSize(200, 50)
self.frame2.setStyleSheet("background-color:blue;color:white;"
"font-family:Trebuchet MS;font-size: 15pt;text-align: center;"
"border-top:1px solid transparent; border-bottom:1px solid transparent;")
self.frame_outer = QFrame()
self.frame_outer.setFixedSize(800, 60)
self.frame_outer.setStyleSheet("background-color:green;color:white;"
"font-family:Trebuchet MS;font-size: 15pt;text-align: center;")
self.frame1_layout = QHBoxLayout(self.frame1)
self.frame2_layout = QHBoxLayout(self.frame2)
self.frame_outer_layout = QHBoxLayout(self.frame_outer)
self.frame_outer_layout.setContentsMargins(5,0,0,0)
self.frame1_layout.addWidget(self.lbl1)
self.frame2_layout.addWidget(self.lbl2)
self.hbox = QHBoxLayout()
self.layout = QHBoxLayout()
self.hbox.addWidget(self.frame1)
self.hbox.addWidget(self.frame2)
self.hbox.addStretch()
self.hbox.setSpacing(0)
# self.layout.addLayout(self.hbox)
self.frame_outer_layout.addLayout(self.hbox)
self.layout.addWidget(self.frame_outer)
self.setLayout(self.layout)
def main():
app = QApplication(sys.argv)
ex = Angle_Border()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Run Code Online (Sandbox Code Playgroud)
import sys
from PyQt5.QtWidgets import QWidget,QHBoxLayout,QLabel,QFrame,QApplication,QSizePolicy
from PyQt5.QtCore import Qt
class MyFrame(QWidget):
def __init__(self,base_color,top_color,width,edge,text,text_color):
super().__init__()
self.base_color = base_color
self.top_color = top_color
self.width = width
self.edge = edge
self.text = text
self.text_color = text_color
self.lbl = QLabel()
self.lbl.setText(self.text)
self.lbl.setFixedHeight(self.width*2)
self.lbl.setMinimumWidth((QSizePolicy.MinimumExpanding)+100)
self.lbl.setContentsMargins(0,0,0,0)
self.lbl.setAlignment(Qt.AlignCenter)
self.lbl.setStyleSheet(f"QLabel"
f"{{background-color: {self.base_color};"
f"color:{self.text_color};"
f"font-family:Trebuchet MS;"
f"font-size: 15pt;}}")
self.frame_triangle = QFrame()
self.frame_triangle.setFixedSize(self.width, self.width * 2)
self.frame_triangle.setContentsMargins(0,0,0,0)
self.hbox = QHBoxLayout()
self.hbox.setSpacing(0)
self.hbox.setContentsMargins(0,0,0,0)
self.setLayout(self.hbox)
if self.edge == "right":
self.border = "border-left"
self.hbox.addWidget(self.lbl)
self.hbox.addWidget(self.frame_triangle)
elif self.edge == "left":
self.border = "border-right"
self.hbox.addWidget(self.frame_triangle)
self.hbox.addWidget(self.lbl)
elif self.edge == "none":
self.border = "border-right"
self.hbox.addWidget(self.lbl)
self.lbl.setMinimumWidth((QSizePolicy.MinimumExpanding) + 150)
self.frame_triangle.setStyleSheet(f"QFrame"
f"{{background-color: {self.base_color};"
f"border-top:100px solid {self.top_color};"
f"{self.border}:100px solid {self.base_color};"
f"border-bottom:100px solid {self.top_color};"
f"}}")
class Main_Frame(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Angled Frame")
triangle_size = 50
self.frame1 = MyFrame("lightgrey","green",triangle_size,"right","","lightgrey")
self.frame2 = MyFrame("green","red",triangle_size,"right","Python","white")
self.frame3 = MyFrame("red","blue",triangle_size,"right","PyQt5","white")
self.frame4 = MyFrame("blue","yellow",triangle_size,"right","Java","white")
self.frame5 = MyFrame("yellow","lightgrey",triangle_size,"right","ASP.Net","black")
self.frame_overall = QFrame()
self.frame_overall.setStyleSheet("background-color:lightgrey;")
self.frame_overall.setSizePolicy(QSizePolicy.Minimum,QSizePolicy.Maximum)
self.frame_overall_layout = QHBoxLayout(self.frame_overall)
self.frame_overall_layout.setSpacing(0)
# self.frame_overall_layout.addWidget(self.frame1)
self.frame_overall_layout.addWidget(self.frame2)
self.frame_overall_layout.addWidget(self.frame3)
self.frame_overall_layout.addWidget(self.frame4)
self.frame_overall_layout.addWidget(self.frame5)
self.vbox = QHBoxLayout()
self.vbox.setContentsMargins(0,0,0,0)
self.vbox.setSpacing(0)
self.vbox.addStretch()
self.vbox.addWidget(self.frame_overall)
self.vbox.addStretch()
self.setLayout(self.vbox)
def main():
app = QApplication(sys.argv)
ex = Main_Frame()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Run Code Online (Sandbox Code Playgroud)
由于 OP 不要求用户交互(鼠标或键盘),因此可能的解决方案可以使用 Qt 的现有功能,特别是 QSS(Qt 样式表)。
虽然当前先前接受的解决方案 确实遵循该方法,但它不是很有效,最重要的是因为它基本上是“静态”的,因为它总是需要知道后续项目的颜色才能定义“箭头”颜色。
这不仅迫使程序员始终考虑“兄弟”项,而且还使此类对象的动态创建变得极其(且不必要)复杂。
解决方案是始终(部分)“重做”布局并使用必要的值更新样式表,这些值考虑当前大小(不应硬编码)、以下项目(如果有)并仔细使用布局属性和基于内容的“间隔”样式表。
以下代码使用更抽象、动态的方法,具有允许添加/插入和删除项目的基本功能。它仍然使用类似的 QSS 方法,但是,具有几乎相同的“行数”,它提供了一种更简单、更直观的方法,允许通过更易于使用的单个函数调用来创建、删除和修改项目。
这种方法的另一个好处是实现“反向”箭头非常容易,并且不会破坏项目创建的逻辑。
考虑到上述所有内容,您可以创建一个只需要基本调用(例如或 )的实际类。addItem()removeItem()
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class ArrowMenu(QWidget):
vMargin = -1
hMargin = -1
def __init__(self, items=None, parent=None):
super().__init__(parent)
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
layout.addStretch()
self.items = []
if isinstance(items, dict):
self.addItems(items.items())
elif items is not None:
self.addItems(items)
def addItems(self, items):
for item in items:
if isinstance(item, str):
self.addItem(item)
else:
self.addItem(*item)
def addItem(self, text, background=None):
self.insertItem(len(self.items), text, background)
def insertItem(self, index, text, background=None):
label = QLabel(text)
if background is None:
background = self.palette().window().color()
background.setAlpha(0)
else:
background = QColor(background)
# human eyes perceive "brightness" in different ways, let's compute
# that value in order to decide a color that has sufficient contrast
# with the background; see https://photo.stackexchange.com/q/10412
r, g, b, a = background.getRgbF()
brightness = r * .3 + g * .59 + b * .11
foreground = 'black' if brightness >= .5 else 'white'
label.setStyleSheet('color: {}; background: {};'.format(
foreground, background.name(background.HexArgb)))
layout = self.layout()
if index < len(self.items):
i = 0
for _label, _spacer, _ in self.items:
if i == index:
i += 1
layout.insertWidget(i * 2, _label)
layout.insertWidget(i * 2 + 1, _spacer)
i += 1
layout.insertWidget(index * 2, label)
spacer = QWidget(objectName='menuArrow')
layout.insertWidget(index * 2 + 1, spacer)
self.items.insert(index, (label, spacer, background))
self.updateItems()
def removeItem(self, index):
label, spacer, background = self.items.pop(index)
label.deleteLater()
spacer.deleteLater()
layout = self.layout()
for i, (label, spacer, _) in enumerate(self.items):
layout.insertWidget(i * 2, label)
layout.insertWidget(i * 2 + 1, spacer)
self.updateItems()
self.updateGeometry()
def updateItems(self):
if not self.items:
return
size = self.fontMetrics().height()
if self.vMargin < 0:
vSize = size * 2
else:
vSize = size + self.vMargin * 2
spacing = vSize / 2
self.setMinimumHeight(vSize)
if self.hMargin >= 0:
labelMargin = self.hMargin * 2
else:
labelMargin = size // 2
it = iter(self.items)
prevBackground = prevSpacer = None
while True:
try:
label, spacer, background = next(it)
label.setContentsMargins(labelMargin, 0, labelMargin, 0)
spacer.setFixedWidth(spacing)
except StopIteration:
background = QColor()
break
finally:
if prevBackground:
if background.isValid():
cssBackground = background.name(QColor.HexArgb)
else:
cssBackground = 'none'
if prevBackground.alpha():
prevBackground = prevBackground.name(QColor.HexArgb)
else:
mid = QColor(prevBackground)
mid.setAlphaF(.5)
prevBackground = '''
qlineargradient(x1:0, y1:0, x2:1, y2:0,
stop:0 {}, stop:1 {})
'''.format(
prevBackground.name(QColor.HexArgb),
mid.name(QColor.HexArgb),
)
prevSpacer.setStyleSheet('''
ArrowMenu > .QWidget#menuArrow {{
background: transparent;
border-top: {size}px solid {background};
border-bottom: {size}px solid {background};
border-left: {spacing}px solid {prevBackground};
}}
'''.format(
size=self.height() // 2,
spacing=spacing,
prevBackground=prevBackground,
background=cssBackground
))
prevBackground = background
prevSpacer = spacer
def resizeEvent(self, event):
self.updateItems()
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
items = (
('Python', 'green'),
('Will delete', 'chocolate'),
('PyQt5', 'red'),
('Java', 'blue'),
('ASP.Net', 'yellow'),
)
ex = ArrowMenu(items)
ex.show()
QTimer.singleShot(2000, lambda: ex.addItem('New item', 'aqua'))
QTimer.singleShot(5000, lambda: ex.removeItem(1))
sys.exit(app.exec_())
Run Code Online (Sandbox Code Playgroud)
这是结果: