I'm creating a GUI in PyQT5 that should provide multiple views on data from multiple related SQLite tables. I have implemented these views to be displayed via a QStackedWidget.
Now, some of these views are for overview purposes, others for more detailled views on a subset of the data displayed in the overviews. I want to access the detailled views from the overviews via rightclick.
I have included a minimal example with cars below. (Sorry, it's a bit long, but that was needed to provide a full working example. I have reduced it as much as I could.) The goal is to show the DetailledView with the cars of the company selected in the Overview.
This already provides access from the Overview to the DetailledView on Rightclick, but the company information is not passed along. So even when accessing the DetailledView from 'VW', self.mycompany gets updated but car_widget doesn't, so the DetailledView shows the info about 'Honda' cars (the default).
Is there a way to update car_widgit with the right company? Or do I need to create the DetailledView at runtime? (Does that even work? But I would prefer not to, anyway, as it could make the index of the Stack unreliable...)
How can I update the QTableModel of one view in a QStackedWidget according to what was selected in another view?
Here's the code:
#!/usr/bin/python3
from PyQt5 import QtSql
from PyQt5.QtWidgets import (QMainWindow, QWidget, QApplication, QGridLayout,
QStackedWidget, QTableView, QMenu)
from PyQt5.QtCore import Qt, QModelIndex
import sys
class MainGUI(QMainWindow):
def __init__(self):
super().__init__()
self.mycompany = "Honda"
self.create_connection()
self.fill_tables()
self.init_UI()
def create_connection(self):
self.db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
self.db.setDatabaseName("test.db")
if not self.db.open():
print("Cannot establish a database connection to {}!".format(self.db_file))
return False
def fill_tables(self):
self.db.transaction()
q = QtSql.QSqlQuery()
q.exec_("DROP TABLE IF EXISTS Manufacturers;")
q.exec_("CREATE TABLE Manufacturers (CompanyId INT PRIMARY KEY, Name TEXT, Country TEXT);")
q.exec_("INSERT INTO Manufacturers VALUES (1, 'VW', 'Germany');")
q.exec_("INSERT INTO Manufacturers VALUES (2, 'Honda' , 'Japan');")
q.exec_("DROP TABLE IF EXISTS Cars;")
q.exec_("CREATE TABLE Cars (Model TEXT, Year INT, Company INT);")
q.exec_("INSERT INTO Cars VALUES ('Civic', 2009, 'Honda');")
q.exec_("INSERT INTO Cars VALUES ('Golf', 2013, 'VW');")
q.exec_("INSERT INTO Cars VALUES ('Polo', 1999, 'VW');")
self.db.commit()
def init_UI(self):
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.grid = QGridLayout()
self.central_widget.setLayout(self.grid)
self.setLayout(self.grid)
self.make_stack()
self.show()
def make_stack(self):
self.Stack = QStackedWidget(self)
company_view = QWidget()
layout = QGridLayout()
company_view.setLayout(layout)
self.company_widget = Overview()
layout.addWidget(self.company_widget, 0, 0)
self.company_widget.table.customContextMenuRequested.connect(self.open_menu)
self.Stack.addWidget(company_view)
car_view = QWidget()
layout2 = QGridLayout()
car_view.setLayout(layout2)
car_widget = DetailedView(self.mycompany)
layout2.addWidget(car_widget, 0, 0)
self.Stack.addWidget(car_view)
self.grid.addWidget(self.Stack, 0,1)
def open_menu(self, pos):
menu = QMenu()
show_act = menu.addAction("Show cars")
action = menu.exec_(self.company_widget.table.mapToGlobal(pos))
if action == show_act:
row = self.company_widget.table.indexAt(pos).row()
myindex = self.company_widget.model.index(row, 1, QModelIndex())
company = self.company_widget.model.data(myindex)
self.mycompany = company
self.Stack.setCurrentIndex(1)
class MyTable(QWidget):
def __init__(self):
super().__init__()
self.create_connection()
self.create_model()
self.init_UI()
def create_connection(self):
self.db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
self.db.setDatabaseName("test.db")
if not self.db.open():
print("Cannot establish a database connection to {}!".format(self.db_file))
return False
def create_model(self):
self.model = None
def init_UI(self):
self.grid = QGridLayout()
self.setLayout(self.grid)
self.table = QTableView()
self.table.setModel(self.model)
self.table.setContextMenuPolicy(Qt.CustomContextMenu)
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)
class Overview(MyTable):
def __init__(self):
super().__init__()
def create_model(self):
self.model = QtSql.QSqlTableModel()
q = QtSql.QSqlQuery()
query = "SELECT * from Manufacturers"
q.exec_(query)
self.model.setQuery(q)
class DetailedView(MyTable):
def __init__(self, company):
self.company = company
super().__init__()
def create_model(self):
self.model = QtSql.QSqlTableModel()
q = QtSql.QSqlQuery()
query = "SELECT * from cars where company = '{}'".format(self.company)
q.exec_(query)
self.model.setQuery(q)
def main():
app = QApplication(sys.argv)
ex = MainGUI()
ex.show()
result = app.exec_()
sys.exit(result)
if __name__ == '__main__':
main()
One of the objectives of the inheritance is that the class implements the common tasks that the children can do, and in your case you do not observe those tasks, for example the creation of the model must be done in the father since all the children will do it.
On the other hand the goal of using QSqlTableModel is not to use the QSqlQuery
but more friendly requests like setTable()
, select()
and setFilter()
, but it is simply a waste because you could use QSqlQueryModel
.
On the other hand I see that you are assuming that the self.mycompany
of MainGui
is the same as the self.company
of DetailedView
for what you are going through in the creation of the DetailView object, and the truth is that they are not the same, in the creation of the object only the value of that moment has been copied, so if you change the self.company
of MainGui
it will not change the self.company
of DetailView
.
Restructuring your project you get the following:
#!/usr/bin/python3
from PyQt5.QtSql import QSqlDatabase, QSqlQuery, QSqlTableModel
from PyQt5.QtWidgets import (QMainWindow, QWidget, QApplication, QVBoxLayout,
QStackedWidget, QTableView, QMenu)
from PyQt5.QtCore import Qt, QModelIndex, pyqtSignal
import sys
DB_PATH = "test.db"
def create_connection():
db = QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName(DB_PATH)
if not db.open():
print("Cannot establish a database connection to {}!".format(DB_PATH))
return False
return True
def fill_tables():
q = QSqlQuery()
q.exec_("DROP TABLE IF EXISTS Manufacturers;")
q.exec_("CREATE TABLE Manufacturers (CompanyId INT PRIMARY KEY, Name TEXT, Country TEXT);")
q.exec_("INSERT INTO Manufacturers VALUES (1, 'VW', 'Germany');")
q.exec_("INSERT INTO Manufacturers VALUES (2, 'Honda' , 'Japan');")
q.exec_("DROP TABLE IF EXISTS Cars;")
q.exec_("CREATE TABLE Cars (Model TEXT, Year INT, Company INT);")
q.exec_("INSERT INTO Cars VALUES ('Civic', 2009, 'Honda');")
q.exec_("INSERT INTO Cars VALUES ('Golf', 2013, 'VW');")
q.exec_("INSERT INTO Cars VALUES ('Polo', 1999, 'VW');")
class MainGUI(QMainWindow):
def __init__(self):
super().__init__()
self.init_UI()
def init_UI(self):
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.lay = QVBoxLayout(self.central_widget)
self.make_stack()
def make_stack(self):
self.stack = QStackedWidget()
self.lay.addWidget(self.stack)
self.company_widget = Overview()
self.car_widget = DetailedView()
self.stack.addWidget(self.company_widget)
self.stack.addWidget(self.car_widget)
self.company_widget.changedCompany.connect(self.changedCompany)
self.car_widget.backSignal.connect(lambda: self.stack.setCurrentIndex(0))
def changedCompany(self, company):
self.car_widget.filter(company)
self.stack.setCurrentIndex(1)
class SQLTable(QTableView):
def __init__(self, table):
super().__init__()
self.init_UI()
self.create_model(table)
def create_model(self, table):
self.model.setTable(table)
self.model.select()
def init_UI(self):
self.model = QSqlTableModel()
self.setModel(self.model)
self.setContextMenuPolicy(Qt.CustomContextMenu)
class Overview(SQLTable):
changedCompany = pyqtSignal(str)
def __init__(self):
SQLTable.__init__(self, "Manufacturers")
self.customContextMenuRequested.connect(self.open_menu)
def open_menu(self, pos):
menu = QMenu()
show_act = menu.addAction("Show cars")
action = menu.exec_(self.mapToGlobal(pos))
if action == show_act:
row = self.indexAt(pos).row()
ix = myindex = self.model.index(row, 1)
company = self.model.data(ix)
self.changedCompany.emit(company)
class DetailedView(SQLTable):
backSignal = pyqtSignal()
def __init__(self):
SQLTable.__init__(self, "cars")
self.customContextMenuRequested.connect(self.open_menu)
def open_menu(self, pos):
menu = QMenu()
back_act = menu.addAction("Show Manufacturers")
action = menu.exec_(self.mapToGlobal(pos))
if action == back_act:
self.backSignal.emit()
def filter(self, company):
self.model.setFilter("company='{}'".format(company))
def main():
app = QApplication(sys.argv)
if not create_connection():
sys.exit(-1)
fill_tables()
ex = MainGUI()
ex.show()
result = app.exec_()
sys.exit(result)
if __name__ == '__main__':
main()