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