pyqt中QMenu的圆角

Rounded corners for QMenu in pyqt

我正在尝试覆盖 QMenupaintEvent() 以使其具有圆角。

上下文菜单应如下所示。

这是我试过的代码但是没有出现:

from PyQt5 import QtWidgets, QtGui, QtCore
import sys


class Example(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):
        self.setGeometry(300, 300, 300, 200)
        self.setWindowTitle('Context menu')
        self.show()

    def contextMenuEvent(self, event):
        cmenu = AddContextMenu(self)

        newAct = cmenu.addAction("New")
        openAct = cmenu.addAction("Open")
        quitAct = cmenu.addAction("Quit")
        action = cmenu.exec_(self.mapToGlobal(event.pos()))


class AddContextMenu(QtWidgets.QMenu):

    def __init__(self, *args, **kwargs):
        super(AddContextMenu, self).__init__()
        self.painter = QtGui.QPainter(self)
        self.setMinimumSize(150, 200)

        self.pen = QtGui.QPen(QtCore.Qt.red)
        #self.setStyleSheet('color:white; background:gray; border-radius:4px; border:2px solid white;')

    def paintEvent(self, event) -> None:
        self.pen.setWidth(2)

        self.painter.setPen(self.pen)
        self.painter.setBrush(QtGui.QBrush(QtCore.Qt.blue))
        self.painter.drawRoundedRect(10, 10, 100, 100, 4.0, 4.0)
        self.update()
        #self.repaint()
        #super(AddContextMenu, self).paintEvent(event)

def main():
    app = QtWidgets.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

注意:设置样式 sheet 对我不起作用:

这是我在使用样式时得到的 sheet 它不是完全圆的。

这是@musicamante建议后的paintEvent(这只是为了him/her检查)

    def paintEvent(self, event) -> None:
        painter = QtGui.QPainter(self)

        #self.pen.setColor(QtCore.Qt.white)
        #painter.setFont(QtGui.QFont("times", 22))
        #painter.setPen(self.pen)
        #painter.drawText(QtCore.QPointF(0, 0), 'Hello')

        self.pen.setColor(QtCore.Qt.red)
        painter.setPen(self.pen)
        painter.setBrush(QtCore.Qt.gray)
        painter.drawRoundedRect(self.rect(), 20.0, 20.0)
      

并在 init()

self.pen = QtGui.QPen(QtCore.Qt.red)
self.pen.setWidth(2)

我无法评论 paintEvent 功能,但可以使用样式表实现圆角。为了禁用背景中的默认矩形,必须修改一些 qmenu 属性,这给了你不想要的结果。

这是使用样式表 + 自定义标志(无框架 + 透明背景)的示例的修改版本:

from PyQt5 import QtWidgets, QtCore
import sys


class Example(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):
        self.setGeometry(300, 300, 300, 200)
        self.setWindowTitle('Context menu')
        self.show()

    def contextMenuEvent(self, event):
        cmenu = QtWidgets.QMenu()
        # disable default frame and background
        cmenu.setWindowFlags(QtCore.Qt.FramelessWindowHint)
        cmenu.setAttribute(QtCore.Qt.WA_TranslucentBackground)
        # set stylesheet, add some padding to avoid overlap of selection with rounded corner
        cmenu.setStyleSheet("""
            QMenu{
                  background-color: rgb(255, 255, 255);
                  border-radius: 20px;
            }
            QMenu::item {
                    background-color: transparent;
                    padding:3px 20px;
                    margin:5px 10px;
            }
            QMenu::item:selected { background-color: gray; }
        """)

        newAct = cmenu.addAction("New")
        openAct = cmenu.addAction("Open")
        quitAct = cmenu.addAction("Quit")
        action = cmenu.exec_(self.mapToGlobal(event.pos()))

def main():
    app = QtWidgets.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

在样式表中为 顶级小部件(具有自己的“window”的小部件)设置边框半径是不够的。

虽然 Christian Karcher 提出的 很好,但需要考虑两个重要的因素:

  1. 系统必须支持合成;虽然这对于大多数现代 OSes 来说是正确的,但至少在 Linux 上,即使是最新的系统也有可能 支持它选择(我在我的电脑上禁用);如果是这样,设置 WA_TranslucentBackground 属性将不起作用。
  2. FramelessWindowHint 应该 设置在 Linux 上,因为它可能会导致 window 管理器出现问题,所以它应该仅在确保 OS 需要它 (Windows) 后设置。

鉴于此,当不支持合成时使用 setMask() 是正确的解决方法,并且这必须在 resizeEvent() 中进行。请注意,遮罩是基于 位图 的,抗锯齿 不支持,因此根据边框半径,圆形边框有时会有点难看。

此外,由于您需要自定义颜色,因此必须使用样式表,因为 QMenu 的自定义绘画确实很难实现。

class AddContextMenu(QtWidgets.QMenu):
    def __init__(self, *args, **kwargs):
        super(AddContextMenu, self).__init__()
        self.setMinimumSize(150, 200)
        self.radius = 4
        self.setStyleSheet('''
            QMenu {{
                background: blue;
                border: 2px solid red;
                border-radius: {radius}px;
            }}
            QMenu::item {{
                color: white;
            }}
            QMenu::item:selected {{
                color: red;
            }}
        '''.format(radius=self.radius))

    def resizeEvent(self, event):
        path = QtGui.QPainterPath()
        # the rectangle must be translated and adjusted by 1 pixel in order to 
        # correctly map the rounded shape
        rect = QtCore.QRectF(self.rect()).adjusted(.5, .5, -1.5, -1.5)
        path.addRoundedRect(rect, self.radius, self.radius)
        # QRegion is bitmap based, so the returned QPolygonF (which uses float
        # values must be transformed to an integer based QPolygon
        region = QtGui.QRegion(path.toFillPolygon(QtGui.QTransform()).toPolygon())
        self.setMask(region)

关于您的 paintEvent 实现的一些旁注,由于上述原因在这种特定情况下不是必需的,但仍然很重要(有些要点与已注释的代码部分相关,但您 试过他们让那些值得一提的方面):

  1. 用于小部件的 QPainter 必须永远paintEvent() 之外实例化:像您那样在 __init__ 中创建实例是一个严重的错误甚至可能导致崩溃。画家只能在收到 paintEvent 时创建,并且 永远不会 被重用。显然,将其设置为实例属性 (self.painter) 毫无用处,因为在绘制事件后没有实际理由访问它。
  2. 如果画笔宽度一直不变,那么在构造函数中设置即可(self.pen = QtGui.QPen(QtCore.Qt.red, 2)),在paintEvent中连续设置是没有用的
  3. QPen 和 QBrush 可以直接接受 Qt 全局颜色,因此无需创建 QBrush 实例,因为画家会自动(内部快速)设置它:self.painter.setBrush(QtCore.Qt.blue).
  4. self.update() 应该 永远不会 在 paintEvent 中调用(甚至 self.repaint() 也不应该)。结果未定义且可能危险。
  5. 如果你用QPainter做一些手工绘画然后然后调用super paintEvent,结果很可能之前画的所有东西都会被隐藏;作为一般规则,基本实现应该被称为 first,然后任何其他自定义绘画应该发生 after(在这种情况下它显然不会工作,因为您将绘制一个填充的圆角矩形,使菜单项不可见)。