1

While trying to display data from QSQLITE table in QML TableView I stumbled upon this answer and tried to follow up with it. I understood necessity of re implementing data() and roleNames() but I have problem when it comes to QVariant which is not present in PySide2. I get NameError: name 'QVariant' is not defined. I have tested reading data from database , its working. Is there any datatype that can be used instead of QVariant in PySide2? My plan is to have multiple datasources from different tables.This Example uses setContextProperty, is it better practice in this case usage of qmlRegisterType ?

SQLITE Database

folder structure

├── ViewModel
│   └── QCond.py
├── Qml
│   └── Modellist.qml
└── qmlengine.py

qmlengine.py

import os
import sys
from PySide2.QtCore import QUrl, QStringListModel, QCoreApplication, Qt
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine, qmlRegisterType
from PySide2.QtSql import QSqlDatabase, QSqlQuery, QSqlQueryModel
from ViewModel.QCond import *
if __name__ == "__main__":
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()
    db = QSqlDatabase.addDatabase("QSQLITE")
    db.setDatabaseName("C:\\Users\\terao\\Documents\\ctmd\\CatData.db")
    db.open()
    qry = QSqlQuery()
    qry=db.exec_("SELECT tip,s FROM uzad")
    tabmodel = QtCond()
    tabmodel.setQuery(qry)
    engine.rootContext().setContextProperty("tabmodel", tabmodel)
    engine.load(QUrl.fromLocalFile('Qml/Modellist.qml'))
    if not engine.rootObjects():
        sys.exit(-2)
    sys.exit(app.exec_())

QCond.py

import sys
from PySide2 import QtCore, QtGui, QtQml
from PySide2.QtCore import QObject, Qt,Signal, Slot, QUrl, QStringListModel, QCoreApplication
import sqlite3
from PySide2.QtSql import QSqlDatabase, QSqlQuery, QSqlQueryModel
class QtCond(QSqlQueryModel):
    def __init__(self):
        super(QtCond, self).__init__()

    def roleNames(self):
        roles = {
            Qt.UserRole + 1 : 'tip',
            Qt.UserRole + 2 : 's'
        }
        return roles

    def data(self, index, role):
        if role < Qt.UserRole:
            # caller requests non-UserRole data, just pass to papa
            return super(QtTabModel, self).data(index, role)

        # caller requests UserRole data, convert role to column (role - Qt.UserRole -1) to return correct data
        return super(QtTabModel, self).data(self.index(index.row(), role - Qt.UserRole -1), Qt.DisplayRole)

    @QtCore.Slot(result=QVariant)  # don't know how to return a python array/list, so just use QVariant
    def roleNameArray(self):
        # This method is used to return a list that QML understands
        list = []
        # list = self.roleNames().items()
        for key, value in self.roleNames().items():
            list.append(str(value))

        return QVariant(list)

Modellist.qml

import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls 2.5

TableView {
    width: 1000
    height: 1000
    model: tabmodel
    TableViewColumn {
        role: "tip" // case-sensitive, must match a role returned by roleNames()
    }
    TableViewColumn {
        role: "s"
    }

}

Any help is appreciated. Thanks in advance.

user2727167
  • 428
  • 1
  • 3
  • 16

1 Answers1

3

In your case roleNameArray returns a list and as I point out in this answer you must use "QVariantList". On the other hand I have improved the example by generalizing the example based on my old answers: (1), (2), considering the solution is:

├── db
│   └── CatData.db
├── qml
│   └── Modellist.qml
├── qmlengine.py
└── ViewModel
    └── model.py

ViewModel/model.py

from PySide2 import QtCore, QtSql


class SqlQueryModel(QtSql.QSqlQueryModel):
    def data(self, index, role=QtCore.Qt.DisplayRole):
        value = None
        if index.isValid():
            if role < QtCore.Qt.UserRole:
                value = super(SqlQueryModel, self).data(index, role)
            else:
                columnIdx = role - QtCore.Qt.UserRole - 1
                modelIndex = self.index(index.row(), columnIdx)
                value = super(SqlQueryModel, self).data(
                    modelIndex, QtCore.Qt.DisplayRole
                )
        return value

    def roleNames(self):
        roles = dict()
        for i in range(self.record().count()):
            roles[QtCore.Qt.UserRole + i + 1] = self.record().fieldName(i).encode()
        return roles

    @QtCore.Slot(result="QVariantList")
    def roleNameArray(self):
        names = []
        for i in range(self.record().count()):
            names.append(self.record().fieldName(i))
        return names

qml/Modellist.qml

import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls 2.5

ApplicationWindow{
    width: 1000
    height: 1000
    visible: true

    TableView {
        anchors.fill: parent
        model: tabmodel

        /*TableViewColumn {
            role: "tip"
        }
        TableViewColumn {
            role: "s"
        }*/

        Component.onCompleted: {
            var roles = model.roleNameArray()
            for (var i=0; i<roles.length; i++) {
              var column = addColumn( Qt.createQmlObject(
                "import QtQuick.Controls 1.1; TableViewColumn {}",
                this) )
              column.role = roles[i]
              column.title = roles[i]
            }
        }
    }
}

qmlengine.py

import os
import sys

from PySide2 import QtCore, QtGui, QtSql, QtQml

from ViewModel.model import SqlQueryModel

def create_connection(path):
    db = QtSql.QSqlDatabase.addDatabase('QSQLITE')
    db.setDatabaseName(path)
    if not db.open():
        print('''Unable to establish a database connection.\n
            This example needs SQLite support. Please read
            the Qt SQL driver documentation for information
            how to build it.\n\n Click Cancel to exit.''')
        return False
    return True

if __name__ == "__main__":
    current_dir = os.path.dirname(os.path.realpath(__file__))
    app = QtGui.QGuiApplication(sys.argv)
    engine = QtQml.QQmlApplicationEngine()
    db_path = os.path.join(current_dir, "db", "CatData.db")
    if not create_connection(db_path):
        sys.exit(-1)

    tabmodel = SqlQueryModel()
    tabmodel.setQuery("SELECT tip,s FROM uzad")
    engine.rootContext().setContextProperty("tabmodel", tabmodel)
    qml_path = os.path.join("qml", "Modellist.qml")
    engine.load(QtCore.QUrl.fromLocalFile(qml_path))
    if not engine.rootObjects():
        sys.exit(-2)
    sys.exit(app.exec_()) 

Output:

enter image description here


The objective of qmlRegisterType is to register Python/C++ types in QML by abstracting the origin and implementation. In your case, if you want to register it, it is better that the query is a qproperty so that it can be accessed from QML:

ViewModel/model.py

from PySide2 import QtCore, QtSql


class SqlQueryModel(QtSql.QSqlQueryModel):

    queryStrChanged = QtCore.Signal(str)

    def get_query_str(self):
        return self.query().lastQuery()

    def set_query_str(self, query):
        if self.get_query_str() == query:
            return
        self.setQuery(query)
        self.queryStrChanged.emit(query)

    query_str = QtCore.Property(
        str, fget=get_query_str, fset=set_query_str, notify=queryStrChanged
    )

    def data(self, index, role=QtCore.Qt.DisplayRole):
        value = None
        if index.isValid():
            if role < QtCore.Qt.UserRole:
                value = super(SqlQueryModel, self).data(index, role)
            else:
                columnIdx = role - QtCore.Qt.UserRole - 1
                modelIndex = self.index(index.row(), columnIdx)
                value = super(SqlQueryModel, self).data(
                    modelIndex, QtCore.Qt.DisplayRole
                )
        return value

    def roleNames(self):
        roles = dict()
        for i in range(self.record().count()):
            roles[QtCore.Qt.UserRole + i + 1] = self.record().fieldName(i).encode()
        return roles

    @QtCore.Slot(result="QVariantList")
    def roleNameArray(self):
        names = []
        for i in range(self.record().count()):
            names.append(self.record().fieldName(i))
        return names

qml/Modellist.qml

import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls 2.5

import mycomponents 1.0

ApplicationWindow{
    width: 1000
    height: 1000
    visible: true

    SqlQueryModel{
        id: tabmodel
        query_str: "SELECT tip,s FROM uzad"
    }

    TableView {
        anchors.fill: parent
        model: tabmodel

        /*TableViewColumn {
            role: "tip"
        }
        TableViewColumn {
            role: "s"
        }*/

        Component.onCompleted: {
            var roles = model.roleNameArray()
            for (var i=0; i<roles.length; i++) {
              var column = addColumn( Qt.createQmlObject(
                "import QtQuick.Controls 1.1; TableViewColumn {}",
                this) )
              column.role = roles[i]
              column.title = roles[i]
            }
        }
    }
}

qmlengine.py

import os
import sys

from PySide2 import QtCore, QtGui, QtSql, QtQml

from ViewModel.model import SqlQueryModel

def create_connection(path):
    db = QtSql.QSqlDatabase.addDatabase('QSQLITE')
    db.setDatabaseName(path)
    if not db.open():
        print('''Unable to establish a database connection.\n
            This example needs SQLite support. Please read
            the Qt SQL driver documentation for information
            how to build it.\n\n Click Cancel to exit.''')
        return False
    return True

if __name__ == "__main__":
    current_dir = os.path.dirname(os.path.realpath(__file__))
    QtQml.qmlRegisterType(SqlQueryModel, "mycomponents", 1, 0, "SqlQueryModel")
    app = QtGui.QGuiApplication(sys.argv)
    engine = QtQml.QQmlApplicationEngine()
    db_path = os.path.join(current_dir, "db", "CatData.db")
    if not create_connection(db_path):
        sys.exit(-1)
    qml_path = os.path.join("qml", "Modellist.qml")
    engine.load(QtCore.QUrl.fromLocalFile(qml_path))
    if not engine.rootObjects():
        sys.exit(-2)
    sys.exit(app.exec_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241