pyqt中QMenu的圆角
Rounded corners for QMenu in pyqt
我正在尝试覆盖 QMenu
的 paintEvent()
以使其具有圆角。
上下文菜单应如下所示。
这是我试过的代码但是没有出现:
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 提出的 很好,但需要考虑两个重要的因素:
- 系统必须支持合成;虽然这对于大多数现代 OSes 来说是正确的,但至少在 Linux 上,即使是最新的系统也有可能 不 支持它选择(我在我的电脑上禁用);如果是这样,设置
WA_TranslucentBackground
属性将不起作用。
-
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 实现的一些旁注,由于上述原因在这种特定情况下不是必需的,但仍然很重要(有些要点与已注释的代码部分相关,但您 试过他们让那些值得一提的方面):
- 用于小部件的 QPainter 必须永远 在
paintEvent()
之外实例化:像您那样在 __init__
中创建实例是一个严重的错误甚至可能导致崩溃。画家只能在收到 paintEvent 时创建,并且 永远不会 被重用。显然,将其设置为实例属性 (self.painter
) 毫无用处,因为在绘制事件后没有实际理由访问它。
- 如果画笔宽度一直不变,那么在构造函数中设置即可(
self.pen = QtGui.QPen(QtCore.Qt.red, 2)
),在paintEvent中连续设置是没有用的
- QPen 和 QBrush 可以直接接受 Qt 全局颜色,因此无需创建 QBrush 实例,因为画家会自动(内部快速)设置它:
self.painter.setBrush(QtCore.Qt.blue)
.
self.update()
应该 永远不会 在 paintEvent 中调用(甚至 self.repaint()
也不应该)。结果未定义且可能危险。
- 如果你用QPainter做一些手工绘画然后然后调用super paintEvent,结果很可能之前画的所有东西都会被隐藏;作为一般规则,基本实现应该被称为 first,然后任何其他自定义绘画应该发生 after(在这种情况下它显然不会工作,因为您将绘制一个填充的圆角矩形,使菜单项不可见)。
我正在尝试覆盖 QMenu
的 paintEvent()
以使其具有圆角。
上下文菜单应如下所示。
这是我试过的代码但是没有出现:
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 提出的
- 系统必须支持合成;虽然这对于大多数现代 OSes 来说是正确的,但至少在 Linux 上,即使是最新的系统也有可能 不 支持它选择(我在我的电脑上禁用);如果是这样,设置
WA_TranslucentBackground
属性将不起作用。 -
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 实现的一些旁注,由于上述原因在这种特定情况下不是必需的,但仍然很重要(有些要点与已注释的代码部分相关,但您 试过他们让那些值得一提的方面):
- 用于小部件的 QPainter 必须永远 在
paintEvent()
之外实例化:像您那样在__init__
中创建实例是一个严重的错误甚至可能导致崩溃。画家只能在收到 paintEvent 时创建,并且 永远不会 被重用。显然,将其设置为实例属性 (self.painter
) 毫无用处,因为在绘制事件后没有实际理由访问它。 - 如果画笔宽度一直不变,那么在构造函数中设置即可(
self.pen = QtGui.QPen(QtCore.Qt.red, 2)
),在paintEvent中连续设置是没有用的 - QPen 和 QBrush 可以直接接受 Qt 全局颜色,因此无需创建 QBrush 实例,因为画家会自动(内部快速)设置它:
self.painter.setBrush(QtCore.Qt.blue)
. self.update()
应该 永远不会 在 paintEvent 中调用(甚至self.repaint()
也不应该)。结果未定义且可能危险。- 如果你用QPainter做一些手工绘画然后然后调用super paintEvent,结果很可能之前画的所有东西都会被隐藏;作为一般规则,基本实现应该被称为 first,然后任何其他自定义绘画应该发生 after(在这种情况下它显然不会工作,因为您将绘制一个填充的圆角矩形,使菜单项不可见)。