- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
我有一个 QTableView,它从继承 QAbstractItemModel 的自定义模型动态加载数据。该模型同时实现了 fetchMore 和 canFetchMore。
问题是我希望能够为小型数据集选择所有行,但如果我在 View 中按 ctrl-a,它只会选择当前加载的行。
是否有某种机制可以强制 QTableView 获取更多行?理想情况下,我想显示一个进度条,指示已从模型加载的数据部分。每隔几秒钟我就想强制模型加载更多的数据,但我仍然想让用户与目前已加载的数据进行交互。这样,当进度条完成时,用户可以按 ctrl-a 并确信已选择所有数据。
编辑:我还有另一个激励用例。我想跳转到特定行,但如果未加载该行,我的界面什么也不做。
如何强制 QAbstractItemModel 获取更多(或最多特定行)然后强制 QTableView 显示它?
如果我不实现 fetchMore 和 canFetchMore,以前的功能可以工作,但加载表格非常慢。当我实现这些方法时,情况恰恰相反。没有这个问题的答案会导致我的 qt 界面的可用性出现问题,所以我正在为这个问题悬赏。
这是我用来选择特定行的方法。
def select_row_from_id(view, _id, scroll=False, collapse=True):
"""
_id is from the iders function (i.e. an ibeis rowid)
selects the row in that view if it exists
"""
with ut.Timer('[api_item_view] select_row_from_id(id=%r, scroll=%r, collapse=%r)' %
(_id, scroll, collapse)):
qtindex, row = view.get_row_and_qtindex_from_id(_id)
if row is not None:
if isinstance(view, QtWidgets.QTreeView):
if collapse:
view.collapseAll()
select_model = view.selectionModel()
select_flag = QtCore.QItemSelectionModel.ClearAndSelect
#select_flag = QtCore.QItemSelectionModel.Select
#select_flag = QtCore.QItemSelectionModel.NoUpdate
with ut.Timer('[api_item_view] selecting name. qtindex=%r' % (qtindex,)):
select_model.select(qtindex, select_flag)
with ut.Timer('[api_item_view] expanding'):
view.setExpanded(qtindex, True)
else:
# For Table Views
view.selectRow(row)
# Scroll to selection
if scroll:
with ut.Timer('scrolling'):
view.scrollTo(qtindex)
return row
return None
如果用户手动滚动到有问题的行,则此功能有效。然而,如果用户没有看到特定的行,这个函数只是滚动回到 View 的顶部。
最佳答案
这里的答案可能为时已晚,但也许它在未来仍会对某些人有益。
下面可以找到一个列表模型的工作示例,其中包含 canFetchMore
和 fetchMore
方法 + 带有几个自定义方法的 View :
示例中的 QMainWindow
子类有一个计时器,用于重复调用上述方法中的第一个,每次都强制将另一批项目从模型加载到 View 中。在较小的时间间隔内批量加载项目可以避免完全阻塞 UI 线程,并且能够编辑目前加载的项目而几乎没有延迟。该示例包含一个进度条,显示到目前为止已加载的部分项目。
QMainWindow
子类还有一个旋转框,它允许选择特定的行显示在 View 中。如果已经从模型中获取了相应的项目,则 View 会简单地滚动到它。否则,它首先从模型中以同步方式(即 UI 阻塞方式)获取该行的项目。
这是解决方案的完整代码,使用 python 3.5.2 和 PyQt5 进行了测试:
import sys
from PyQt5 import QtWidgets, QtCore
class DelayedFetchingListModel(QtCore.QAbstractListModel):
def __init__(self, batch_size=100, max_num_nodes=1000):
QtCore.QAbstractListModel.__init__(self)
self.batch_size = batch_size
self.nodes = []
for i in range(0, self.batch_size):
self.nodes.append('node ' + str(i))
self.max_num_nodes = max(self.batch_size, max_num_nodes)
def flags(self, index):
if not index.isValid():
return QtCore.Qt.ItemIsEnabled
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable;
def rowCount(self, index):
if index.isValid():
return 0
return len(self.nodes)
def data(self, index, role):
if not index.isValid():
return None
if role != QtCore.Qt.DisplayRole:
return None
row = index.row()
if row < 0 or row >= len(self.nodes):
return None
else:
return self.nodes[row]
def setData(self, index, value, role):
if not index.isValid():
return False
if role != QtCore.Qt.EditRole:
return False
row = index.row()
if row < 0 or row >= len(self.nodes):
return False
self.nodes[row] = value
self.dataChanged.emit(index, index)
return True
def headerData(self, section, orientation, role):
if section != QtCore.Qt.Horizontal:
return None
if section != 0:
return None
if role != QtCore.Qt.DisplayRole:
return None
return 'node'
def canFetchMore(self, index):
if index.isValid():
return False
return (len(self.nodes) < self.max_num_nodes)
def fetchMore(self, index):
if index.isValid():
return
current_len = len(self.nodes)
target_len = min(current_len + self.batch_size, self.max_num_nodes)
self.beginInsertRows(index, current_len, target_len - 1)
for i in range(current_len, target_len):
self.nodes.append('node ' + str(i))
self.endInsertRows()
class ListView(QtWidgets.QListView):
def __init__(self, parent=None):
QtWidgets.QListView.__init__(self, parent)
def jumpToRow(self, row):
model = self.model()
if model == None:
return False
num_rows = model.rowCount()
while(row >= num_rows):
res = fetchMoreRows(QtCore.QModelIndex())
if res == False:
return False
num_rows = model.rowCount()
index = model.index(row, 0, QtCore.QModelIndex())
self.scrollTo(index, QtCore.QAbstractItemView.PositionAtCenter)
return True
def fetchMoreRows(self, index):
model = self.model()
if model == None:
return False
if not model.canFetchMore(index):
return False
model.fetchMore(index)
return True
class MainForm(QtWidgets.QMainWindow):
def __init__(self, parent=None):
QtWidgets.QMainWindow.__init__(self, parent)
# Setup the model
self.max_num_nodes = 10000
self.batch_size = 100
self.model = DelayedFetchingListModel(batch_size=self.batch_size, max_num_nodes=self.max_num_nodes)
# Setup the view
self.view = ListView()
self.view.setModel(self.model)
# Update the currently selected row in the spinbox
self.view.selectionModel().currentChanged.connect(self.onCurrentItemChanged)
# Select the first row in the model
index = self.model.index(0, 0, QtCore.QModelIndex())
self.view.selectionModel().clearSelection()
self.view.selectionModel().select(index, QtCore.QItemSelectionModel.Select)
# Setup the spinbox
self.spinBox = QtWidgets.QSpinBox()
self.spinBox.setMinimum(0)
self.spinBox.setMaximum(self.max_num_nodes-1)
self.spinBox.setSingleStep(1)
self.spinBox.valueChanged.connect(self.onSpinBoxNewValue)
# Setup the progress bar showing the status of model data loading
self.progressBar = QtWidgets.QProgressBar()
self.progressBar.setRange(0, self.max_num_nodes)
self.progressBar.setValue(0)
self.progressBar.valueChanged.connect(self.onProgressBarValueChanged)
# Add status bar but initially hidden, will only show it if there's something to say
self.statusBar = QtWidgets.QStatusBar()
self.statusBar.hide()
# Collect all this stuff into a vertical layout
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.view)
self.layout.addWidget(self.spinBox)
self.layout.addWidget(self.progressBar)
self.layout.addWidget(self.statusBar)
self.window = QtWidgets.QWidget()
self.window.setLayout(self.layout)
self.setCentralWidget(self.window)
# Setup timer to fetch more data from the model over small time intervals
self.timer = QtCore.QBasicTimer()
self.timerPeriod = 1000
self.timer.start(self.timerPeriod, self)
def onCurrentItemChanged(self, current, previous):
if not current.isValid():
return
row = current.row()
self.spinBox.setValue(row)
def onSpinBoxNewValue(self, value):
try:
value_int = int(value)
except ValueError:
return
num_rows = self.model.rowCount(QtCore.QModelIndex())
if value_int >= num_rows:
# There is no such row within the model yet, trying to fetch more
while(True):
res = self.view.fetchMoreRows(QtCore.QModelIndex())
if res == False:
# We shouldn't really get here in this example since out
# spinbox's range is limited by exactly the number of items
# possible to fetch but generally it's a good idea to handle
# cases like this, when someone requests more rows than
# the model has
self.statusBar.show()
self.statusBar.showMessage("Can't jump to row %d, the model has only %d rows" % (value_int, self.model.rowCount(QtCore.QModelIndex())))
return
num_rows = self.model.rowCount(QtCore.QModelIndex())
if value_int < num_rows:
break;
if num_rows < self.max_num_nodes:
# If there are still items to fetch more, check if we need to update the progress bar
if self.progressBar.value() < value_int:
self.progressBar.setValue(value_int)
elif num_rows == self.max_num_nodes:
# All items are loaded, nothing to fetch more -> no need for the progress bar
self.progressBar.hide()
# Update the selection accordingly with the new row and scroll to it
index = self.model.index(value_int, 0, QtCore.QModelIndex())
selectionModel = self.view.selectionModel()
selectionModel.clearSelection()
selectionModel.select(index, QtCore.QItemSelectionModel.Select)
self.view.scrollTo(index, QtWidgets.QAbstractItemView.PositionAtCenter)
# Ensure the status bar is hidden now
self.statusBar.hide()
def timerEvent(self, event):
res = self.view.fetchMoreRows(QtCore.QModelIndex())
if res == False:
self.timer.stop()
else:
self.progressBar.setValue(self.model.rowCount(QtCore.QModelIndex()))
if not self.timer.isActive():
self.timer.start(self.timerPeriod, self)
def onProgressBarValueChanged(self, value):
if value >= self.max_num_nodes:
self.progressBar.hide()
def main():
app = QtWidgets.QApplication(sys.argv)
form = MainForm()
form.show()
app.exec_()
if __name__ == '__main__':
main()
我还想指出的另一件事是,此示例要求 fetchMore
方法同步执行其工作。但在更复杂的方法中,fetchMore
实际上不必这样做。如果您的模型从数据库加载其项目,那么在 UI 线程中与数据库同步对话将不是一个好主意。相反,fetchMore
实现可以启动信号/槽通信的异步序列,其中某些对象处理与发生在某些后台线程中的数据库的通信。
关于python - PyQt4 强制 View 从 QAbstractItemModel 获取更多,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38506808/
我现在已经用 PyQt 做了几个项目,而且我越来越熟悉 Qt 采用的模型/ View 思想流派。我已经将它用于列表和表格 View 之类的东西,它们背后有一个自定义模型来显示和操作数据。我使用委托(d
import sys from PyQt4.QtCore import * from PyQt4.QtGui import * class MainWindow(QMainWindow):
使用下面的示例代码(受 here 的严重影响),右键单击上下文菜单并没有真正正确对齐。 从屏幕截图中可以看出,生成的菜单在鼠标光标上方相当多的位置。我希望菜单的左上角与鼠标指针完全对齐。 有没有办法对
所以我创建了一个自定义上下文菜单,但我想根据某些值将树小部件的某些行中的某些项目灰显。如何禁用菜单上的项目? myUI.setContextMenuPolicy( Qt.CustomContextMe
是否可以禁用 QComboBox在 pyqt 中,就像我们可以在 Win Forms(C#) 中那样做,因为我在 QComboBox 中找不到任何选项手动的。我想启用 QcomboBox仅当管理员登录
我想将 QComboBox 与元组中的“键”和“值”一起使用,该元组类似于 django 模型中使用的元组。例如,我对一个人的性别有以下结构。 SEX_CHOICES = (('M', 'Male')
是否可以让 Altair 或 Vega(-Lite) 渲染到 PyQt 小部件,类似于支持多个后端的 Matplotlib?我知道我可以使用 Qt WebView 小部件来呈现带有 Vega 嵌入的网
在下面的示例代码中(受 here 的影响很大),我希望选择单击单元格的整行而不是单个单元格。如何更改代码以合并它? import re import operator import os import
我正在尝试禁用关闭“x”按钮,并且我认为通过将 DockWidgetFeature 设置为仅可移动和可 float 即可工作。 def CreateDockWidget (self): Pan
我已经按照 Yasin Uludag 的一些有用的在线教程来尝试使用 PyQt(或者更确切地说是 PySide)来创建一个简单的 TreeView ,但是我在使用工具提示时遇到了问题。在以下代码中,工
我正在尝试创建一个场景,我需要从 mousePressEvent 位置画线到最新的鼠标 moveposition 这意味着我需要调用 paintEvent 来自 mousePressEvent ,这可
是Python 3的组合和 PyQt 4受到推崇的?有没有其他选择? 最佳答案 我不明白为什么不,有一个 version available对于正常工作的 Python 3,如果你真的需要 Qt,唯一
我正在尝试显示从二进制文件中读取的图像数据(我编写了用于从文件中检索此数据并将其存储为图像以供 QImage() 使用的代码)。我想做的是将 slider 连接到图形 View 小部件,以便当您移动
我已经准备了很多关于如何在 python 和 pyqt 中将多个信号连接到同一个事件处理程序的帖子。例如,将多个按钮或组合框连接到同一功能。 许多示例展示了如何使用 QSignalMapper 执行此
我有一个 PyQt 主窗口,当用户按下某个按钮时,我需要从中获取一串用户输入。 这是我的用户输入窗口代码: class InputDialog(QtGui.QDialog): ''' t
编辑: 以下 buildout.cfg 用于构建 Qt、PyQt 和 SIP [buildout] parts = pyqt [pyqt] recipe = zc.recipe.cmmi ur
我目前正在开发一个应用程序,该应用程序可以使用 PyQt 访问 sqlalchemy 数据库并将其内容显示到 TableView 或其他一些小部件中。现在为了简单起见,我们只说这是一个电话簿,上面有姓
使用我现在拥有的代码,我可以成功地播放文件中的 .mp3 数据。但是我需要使用 QtCore.QBuffer(不是来自文件)播放相同的数据。当我使用文档的示例时,它会出现意外类型的 QBuffer 错
安装 sip 后,我在尝试安装 PyQt-x11-gpl-4.11 时不断收到这个可爱的错误消息。 mycommandline$ python configure-ng.py --verbose Qu
我正在为一个项目使用 PyQt。但并非突然间我收到一个错误: QPixmap: It is not safe to use pixmaps outside the GUI thread in PyQt
我是一名优秀的程序员,十分优秀!