Python PyQt-使用QListWidget自定义滚动

Python PyQt-使用QListWidget自定义滚动,python,pyqt,pyqt5,qlistwidget,qscrollbar,Python,Pyqt,Pyqt5,Qlistwidget,Qscrollbar,我试图找到一种方法来定制QListWidget的滚动条,使滚动条位于QListWidget的上方和下方,而不是普通的垂直和水平滚动条 如果您不明白我的意思,请查看下面的示例。 在下面的示例中,我使用QPushButtons和QTimers控制滚动来代替滚动条,但我要寻找的是启用菜单滚动时与QMenu中类似的滚动条 如果这不是一个选项,我想知道是否有一个滚动条信号,或者我可以尝试使用它来知道滚动条何时被正常激活?这样我可以根据需要显示/隐藏按钮。谢谢 import sys from PyQt5.Q

我试图找到一种方法来定制
QListWidget
的滚动条,使滚动条位于
QListWidget
的上方和下方,而不是普通的垂直和水平滚动条

如果您不明白我的意思,请查看下面的示例。
在下面的示例中,我使用
QPushButtons
QTimers
控制滚动来代替滚动条,但我要寻找的是启用菜单滚动时与
QMenu
中类似的滚动条

如果这不是一个选项,我想知道是否有一个滚动条信号,或者我可以尝试使用它来知道滚动条何时被正常激活?这样我可以根据需要显示/隐藏按钮。谢谢

import sys
from PyQt5.QtCore import pyqtSignal, QTimer, Qt
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QWidget, QPushButton, QVBoxLayout, \
    QApplication, QStyle, QListWidget, QStyleOptionButton, QListWidgetItem

class UpBtn(QPushButton):
    mouseHover = pyqtSignal()
    def __init__(self):
        QPushButton.__init__(self)
        self.setMouseTracking(True)
        self.timer = QTimer()

    def paintEvent(self, event):
        painter = QPainter()
        painter.begin(self)
        opt = QStyleOptionButton()
        self.initStyleOption(opt)
        self.style().drawControl(QStyle.CE_ScrollBarSubLine, opt, painter, self)
        painter.end()

    def startScroll(self):
        self.mouseHover.emit()

    def enterEvent(self, event):
        self.timer.timeout.connect(self.startScroll)
        self.timer.start(120)

    def leaveEvent(self, event):
        self.timer.stop()

class DwnBtn(QPushButton):
    mouseHover = pyqtSignal()
    def __init__(self):
        QPushButton.__init__(self)
        self.setMouseTracking(True)
        self.timer = QTimer()

    def paintEvent(self, event):
        painter = QPainter()
        painter.begin(self)
        opt = QStyleOptionButton()
        self.initStyleOption(opt)
        self.style().drawControl(QStyle.CE_ScrollBarAddLine, opt, painter, self)
        painter.end()

    def startScroll(self):
        self.mouseHover.emit()

    def enterEvent(self, event):
        self.timer.timeout.connect(self.startScroll)
        self.timer.start(120)

    def leaveEvent(self, event):
        self.timer.stop()

class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.layout = QVBoxLayout()
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.setSpacing(0)
        self.upBtn = UpBtn()
        self.upBtn.setFixedWidth(230)
        self.layout.addWidget(self.upBtn)

        self.listWidget = QListWidget()
        self.listWidget.setFixedWidth(230)
        self.listWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.listWidget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.layout.addWidget(self.listWidget)

        self.downBtn = DwnBtn()
        self.downBtn.setFixedWidth(230)
        self.layout.addWidget(self.downBtn)

        self.setLayout(self.layout)
        self.upBtn.clicked.connect(self.upBtnClicked)
        self.upBtn.mouseHover.connect(self.upBtnClicked)
        self.downBtn.clicked.connect(self.downBtnClicked)
        self.downBtn.mouseHover.connect(self.downBtnClicked)

        for i in range(100):
            item = QListWidgetItem()
            item.setText("list item " + str(i))
            self.listWidget.addItem(item)

    def upBtnClicked(self):
        cur = self.listWidget.currentRow()
        self.listWidget.setCurrentRow(cur - 1)

    def downBtnClicked(self):
        cur = self.listWidget.currentRow()
        self.listWidget.setCurrentRow(cur + 1)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())
编辑: 下面是我所说的示例图像。这是一个可滚动的
QMenu

编辑:
可滚动
QMenu
code.
取消注释注释注释的部分,以获得固定大小,如图中所示。通常,只有当菜单项超过屏幕高度时,Qmenu滚动才起作用。我只是在寻找顶部和底部悬停式滚动,但要在
QListWidget
中使用

import sys
from PyQt5.QtCore import QPoint, QEvent
from PyQt5.QtWidgets import QWidget, QPushButton, QVBoxLayout, \
    QApplication, QAction, QMenu, QProxyStyle, QStyle

class MyMenu(QMenu):
    def event(self, event):
        if event.type() == QEvent.Show:
            self.move(self.parent().mapToGlobal(QPoint(-108, 0)))
        return super(MyMenu, self).event(event)

# class CustomStyle(QProxyStyle):
#     def pixelMetric(self, QStyle_PixelMetric, option=None, widget=None):
#         if QStyle_PixelMetric == QStyle.PM_MenuScrollerHeight:
#             return 15
#         if QStyle_PixelMetric == QStyle.PM_MenuDesktopFrameWidth:
#             return 290
#         else:
#             return QProxyStyle.pixelMetric(self, QStyle_PixelMetric, option, widget)

class MainWindow(QWidget):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.layout = QVBoxLayout()
        self.btn = QPushButton("Button")
        self.btn.setFixedHeight(30)
        self.btn.setFixedWidth(100)
        self.myMenu = MyMenu("Menu", self.btn)
        self.btn.setMenu(self.myMenu)
        self.layout.addWidget(self.btn)
        self.setLayout(self.layout)
        menus = []
        for _ in range(5):
            myMenus = QMenu("Menu"+str(_+1), self.btn)
            # myMenus.setFixedHeight(120)
            myMenus.setStyleSheet("QMenu{menu-scrollable: 1; }")
            menus.append(myMenus)
        for i in menus:
            self.btn.menu().addMenu(i)
            for item in range(100):
                action = QAction("item" + str(item), self)
                i.addAction(action)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    # app.setStyle(CustomStyle())
    w = MainWindow()
    w.show()
    app.exec_()

其思想是获得决定按钮是否隐藏的上下元素行,因为我们使用方法itemAt()返回给定几何坐标的项。另一方面,我改进了每次他们更改QListView中的项数时的计算,因为我们使用内部模型的信号

import sys
from PyQt5 import QtCore, QtGui, QtWidgets


class Button(QtWidgets.QPushButton):
    moveSignal = QtCore.pyqtSignal()
    def __init__(self, *args, **kwargs):
        super(Button, self).__init__(*args, **kwargs)
        self.m_timer = QtCore.QTimer(self, interval=120)
        self.m_timer.timeout.connect(self.moveSignal)
        self.setMouseTracking(True)
        self.setFixedHeight(20)

    def mouseReleaseEvent(self, e):
        super(Button, self).mousePressEvent(e)
        self.setDown(True)

    def enterEvent(self, e):
        self.setDown(True)
        self.m_timer.start()
        super(Button, self).enterEvent(e)

    def leaveEvent(self, e):
        self.setDown(False)
        self.m_timer.stop()
        super(Button, self).leaveEvent(e)


class Window(QtWidgets.QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.setFixedWidth(230)

        icon = self.style().standardIcon(QtWidgets.QStyle.SP_ArrowUp)
        self.upBtn = Button(icon=icon)
        self.upBtn.moveSignal.connect(self.moveUp)
        icon = self.style().standardIcon(QtWidgets.QStyle.SP_ArrowDown)
        self.downBtn = Button(icon=icon)
        self.downBtn.moveSignal.connect(self.moveDown)

        self.listWidget = QtWidgets.QListWidget()
        self.listWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.listWidget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)

        layout = QtWidgets.QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self.upBtn)
        layout.addWidget(self.listWidget)
        layout.addWidget(self.downBtn)
        self.adjust_buttons()
        self.create_connections()

    def create_connections(self):
        self.listWidget.currentItemChanged.connect(self.adjust_buttons)
        model = self.listWidget.model()
        model.rowsInserted.connect(self.adjust_buttons)
        model.rowsRemoved.connect(self.adjust_buttons)
        model.rowsMoved.connect(self.adjust_buttons)
        model.modelReset.connect(self.adjust_buttons)
        model.layoutChanged.connect(self.adjust_buttons)

    @QtCore.pyqtSlot()
    def adjust_buttons(self):
        first = self.listWidget.itemAt(QtCore.QPoint())
        r = self.listWidget.row(first)
        self.upBtn.setVisible(r != 0 and  r!= -1)
        last = self.listWidget.itemAt(self.listWidget.viewport().rect().bottomRight())
        r = self.listWidget.row(last)
        self.downBtn.setVisible( r != (self.listWidget.count() -1) and r != -1)

    @QtCore.pyqtSlot()
    def moveUp(self):
        ix = self.listWidget.moveCursor(QtWidgets.QAbstractItemView.MoveUp, QtCore.Qt.NoModifier)
        self.listWidget.setCurrentIndex(ix)

    @QtCore.pyqtSlot()
    def moveDown(self):
        ix = self.listWidget.moveCursor(QtWidgets.QAbstractItemView.MoveDown, QtCore.Qt.NoModifier)
        self.listWidget.setCurrentIndex(ix)

    @QtCore.pyqtSlot(str)
    def add_item(self, text):
        item = QtWidgets.QListWidgetItem(text)
        self.listWidget.addItem(item)


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    window = Window()
    for i in range(100):
        window.add_item("item {}".format(i))
    window.show()
    sys.exit(app.exec_())

你可以展示你想要得到的东西的图片。您希望何时显示按钮?如果我执行你的代码,只要看看滚动如果鼠标在按钮上,我不知道你想得到什么。我几乎是试图获得相同的功能或接近如何滚动在QMenu工作,如果可能的话。如果我必须使用我的示例中的按钮,我希望按钮只在需要时显示,就像滚动条在列表很长且项目不可见时通常所做的那样。当列表很短且在listwidget中完全可见时,我想隐藏按钮,这就是为什么我要寻找使用滚动条的内置方法,或者尝试捕获滚动条正常激活的时间,以便我知道何时显示/隐藏按钮。QMenu取决于样式和操作系统,所以我不明白您想要获得什么,您可以显示QMenu的图像,以便更好地了解您。另一方面,第二点我已经理解了。哦,对不起。我刚刚用一张图片更新了我的帖子。最后一件事,你可以分享可滚动的QMenu代码它似乎工作得很好,非常感谢,非常感谢@Richard如果您提供图像或代码等信息,使我们能够了解您的需求,那么您就更有可能找到解决方案。:-)是的,我知道,一开始我认为我对代码非常清楚,但在你提到QMenus后,我记得QMenus依赖于平台,所以我现在明白了为什么我的问题在没有更多信息的情况下有点难以回答。