ich habe all meinen Mut zusammengefasst und mir das Thema fetchmore angenommen. Dazu gibt es einige Beispiele, die in von C++ in PyQt dargestellt werden: fetchmore.py, editabletreemodel und simpletreemodel.
In Anlehnung dieser Beispiele habe ich versucht die Logik zu verfolgen und diese umzusetzen. Mir ist aufgefallen, dass in all diesen Beispielen die QAbstractListModel()-Klasse implementiert werden. Da QStandardItemModel() von QAbstractListModel() erbt, habe ich mir gedacht, ich implementiere einfach QStandardItemModel(). Mein Beispiel, welches im Anschluss folgt, macht momentan noch nicht das was es soll. Ich möchte quasi den bisherigen Stand zeigen, wie weit ich gekommen bin und wo ich noch feststecke. Das Beispielprogramm ist lauffähig.
Stand der Dinge
In der StandardItemModel()-Klasse habe ich drei Methoden re-implementiert: canFetchMore(), fetchMore() und appendRow(). Die letzte Methode habe ich deshalb implementiert, weil ich die einzelnen Items in die Liste speichern möchte. Die die generierten Daten werden über Multithreading in das Model geladen - schließlich möchte ich nicht, dass die GUI »einfriert«.
Fazit
Die Daten werden leider noch nicht in Stapeln (Batch) geladen. Mir fehlt an dieser Stelle der Gedankenanstoß, wie ich jetzt vorgehen soll. Denn im TaskWorker(), welcher später zum QThread hinzugefügt wird, sehen wir, dass in der start_worker()-Methode, einen simulate_query()-Generator erstellt, damit ich einen Generator erhalte. In diesem Generator sollen später die Daten aus der Datenbank geholt werden. Der einfachhaltshalber habe ich für die Datenbank-Simulation einen xrange() eingesetzt, die 100 Daten generiert.
Was mache ich hier gedanklich falsch?
Code: Alles auswählen
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from sys import exit, argv
from os import path
from PyQt4.QtCore import QTimer, QObject, pyqtSignal, \
QThread, Qt, QModelIndex, QAbstractItemModel
from PyQt4.QtGui import QDialog, QLabel, QPushButton, \
QApplication, QVBoxLayout, QTreeWidget, QTreeWidgetItem, \
QTreeView, QStandardItem, QStandardItemModel, qApp
def output(text):
print "Message: {text}".format(text = text)
return
def update_log(number):
print "{number} items added".format(number = number)
return
class StandardItemModel(QStandardItemModel):
'''
Re-implement QStandardItemModel for large data sets. This
subclassed model will add items in batches, and preferably
only when the items are needed by the view
(i.e., when they are visible in the view
'''
number_populated = pyqtSignal(int)
def __init__(self, parent = None):
QStandardItemModel.__init__(self, parent)
self.item_count = 0
self.item_list = []
# canFetchMore() and fetchMore()
# are called by the item view when it needs more items.
def canFetchMore(self, index):
return self.item_count < len(self.item_list)
def fetchMore(self, index):
remainder = len(self.item_list) - self.item_count
items_to_fetch = min(5, remainder)
self.beginInsertRows(QModelIndex(), self.item_count,
self.item_count + items_to_fetch)
self.item_count += items_to_fetch
self.endInsertRows()
self.number_populated.emit(items_to_fetch)
def appendRow(self, item):
# Reimplementing the appendRow() method, for storing items in a list
self.item_list.append(item)
QStandardItemModel.appendRow(self, item)
class TaskWorker(QObject):
notify_progress = pyqtSignal(str)
notify_item = pyqtSignal(object)
finish_progress = pyqtSignal()
fire_label = pyqtSignal(int)
def __init__(self, parent=None):
QObject.__init__(self, parent)
self.item_counter = 0
def start_worker(self):
self.element = self.simulate_query()
self.timer = QTimer()
self.timer.setSingleShot(False)
self.timer.setInterval(1)
self.timer.timeout.connect(self.increment)
self.timer.start()
def simulate_query(self):
for name in xrange(100):
yield name
def increment(self):
try:
self.notify_item.emit(next(self.element))
self.item_counter += 1
self.fire_label.emit(self.item_counter)
except StopIteration:
self.notify_progress.emit('Break the loop by raising StopIteration')
self.finish_progress.emit()
self.timer.stop()
return
def stop(self):
self.notify_progress.emit('Stop the loop')
self.timer.stop()
return
class MyCustomDialog(QDialog):
finish = pyqtSignal()
def __init__(self, parent=None):
QDialog.__init__(self, parent)
self._standard_item_model = None
self.tree = QTreeView(self)
self.label = QLabel(self)
self.pushButton_start = QPushButton("Start", self)
self.pushButton_stopp = QPushButton("Stopp", self)
self.pushButton_close = QPushButton("Close", self)
layout = QVBoxLayout(self)
layout.addWidget(self.label)
layout.addWidget(self.tree)
layout.addWidget(self.pushButton_start)
layout.addWidget(self.pushButton_stopp)
layout.addWidget(self.pushButton_close)
self.pushButton_start.clicked.connect(self.on_start)
self.pushButton_stopp.clicked.connect(self.on_finish)
self.pushButton_close.clicked.connect(self.close)
self.standard_item_model()
self.tree.setModel(self._standard_item_model)
self.tree.sortByColumn(1, Qt.AscendingOrder)
def populate_model(self, model = None, item = None):
two_columns_item = [QStandardItem(str(item))]
model.appendRow(two_columns_item)
return
def standard_item_model(self, category=None, rows = None, columns = None, parent = None):
if not self._standard_item_model is None:
# When model already exists only clear the model for adding new items
self._standard_item_model.clear()
else:
# The model doesn't exists, create a new one
self._standard_item_model = StandardItemModel()
self._standard_item_model.number_populated.connect(update_log)
def on_label(self, i):
self.label.setText("Result: {}".format(i))
return
def on_start(self):
self.label.clear()
self.standard_item_model(rows = 0, columns = 3, parent = self)
task_thread = QThread(self)
task_thread.work = TaskWorker()
task_thread.work.moveToThread(task_thread)
task_thread.work.fire_label.connect(self.on_label)
task_thread.work.notify_progress.connect(output)
task_thread.work.notify_item.connect(lambda item:
self.populate_model(item = item, model = self._standard_item_model))
task_thread.work.finish_progress.connect(task_thread.quit)
self.finish.connect(task_thread.work.stop)
task_thread.started.connect(task_thread.work.start_worker)
task_thread.finished.connect(task_thread.deleteLater)
task_thread.start()
return
def on_finish(self):
self.finish.emit()
return
def main():
app = QApplication(argv)
window = MyCustomDialog()
window.resize(600, 400)
window.show()
exit(app.exec_())
if __name__ == "__main__":
main()