0

Need to save current values of QTableWidget. The QTableWidget will be iniside QTabWidget and there will be several adjacent tabs containing multiple tables in it. And inside the table there will be QCheckBox, QComoBox as cellwidgetitem which values also need to be saved. Beside this I also need to save value of QSpinBox and QDoubleSpinBox which are inside gridlayout. I am taking help of a code from https://gist.github.com/eyllanesc/be8a476bb7038c7579c58609d7d0f031 which saves values of QLineEdit inside a QFormLayout where the QFormLayout is inside a QTabWidget. If multiple tabs are instantiated then QLineEdit values inside the tabs cannot be saved. Moreover, if a QTableWidget is added below a QTabWidget then the values of the QTableWidget cannot be saved as well.

# -*- coding: utf-8 -*-
import sys

from PyQt5.QtCore import QFileInfo, QSettings
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import qApp, QApplication, QMainWindow, QFormLayout, QLineEdit, QTabWidget, QWidget, QAction, QVBoxLayout, QTableWidget, QTableWidgetItem


def restore(settings):
    finfo = QFileInfo(settings.fileName())
    print(settings.fileName())
    if finfo.exists() and finfo.isFile():
        for w in qApp.allWidgets():
            mo = w.metaObject()
            if w.objectName() != "":
                for i in range(mo.propertyCount()):
                    name = mo.property(i).name()
                    val = settings.value("{}/{}".format(w.objectName(), name), w.property(name))
                    w.setProperty(name, val)


def save(settings):
    for w in qApp.allWidgets():
        mo = w.metaObject()
        if w.objectName() != "":
            for i in range(mo.propertyCount()):
                name = mo.property(i).name()
                settings.setValue("{}/{}".format(w.objectName(), name), w.property(name))


class mainwindow(QMainWindow):
    settings = QSettings("gui.ng", QSettings.IniFormat)

    def __init__(self, parent=None):
        super(mainwindow, self).__init__(parent)
        self.setObjectName("MainWindow")
        self.initUI()

        restore(self.settings)

    def initUI(self):
        exitAction = QAction(QIcon('icon\\exit.png'), 'Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.triggered.connect(self.close)

        self.toolbar = self.addToolBar('Exit')
        self.toolbar.setMovable(False)
        self.toolbar.addAction(exitAction)

        self.tab_widget = QTabWidget(self)  # add tab
        self.tab_widget.setObjectName("tabWidget")

        self.tab2 = QWidget()
        self.tab2.setObjectName("tab2")

        self.tab3 = QWidget()
        self.tab3.setObjectName("tab3")

        self.tab_widget.addTab(self.tab2, "Tab_2")
        self.tab_widget.addTab(self.tab3, "Tab_3")
        self.tab2UI()
        self.tab3UI()

        self.vlay = QVBoxLayout()
        self.vlay.addWidget(self.tab_widget)

        self.qtable = QTableWidget()
        self.qtable.setRowCount(3)
        self.qtable.setColumnCount(3)
        self.qtable.setItem(0, 0, QTableWidgetItem("text1"))
        self.qtable.setItem(0, 1, QTableWidgetItem("text1"))
        self.qtable.setItem(0, 2, QTableWidgetItem("text1"))
        self.qtable.setItem(1, 0, QTableWidgetItem("text2"))
        self.qtable.setItem(1, 1, QTableWidgetItem("text2"))
        self.qtable.setItem(1, 2, QTableWidgetItem("text2"))
        self.qtable.setItem(2, 0, QTableWidgetItem("text3"))
        self.qtable.setItem(2, 1, QTableWidgetItem("text3"))
        self.qtable.setItem(2, 2, QTableWidgetItem("text3"))

        self.vlay.addWidget(self.qtable)

        self.qVlayWidget = QWidget()
        self.qVlayWidget.setLayout(self.vlay)

        self.setCentralWidget(self.qVlayWidget)

    def tab2UI(self):
        self.layout_2 = QFormLayout()
        nameLe = QLineEdit(self)
        nameLe.setObjectName("nameLe_2")
        self.layout_2.addRow("Name_2", nameLe)

        addressLe = QLineEdit()
        addressLe.setObjectName("addressLe_2")
        self.layout_2.addRow("Address_2", addressLe)

        self.tab2.setLayout(self.layout_2)

    def tab3UI(self):
        self.layout_3 = QFormLayout()
        nameLe = QLineEdit(self)
        nameLe.setObjectName("nameLe_3")
        self.layout_3.addRow("Name_3", nameLe)

        addressLe = QLineEdit()
        addressLe.setObjectName("addressLe_3")
        self.layout_3.addRow("Address_3", addressLe)

        self.tab3.setLayout(self.layout_3)    

    def closeEvent(self, event):
        save(self.settings)
        QMainWindow.closeEvent(self, event)


def main():
    app = QApplication(sys.argv)
    ex = mainwindow()
    ex.setGeometry(100, 100, 1000, 600)
    ex.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

1 Answers1

0

The QLineEdit data is stored, the problem is that its not shown. The visible property of a widget depends on its ancestor[s]; since a QTabWidget has a QWidget for each of its tab, only the current one is visible, while the other (including all their children) are not visible. A QLineEdit is a complex widget, and if you manually unset the visible property to all its internal components, they won't be correctly restored again.
This becomes clear if you close the window with the second tab set as current: once you'll open it again, the second tab will be shown and its contents correctly displayed, while the first one QLineEdit won't.

A workaround for this is to check if the widget is "stored" as not visible and has a parent. If that's the case, just do not apply the visible property.

def restore(settings):
    # ...
    for w in qApp.allWidgets():
        mo = w.metaObject()
        parent = w.parent()
        if w.objectName() != "":
            for i in range(mo.propertyCount()):
                name = mo.property(i).name()
                val = settings.value("{}/{}".format(w.objectName(), name), w.property(name))
                if name == 'visible' and val == 'false' and parent:
                    continue
                w.setProperty(name, val)

That said, this is clearly not the good way to save the data of any widget, since it saves every property it has: the title of the example is "Functions to save and restore Widgets", not its editable data which is what you need.
Also, QTableWidget contents cannot be stored using this method, as its data is not part of the widget properties (and nothing else was saved anyway, because you didn't set its object name).

You have to find your own implementation to save those contents, which might be something similar to the following code.
Note that I'm using a QByteArray to store the table data. You create a QByteArray, then a QDataStream with it as argument, which is used for reading or writing. Note that both reading and writing are consequential, as data is always appended on writing or "popped-out" each time it has been read.

class mainwindow(QMainWindow):
    # ...
    def save(self):
        # using findChildren is for simplicity, it's probably better to create
        # your own list of widgets to cycle through
        for w in self.findChildren((QLineEdit, QTableWidget)):
            name = w.objectName()
            if isinstance(w, QLineEdit):
                self.settings.setValue("{}/text".format(name), w.text())
            elif isinstance(w, QTableWidget):
                # while we could add sub-setting keys for each combination of
                # row/column items, it's better to store the data in a single
                # "container"
                data = QByteArray()
                stream = QDataStream(data, QIODevice.WriteOnly)

                rowCount = w.rowCount()
                columnCount = w.columnCount()
                # write the row and column count first
                stream.writeInt(rowCount)
                stream.writeInt(columnCount)

                # then write the data
                for row in range(rowCount):
                    for col in range(columnCount):
                        stream.writeQString(w.item(row, col).text())
                self.settings.setValue("{}/data".format(name), data)

    def restore(self):
        for w in self.findChildren((QLineEdit, QTableWidget)):
            name = w.objectName()
            if isinstance(w, QLineEdit):
                w.setText(self.settings.value("{}/text".format(name), w.text()))
            elif isinstance(w, QTableWidget):
                data = self.settings.value("{}/data".format(name))
                if not data:
                    continue
                stream = QDataStream(data, QIODevice.ReadOnly)

                # read the row and column count first
                rowCount = stream.readInt()
                columnCount = stream.readInt()
                w.setRowCount(rowCount)
                w.setColumnCount(columnCount)

                # then read the data
                for row in range(rowCount):
                    for col in range(columnCount):
                        cellText = stream.readQString()
                        if cellText:
                            w.item(row, col).setText(cellText)
musicamante
  • 41,230
  • 6
  • 33
  • 58
  • Your answer is interesting but it may have a small problem, a QTableWidgetItem not only saves text but also colors, fonts, etc. Instead of saving the text you could use the ">>" and "<<" operators to get all the information about the roles as I show in my answer to the duplicate question: https://stackoverflow.com/a/56317981/6622587 – eyllanesc Sep 01 '19 at 18:17
  • Yes, I initially used that, but I opted for a "simpler" solution due to the nature of the question. If I've time later I'll edit it to explain that too. – musicamante Sep 01 '19 at 18:27