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()
