带有 Pyside/PyQt 的径向菜单
Posted
技术标签:
【中文标题】带有 Pyside/PyQt 的径向菜单【英文标题】:Radial Menu With Pyside/PyQt 【发布时间】:2020-02-19 22:22:39 【问题描述】:基本上我正在尝试创建一个像这样的径向菜单
我使用的是 QPainter,这是我的尝试。但我不知道如何在像素图上添加点击事件。有没有可用的库?
Images Link
from PySide2 import QtWidgets, QtGui, QtCore
import sys
import os
RESPATH = "/your_folder/radialMenu/res"
class RadialMenu(QtWidgets.QGraphicsRectItem):
addButton = 1
disableButton = 2
clearButton = 3
exportButton = 4
infoButton = 5
runButton = 6
scriptsButton = 7
def __init__(self, parent=None):
super(RadialMenu, self).__init__(parent)
def paint(self, painter, option, widget=None):
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setBrush(QtGui.QBrush(QtGui.QColor(71, 71, 71, 0)))
tempPen = QtGui.QPen(QtGui.QColor(178, 141, 58), 20.0, QtCore.Qt.CustomDashLine)
tempPen.setDashPattern([4, 4, 4, 4])
painter.setPen(tempPen)
painter.drawEllipse(0, 0, 150, 150)
topX = 0
topY = 0
pixmap1 = QtGui.QPixmap(os.path.join(RESPATH, "add.png"))
painter.drawPixmap(topX + 50, topY - 8, pixmap1)
pixmap2 = QtGui.QPixmap(os.path.join(RESPATH, "disable.png"))
painter.drawPixmap(topX + 90, topY - 5, pixmap2)
pixmap3 = QtGui.QPixmap(os.path.join(RESPATH, "clear.png"))
painter.drawPixmap(topX - 10, topY + 70, pixmap3)
pixmap4 = QtGui.QPixmap(os.path.join(RESPATH, "export.png"))
pixmap4 = pixmap4.transformed(QtGui.QTransform().rotate(15))
painter.drawPixmap(topX - 2, topY + 100, pixmap4)
pixmap5 = QtGui.QPixmap(os.path.join(RESPATH, "info.png"))
painter.drawPixmap(topX + 20, topY + 125, pixmap5)
pixmap6 = QtGui.QPixmap(os.path.join(RESPATH, "run.png"))
painter.drawPixmap(topX + 113, topY + 125, pixmap6)
pixmap6 = QtGui.QPixmap(os.path.join(RESPATH, "scripts.png"))
painter.drawPixmap(topX + 137, topY + 85, pixmap6)
class RadialTest(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.scene=QtWidgets.QGraphicsScene(self)
buttonItem = RadialMenu()
self.scene.addItem(buttonItem)
buttonItem.setPos(100,100)
buttonItem.setZValue(1000)
self.scene.update()
self.view = QtWidgets.QGraphicsView(self.scene, self)
self.scene.setSceneRect(0, 0, 300, 300)
self.setGeometry(50, 50, 305, 305)
self.show()
if __name__ == "__main__":
app=QtWidgets.QApplication(sys.argv)
firstScene = RadialTest()
sys.exit(app.exec_())
这段代码会给出这个结果
【问题讨论】:
你能分享一下.png @eyllanesc 也添加了图片链接 @eyllanesc 我刚刚添加了一个完整的工作代码.. 但没有任何操作 【参考方案1】:对于此类对象,始终建议保持分层对象结构。此外,在处理可能具有“固定”尺寸的对象(如图像,但不仅如此)时,固定定位可能会很棘手,特别是对于支持不同 DPI 屏幕值的较新系统。
通过这种方法,我根本不使用映射图像(但仍然可以设置按钮图标),而是选择使用纯几何概念,为每个按钮使用像素“半径”值和角度。
from PyQt5 import QtWidgets, QtGui, QtCore
from math import sqrt
class RadialMenu(QtWidgets.QGraphicsObject):
buttonClicked = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super().__init__(parent)
self.setAcceptHoverEvents(True)
self.buttons =
def addButton(self, id, innerRadius, size, startAngle, angleSize, pen=None,
brush=None, icon=None):
# if a button already exists with the same id, remove it
if id in self.buttons:
oldItem = self.buttons.pop(id)
if self.scene():
self.scene().removeItem(oldItem)
oldItem.setParent(None)
# compute the extents of the inner and outer "circles"
startRect = QtCore.QRectF(
-innerRadius, -innerRadius, innerRadius * 2, innerRadius * 2)
outerRadius = innerRadius + size
endRect = QtCore.QRectF(
-outerRadius, -outerRadius, outerRadius * 2, outerRadius * 2)
# create the circle section path
path = QtGui.QPainterPath()
# move to the start angle, using the outer circle
path.moveTo(QtCore.QLineF.fromPolar(outerRadius, startAngle).p2())
# draw the arc to the end of the angle size
path.arcTo(endRect, startAngle, angleSize)
# draw a line that connects to the inner circle
path.lineTo(QtCore.QLineF.fromPolar(innerRadius, startAngle + angleSize).p2())
# draw the inner circle arc back to the start angle
path.arcTo(startRect, startAngle + angleSize, -angleSize)
# close the path back to the starting position; theoretically unnecessary,
# but better safe than sorry
path.closeSubpath()
# create a child item for the "arc"
item = QtWidgets.QGraphicsPathItem(path, self)
item.setPen(pen if pen else (QtGui.QPen(QtCore.Qt.transparent)))
item.setBrush(brush if brush else QtGui.QColor(180, 140, 70))
self.buttons[id] = item
if icon is not None:
# the maximum available size is at 45 degrees, use the Pythagorean
# theorem to compute it and create a new pixmap based on the icon
iconSize = int(sqrt(size ** 2 / 2))
pixmap = icon.pixmap(iconSize)
# create the child icon (pixmap) item
iconItem = QtWidgets.QGraphicsPixmapItem(pixmap, self)
# push it above the "arc" item
iconItem.setZValue(item.zValue() + 1)
# find the mid of the angle and put the icon there
midAngle = startAngle + angleSize / 2
iconPos = QtCore.QLineF.fromPolar(innerRadius + size * .5, midAngle).p2()
iconItem.setPos(iconPos)
# use the center of the pixmap as the offset for centering
iconItem.setOffset(-pixmap.rect().center())
def itemAtPos(self, pos):
for button in self.buttons.values():
if button.shape().contains(pos):
return button
def checkHover(self, pos):
hoverButton = self.itemAtPos(pos)
for button in self.buttons.values():
# set a visible border only for the hovered item
button.setPen(QtCore.Qt.red if button == hoverButton else QtCore.Qt.transparent)
def hoverEnterEvent(self, event):
self.checkHover(event.pos())
def hoverMoveEvent(self, event):
self.checkHover(event.pos())
def hoverLeaveEvent(self, event):
for button in self.buttons.values():
button.setPen(QtCore.Qt.transparent)
def mousePressEvent(self, event):
clickButton = self.itemAtPos(event.pos())
if clickButton:
for id, btn in self.buttons.items():
if btn == clickButton:
self.buttonClicked.emit(id)
def boundingRect(self):
return self.childrenBoundingRect()
def paint(self, qp, option, widget):
# required for QGraphicsObject subclasses
pass
ButtonData = [
(50, 40, QtWidgets.QStyle.SP_MessageBoxInformation),
(90, 40, QtWidgets.QStyle.SP_MessageBoxQuestion),
(180, 20, QtWidgets.QStyle.SP_FileDialogBack),
(200, 20, QtWidgets.QStyle.SP_DialogOkButton),
(220, 20, QtWidgets.QStyle.SP_DialogOpenButton),
(290, 30, QtWidgets.QStyle.SP_ArrowDown),
(320, 30, QtWidgets.QStyle.SP_ArrowUp),
]
class RadialTest(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.scene = QtWidgets.QGraphicsScene(self)
buttonItem = RadialMenu()
self.scene.addItem(buttonItem)
buttonItem.buttonClicked.connect(self.buttonClicked)
for index, (startAngle, extent, icon) in enumerate(ButtonData):
icon = self.style().standardIcon(icon, None, self)
buttonItem.addButton(index, 64, 20, startAngle, extent, icon=icon)
buttonItem.setPos(150, 150)
buttonItem.setZValue(1000)
self.view = QtWidgets.QGraphicsView(self.scene, self)
self.view.setRenderHints(QtGui.QPainter.Antialiasing)
self.scene.setSceneRect(0, 0, 300, 300)
self.setGeometry(50, 50, 305, 305)
self.show()
def buttonClicked(self, id):
print('Button id has been clicked'.format(id))
结果如下:
【讨论】:
完美.. 非常感谢,这正是我想要的。以上是关于带有 Pyside/PyQt 的径向菜单的主要内容,如果未能解决你的问题,请参考以下文章