Python 如何使QMenu可滚动并同时固定其位置?

Python 如何使QMenu可滚动并同时固定其位置?,python,pandas,pyqt5,filtering,qmenu,Python,Pandas,Pyqt5,Filtering,Qmenu,我想在PyQt中可视化和过滤pandas数据帧中的数据。我已经设法将数据可视化,并通过单击标题打开菜单。其思想是,您可以在菜单中选择要保留在表中的元素,并以这种方式过滤数据(如在Excel中) Excel过滤示例 就像在Excel中一样,我想限制菜单的高度,让它可以滚动。虽然我将样式设置为QMenu{menu scrollable:1;},但如果它们在列中没有足够的唯一值,则不允许进行任何滚动 菜单不显示任何滚动选项 如果存在一定数量的唯一值(大于物理屏幕大小所允许的值),则允许滚动。在这种

我想在PyQt中可视化和过滤pandas数据帧中的数据。我已经设法将数据可视化,并通过单击标题打开菜单。其思想是,您可以在菜单中选择要保留在表中的元素,并以这种方式过滤数据(如在Excel中)

Excel过滤示例

就像在Excel中一样,我想限制菜单的高度,让它可以滚动。虽然我将样式设置为
QMenu{menu scrollable:1;}
,但如果它们在列中没有足够的唯一值,则不允许进行任何滚动

菜单不显示任何滚动选项

如果存在一定数量的唯一值(大于物理屏幕大小所允许的值),则允许滚动。在这种情况下,它显示了一切都是正确的。但如果我尝试滚动菜单,菜单会向上移动,直到到达屏幕顶部,然后才开始滚动

具有正确高度和位置的可滚动菜单

滚动后,菜单向上移动,直到到达顶部

在上面的图片中,你可以看到,它显示了可滚动菜单,但如果我试图滚动,菜单只是向上移动。只有当菜单到达顶部时,它才允许我滚动数值

class CustomProxyModel(QtCore.QSortFilterProxyModel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._filters = dict()

    @property
    def filters(self):
        return self._filters

    def setFilter(self, expresion, column):
        if expresion:
            self.filters[column] = expresion
        elif column in self.filters:
            del self.filters[column]
        self.invalidateFilter()

    def filterAcceptsRow(self, source_row, source_parent):
        for column, expresion in self.filters.items():
            text = self.sourceModel().index(source_row, column, source_parent).data()
            regex = QtCore.QRegExp(
                expresion, QtCore.Qt.CaseInsensitive, QtCore.QRegExp.RegExp
            )
            if regex.indexIn(text) == -1:
                return False
        return True

class PandasModel(QtCore.QAbstractTableModel):
    def __init__(self, df=pandas.DataFrame(), parent=None):
        QtCore.QAbstractTableModel.__init__(self, parent=parent)
        self._df = df.copy()

    def toDataFrame(self):
        return self._df.copy()

    def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
        if role != QtCore.Qt.DisplayRole:
            return QtCore.QVariant()

        if orientation == QtCore.Qt.Horizontal:
            try:
                return self._df.columns.tolist()[section]
            except (IndexError, ):
                return QtCore.QVariant()
        elif orientation == QtCore.Qt.Vertical:
            try:
                return self._df.index.tolist()[section]
            except (IndexError, ):
                return QtCore.QVariant()

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if role != QtCore.Qt.DisplayRole:
            return QtCore.QVariant()

        if not index.isValid():
            return QtCore.QVariant()

        return QtCore.QVariant(str(self._df.iloc[index.row(), index.column()]))

    def setData(self, index, value, role):
        row = self._df.index[index.row()]
        col = self._df.columns[index.column()]
        if hasattr(value, 'toPyObject'):
            # PyQt4 gets a QVariant
            value = value.toPyObject()
        else:
            # PySide gets an unicode
            dtype = self._df[col].dtype
            if dtype != object:
                value = None if value == '' else dtype.type(value)
        self._df.set_value(row, col, value)
        return True

    def rowCount(self, parent=QtCore.QModelIndex()):
        return len(self._df.index)

    def columnCount(self, parent=QtCore.QModelIndex()):
        return len(self._df.columns)

    def sort(self, column, order):
        colname = self._df.columns.tolist()[column]
        self.layoutAboutToBeChanged.emit()
        self._df.sort_values(colname, ascending=order == QtCore.Qt.AscendingOrder, inplace=True)
        self._df.reset_index(inplace=True, drop=True)
        self.layoutChanged.emit()

class Ui(QtWidgets.QMainWindow):

    def __init__(self):
        super(Ui, self).__init__()  # Call the inherited classes __init__ method
        uic.loadUi('../test.ui', self)  # Load the .ui file

        button = self.findChild(QtWidgets.QPushButton, 'button_load')
        button.clicked.connect(self.read)

        self.view = self.findChild(QtWidgets.QTableView, 'tableView')

        self.view.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
        self.horizontalHeader = self.view.horizontalHeader()
        self.horizontalHeader.sectionClicked.connect(self.on_view_horizontalHeader_sectionClicked)

        self.show()  # Show the GUI

    def read(self):
        proxy = CustomProxyModel(self)
        self.model = PandasModel(pandas.read_csv('C:/Users/Sergey/Downloads/10000 Sales Records.csv'))
        proxy.setSourceModel(self.model)
        self.view.setModel(proxy)
        self.view.resizeColumnsToContents()

    def on_view_horizontalHeader_sectionClicked(self, index):
        menu = QtWidgets.QMenu(self.view)
        signalMapper = QtCore.QSignalMapper(self)

        valuesUnique = self.model._df.iloc[:, index].unique().astype(str)

        actionAll = QtWidgets.QAction("All", self)
        # actionAll.triggered.connect(self.on_actionAll_triggered)
        menu.addAction(actionAll)
        menu.addSeparator()
        for actionNumber, actionName in enumerate(sorted(list(valuesUnique))):
            action = QtWidgets.QAction(actionName, self)
            action.setCheckable(True)
            action.setChecked(True)
            signalMapper.setMapping(action, actionNumber)
            # action.triggered.connect(signalMapper.map)
            menu.addAction(action)
        # self.signalMapper.mapped.connect(self.on_signalMapper_mapped)
        headerPos = self.view.mapToGlobal(self.horizontalHeader.pos())
        posX = headerPos.x() + self.horizontalHeader.sectionPosition(index)
        posY = headerPos.y() + self.horizontalHeader.height()

        menu.setStyleSheet("QMenu { menu-scrollable: 1; }")
        menu.setMaximumHeight(155)
        menu.popup(QtCore.QPoint(posX, posY))
        menu.move(posX, posY)


app = QtWidgets.QApplication(sys.argv)  # Create an instance of QtWidgets.QApplication
window = Ui()  # Create an instance of our class
app.exec_()  # Start the application
test.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>800</width>
    <height>600</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <widget class="QPushButton" name="button_load">
    <property name="geometry">
     <rect>
      <x>350</x>
      <y>440</y>
      <width>75</width>
      <height>23</height>
     </rect>
    </property>
    <property name="text">
     <string>Load</string>
    </property>
   </widget>
   <widget class="QTableView" name="tableView">
    <property name="geometry">
     <rect>
      <x>80</x>
      <y>30</y>
      <width>621</width>
      <height>351</height>
     </rect>
    </property>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>800</width>
     <height>21</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>

主窗口
0
0
800
600
主窗口
350
440
75
23
负载
80
30
621
351
0
0
800
21
示例数据集:

提供、共享.ui和.csv