Обръщане на колони и редове в QTableView с помощта на PyQt5

За джаджа на PyQT5 трябва да покажа данни от SQL заявка към SQLite база данни с обърнати/завъртани колони и редове. В идеалния случай в QTableView. (Тази таблица ще има само 2 колони, една за имената на предишните колони и една за техните стойности. Таблицата е предназначена да показва статистически данни, които ще бъдат обобщени в SQL заявката, която ще върне само един ред. Така че искам да отида от един ред с множество колони, до 2 колони с множество редове.)

Измислих заобиколно решение, което прави правилното нещо, използвайки вместо това QFormLayout, но изглежда грозно и много неелегантно. (Вижте метода display_data(self).)

#!/usr/bin/python3

from PyQt5 import QtSql
from PyQt5.QtWidgets import (QFormLayout, QWidget, 
                             QLabel, QLineEdit, QApplication)

import sys

class InvertedTable(QWidget):
    def __init__(self, company):
        super().__init__()
        self.db_file = "test.db"
        self.company = company
        self.create_connection()
        self.fill_table()
        self.init_UI()
        self.display_data()

    def create_connection(self):
        self.db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
        self.db.setDatabaseName(self.db_file)
        if not self.db.open():
            print("Cannot establish a database connection to {}!".format(self.db_file))
            return False

    def fill_table(self):
        self.db.transaction()
        q = QtSql.QSqlQuery()
        q.exec_("DROP TABLE IF EXISTS Cars;")
        q.exec_("""CREATE TABLE Cars (Company TEXT, Model TEXT, Cars TEXT)""") 
        q.exec_("INSERT INTO Cars VALUES ('Honda', 'Civic', 5)") 
        q.exec_("INSERT INTO Cars VALUES ('Volkswagen', 'Golf', 3)")
        self.db.commit()

    def init_UI(self):
        self.resize(300,100)
        self.layout = QFormLayout()
        self.setLayout(self.layout)

    def display_data(self):
        query = "select * from cars where company = '{}'".format(self.company)
        q = QtSql.QSqlQuery()
        q.exec_(query)
        self.check_error(q)
        record = q.record()
        columns = record.count()
        q.next()
        for i in range(columns):
            column_name = record.field(i).name()
            col_field = QLabel(column_name, self)
            value = q.value(i)
            value_field = QLineEdit(self)
            value_field.setText(value)
            self.layout.addRow(col_field, value_field)

    def closeEvent(self, e):
        if (self.db.open()):
            self.db.close()

    def check_error(self, q):
        lasterr = q.lastError()
        if lasterr.isValid():
            print(lasterr.text())
            self.db.close()
            exit(1)


def main():
    app = QApplication(sys.argv)
    ex = InvertedTable("Honda")
    ex.show()

    result = app.exec_()
    sys.exit(result)


if __name__ == '__main__':
    main()      

Какъв е правилният начин да се постигне това с помощта на QTableView?


person CodingCat    schedule 27.03.2018    source източник
comment
Наистина не виждам какъв е проблемът. Защо просто не можете да попълните таблицата с необходимите стойности? Защо смятате, че използването на оформление на формуляр ще е необходимо?   -  person ekhumoro    schedule 27.03.2018
comment
@ekhumoro: да, мога да го направя. Струва ми се стоманено тромаво и неелегантно да правя заявки и след това да попълвам таблицата, но не знаех как да накарам QTableModel да се държи по начина, по който искам, затова използвах FormLayout, за да илюстрирам това, което ми трябва.   -  person CodingCat    schedule 28.03.2018
comment
Би било много, много по-лесно да попълните таблица с помощта на QStandardItemModel - всичко, което трябва да направите, е да обърнете нормалните цикли на ред/колона. Така че все още не разбирам какъв е смисълът на оформлението на формуляра. (И разбира се, знам, че това не е най-ефективният начин за взаимодействие с база данни - просто ми беше любопитно защо не можете да използвате таблица).   -  person ekhumoro    schedule 28.03.2018


Отговори (2)


Правилният начин за работа с QTableView би бил да имате QTableModel.

За късмет има QSqlTableModel, който ви позволява да изградите модел на таблица срещу SQL таблица. Funt отговори на подобен въпрос, като посочи QIdentityProxyModel, който може да се използва „на всичкото отгоре“ за промяна на представянето на модел на данни чрез предефиниране на mapToSource и mapFromSource методи.

Има и начини за транспониране на резултата от SQL заявка директно от SQL командата. Вижте тук.

Също така си струва да прочетете: Програмиране на изглед на модел с Qt. Това е C++ версията, но PyQt следва същите принципи (и класовете имат едно и също име).

Надявам се това да помогне.

person PlikPlok    schedule 27.03.2018
comment
Благодаря за насоките. (Особено последното беше полезно - голяма част от документацията на Qt не е много удобна за начинаещи и е трудно да се намерят тези сложни части.) Наистина се надявах, че някой от проксимоделите на Qt предоставя обръщане на колони и редове като опция, без да има към подклас, тъй като ми се струва, че това вероятно е често срещан проблем. - person CodingCat; 28.03.2018

След още малко търсене и четене на полезните указания, оставени от @PlikPlok, намерих решение тук:

Очевидно тази функционалност не се предоставя от никакви Qt-класове извън кутията, така че трябва да подкласирате както QAbstractProxyModel, така и QSqlRelationalDelegate, и след това да ги използвате на вашата таблица:

#!/usr/bin/python3

import sys
from PyQt5 import QtSql
from PyQt5.QtWidgets import (QWidget, QApplication,
                             QGridLayout, QTableView)
from PyQt5.Qt import (QModelIndex, QAbstractProxyModel, QSqlRelationalDelegate)
from PyQt5.QtCore import Qt

class FlippedProxyModel(QAbstractProxyModel):
    def __init__(self, parent=None):
        super().__init__(parent)

    def mapFromSource(self, index):
        return self.createIndex(index.column(), index.row())

    def mapToSource(self, index):
        return self.sourceModel().index(index.column(), index.row(), QModelIndex())

    def columnCount(self, parent):
        return self.sourceModel().rowCount(QModelIndex())

    def rowCount(self, parent):
        return self.sourceModel().columnCount(QModelIndex())

    def index(self, row, column, parent):
        return self.createIndex(row, column)

    def parent(self, index):
        return QModelIndex()

    def data(self, index, role):
        return self.sourceModel().data(self.mapToSource(index), role)

    def headerData(self, section, orientation, role):
        if orientation == Qt.Horizontal:
            return self.sourceModel().headerData(section, Qt.Vertical, role)
        if orientation == Qt.Vertical:
            return self.sourceModel().headerData(section, Qt.Horizontal, role)


class FlippedProxyDelegate(QSqlRelationalDelegate):
    def createEditor(self, parent, option, index):
        proxy = index.model()
        base_index = proxy.mapToSource(index)
        return super().createEditor(parent, option, base_index)

    def setEditorData(self, editor, index):
        proxy = index.model()
        base_index = proxy.mapToSource(index)
        return super().setEditorData(editor, base_index)

    def setModelData(self, editor, model, index):
        base_model = model.sourceModel()
        base_index = model.mapToSource(index)
        return super().setModelData(editor, base_model, base_index)


class InvertedTable(QWidget):
    def __init__(self, company):
        super().__init__()
        self.db_file = "test.db"
        self.company = company
        self.create_connection()
        self.fill_table()
        self.create_model()
        self.init_UI()

    def create_connection(self):
        self.db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
        self.db.setDatabaseName(self.db_file)
        if not self.db.open():
            print("Cannot establish a database connection to {}!".format(self.db_file))
            return False

    def fill_table(self):
        self.db.transaction()
        q = QtSql.QSqlQuery()
        q.exec_("DROP TABLE IF EXISTS Cars;")
        q.exec_("""CREATE TABLE Cars (Company TEXT, Model TEXT, Cars TEXT)""") 
        q.exec_("INSERT INTO Cars VALUES ('Honda', 'Civic', 5)") 
        q.exec_("INSERT INTO Cars VALUES ('Volkswagen', 'Golf', 3)")
        self.db.commit()

    def create_model(self):
        self.model = QtSql.QSqlTableModel()
        q = QtSql.QSqlQuery()
        query = """SELECT * from cars where company = 'Honda'
         """
        q.exec_(query)
        self.model.setQuery(q)
        self.proxy = FlippedProxyModel() # use flipped proxy model
        self.proxy.setSourceModel(self.model)

    def init_UI(self):
        self.grid = QGridLayout()
        self.setLayout(self.grid)
        self.table = QTableView()
        self.table.setModel(self.proxy)
        self.table.setItemDelegate(FlippedProxyDelegate(self.table)) # use flipped proxy delegate
        self.table.horizontalHeader().hide()

        self.grid.addWidget(self.table, 0, 0)

    def closeEvent(self, e):
        if (self.db.open()):
            self.db.close()

    def check_error(self, q):
        lasterr = q.lastError()
        if lasterr.isValid():
            print(lasterr.text())
            self.db.close()
            exit(1)


def main():
    app = QApplication(sys.argv)
    ex = InvertedTable("Honda")
    ex.show()

    result = app.exec_()
    sys.exit(result)


if __name__ == '__main__':
    main()      
person CodingCat    schedule 28.03.2018