【问题标题】:PyQt - Custom scrolling with QListWidgetPyQt - 使用 QListWidget 自定义滚动
【发布时间】:2019-04-02 12:55:38
【问题描述】:

我正在尝试找出一种方法来自定义QListWidget 的滚动条,以使滚动条位于QListWidget 的上方和下方,而不是正常的垂直和水平滚动条。

如果您不明白我的意思,请查看下面的示例。
在下面的示例中,我使用 QPushButtonsQTimers 来控制滚动条来代替滚动条,但我正在寻找的是滚动条,如启用菜单滚动时 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 代码。
取消注释注释部分以获得图像中的固定大小。通常 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_()

【问题讨论】:

  • 你可以展示你想要得到的东西的图片。您希望何时显示按钮?如果我执行你的代码,如果鼠标在按钮上,只看滚动,我不知道你想得到什么。
  • 如果可能的话,我非常想获得相同的功能或接近 QMenu 中滚动的工作方式。如果我必须使用我的示例中的按钮,我希望按钮仅在需要时显示,就像滚动条通常在列表很长并且项目不在视野中时所做的那样。当列表很短且在列表小部件中完全显示时,我想隐藏按钮,这就是为什么我正在寻找使用滚动条的内置方法或尝试捕获滚动条何时正常激活以便我知道何时显示/隐藏按钮。
  • QMenu 依赖于样式和操作系统,所以我不明白您想要获得什么,您可以显示您想要更好地了解您的 QMenu 的图像。另一方面,第二点我已经明白了。
  • 哦,对不起。我刚刚用一张图片更新了我的帖子。
  • 最后一件事,你可以分享可滚动的 QMenu 代码

标签: python pyqt pyqt5 qlistwidget qscrollbar


【解决方案1】:

我们的想法是获取上下元素的行,这将决定按钮是否隐藏,为此我们使用 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_())

【讨论】:

  • 它似乎工作得很好,非常感谢,非常感谢!
  • @Richard 你看你是否提供了图片或代码等信息让我们了解你的需求更有可能找到解决方案。 :-)
  • 是的,我知道,起初我以为我对代码很清楚,但后来我记得 QMenus 在你提到它之后依赖于平台,所以我现在明白为什么我的问题在没有更多的情况下有点难以回答信息。
猜你喜欢
  • 1970-01-01
  • 2020-10-16
  • 2013-04-20
  • 2021-04-26
  • 2018-04-20
  • 1970-01-01
  • 2011-10-15
  • 2017-11-19
  • 2011-06-30
相关资源
最近更新 更多