1

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())
2
  • 1
    What is the purpose of TextEditModel? If you're only using it to "verbosely display" the dataframe, then it's quite useless. Also, QDataWidgetMapper is intended to display a row (or column if the orientation is vertical), since you actually need the whole model, then it's not what you need. Commented Oct 20, 2024 at 16:58
  • With TextEditModel, I just wanted to display the whole dataframe, but looks like I can do this with just simple function that is called on the dataChanged signal of TableModel. Thank you! Commented Oct 20, 2024 at 18:20

1 Answer 1

0

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())
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.