EDIT: I've changed my approach since asking this question - see answer below.
I'm building an application which displays data in a series of tables. I'm currently using PyQT's item-based QTableWidget and manually updating the tables whenever data changes. I'd like to migrate to a model/view architecture using QAbstractItemModel and QTableView.
My data has 3 dimensions:
record_number (record1, record2, record3 etc.)
attribute (name, address, phone etc.)
data_source (db, edited, csv)
I'd like to store this data in a single model, and display different dimensions in different tables.
Data example:
data = {
"record1": {
"name": {"csv": "a", "edited": "b", "db": "c"},
"address": {"csv": "d", "edited": "e", "db": "f"},
"phone": {"csv": "g", "edited": "h", "db": "i"},
},
"record2": {
"name": {"csv": "j", "edited": "k", "db": "l"},
"address": {"csv": "m", "edited": "n", "db": "o"},
"phone": {"csv": "p", "edited": "q", "db": "r"},
},
"record3": {
"name": {"csv": "s", "edited": "t", "db": "u"},
"address": {"csv": "v", "edited": "w", "db": "x"},
"phone": {"csv": "y", "edited": "z", "db": "aa"},
}
}
In table1 I want to display records on the y-axis, and attributes on the x-axis, using the "edited" data_source:
name | address | phone
1: b e h
2: k n q
3: t w z
In table2 I want to display a single record, with attribute on the y-axis and data_source on the x-axis:
Record1
csv | edited | db
name a b c
address d e f
phone g h i
How would I implement this using PyQT's model/view framework, so that data is stored in a single model but represented differently for table1 and table2?
CodePudding user response:
I've changed my approach since asking this question. The question came from a misunderstanding of Qt's model/view architecture - I assumed that the model == datastore, whereas it looks like in most cases the model contains data access and update methods.
I found these two tutorials helpful in demonstrating this:
As per @musicamante's comment, I'm now using two models to access data. Data is stored in a Pandas DataFrame with a MultiIndex (to account for the nested dictionary data), and the selection in table1 is used to set the which data is accessed in model2.
class Model1(QtCore.QAbstractTableModel):
index: QtCore.QModelIndex
data: pd.DataFrame
_data: pd.DataFrame
h_header: list
def __init__(self, data, h_header):
super(Model1, self).__init__()
self._data = data
self.h_header = h_header
def data(self, index: QtCore.QModelIndex, role=None):
row = index.row()
col = index.column()
col_name = self.h_header[col]
if role == Qt.DisplayRole:
source = "edited"
value = self._data.loc[row, (col_name, source)]
return str(value)
def rowCount(self, index):
return self._data.shape[0]
def columnCount(self, index):
return len(self.h_header)
def headerData(self, section, orientation, role=None):
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return str(self.h_header[section])
if orientation == Qt.Vertical:
return str(self._data.index[section])
class Model2(QtCore.QAbstractTableModel):
index: QtCore.QModelIndex
data: pd.DataFrame
_data: pd.DataFrame
h_header: list
v_header: list
def __init__(self, data, h_header, v_header):
super(Model2, self).__init__()
self._data = data
self.h_header = h_header
self.v_header = v_header
self.current_row = -1
def set_current_row(self, row: int):
self.current_row = row
self.layoutChanged.emit()
def data(self, index: QtCore.QModelIndex, role=None):
row = index.row()
col = index.column()
row_name = self.v_header[row]
col_name = self.h_header[col]
if role == Qt.DisplayRole:
if self.current_row == -1:
return ""
else:
value = self._data.loc[self.current_row, (row_name, col_name)]
return str(value)
def rowCount(self, index):
return len(self.v_header)
def columnCount(self, index):
return len(self.h_header)
def headerData(self, section, orientation, role=None):
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return str(self.h_header[section])
if orientation == Qt.Vertical:
return str(self.v_header[section])
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.table1 = QtWidgets.QTableView()
self.table2 = QtWidgets.QTableView()
...
def connect_models_tables(self):
self.model1 = Model1(self.data, self.header_list)
self.model2 = Model2(self.header_list)
self.table1.setModel(self.model1)
self.table2.setModel(self.model2)
sel1 = QtCore.QItemSelectionModel(self.model1)
self.table1.setSelectionModel(sel1)
self.table1.selectionModel().selectionChanged.connect(self.on_sel_change)
def on_sel_change(self):
if len(self.table1.selectedIndexes()):
self.model2.set_current_row(self.table1.selectedIndexes()[0].row())