如何在 PyQt5 中制作有角度的箭头样式边框?

Bal*_*ala 8 python pyqt pyqt5

如何在 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)

样本图片 在此输入图像描述

Bal*_*ala 5

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)

在此输入图像描述


mus*_*nte 5

由于 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)

这是结果:

示例的屏幕截图