如何解决在 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 举报,一经查实,本站将立刻删除。