微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在 PyQt5 中,如何保存和恢复插入到 QTableWidget 单元格中的用户键入的值和动态创建的小部件?

如何解决在 PyQt5 中,如何保存和恢复插入到 QTableWidget 单元格中的用户键入的值和动态创建的小部件?

描述
我有一个 pyqt5 UI,它有一个带有动态行数的 QTableWidget;有一个添加行的按钮。添加一行时,一些单元格包含 QSpinBox(s),一个包含 QComboBox。该程序还具有保存和恢复QPushButton(S),即选择,将所有小部件保存到PY文件旁边的INI文件中。由 @eyllanesc 创建并找到 here 的保存和恢复方法在我在 SO 上找到的内容中几乎普遍使用。

问题
下面的代码很好地保存了 QSpinBox(s) 和 QComboBox。它们在 ini 文件中。恢复功能不会将这些小部件恢复到 QTableWidget 中。行数已恢复,但没有输入文本或小部件位于它们所在的单元格内。

我尝试了什么

  • 我使用前缀__ 名称命名动态创建的小部件。我尝试在 UI 的初始化中创建这些,认为可能存在指向 qApp.allWidgets() 和启动的链接,但这不起作用。我只是猜测。
  • 阅读 @zythyr's 帖子 here,我想我可能需要将小部件添加到 QTableWidget 以父子排序(这是我不知道的),但 QTableWidget 没有没有 addWidget() 方法。然后我在 QComboBox 上尝试了 *.setParent(在下面的代码中注释掉了),但也没有奏效。

问题
如何保存和恢复用户输入(在空单元格中键入)数据以及 QTableWidget 中的 QWidget(即 QSpinBox 和 QComboBox)?

代码

from PyQt5.QtGui import Qpixmap
from PyQt5.QtCore import QSettings,QFileInfo
from PyQt5.QtWidgets import (QApplication,qApp,QMainWindow,QWidget,QVBoxLayout,QTableWidget,QTableWidgetItem,QHeaderView,QPushButton,QLineEdit,QSpinBox,QComboBox)


def value_is_valid(val):
    #https://stackoverflow.com/a/60028282/4988010
    if isinstance(val,Qpixmap):
        return not val.isNull()
    return True

def restore(settings):
    #https://stackoverflow.com/a/60028282/4988010
    finfo = QFileInfo(settings.fileName())

    if finfo.exists() and finfo.isFile():
        for w in qApp.allWidgets():
            if w.objectName():
                mo = w.MetaObject()
                for i in range(mo.propertyCount()):
                    prop = mo.property(i)
                    name = prop.name()
                    last_value = w.property(name)
                    key = "{}/{}".format(w.objectName(),name)
                    if not settings.contains(key):
                        continue
                    val = settings.value(key,type=type(last_value),)
                    if (
                        val != last_value
                        and value_is_valid(val)
                        and prop.isValid()
                        and prop.isWritable()
                    ):
                        w.setProperty(name,val)

def save(settings):
    #https://stackoverflow.com/a/60028282/4988010
    for w in qApp.allWidgets():
        if w.objectName():
            mo = w.MetaObject()
            for i in range(mo.propertyCount()):
                prop = mo.property(i)
                name = prop.name()
                key = "{}/{}".format(w.objectName(),name)
                val = w.property(name)
                if value_is_valid(val) and prop.isValid() and prop.isWritable():
                    settings.setValue(key,w.property(name))


# custom spin Box allowing for optional maximum           
class CustomSpinBox(QSpinBox):
    def __init__(self,sb_value=1,sb_step=1,sb_min=1,*args):
        super(CustomSpinBox,self).__init__()

        self.setValue(sb_value)
        self.setSingleStep(sb_step)
        self.setMinimum(sb_min)
        if len(args) > 0:
            sb_max = args[0]
            self.setMaximum(sb_max)


class MainWindow(QMainWindow):
    
    # save settings alongside *py file
    settings = QSettings("temp.ini",QSettings.IniFormat)
    
    def __init__(self):
        super().__init__()
        self.initUI()
        self.initSignals()
        
    def initUI(self):
        # standar UI stuff
        self.setobjectName('MainWindow')
        self.setwindowTitle('Program Title')
        self.setGeometry(400,400,100)
        wid = QWidget(self)
        self.setCentralWidget(wid)
        
        # create some widgets
        self.pb_add_row = QPushButton('Add Row')
        self.pb_remove_row = QPushButton('Remove Selected Row')
        self.pb_save = QPushButton('Save')
        self.pb_restore = QPushButton('Restore')
        
        self.le = QLineEdit()
        self.le.setobjectName('le')
        
        self.tbl = QTableWidget()
        self.tbl.setobjectName('r_tbl')
        header = self.tbl.horizontalHeader()
        self.tbl.setRowCount(0)
        self.tbl.setColumnCount(4)
        input_header = ['Label','X','Y','Comment']
        self.tbl.setHorizontalHeaderLabels(input_header)
        header.setSectionResizeMode(QHeaderView.Stretch)
        
        # add widgets to UI
        self.vBox = QVBoxLayout()
        self.vBox.addWidget(self.le)
        self.vBox.addWidget(self.tbl)
        self.vBox.addWidget(self.pb_add_row)
        self.vBox.addWidget(self.pb_remove_row)
        self.vBox.addWidget(self.pb_save)
        self.vBox.addWidget(self.pb_restore)
        wid.setLayout(self.vBox)
        
        # restore prevIoUs settings from *.ini file
        restore(self.settings)
    
    # pb signals
    def initSignals(self):
        self.pb_add_row.clicked.connect(self.pb_add_row_clicked)
        self.pb_remove_row.clicked.connect(self.pb_remove_row_clicked)
        self.pb_save.clicked.connect(self.pb_save_clicked)
        self.pb_restore.clicked.connect(self.pb_restore_clicked)
        
    # add a new row to the end - add spin Boxes and a comboBox
    def pb_add_row_clicked(self):
        current_row_count = self.tbl.rowCount()
        row_count = current_row_count + 1
        self.tbl.setRowCount(row_count)
        
        self.sb_1 = CustomSpinBox(1,1,1)
        self.sb_1.setobjectName(f'sb_{row_count}_1')
        self.sb_2 = CustomSpinBox(3,2,6)
        self.sb_2.setobjectName(f'sb_{row_count}_2')
        self.cmb = QComboBox()
        choices = ['choice_1','choice_2','choice_3']
        self.cmb.addItems(choices)
        self.cmb.setobjectName(f'cmb_{row_count}_0')
        
        self.tbl.setCellWidget(current_row_count,self.cmb)
        self.tbl.setCellWidget(current_row_count,self.sb_1)
        self.tbl.setCellWidget(current_row_count,self.sb_2)
        
        #self.cmb.setParent(self.tbl) # <<<< this didn't work   

    def pb_remove_row_clicked(self):
        self.tbl.removeRow(self.tbl.currentRow()) 
        
    def pb_save_clicked(self):
        print(f'{self.pb_save.text()} clicked')
        save(self.settings)
        
    def pb_restore_clicked(self):
        print(f'{self.pb_restore.text()} clicked')
        restore(self.settings)


if __name__ == "__main__":
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec_()

EDIT1 ...删除,因为它没有帮助,只是让它更混乱...

EDIT2
除了小部件,我已经研究了如何使用 QSettings 从 QTableWidget 保存和恢复用户输入的单元格数据。希望以下代码可以帮助某人。我毫不怀疑它可以做得更好,我欢迎改进建议。如果我能解决向单元格添加小部件(QSpinBoxes 和 QComboBoxes)的问题,我会更新。

import sys
from PyQt5.QtCore import QSettings
from PyQt5.QtWidgets import (QApplication,QPushButton)

class MainWindow(QMainWindow):
    # save settings alongside *py file
    settings = QSettings("temp.ini",QSettings.IniFormat)
     
    def __init__(self):
        super().__init__()
        self.initUI()
        self.initSignals()
        self.restore_settings()
        
    def initUI(self):
        # standar UI stuff
        self.setobjectName('MainWindow')
        self.setwindowTitle('Program Title')
        self.setGeometry(400,500,300)
        wid = QWidget(self)
        self.setCentralWidget(wid)
        
        # create some widgets
        self.pb_add_row = QPushButton('Add Row')
        self.pb_remove_row = QPushButton('Remove Selected Row')
        self.pb_save = QPushButton('Save')
        self.pb_restore = QPushButton('Restore')
        self.tbl = QTableWidget(0,4,self)
        
        # config up the table        
        header = self.tbl.horizontalHeader()
        input_header = ['Label','Comment']
        self.tbl.setHorizontalHeaderLabels(input_header)
        header.setSectionResizeMode(QHeaderView.Stretch)
        
        # add widgets to UI
        self.vBox = QVBoxLayout()
        self.vBox.addWidget(self.tbl)
        self.vBox.addWidget(self.pb_add_row)
        self.vBox.addWidget(self.pb_remove_row)
        self.vBox.addWidget(self.pb_save)
        self.vBox.addWidget(self.pb_restore)
        wid.setLayout(self.vBox)
            
    # pb signals
    def initSignals(self):#
        self.pb_add_row.clicked.connect(self.pb_add_row_clicked)
        self.pb_remove_row.clicked.connect(self.pb_remove_row_clicked)
        self.pb_save.clicked.connect(self.pb_save_clicked)
        self.pb_restore.clicked.connect(self.pb_restore_clicked)

    # reads in the ini file adn re-generate the table contents
    def restore_settings(self):
        self.setting_value = self.settings.value('table')
        self.setting_row = self.settings.value('rows')
        self.setting_col = self.settings.value('columns')
        print(f'RESTORE: {self.setting_value}')
        
        # change the table row/columns,create a dictionary out of the saved table
        try:
            self.tbl.setRowCount(int(self.setting_row))
            self.tbl.setColumnCount(int(self.setting_col))
            self.my_dict = dict(eval(self.setting_value))
        except TypeError:
            print(f'RESTORE: No ini file,resulting in no rows/columns')
        
        # loop over each table cell and populate with old values
        for row in range(self.tbl.rowCount()):
            for col in range(self.tbl.columnCount()):
                try:
                    if col == 0: self.tbl.setItem(row,col,QTableWidgetItem(self.my_dict['Label'][row]))
                    if col == 1: self.tbl.setItem(row,QTableWidgetItem(self.my_dict['X'][row]))
                    if col == 2: self.tbl.setItem(row,QTableWidgetItem(self.my_dict['Y'][row]))
                    if col == 3: self.tbl.setItem(row,QTableWidgetItem(self.my_dict['Comment'][row]))                              
                except IndexError:
                    print(f'INDEX ERROR')          
        
    # add a new row to the end 
    def pb_add_row_clicked(self):
        current_row_count = self.tbl.rowCount()
        row_count = current_row_count + 1
        self.tbl.setRowCount(row_count)

    # remove selected row
    def pb_remove_row_clicked(self):
        self.tbl.removeRow(self.tbl.currentRow())
    
    # save the table contents and table row/column to the ini file
    def pb_save_clicked(self):
        # create an empty dictionary
        self.tbl_dict = {'Label':[],'X':[],'Y':[],'Comment':[]}
        
        # loop over the cells and add to the table
        for column in range(self.tbl.columnCount()):
            for row in range(self.tbl.rowCount()):
                itm = self.tbl.item(row,column)
                try:
                    text = itm.text()
                except AttributeError: # happens when the cell is empty
                    text = ''
                if column == 0: self.tbl_dict['Label'].append(text)
                if column == 1: self.tbl_dict['X'].append(text)
                if column == 2: self.tbl_dict['Y'].append(text)
                if column == 3: self.tbl_dict['Comment'].append(text)
        
        # write values to ini file      
        self.settings.setValue('table',str(self.tbl_dict))
        self.settings.setValue('rows',self.tbl.rowCount())
        self.settings.setValue('columns',self.tbl.columnCount())
        print(f'WRITE: {self.tbl_dict}')
        
    def pb_restore_clicked(self):
        self.restore_settings()

    def closeEvent(self,event):
        self.pb_save_clicked()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec_()

解决方法

挑战
我相信有比这更好的方法,我挑战那些精通 Qt (pyqt5) 的人找到更好的解决方案。

答案
同时,我希望以下内容可以帮助某人,因为我在任何地方都找不到这些信息。我真的看不懂 C++ Qt 的东西,而且我不是 pyqt5 Harry Potter,所以这需要一些努力。

这里的一般要点是从未保存小部件。这些值被保存到字典中,然后作为字符串保存到 QSettings ini 文件中。在启动时,解析了 ini 文件并重建了表 - 行和列。然后从头开始构建小部件并重新插入到表格中。然后解析 ini 文件中的字典,并根据需要将值应用于小部件或空白单元格。

愿望
我真的希望根据 OP 链接有一种与保存/恢复方法内联的方法。我发现如果我在 init 中包含这些保存/恢复方法,它有时会完全弄乱表格。因此,对于我更大的程序,看起来我将不得不手动保存和恢复所有设置。

MRE 代码

import sys

from PyQt5.QtCore import QSettings
from PyQt5.QtWidgets import (QApplication,QMainWindow,QWidget,QVBoxLayout,QTableWidget,QTableWidgetItem,QHeaderView,QPushButton,QComboBox,QSpinBox)


# custom spin box allowing for optional maximum           
class CustomSpinBox(QSpinBox):
    def __init__(self,sb_value=1,sb_step=1,sb_min=1,*args):
        super(CustomSpinBox,self).__init__()

        self.setValue(sb_value)
        self.setSingleStep(sb_step)
        self.setMinimum(sb_min)
        if len(args) > 0:
            sb_max = args[0]
            self.setMaximum(sb_max)


class MainWindow(QMainWindow):
    # save settings alongside *py file
    settings = QSettings("temp.ini",QSettings.IniFormat)
     
    def __init__(self):
        super().__init__()
        self.initUI()
        self.initSignals()
        self.restore_settings()
        
    def initUI(self):
        # standar UI stuff
        self.setObjectName('MainWindow')
        self.setWindowTitle('Program Title')
        self.setGeometry(400,400,500,300)
        wid = QWidget(self)
        self.setCentralWidget(wid)
        
        # create some widgets
        self.pb_add_row = QPushButton('Add Row')
        self.pb_remove_row = QPushButton('Remove Selected Row')
        self.pb_save = QPushButton('Save')
        self.pb_restore = QPushButton('Restore')
        self.tbl = QTableWidget(0,4,self)
        
        # config up the table        
        header = self.tbl.horizontalHeader()
        input_header = ['Label','X','Y','Comment']
        self.tbl.setHorizontalHeaderLabels(input_header)
        header.setSectionResizeMode(QHeaderView.Stretch)
        
        # name the table for QSettings
        self.tbl.setObjectName('input_table')
        
        # add widgets to UI
        self.vbox = QVBoxLayout()
        self.vbox.addWidget(self.tbl)
        self.vbox.addWidget(self.pb_add_row)
        self.vbox.addWidget(self.pb_remove_row)
        self.vbox.addWidget(self.pb_save)
        wid.setLayout(self.vbox)
        
        # create an empty dictionary to house the table widgets 
        self.table_widgets = {'cmb':[],'sb_1':[],'sb_2':[]}
        # combobox values
        self.choices = ['choice_1','choice_2','choice_3']
            
    # pb signals
    def initSignals(self):#
        self.pb_add_row.clicked.connect(self.pb_add_row_clicked)
        self.pb_remove_row.clicked.connect(self.pb_remove_row_clicked)
        self.pb_save.clicked.connect(self.pb_save_clicked)

    # reads in the ini file and re-generate the table contents
    def restore_settings(self):
        try:
            self.setting_tbl = self.settings.value('table')
            self.setting_row = self.settings.value('rows')
            self.setting_col = self.settings.value('columns')
            self.my_dict = dict(eval(self.setting_tbl))
            
            # need to rebuild the table first
            self.tbl.setRowCount(int(self.setting_row))
            self.tbl.setColumnCount(int(self.setting_col))
            
            print(f'RESTORE: row:{self.setting_row} and col:{self.setting_col} and table:{self.setting_tbl}')
            
            # probably don't need to build and return values from the dictionary
            for row in range(int(self.setting_row)):
                self.table_widgets['cmb'].append(QComboBox())
                self.table_widgets['sb_1'].append(CustomSpinBox(1,1,1))
                self.table_widgets['sb_2'].append(CustomSpinBox(3,2,6))
                    
                self.table_widgets['cmb'][row].addItems(self.choices)  

                self.tbl.setCellWidget(row,self.table_widgets['cmb'][row])
                self.tbl.setCellWidget(row,self.table_widgets['sb_1'][row])
                self.tbl.setCellWidget(row,self.table_widgets['sb_2'][row])
                
                self.tbl.cellWidget(row,0).setCurrentText(self.my_dict['Label'][row])
                self.tbl.cellWidget(row,1).setValue(self.my_dict['X'][row])
                self.tbl.cellWidget(row,2).setValue(self.my_dict['Y'][row])
                self.tbl.setItem(row,3,QTableWidgetItem(self.my_dict['Comment'][row]))
                
        except TypeError:
            print('NO INI FILE PRESENT')

        
    # add a new row to the end 
    def pb_add_row_clicked(self):
        current_row_count = self.tbl.rowCount()
        row_count = current_row_count + 1
        self.tbl.setRowCount(row_count)
        self.table_widgets['cmb'].append(QComboBox())
        self.table_widgets['sb_1'].append(CustomSpinBox(1,1))
        self.table_widgets['sb_2'].append(CustomSpinBox(3,6))
        self.table_widgets['cmb'][-1].addItems(self.choices)  
        self.tbl.setCellWidget(current_row_count,self.table_widgets['cmb'][current_row_count])
        self.tbl.setCellWidget(current_row_count,self.table_widgets['sb_1'][current_row_count])
        self.tbl.setCellWidget(current_row_count,self.table_widgets['sb_2'][current_row_count])
    
    # save the table contents and table row/column to the ini file
    def pb_save_clicked(self):
        #save(self.settings)

        # create an empty dictionary
        self.tbl_dict = {'Label':[],'X':[],'Y':[],'Comment':[]}
        
        # loop over the cells and add to the dictionary
        for row in range(self.tbl.rowCount()):
            
            cmb_text = self.tbl.cellWidget(row,0).currentText()
            sb_1_value = self.tbl.cellWidget(row,1).value()
            sb_2_value = self.tbl.cellWidget(row,2).value()
            comment_text = self.tbl.item(row,3)
 
            try:
                comment_text = comment_text.text()
            except AttributeError: # happens when the cell is empty or a widget
                comment_text = ''
                
            self.tbl_dict['Label'].append(cmb_text)
            self.tbl_dict['X'].append(sb_1_value)
            self.tbl_dict['Y'].append(sb_2_value)
            self.tbl_dict['Comment'].append(comment_text)
        
        # write values to ini file      
        self.settings.setValue('table',str(self.tbl_dict))
        self.settings.setValue('rows',self.tbl.rowCount())
        self.settings.setValue('columns',self.tbl.columnCount())
        print(f'WRITE TO INI FILE: {self.tbl_dict}')

    # remove selected row
    def pb_remove_row_clicked(self):
        self.tbl.removeRow(self.tbl.currentRow())

    def closeEvent(self,event):
        self.pb_save_clicked()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec_()

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。