我有一个 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/
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>
我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R
我是rails的新手,想在form字段上应用验证。myviewsnew.html.erb.....模拟.rbclassSimulation{:in=>1..25,:message=>'Therowmustbebetween1and25'}end模拟Controller.rbclassSimulationsController我想检查模型类中row字段的整数范围,如果不在范围内则返回错误信息。我可以检查上面代码的范围,但无法返回错误消息提前致谢 最佳答案 关键是您使用的是模型表单,一种显示ActiveRecord模型实例属性的表单。c
有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url
我有一个存储主机名的Ruby数组server_names。如果我打印出来,它看起来像这样:["hostname.abc.com","hostname2.abc.com","hostname3.abc.com"]相当标准。我想要做的是获取这些服务器的IP(可能将它们存储在另一个变量中)。看起来IPSocket类可以做到这一点,但我不确定如何使用IPSocket类遍历它。如果它只是尝试像这样打印出IP:server_names.eachdo|name|IPSocket::getaddress(name)pnameend它提示我没有提供服务器名称。这是语法问题还是我没有正确使用类?输出:ge
我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c
我安装了ruby版本管理器,并将RVM安装的ruby实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby。有没有办法让emacs像shell一样尊重ruby的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el
假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit