python-3.xpyside6qtexteditqabstracttablemodelqdatawidgetmapper

Displaying Pandas Dataframe in QTextEdit using QDataWidgetMapper


I have a pandas dataframe (df) that is displayed in QTableView using TableModel. I also have QTextEditor, that should do some math operations with the df dataframe used in TableModel and should display data set name (string variable) and below the modified dataframe in a QTextEdit. For this I have created QTextEditModel. Users can change values in QTableView and QTextEdit view should be updated correspondingly. Users can not input data in QTextEdit. As QTextEditModel should monitor the dataChanged/layoutChanged signals from TableModel, I initialised the TableModel in QTextEditModel as to me it looks the easiest way to arrange such monitoring (maybe it is wrong). I managed to get it working partially. I have several questions:

  1. I don't know how to display the pandas dataframe row by row in QTextEdit using QWidgetMapper. Right now it displays all data in one row. Current display: GUI of the software tool Desired output in QTextEdit:

Desired output in QTextEdit

  1. In TextEditModel when I monitor data changes in TableModel the following line in the code:

self.table1ViewModel.dataChanged.connect(lambda: self.setData(self.index_value, self.table1ViewModel.df.copy()))

I connected signal to setData and I need to provide value of QModelIndex, but I don't understand how can I get QModelIndex value here. I did some cheating with saving QModelIndex in index_value variable during the initialising the Models.

  1. How can I initialise the self.df dataframe in TextEditModel in way that it always will be updated as soon as the dataframe in TableModel changes? Right now I'm making a copy of dataframe in modify_data_frame method, which doesn't look correct way to do so.

Full code:

from PySide6.QtCore import QAbstractTableModel, Qt
from PySide6.QtWidgets import QWidget, QTableView, QTextEdit, QHBoxLayout, QApplication, QDataWidgetMapper
import pandas as pd
import sys
class TableModel(QAbstractTableModel):
    def __init__(self, df):
        super(TableModel, self).__init__()
        self.df = df

    def data(self, index, role):
        if index.isValid():
            row = index.row()
            col = index.column()
            if role == Qt.DisplayRole:
                value = self.df.iloc[row, col]
                return value

            elif role == Qt.EditRole:
                value = self.df.iloc[row, col]
                return value

    def setData(self, index, value, role=Qt.EditRole):
        if role == Qt.EditRole:
            row = index.row()
            col = index.column()
            self.df.iloc[row, col]=value
            self.dataChanged.emit(index, index)
            return True
        return False

    def flags(self, index):
        return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable

    def rowCount(self, index):
        return self.df.shape[0]

    def columnCount(self, index):
        return self.df.shape[1]

class TextEditModel(QAbstractTableModel):
    def __init__(self, df):
        super(TextEditModel, self).__init__()
        self.table1ViewModel = TableModel(df)
        self.df = self.modify_data_frame(self.table1ViewModel.df)
        self.index_value = None
        self.table1ViewModel.dataChanged.connect(lambda: self.setData(self.index_value, self.modify_data_frame(self.table1ViewModel.df)))
        # self.table1ViewModel.layoutChanged.connect(lambda: self.setData(self.index_value, self.table1ViewModel.df.copy()))

    def data(self, index, role):
        if index.isValid():
            row = index.row()
            col = index.column()
            if role == Qt.EditRole:
                self.index_value = index
                name = "data set"
                value = name + '\n' + self.df.to_string()
                return value

    def setData(self, index, df, role=Qt.EditRole):
        if role == Qt.EditRole:
            print("!")
            print(type(df))
            name = "data set"
            # df["number"] = df["number"].astype(int) + 1
            # self.df = df.copy()
            # df["number"] = df["number"].astype(str)
            df = name + '\n' + df.to_string()
            print(df)
            print(type(df))
            self.dataChanged.emit(index, index)
            return True
        return False

    def flags(self, index):
        return Qt.ItemIsEnabled

    def rowCount(self, index):
        return self.df.shape[0]

    def columnCount(self, index):
        return self.df.shape[1]

    def modify_data_frame(self, df):
        print("!!!")
        self.df = df.copy()
        print(self.df)
        self.df["number"] = self.df["number"].astype(int) + 1
        self.df["number"] = self.df["number"].astype(str)
        return self.df

class MainWidget(QWidget):
    def __init__(self, parent=None):
        super(MainWidget, self).__init__(parent)

        df = pd.DataFrame({"name":["a", "b", "c"], "number": ["1", "2", "3"]})

        self.table1View = QTableView()
        self.textEdit = QTextEdit()

        layout = QHBoxLayout(self)
        layout.addWidget(self.table1View)
        layout.addWidget(self.textEdit)

        model = TextEditModel(df)
        # self.textEdit.setModel(model)
        self.table1View.setModel(model.table1ViewModel)

        self.mapper = QDataWidgetMapper(self)
        self.mapper.setModel(model)
        self.mapper.addMapping(self.textEdit, 0)
        self.mapper.toFirst()


app = QApplication(sys.argv)
mywindow = MainWidget()
mywindow.show()
sys.exit(app.exec())

Solution

  • Just in case if someone will come across the same problem. I solved this in this way:

    from PySide6.QtCore import QAbstractTableModel, Qt
    from PySide6.QtWidgets import QWidget, QTableView, QTextEdit, QHBoxLayout, QApplication, QDataWidgetMapper
    import pandas as pd
    import sys
    class TableModel(QAbstractTableModel):
        def __init__(self, df):
            super(TableModel, self).__init__()
            self.df = df
    
        def data(self, index, role):
            if index.isValid():
                row = index.row()
                col = index.column()
                if role == Qt.DisplayRole:
                    value = self.df.iloc[row, col]
                    return value
    
                elif role == Qt.EditRole:
                    value = self.df.iloc[row, col]
                    return value
    
        def setData(self, index, value, role=Qt.EditRole):
            if role == Qt.EditRole:
                row = index.row()
                col = index.column()
                self.df.iloc[row, col]=value
                self.dataChanged.emit(index, index)
                return True
            return False
    
        def flags(self, index):
            return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable
    
        def rowCount(self, index):
            return self.df.shape[0]
    
        def columnCount(self, index):
            return self.df.shape[1]
    
    class MainWidget(QWidget):
        def __init__(self, parent=None):
            super(MainWidget, self).__init__(parent)
    
            df = pd.DataFrame({"name":["a", "b", "c"], "number": ["1", "2", "3"]})
    
            self.table1View = QTableView()
            self.textEdit = QTextEdit()
    
            layout = QHBoxLayout(self)
            layout.addWidget(self.table1View)
            layout.addWidget(self.textEdit)
    
            model = TableModel(df)
            self.table1View.setModel(model)
            model.dataChanged.connect(lambda: self.display_data(self.modify_data_frame(model.df)))
            model.layoutChanged.connect(lambda: self.display_data(self.modify_data_frame(model.df)))
            self.display_data(self.modify_data_frame(model.df))
    
        def modify_data_frame(self, df):
            self.df = df.copy()
            self.df["number"] = self.df["number"].astype(int) + 1
            self.df["number"] = self.df["number"].astype(str)
            return self.df
    
        def display_data(self, df):
            self.textEdit.clear()
            self.textEdit.append(df.to_string())
    
    app = QApplication(sys.argv)
    mywindow = MainWidget()
    mywindow.show()
    sys.exit(app.exec())