QStandardItemModel: Inkrementelles nachladen von Datensätzen

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Benutzeravatar
Sophus
User
Beiträge: 1031
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

QStandardItemModel: Inkrementelles nachladen von Datensätzen

Beitragvon Sophus » Sonntag 12. November 2017, 23:38

Hallo Leute,

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?

  1. #!/usr/bin/env python
  2. #-*- coding:utf-8 -*-
  3. from sys import exit, argv
  4. from os import path
  5.  
  6. from PyQt4.QtCore import QTimer, QObject, pyqtSignal, \
  7.      QThread, Qt, QModelIndex, QAbstractItemModel
  8.  
  9. from PyQt4.QtGui import QDialog, QLabel, QPushButton, \
  10.      QApplication, QVBoxLayout, QTreeWidget, QTreeWidgetItem, \
  11.      QTreeView, QStandardItem, QStandardItemModel, qApp
  12.  
  13. def output(text):
  14.     print "Message: {text}".format(text = text)
  15.     return
  16.  
  17. def update_log(number):
  18.     print "{number} items added".format(number = number)
  19.     return
  20.    
  21. class StandardItemModel(QStandardItemModel):
  22.     '''
  23.        Re-implement QStandardItemModel for large data sets. This
  24.        subclassed model will add items in batches, and preferably
  25.        only when the items are needed by the view
  26.        (i.e., when they are visible in the view
  27.    '''
  28.     number_populated = pyqtSignal(int)
  29.    
  30.     def __init__(self, parent = None):
  31.         QStandardItemModel.__init__(self, parent)
  32.        
  33.         self.item_count = 0    
  34.         self.item_list = []
  35.  
  36.     #   canFetchMore() and fetchMore()
  37.     #   are called by the item view when it needs more items.
  38.     def canFetchMore(self, index):
  39.         return self.item_count < len(self.item_list)        
  40.  
  41.     def fetchMore(self, index):
  42.         remainder = len(self.item_list) - self.item_count
  43.         items_to_fetch = min(5, remainder)
  44.  
  45.         self.beginInsertRows(QModelIndex(), self.item_count,
  46.                 self.item_count + items_to_fetch)
  47.  
  48.         self.item_count += items_to_fetch
  49.  
  50.         self.endInsertRows()
  51.  
  52.         self.number_populated.emit(items_to_fetch)
  53.  
  54.     def appendRow(self, item):
  55.         #   Reimplementing the appendRow() method, for storing items in a list
  56.    
  57.         self.item_list.append(item)
  58.        
  59.         QStandardItemModel.appendRow(self, item)
  60.  
  61.    
  62. class TaskWorker(QObject):
  63.  
  64.     notify_progress = pyqtSignal(str)
  65.     notify_item = pyqtSignal(object)
  66.     finish_progress = pyqtSignal()
  67.     fire_label = pyqtSignal(int)
  68.    
  69.     def __init__(self, parent=None):
  70.         QObject.__init__(self, parent)
  71.  
  72.         self.item_counter = 0
  73.  
  74.     def start_worker(self):
  75.  
  76.         self.element = self.simulate_query()
  77.  
  78.         self.timer = QTimer()
  79.         self.timer.setSingleShot(False)
  80.         self.timer.setInterval(1)
  81.         self.timer.timeout.connect(self.increment)
  82.         self.timer.start()
  83.  
  84.     def simulate_query(self):
  85.                
  86.         for name in xrange(100):          
  87.             yield name
  88.            
  89.     def increment(self):
  90.  
  91.         try:
  92.             self.notify_item.emit(next(self.element))
  93.             self.item_counter += 1
  94.             self.fire_label.emit(self.item_counter)
  95.  
  96.         except StopIteration:
  97.  
  98.             self.notify_progress.emit('Break the loop by raising StopIteration')
  99.             self.finish_progress.emit()
  100.             self.timer.stop()
  101.         return
  102.        
  103.     def stop(self):
  104.         self.notify_progress.emit('Stop the loop')
  105.         self.timer.stop()
  106.         return
  107.  
  108.        
  109. class MyCustomDialog(QDialog):
  110.  
  111.     finish = pyqtSignal()
  112.  
  113.     def __init__(self, parent=None):
  114.         QDialog.__init__(self, parent)
  115.  
  116.         self._standard_item_model = None
  117.        
  118.         self.tree = QTreeView(self)
  119.         self.label = QLabel(self)
  120.        
  121.         self.pushButton_start = QPushButton("Start", self)
  122.         self.pushButton_stopp = QPushButton("Stopp", self)
  123.         self.pushButton_close = QPushButton("Close", self)
  124.  
  125.         layout = QVBoxLayout(self)
  126.         layout.addWidget(self.label)
  127.         layout.addWidget(self.tree)
  128.         layout.addWidget(self.pushButton_start)
  129.         layout.addWidget(self.pushButton_stopp)
  130.         layout.addWidget(self.pushButton_close)
  131.  
  132.         self.pushButton_start.clicked.connect(self.on_start)
  133.         self.pushButton_stopp.clicked.connect(self.on_finish)
  134.         self.pushButton_close.clicked.connect(self.close)
  135.  
  136.         self.standard_item_model()
  137.  
  138.         self.tree.setModel(self._standard_item_model)
  139.         self.tree.sortByColumn(1, Qt.AscendingOrder)
  140.  
  141.     def populate_model(self, model = None, item = None):
  142.         two_columns_item = [QStandardItem(str(item))]
  143.         model.appendRow(two_columns_item)
  144.         return
  145.        
  146.     def standard_item_model(self, category=None, rows = None, columns = None, parent = None):
  147.  
  148.         if not self._standard_item_model is None:
  149.             #   When model already exists only clear the model for adding new items
  150.             self._standard_item_model.clear()
  151.         else:
  152.             #   The model doesn't exists, create a new one
  153.             self._standard_item_model = StandardItemModel()
  154.             self._standard_item_model.number_populated.connect(update_log)
  155.        
  156.     def on_label(self, i):
  157.          self.label.setText("Result: {}".format(i))
  158.          return
  159.        
  160.     def on_start(self):
  161.  
  162.         self.label.clear()
  163.  
  164.         self.standard_item_model(rows = 0, columns = 3, parent = self)
  165.  
  166.         task_thread = QThread(self)
  167.         task_thread.work = TaskWorker()
  168.         task_thread.work.moveToThread(task_thread)
  169.  
  170.         task_thread.work.fire_label.connect(self.on_label)
  171.         task_thread.work.notify_progress.connect(output)
  172.  
  173.         task_thread.work.notify_item.connect(lambda item:
  174.                                              self.populate_model(item = item, model = self._standard_item_model))
  175.  
  176.         task_thread.work.finish_progress.connect(task_thread.quit)
  177.         self.finish.connect(task_thread.work.stop)
  178.  
  179.         task_thread.started.connect(task_thread.work.start_worker)
  180.         task_thread.finished.connect(task_thread.deleteLater)
  181.         task_thread.start()
  182.         return
  183.  
  184.     def on_finish(self):
  185.          self.finish.emit()
  186.          return
  187.      
  188. def main():
  189.     app = QApplication(argv)
  190.     window = MyCustomDialog()
  191.     window.resize(600, 400)
  192.     window.show()
  193.     exit(app.exec_())
  194.  
  195. if __name__ == "__main__":
  196.     main()
Benutzeravatar
Sophus
User
Beiträge: 1031
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: QStandardItemModel: Inkrementelles nachladen von Datensätzen

Beitragvon Sophus » Montag 13. November 2017, 01:04

Hallo Leute, ich habe mein Beispiel-Programm im Import etwas aufgeräumt und eine Methode hinzugefügt, die ich vergessen habe. Es handelt sich hierbei um die rowCount()-Methode der implementierten StandardItemModel()-Klasse. Allerdings verhält sich das Programm immer noch nicht wie gewünscht.

  1. #!/usr/bin/env python
  2. #-*- coding:utf-8 -*-
  3. from sys import exit, argv
  4. from os import path
  5.  
  6. from PyQt4.QtCore import QTimer, QObject, pyqtSignal, \
  7.      QThread, Qt, QModelIndex
  8.  
  9. from PyQt4.QtGui import QDialog, QLabel, QPushButton, \
  10.      QApplication, QVBoxLayout, QTreeView, QStandardItem, \
  11.      QStandardItemModel, qApp
  12.  
  13. def output(text):
  14.     print "Message: {text}".format(text = text)
  15.     return
  16.  
  17. def update_log(number):
  18.     print "{number} items added".format(number = number)
  19.     return
  20.    
  21. class StandardItemModel(QStandardItemModel):
  22.     '''
  23.        Re-implement QStandardItemModel for large data sets. This
  24.        subclassed model will add items in batches, and preferably
  25.        only when the items are needed by the view
  26.        (i.e., when they are visible in the view
  27.    '''
  28.     number_populated = pyqtSignal(int)
  29.    
  30.     def __init__(self, parent = None):
  31.         QStandardItemModel.__init__(self, parent)
  32.        
  33.         self.item_count = 0    
  34.         self.item_list = []
  35.  
  36.     def rowCount(self, parent = QModelIndex()):
  37.         return self.item_count
  38.  
  39.     #   canFetchMore() and fetchMore()
  40.     #   are called by the item view when it needs more items.
  41.     def canFetchMore(self, index):
  42.         return self.item_count < len(self.item_list)        
  43.  
  44.     def fetchMore(self, index):
  45.         remainder = len(self.item_list) - self.item_count
  46.         items_to_fetch = min(20, remainder)
  47.  
  48.         self.beginInsertRows(QModelIndex(), self.item_count,
  49.                 self.item_count + items_to_fetch)
  50.  
  51.         self.item_count += items_to_fetch
  52.  
  53.         self.endInsertRows()
  54.  
  55.         self.number_populated.emit(items_to_fetch)
  56.  
  57.     def appendRow(self, item):
  58.         #   Reimplementing the appendRow() method, for storing items in a list
  59.    
  60.         self.item_list.append(item)
  61.        
  62.         QStandardItemModel.appendRow(self, item)
  63.  
  64.    
  65. class TaskWorker(QObject):
  66.  
  67.     notify_progress = pyqtSignal(str)
  68.     notify_item = pyqtSignal(object)
  69.     finish_progress = pyqtSignal()
  70.     fire_label = pyqtSignal(int)
  71.    
  72.     def __init__(self, parent=None):
  73.         QObject.__init__(self, parent)
  74.  
  75.         self.item_counter = 0
  76.  
  77.     def start_worker(self):
  78.  
  79.         self.element = self.simulate_query()
  80.  
  81.         self.timer = QTimer()
  82.         self.timer.setSingleShot(False)
  83.         self.timer.setInterval(1)
  84.         self.timer.timeout.connect(self.increment)
  85.         self.timer.start()
  86.  
  87.     def simulate_query(self):
  88.                
  89.         for name in xrange(500):          
  90.             yield name
  91.            
  92.     def increment(self):
  93.  
  94.         try:
  95.             self.notify_item.emit(next(self.element))
  96.             self.item_counter += 1
  97.             self.fire_label.emit(self.item_counter)
  98.  
  99.         except StopIteration:
  100.  
  101.             self.notify_progress.emit('Break the loop by raising StopIteration')
  102.             self.finish_progress.emit()
  103.             self.timer.stop()
  104.         return
  105.        
  106.     def stop(self):
  107.         self.notify_progress.emit('Stop the loop')
  108.         self.timer.stop()
  109.         return
  110.  
  111.        
  112. class MyCustomDialog(QDialog):
  113.  
  114.     finish = pyqtSignal()
  115.  
  116.     def __init__(self, parent=None):
  117.         QDialog.__init__(self, parent)
  118.  
  119.         self._standard_item_model = None
  120.        
  121.         self.tree = QTreeView(self)
  122.         self.label = QLabel(self)
  123.        
  124.         self.pushButton_start = QPushButton("Start", self)
  125.         self.pushButton_stopp = QPushButton("Stopp", self)
  126.         self.pushButton_close = QPushButton("Close", self)
  127.  
  128.         layout = QVBoxLayout(self)
  129.         layout.addWidget(self.label)
  130.         layout.addWidget(self.tree)
  131.         layout.addWidget(self.pushButton_start)
  132.         layout.addWidget(self.pushButton_stopp)
  133.         layout.addWidget(self.pushButton_close)
  134.  
  135.         self.pushButton_start.clicked.connect(self.on_start)
  136.         self.pushButton_stopp.clicked.connect(self.on_finish)
  137.         self.pushButton_close.clicked.connect(self.close)
  138.  
  139.         self.standard_item_model()
  140.  
  141.         self.tree.setModel(self._standard_item_model)
  142.         self.tree.sortByColumn(1, Qt.AscendingOrder)
  143.  
  144.     def populate_model(self, model = None, item = None):
  145.         two_columns_item = [QStandardItem(str(item))]
  146.         model.appendRow(two_columns_item)
  147.         return
  148.        
  149.     def standard_item_model(self, category=None, rows = None, columns = None, parent = None):
  150.  
  151.         if not self._standard_item_model is None:
  152.             #   When model already exists only clear the model for adding new items
  153.             self._standard_item_model.clear()
  154.         else:
  155.             #   The model doesn't exists, create a new one
  156.             self._standard_item_model = StandardItemModel()
  157.             self._standard_item_model.number_populated.connect(update_log)
  158.        
  159.     def on_label(self, i):
  160.          self.label.setText("Result: {}".format(i))
  161.          return
  162.        
  163.     def on_start(self):
  164.  
  165.         self.label.clear()
  166.  
  167.         self.standard_item_model(rows = 0, columns = 3, parent = self)
  168.  
  169.         task_thread = QThread(self)
  170.         task_thread.work = TaskWorker()
  171.         task_thread.work.moveToThread(task_thread)
  172.  
  173.         task_thread.work.fire_label.connect(self.on_label)
  174.         task_thread.work.notify_progress.connect(output)
  175.  
  176.         task_thread.work.notify_item.connect(lambda item:
  177.                                              self.populate_model(item = item, model = self._standard_item_model))
  178.  
  179.         task_thread.work.finish_progress.connect(task_thread.quit)
  180.         self.finish.connect(task_thread.work.stop)
  181.  
  182.         task_thread.started.connect(task_thread.work.start_worker)
  183.         task_thread.finished.connect(task_thread.deleteLater)
  184.         task_thread.start()
  185.         return
  186.  
  187.     def on_finish(self):
  188.          self.finish.emit()
  189.          return
  190.      
  191. def main():
  192.     app = QApplication(argv)
  193.     window = MyCustomDialog()
  194.     window.resize(600, 400)
  195.     window.show()
  196.     exit(app.exec_())
  197.  
  198. if __name__ == "__main__":
  199.     main()
Benutzeravatar
__deets__
User
Beiträge: 1331
Registriert: Mittwoch 14. Oktober 2015, 14:29

Re: QStandardItemModel: Inkrementelles nachladen von Datensätzen

Beitragvon __deets__ » Montag 13. November 2017, 01:36

Du musst schon von QAbstractItemModel ableiten. Sonst kommen sich Standard- und deine eigene Datenhaltung in die Quere.

Des Weiteren ist deine Logik verquer: im Original Beispiel wird VOR dem setzen des Modells auf einem view schon der erste Schwung Daten gelesen. Dadurch ist canFetchMore True. Das ist bei dir False, und dein Modell ist eine Bleiente. Subsequentes hinzufügen von Daten durch appendRow ändert das nicht.

Eigentlich wäre es besser auf das Thread-gerödel zu verzichten, und den jeweils nächsten batch Daten dann bei Bedarf nachzuladen. Nur so wird ja überhaupt dein Ziel erreicht, nicht alles an Daten in den Speicher zu ziehen, sondern das vom View aus zu bestimmen.... wenn du im Hintergrund eh die ganze DB in den Speicher lädst, brauchst du dir den Aufriss erst gar nicht machen.

Wenn es aber erstmal darum geht das zum laufen zu bekommen (zusätzlich zur Ableitung die anders muss!), musst du in appendRow das Signal schmeißen, mit welchem dem view signalisiert wird “Daten haben sich geändert”. Ich bin jetzt zu faul das nachzulesen, aber das erkennt man.
Benutzeravatar
Sophus
User
Beiträge: 1031
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: QStandardItemModel: Inkrementelles nachladen von Datensätzen

Beitragvon Sophus » Montag 13. November 2017, 03:43

@__deets__: Bist du kein Freund von Nebenläufigkeit? Ich frage nur, weil du abwertend von "Thread gerödel" redest. Ich möchte einfach nicht, dass die GUI einfriert und habe daher gehofft, dies irgendwie mit unter zu bringen. Denn was ist, wenn der Benutzer mit dem Scrollrad auf der Maus zu schnell scrollt? Dann ruckelt die GUI ganz schön. Oder was wenn man - spaßhaltshalber - die Größe der GUI verändert? Dann lädt das Model ständig nach und die Last liegt dann im Hauptthread. Ich werde mich morgen dransetzen und mich mut QAbstractItemModel auseinandersetzen.
Benutzeravatar
__deets__
User
Beiträge: 1331
Registriert: Mittwoch 14. Oktober 2015, 14:29

Re: QStandardItemModel: Inkrementelles nachladen von Datensätzen

Beitragvon __deets__ » Montag 13. November 2017, 04:07

Wenn man kann, sollte man Threads vermeiden. Sie verkomplizieren Probleme auf subtile Weise, die oft nicht verstanden und schwer zu debuggen sind.

Und meistens finden sich Lösungen auch ohne sie. Insofern: ja, ich bin da kein Freund von.

Für dein konkretes Problem zb habe ich große Schwierigkeiten mir vorzustellen, das die Zerteilung der Datenbankoperationen in kleinere Häppchen, wie sie für genau dieses Vorgehen eh gefragt ist, die Notwendigkeit für Threads nicht eliminiert.

Außerdem ist es durchaus fraglich, ob eine kurze Pause nicht erträglicher ist, als ein permanentes Update der GUI, welches zb die Scrollposition verhunzt etc. Da steht ein Ansatz dem anderen nicht überlegen gegenüber.

Und last but not least: du hast die Interaktion des Models mit dem view offensichtlich immer noch nicht völlig durchdrungen. Zb den Unterschied zwischen Standard (ist selbst eine Liste) und dem eigentlich wichtigeren abstract model (stellt nur eine Schnittstelle dar, die Daten auch in demand und transformiert darbietet). Solange du damit kämpfst, kommen dir die Thread-Geschichten oben drauf nur in die Quere.
Benutzeravatar
Sophus
User
Beiträge: 1031
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: QStandardItemModel: Inkrementelles nachladen von Datensätzen

Beitragvon Sophus » Montag 13. November 2017, 13:52

__deets__ hat geschrieben:Du musst schon von QAbstractItemModel ableiten. Sonst kommen sich Standard- und deine eigene Datenhaltung in die Quere.

Kannst du mir mal erklären, wieso? Denn in meinem derzeitigen Projekt arbeite ich ja auch mit QStandardItemModel() und dort klappt bisher alles wunderbar. Alles wird wie gewohnt korrekt über QTreeView() ausgegeben. Was und inwiefern kann in die Quere kommen? Und dann ist mir zudem aufgefallen, dass ein QAbstractItemModel () ziemlich nackig ist, und kaum Funktionalität aufweist und man daher sehr viel zu Fuß gehen muss, indem man viele fehlende Methoden implementiert. Dagegen bringt QStandardItemModel von Haus aus schon vieles mit. Ich will keine Grundsatzdiskussion, sondern nur verstehen, wer hier wann und wo in die Quere kommt und wieso es besser ist mit AbstractItemModel() zu arbeiten. Schließlich will ich ja was lernen.
Benutzeravatar
__deets__
User
Beiträge: 1331
Registriert: Mittwoch 14. Oktober 2015, 14:29

Re: QStandardItemModel: Inkrementelles nachladen von Datensätzen

Beitragvon __deets__ » Montag 13. November 2017, 13:56

StandartItemModel hat seine eigene Liste. Du hast ein deiner Ableitung eine zweite (item_list) eingeführt, die aber völlig egal ist, weil sie nicht die data-Methode befüttert. Und es unterstützt eben genau NICHT die inkrementelle Ansammlung von Daten.
Benutzeravatar
Sophus
User
Beiträge: 1031
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: QStandardItemModel: Inkrementelles nachladen von Datensätzen

Beitragvon Sophus » Montag 13. November 2017, 14:10

Ok. Klingt plausible. Danke. Dann muss ich wohl oder übel die furchteinflössende AbstractItemModel() verwenden. Wird wohl mein erstes Mal sein.
Benutzeravatar
Sophus
User
Beiträge: 1031
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: QStandardItemModel: Inkrementelles nachladen von Datensätzen

Beitragvon Sophus » Dienstag 14. November 2017, 17:17

Hallo Leute, ich habe das Model überarbeitet. Zunächst erbt die AbstractItemModel()-Klasse von QAbstractItemModel(). In dieser Klasse sind zur Zeit sieben Methoden re-implementiert worden. Die Klasse funktioniert soweit, dass ich in der Lage bin die Kopfzeilen im QTreeView() anzeigen zu lassen. Die TreeNodeItem()-Klasse dient dazu, falls später mit Kinder-Items gearbeitet wird. Zur Zeit wird in dieser Klasse nicht viel gemacht, außer mit der Anzahl von Spalten zu arbeiten.

Das Programm ist lauffähig, jedoch nicht vollständig. Nun stecke ich gedanklich fest, und frage mich, an welche Methoden ich noch denken sollte, und noch wichtiger ist, wie ich nun mit den Daten verfahren soll? Denn im fetchmore.py-Beispiel wird in der setDirPath()-Methode der FileListModel()-Klasse mit QDir() gearbeitet. Da ich mit der Datenbank arbeite, fällt dieses Konstrukt schon mal weg. Jemand eine Idee, wie ich nun weiter verfahren sollte?

  1. # -*- coding: utf-8 -*-
  2. #!/usr/bin/env python
  3.  
  4. from sys import argv
  5.  
  6. from PyQt4.QtCore import Qt, QAbstractItemModel, QModelIndex, QVariant
  7. from PyQt4.QtGui import QDialog, QApplication, QVBoxLayout, QTreeView
  8.  
  9. class WIndow(QDialog):
  10.     def __init__(self):
  11.         #   Initialize the application
  12.  
  13.         QDialog.__init__(self)
  14.  
  15.         #   Create attributes
  16.         self.counter = 0
  17.         self.horizontal_header = ["First Column", "Second Column"]
  18.        
  19.         # Make the GUI
  20.         self.dbs_tree_view = QTreeView(self)
  21.         self.setWindowTitle('TreeView')
  22.  
  23.         # Create Layout
  24.         central_layout = QVBoxLayout(self)
  25.         central_layout.addWidget(self.dbs_tree_view)
  26.  
  27.         # The tree of databases model/view
  28.         self.dbs_tree_model = AbstractItemModel(self)
  29.         self.dbs_tree_model.setHeaderData(section = len(self.horizontal_header), value = self.horizontal_header)
  30.        
  31.         self.dbs_tree_view.setModel(self.dbs_tree_model)
  32.  
  33.     def closeEvent(self, event):
  34.         #   Handle close events.
  35.         self.close
  36.  
  37. class TreeNodeItem(object):
  38.     '''
  39.    This subclass used to return row/column data,
  40.    and keep note of it's parents and/or children.
  41.    '''
  42.     def __init__(self, item_data = None, parent = None):
  43.         #   Initialize the class
  44.  
  45.         #   Create attributes
  46.         self.parent_item = parent
  47.         self._item_data = item_data
  48.         self._child_items = []
  49.  
  50.     def __len__(self):
  51.         return len(self._child_items)
  52.  
  53.     def childCount(self):
  54.         return len(self._child_items)
  55.  
  56.     def data(self, column):
  57.         try:
  58.             return self._item_data[column]
  59.         except IndexError:
  60.             pass
  61.  
  62.     def setData(self, column, value):
  63.         try:
  64.             if column < 0 or column >= len(self._item_data):
  65.                 return False
  66.         except TypeError:
  67.             pass
  68.        
  69.         try:
  70.             self._item_data[column] = value
  71.         except (IndexError, TypeError):
  72.             pass
  73.        
  74.         return True
  75.  
  76.     def row(self):
  77.         '''
  78.            NOTICE:
  79.            =======
  80.            The position of this node in the parent's list of children.
  81.        '''
  82.  
  83.         if self.parent_item:
  84.             return self.parent_item.children.index(self)
  85.  
  86.         return 0
  87.        
  88. class AbstractItemModel(QAbstractItemModel):
  89.     """The tree of databases model."""
  90.  
  91.     def __init__(self, parent = None):
  92.         #   Initialize the class for model
  93.  
  94.         QAbstractItemModel.__init__(self, parent)
  95.  
  96.         #   Create attributes
  97.         self._root_data = None
  98.         self._root_item = None
  99.  
  100.     def flags(self, index):
  101.         """Returns the item flags for the given index. """
  102.         return Qt.ItemIsEnabled|Qt.ItemIsSelectable
  103.  
  104.  
  105.     def data(self, index, role):
  106.         """Returns the data stored under the given role for the item
  107.        referred to by the index."""
  108.  
  109.         if not index.isValid():
  110.             return QVariant()
  111.         node = self.nodeFromIndex(index)
  112.         if role == Qt.DisplayRole:
  113.             return QVariant(node.name)
  114.         else:
  115.             return QVariant()
  116.  
  117.  
  118.     def setData(self, index, value, role=Qt.DisplayRole):
  119.         """Sets the role data for the item at index to value."""
  120.  
  121.         if not index.isValid():
  122.             return False
  123.         node = self.nodeFromIndex(index)
  124.         if role == Qt.DisplayRole:
  125.             node.name = value
  126.             self.emit(SIGNAL(
  127.                 'dataChanged(QModelIndex, QModelIndex)'), index, index)
  128.             return True
  129.         return False
  130.  
  131.     def headerData(self, section = None, orientation = Qt.Horizontal, role = Qt.DisplayRole):
  132.         '''
  133.            NOTICE:
  134.            =======
  135.            Returns the data for the given role and section in the header
  136.            with the specified orientation.
  137.  
  138.            PARAMETERS:
  139.            ===========
  140.            :section        -
  141.            :orientation    -
  142.            :role           -
  143.  
  144.            RETURNS:
  145.            ========
  146.            All return values ​​are returned as QVariant for security,
  147.            because Qt expects this type
  148.        '''
  149.         #   Check if the validity of a requested section for
  150.         #   horizontal / vertical, otherwise returns `False` for invalid values
  151.         if orientation == Qt.Horizontal and role == Qt.DisplayRole:
  152.             return QVariant(self._root_item.data(section))
  153.  
  154.         if orientation == Qt.Vertical and role == Qt.DisplayRole:
  155.             return QVariant(self._root_item.data(section))
  156.  
  157.         return QVariant()
  158.  
  159.     def columnCount(self, parent = None):
  160.         '''
  161.            NOTICE:
  162.            =======
  163.            The number of columns for the children of the given index,
  164.            otherwise return zero.
  165.        '''
  166.         if parent and parent.isValid():
  167.             return parent.internalPointer().columnCount()
  168.         else:
  169.             if not self._root_data is None:
  170.                 return len(self._root_data)
  171.             else:
  172.                 return 0
  173.  
  174.    
  175.     def setHeaderData(self, section = None, orientation = Qt.Horizontal, value = None, role = Qt.DisplayRole):
  176.         '''
  177.            NOTICE:
  178.            =======
  179.            
  180.  
  181.            PARAMETERS:
  182.            ===========
  183.            :section        -
  184.            :orientation    -
  185.            :role           -
  186.  
  187.            RETURNS:
  188.            ========
  189.            
  190.        '''
  191.        
  192.         created_value_list = []
  193.        
  194.         if role != Qt.DisplayRole or orientation == Qt.Vertical:
  195.             return False
  196.         else:
  197.             #   We have to check when given value is a list or not.
  198.             #   Because, it is possible that the user only wants one column,
  199.             #   and therefore forgot to pass the header as a list.
  200.             if not value is None:
  201.                 if isinstance(value, list):
  202.                     self._root_data = [header for header in value]
  203.                     self._root_item = TreeNodeItem(item_data = self._root_data)
  204.                 else:
  205.                     created_value_list.append(value)
  206.                     self._root_data = [header for header in created_value_list]
  207.                     self._root_item = TreeNodeItem(item_data = self._root_data)
  208.              
  209.         if created_value_list:
  210.             result = self._root_item.setData(section, value)
  211.         else:
  212.             result = self._root_item.setData(section, created_value_list)
  213.        
  214.         if result:
  215.             self.headerDataChanged.emit(orientation, section, section)
  216.        
  217.            
  218.         return QAbstractItemModel.setHeaderData(self, section, orientation, value)
  219.  
  220.     def rowCount(self, parent = QModelIndex()):
  221.         """The number of rows of the given index."""
  222.         if parent.column() > 0:
  223.             return 0
  224.  
  225.         if not parent.isValid():
  226.             parent_node = self._root_item
  227.         else:
  228.             parent_node = parent.internalPointer()
  229.  
  230.         if not parent_node is None:
  231.             return parent_node.childCount()
  232.         else:
  233.             return 0
  234.  
  235.  
  236. if __name__ == "__main__":
  237.     app = QApplication(argv)
  238.     tester = WIndow()
  239.     tester.show()
  240.     app.exec_()
Benutzeravatar
__deets__
User
Beiträge: 1331
Registriert: Mittwoch 14. Oktober 2015, 14:29

Re: QStandardItemModel: Inkrementelles nachladen von Datensätzen

Beitragvon __deets__ » Mittwoch 15. November 2017, 01:30

Du benutzt da eine nodeFromIndex Methode. Die sehe ich nicht. Und warum ist das ganze plötzlich ein Baum? Vorher hat dir doch eine Liste gereicht?
Benutzeravatar
Sophus
User
Beiträge: 1031
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: QStandardItemModel: Inkrementelles nachladen von Datensätzen

Beitragvon Sophus » Mittwoch 15. November 2017, 01:40

@__deets__: Tatsache, die nodeFromIndex ()-Methode ist nirgends bekannt. Hatte ich ein Dusel, dass das Programm bisher noch nicht angeeckt ist. Das mit dem Baum. Es ist ja kein Entprodukt. Mir geht es jetzt darum, dass ich mich frage, wo ich jetzt nun ansetze. Lade ich jetzt, ähnlich wie bei dem fetchmore-Beispiel die Datensätze zunächst in eine Liste? Ich ging nämlich davon aus, dass die benötigten Datensätze Häppchenweise aus der Datenbank geladen werden, und nicht aus der Liste? Deswegen stecke ich erst einmal fest.
Benutzeravatar
__deets__
User
Beiträge: 1331
Registriert: Mittwoch 14. Oktober 2015, 14:29

Re: QStandardItemModel: Inkrementelles nachladen von Datensätzen

Beitragvon __deets__ » Mittwoch 15. November 2017, 01:58

Du solltest die schon in eine Liste laden. Du weißt ja nie, wann der view auf welches item zugreifen will. Da will man eine Cache.
Benutzeravatar
Sophus
User
Beiträge: 1031
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: QStandardItemModel: Inkrementelles nachladen von Datensätzen

Beitragvon Sophus » Mittwoch 15. November 2017, 02:07

Ok, jetzt bin ich ein Bisschen verwirrt. Welchen Unterschied ergibt sich daraus, dass die gesamten Datensätze zunächst in eine Liste geladen werden oder ob die ganzen Datensätze, wie bei meinem derzeitigen Projekt, in QStandardItemModel() geladen werden? Ob Liste oder QStandardItemModel(), in beiden Fällen werden die gesamten Datensätze in ein Container geladen. Das verwirrt mich jetzt. Ich frage mich jetzt nun, worin jetzt da der entscheidende Vorteil liegt?
Benutzeravatar
__deets__
User
Beiträge: 1331
Registriert: Mittwoch 14. Oktober 2015, 14:29

Re: QStandardItemModel: Inkrementelles nachladen von Datensätzen

Beitragvon __deets__ » Mittwoch 15. November 2017, 02:18

der Vorteil ist, das du nur das lädst, was der User anfragt. Und damit weniger lange wartest. Ich bin mir selbst noch unsicher wann man ggf items aus der Liste rauswerfen kann, das fetchmore Beispiel macht es ja nicht.
Benutzeravatar
__deets__
User
Beiträge: 1331
Registriert: Mittwoch 14. Oktober 2015, 14:29

Re: QStandardItemModel: Inkrementelles nachladen von Datensätzen

Beitragvon __deets__ » Mittwoch 15. November 2017, 02:35

Also nachdem ich da nochmal ein paar StackOverflow Artikel zu gelesen habe, muss ich sagen: das ist ein unausgegorenes Feature. Deine Uralt-Qt-Version hat einen bug, wodurch das eh immer nur für Listen, nie für Bäume gilt. Und einen Weg, Daten wegzuwerfen sehe ich nicht.

Trotzdem könnte QAbstractItemModel die Lösung sein, weil du in data erst die Daten liefern musst, und das ggf eben lazy machen kannst. Mit einem LRU-Cache darunter. Das zumindest würde ich probieren.

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder