Seite 1 von 1

[gelöst] QSortFilterProxyModel: Fehler bei Sortierung, Bug?

Verfasst: 16. April 2011 18:09
von gk_17
Hallo Leute,

ich habe ein Problem mit der Sortierung von Daten einer SQLite-Tabelle mit Hilfe von QSortFilterProxyModel.

Hier ist ein Minimalbeispiel, dass den Fehler zeigt:

main.cpp

Code: Alles auswählen

#include <QApplication>
#include <QMainWindow>
#include <QTableView>
#include <QSqlQuery>
#include <QSqlQueryModel>
#include <QSortFilterProxyModel>

int main(int argc, char *argv[])
{	    
    //create test data
	QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
	db.setDatabaseName(":memory");
	db.open();
	QSqlQuery query(QSqlDatabase::database());
	//clean up before
	query.exec("DELETE FROM testtable");
	query.exec("DROP TABLE testtable");
	//new table and some manually created rows
	query.exec("CREATE TABLE testtable(col1 INTEGER, col2 INTEGER, col3 INTEGER)");
	query.exec("INSERT INTO testtable VALUES(1234, random(), random())");
	query.exec("INSERT INTO testtable VALUES(4567, random(), random())");
	query.exec("INSERT INTO testtable VALUES(3456, random(), random())");
	query.exec("INSERT INTO testtable VALUES(2345, random(), random())");
	//300 automatically generated data rows
	for(int i = 0; i < 300; i++)
	{
		int c1 = 5000 + i;
		int c2 = rand() % 2000 + 1000;
		int c3 = rand() % 2000 + 1000;
		query.prepare("INSERT INTO testtable (col1, col2, col3) VALUES(:col1, :col2, :col3)");
		query.bindValue(":col1", c1);
		query.bindValue(":col2", c2);
		query.bindValue(":col3", c3);
		query.exec();
	}
	//a last manually data set that should appear in row 2 if sorted
	query.exec("INSERT INTO testtable VALUES(1357, random(), random())");
	
	//create table model
	QSqlQueryModel* sqlQueryModel = new QSqlQueryModel();
	sqlQueryModel->setQuery("SELECT * FROM testtable", QSqlDatabase::database());
	
	//create proxy model
	QSortFilterProxyModel* sortFilterProxyModel = new QSortFilterProxyModel();
	sortFilterProxyModel->setSourceModel(sqlQueryModel);
    sortFilterProxyModel->sort(0);
    
    //create GUI and connect with data
    QApplication app(argc, argv);
    QMainWindow *mainWindow = new QMainWindow();
    QTableView* tableView = new QTableView();
    mainWindow->setCentralWidget(tableView);
    tableView->setModel(sortFilterProxyModel);
    
    mainWindow->showMaximized();
    return app.exec();
}
test.pro

Code: Alles auswählen

SOURCES +=  main.cpp
TEMPLATE = app
LANGUAGE = C++
CONFIG += qt warn_on debug_and_release
TARGET = test
QT += sql
Zuerst wird eine SQLite-Tabelle im Arbeitsspeicher erstellt, die etwas mehr als 300 Zeilen hat und deren letzte Zeile bei Sortierung nach der ersten Spalte in Zeile 2 stehen sollte.
Diese Tabelle wird von einem QSqlQueryModel geladen. Wenn nur dieses in der QTableView angezeigt wird, sieht man die unsortierten Daten.
Ich will aber nach der ersten Spalte sortieren und erzeuge ein QSortFilterProxyModel.

Dieses zeigt die ersten 256 Zeilen auch richtig sortiert an, nur fehlt in Zeile 2 der letzte Datensatz der SQLite-Tabelle. Beim runterscrollen, kurz bevor Zeile 257 sichtbar würde, werden anscheinend Daten nachgeladen, der Scrollbalken wird kleiner und der Rest der Daten ist verfügbar. Wenn ich jetzt wieder hochscrolle, steht dort der verschwundene Datensatz an der richtigen Stelle. Die Daten scheinen also stückweise in 256-Zeilen-Häppchen geladen zu werden und es werden nur die bisher geladenen Daten sortiert.
Ist das ein Fehler? Wenn nicht, wie kann ich erreichen, dass die gesamte Tabelle vollständig geladen und richtig sortiert angezeigt wird?

Bin für jeden Hinweis dankbar!
Grüße,
Gernot

Verfasst: 16. April 2011 20:01
von Christian81
Etwas versteckt aber es steht in der Doku:

Code: Alles auswählen

int QSqlQueryModel::rowCount ( const QModelIndex & parent = QModelIndex() ) const [virtual]

Reimplemented from QAbstractItemModel::rowCount().

If the database supports returning the size of a query (see QSqlDriver::hasFeature()), the amount of rows of the current query is returned. Otherwise, returns the amount of rows currently cached on the client.

Verfasst: 17. April 2011 14:18
von gk_17
Vielen Dank!
Es funktioniert. Wenn ich die rowCount()-Methode überschreibe und die echte Tabellengröße zurückliefere (305 im Beispiel), ist das nervige Nachladen weg und die Tabelle ist von Beginn an richtig sortiert.

Verfasst: 17. April 2011 15:24
von Christian81
Ja, die Funktion ist auf den ersten Blick schon etwas verwunderlich -aber eigentlich bleibt dem Model ja nicht viel anderes übrig. Musste auch erst ein wenig rumschauen um zu kapieren was los war... :

Verfasst: 18. April 2011 23:18
von gk_17
zu früh gefreut :(
Es gibt noch mehr Probleme. Ich hab bei obigem Beispiel eine eigene Klasse MyTableModel erstellt, abgeleitet von QSqlQueryModel. In dieser habe ich die rowCount()-Methode so überschrieben, dass sie 305 (die Anzahl der Zeilen im Beispiel) zurückgibt. So weit so gut. Aber wenn ich 805 Zeilen erzeuge und die rowCount() entsprechend anpasse, sieht das Ergebnis ganz schrecklich aus. Die TableView zeigt die Daten zerstückelt an, nach einigen hundert Zeilen gibt es viele Leerzeilen mit der Zeilennummer 0, dann wieder ein paar Datenzeilen, dann wieder leere Zeilen.

Ich habe eine leise Ahnung, dass das mit canFetchMore() und fetchMore() zusammenhängt, und dass diese Methoden alle nicht funktionieren, weil der Qt-SQLite-Treiber nicht die Größe eines Querys ermitteln kann. Ich verstehe nur nicht, warum das so ist? Benutzt denn sonst niemand diese Klassen?

Sollte ich vielleicht lieber versuchen, eine eigene Klasse von QAbstractTableModel abzuleiten, die Daten aus einem großen array darstellt und ich somit auf die SQL-Funktionalität verzichten kann? Oder werde ich dabei ähnliche Probleme zu erwarten haben?

Wird die TableView auch dann die Daten inkrementell laden? Wenn ich das abstellen könnte, wäre mir schon geholfen.

Bin völlig ratlos...
Gernot

Verfasst: 19. April 2011 06:34
von Christian81
Wenn der Treiber nunmal nichts besseres hergibt kann Qt auch nichts machen. QSqlQueryModel benutzen recht wenige und noch weniger mit sqlite... meistens will man eh was spezielleres so dass man um ein eigenes Model nicht drum rum kommt.

Verfasst: 12. Juni 2011 10:58
von gk_17
als Nachtrag, falls jemand anderes ein ähnliches Problem hat: ich verwende inzwischen ein eigenes TableModel, das ich von QAbstractTableModel abgeleitet habe. Es reichte aus, die Methoden flags(), setData(), data(), rowCount(), columnCount() und headerData() zu überschreiben, was sehr schnell ging. data() holt jetzt die anzuzeigenden Daten nicht mehr aus einer sqlite-Tabelle, sondern aus meiner eigenen Datenstruktur.
Grüße,
gk_17