<p>为此,您必须继承<code>QMenu</code>并重写<code>wheelEvent</code></p>
<p>下面是一个示例,您可以对其进行改进</p>
<pre class="lang-py prettyprint-override"><code>import sys
from PyQt5 import QtWidgets, QtCore, QtGui
class CustomMenu(QtWidgets.QMenu):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setStyleSheet('QMenu{background: gray;} QMenu::item:selected {background: rgb(58, 145, 232);}')
self.setFixedHeight(100)
self.visible_lst = []
self.index = -1
self.visibleCount = None
self.maxHeightOfAction = 0
# Remove Below code if you don't want the arrow -#
self.topArrow = None
self.bottomArrow = None
self.painter = QtGui.QPainter()
# #
def actionEvent(self, event):
if event.type() == QtCore.QEvent.ActionAdded:
if self.maxHeightOfAction < self.actionGeometry(self.actions()[-1]).height():
self.maxHeightOfAction = self.actionGeometry(self.actions()[-1]).height()
self.actions()[-1].setVisible(False)
self.updateActions()
if event.type() == QtCore.QEvent.ActionRemoved:
super(CustomMenu, self).actionEvent(event)
if self.index == len(self.actions()):
self.index -= 1
if event.action() in self.visible_lst:
self.visible_lst.remove(event.action())
self.removed()
super(CustomMenu, self).actionEvent(event)
def updateActions(self):
if self.actions():
if self.findVisibleCount() > len(self.actions()) and self.index == -1:
self.visible_lst = self.actions()
self.updateVisible()
elif self.findVisibleCount() < len(self.actions()) and self.index == -1:
self.index += 1
self.visible_lst = self.actions()[0: self.findVisibleCount()]
self.updateVisible()
self.setActiveAction(self.visible_lst[0])
def removed(self):
if len(self.actions()) > self.findVisibleCount():
if self.index < len(self.actions())-2:
index = self.findIndex(self.visible_lst, self.activeAction())
self.visible_lst.append(self.actions()[self.index + (index-self.findVisibleCount())-1])
elif self.index == len(self.actions())-1:
self.visible_lst.insert(0, self.actions()[-self.findVisibleCount()-1])
self.updateVisible()
def findVisibleCount(self): # finds how many QActions will be visible
visibleWidgets = 0
if self.actions():
try:
visibleWidgets = self.height()//self.maxHeightOfAction
except ZeroDivisionError:
pass
return visibleWidgets
def mousePressEvent(self, event) -> None:
if self.topArrow.containsPoint(event.pos(), QtCore.Qt.OddEvenFill) and self.index>0:
self.scrollUp()
elif self.bottomArrow.containsPoint(event.pos(), QtCore.Qt.OddEvenFill) and self.index < len(self.actions()) -1:
self.scrollDown()
else:
super(CustomMenu, self).mousePressEvent(event)
def keyPressEvent(self, event):
if self.actions():
if self.activeAction() is None:
self.setActiveAction(self.actions()[self.index])
if event.key() == QtCore.Qt.Key_Up:
self.scrollUp()
elif event.key() == QtCore.Qt.Key_Down:
self.scrollDown()
elif event.key() == QtCore.Qt.Key_Return:
super(CustomMenu, self).keyPressEvent(event)
def wheelEvent(self, event):
if self.actions():
if self.activeAction() is None:
self.setActiveAction(self.actions()[self.index])
delta = event.angleDelta().y()
if delta < 0: # scroll down
self.scrollDown()
elif delta > 0: # scroll up
self.scrollUp()
def scrollDown(self):
if self.index < len(self.actions())-1:
self.index = self.findIndex(self.actions(), self.activeAction()) + 1
try:
self.setActiveAction(self.actions()[self.index])
if self.activeAction() not in self.visible_lst and len(self.actions()) > self.findVisibleCount():
self.visible_lst[0].setVisible(False)
self.visible_lst.pop(0)
self.visible_lst.append(self.actions()[self.index])
self.visible_lst[-1].setVisible(True)
except IndexError:
pass
def scrollUp(self):
if self.findIndex(self.actions(), self.activeAction()) > 0:
self.index = self.findIndex(self.actions(), self.activeAction()) - 1
try:
self.setActiveAction(self.actions()[self.index])
if self.activeAction() not in self.visible_lst and len(self.actions()) > self.findVisibleCount():
self.visible_lst[-1].setVisible(False)
self.visible_lst.pop()
self.visible_lst.insert(0, self.actions()[self.index])
self.visible_lst[0].setVisible(True)
except IndexError:
pass
def updateVisible(self):
for item in self.visible_lst:
if not item.isVisible():
item.setVisible(True)
def findIndex(self, lst, element):
for index, item in enumerate(lst):
if item == element:
return index
return -1
def paintEvent(self, event): # remove this if you don't want the arrow
super(CustomMenu, self).paintEvent(event)
height = int(self.height())
width = self.width()//2
topPoints = [QtCore.QPoint(width-5, 7), QtCore.QPoint(width, 2), QtCore.QPoint(width+5, 7)]
bottomPoints = [QtCore.QPoint(width-5, height-7), QtCore.QPoint(width, height-2), QtCore.QPoint(width+5, height-7)]
self.topArrow = QtGui.QPolygon(topPoints)
self.bottomArrow = QtGui.QPolygon(bottomPoints)
self.painter.begin(self)
self.painter.setBrush(QtGui.QBrush(QtCore.Qt.white))
self.painter.setPen(QtCore.Qt.white)
if len(self.actions()) > self.findVisibleCount():
if self.index>0:
self.painter.drawPolygon(self.topArrow)
if self.index < len(self.actions()) -1:
self.painter.drawPolygon(self.bottomArrow)
self.painter.end()
class ExampleWindow(QtWidgets.QWidget):
def contextMenuEvent(self, event) -> None:
menu = CustomMenu()
menu.addAction('Hello0')
menu.addAction('Hello1')
menu.addAction('Hello2')
menu.addAction('Hello3')
menu.addAction('Hello4')
menu.addAction('Hello5')
menu.addAction('Hello6')
menu.addAction('Hello7')
menu.addAction('Hello8')
menu.exec_(QtGui.QCursor.pos())
def main():
app = QtWidgets.QApplication(sys.argv)
window = ExampleWindow()
window.setWindowTitle('PyQt5 App')
window.show()
app.exec_()
if __name__ == '__main__':
main()
</code></pre>
<p><strong>上述代码的解释:</strong></p>
<ul>
<li><p>将所有可见操作存储在一个列表中,例如<code>visible_lst</code></p>
</li>
<li><p>向下滚动:</p>
<ol>
<li>向下滚动时增加索引</li>
<li>使用<code>self.visible_lst[0].setVisible(False)</code>将使该操作不可见,然后从前面弹出列表</李>
<li>使用<code>self.actions()[self.index]</code>将下一个操作附加到<code>visible_lst</code></li>
</ol>
</li>
<li><p>向上滚动:</p>
<ol>
<li>向上滚动时减小索引</li>
<li>使用<code> self.visible_lst[-1].setVisible(False)</code>将隐藏列表中的最后一项,并从列表中弹出最后一个元素</li>
<li>将上一个元素插入到<code>visible_lst</code>的第0个索引中,并使用<code>self.actions()[index].setVisible(True)</code>使其可见</li>
</ol>
</li>
</ul>
<p>滚动上下文菜单代码的输出:</p>
<p><a href="https://i.stack.imgur.com/zgvdA.gif" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/zgvdA.gif" alt="enter image description here"/></a></p>
<p>读者,如果您有任何建议或疑问,请留言</p>