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?
Advertisement
Answer
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())