Для виджета 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?
@ekhumoro: да, я могу это сделать. Стало кажется неуклюжим и неэлегантным запрашивать, а затем заполнять таблицу, но я не знал, как заставить QTableModel вести себя так, как я хочу, поэтому я использовал FormLayout, чтобы проиллюстрировать, что мне нужно.
Было бы намного проще заполнить таблицу с помощью QStandardItemModel - все, что вам нужно сделать, это инвертировать обычные циклы строки / столбца. Так что я до сих пор не понимаю, в чем смысл макета формы. (И, конечно, я знаю, что это не самый эффективный способ взаимодействия с базой данных - мне просто было любопытно, почему вы не можете использовать таблицу).






Правильный способ работы с QTableView - это иметь QTableModel.
Как назло, существует QSqlTableModel, который позволяет вам построить модель таблицы на основе таблицы SQL.
Funt ответил на аналогичный вопрос, указав на QIdentityProxyModel, который можно использовать «поверх этого» для изменения представления модели данных путем переопределения методов mapToSource и mapFromSource.
Есть также способы транспонировать результат запроса SQL непосредственно из команды SQL. См. здесь.
Также стоит прочитать: Программирование в представлении модели с помощью Qt. Это версия C++, но PyQt следует тем же принципам (и классы имеют то же имя).
Надеюсь, это поможет.
Спасибо за указатели. (Особенно полезно было последнее - большая часть документации Qt не очень удобна для новичков, и трудно найти эти сложные части.) Я действительно надеялся, что любая из ProxyModels Qt предоставит в качестве опции переворачивание столбцов и строк, не имея в подкласс, поскольку мне кажется, что это, вероятно, обычная проблема.
После еще нескольких поисков и прочтения полезных указателей, оставленных @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()
Я действительно не понимаю, в чем проблема. Почему нельзя просто заполнить таблицу необходимыми значениями? Как вы думаете, почему необходимо использовать макет формы?