pyqt中QMenu的圆角

2024-09-30 22:16:35 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图覆盖QMenupaintEvent(),使其具有圆角

上下文菜单应该是这样的

enter image description here

以下是我尝试过的代码,但没有显示任何内容:

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()

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

这就是我在使用样式表时得到的结果,它不是完全四舍五入的

enter image description here

这是@musicamante建议后的绘画活动(仅供他/她检查)

    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)

Tags: selfeventinitdefsysqtsuperpainter
2条回答

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

虽然克里斯蒂安·卡彻提出的solution很好,但需要考虑两个重要因素:

  1. 系统必须支持合成;虽然大多数现代操作系统都是如此,但至少在Linux上,即使是最新的系统也有可能选择不支持它(我在计算机上禁用了它);如果是这种情况,设置WA_TranslucentBackground属性将不起作用
  2. 在Linux上不应设置FramelessWindowHint,因为它可能会导致窗口管理器出现问题,因此只有在确保操作系统需要它(Windows)后才应设置它

有鉴于此,每当不支持合成时,使用^{}是正确的修复方法,这必须发生在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__中创建实例是一个严重错误,甚至可能导致崩溃。油漆工只能在收到油漆事件时创建,且不得重复使用。这显然使得将其设置为实例属性(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. 如果您使用QPaint进行一些手动绘制,然后调用super paintEvent,结果很可能是之前绘制的所有内容都将被隐藏;一般来说,基本实现应该首先被称为,然后任何其他自定义绘制都应该在之后进行(在这种情况下,它显然不起作用,因为您将绘制一个填充的圆形矩形,使菜单项不可见)

我不能评论paintEvent功能,但可以使用样式表实现圆角。必须修改某些qmenu属性才能禁用背景中的默认矩形,这会产生不需要的结果

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

enter image description here

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()

相关问题 更多 >