![](/img/trans.png)
[英]Python: how to input object properties and build and save object with PyQt widget?
[英]How to save and restore widget properties that is unique for each instance of the widget?
我希望能夠在我的 PyQt5 應用程序中保存和恢復小部件狀態(屬性和值)。 小部件狀態應保存到 .ini 文件中。 這已經在以下 Stackoverflow 答案中得到了證明:
但是,上述問題的答案並未解決我在下面的示例代碼中面臨的特定問題。 我希望從設置 .ini 文件中保存和恢復對於正在運行的父小部件(主窗口應用程序)的每個實例都是唯一的。 因此,在保存小部件屬性時,對所有小部件的迭代應該只發生在父小部件(主窗口)的子部件上,而不是通過當前在全局應用程序中運行的所有小部件。
我認為下面示例代碼中的問題與for w in QtWidgets.qApp.allWidgets():
的行for w in QtWidgets.qApp.allWidgets():
。 我認為這一行迭代了當前在 PyQt5 全局應用程序中打開的所有小部件。 但是,當同一個父widget有多個實例時,objectName就會出現重復。 盡管可以在 init() 期間為每個實例指定一個唯一名稱(例如: app_name
)並在 QSettings 鍵中進行說明,但這可能不是最好的通用解決方案。 因此,我該如何解決我面臨的問題? 如何讓settings_save()
函數遍歷父窗口小部件(主窗口應用程序)實例的所有子窗口小部件,而不是遍歷當前在全局應用程序中運行的所有父窗口小部件? 在 Qt 文檔中,我找不到類似於allWidgets()
的函數,它允許我指定父小部件(例如:QMainWindow)並為我提供其下的所有小部件和對象。 如果可以在父小部件中獲取所有小部件,那么我可以輕松修改函數settings_save()
不僅包括 QSetting 變量作為參數,還包括我想要為其保存設置的小部件實例。
main_app.py
import sys
from PyQt5 import QtWidgets, uic, QtCore, QtGui
from PyQt5.QtCore import QFileInfo, QSettings, QObject
from PyQt5.QtWidgets import qApp
from ui_mainwindow import Ui_MainWindow
def settings_value_is_valid(val):
# https://stackoverflow.com/a/60028282/4988010
if isinstance(val, QtGui.QPixmap):
return not val.isNull()
return True
def settings_restore(settings):
# https://stackoverflow.com/a/60028282/4988010
finfo = QtCore.QFileInfo(settings.fileName())
if finfo.exists() and finfo.isFile():
for w in QtWidgets.qApp.allWidgets():
if w.objectName() and not w.objectName().startswith("qt_"):
# 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)
# print(prop, name, last_value, key)
if not settings.contains(key):
continue
val = settings.value(key, type=type(last_value),)
if (
val != last_value
and settings_value_is_valid(val)
and prop.isValid()
and prop.isWritable()
):
w.setProperty(name, val)
def settings_save(settings):
# https://stackoverflow.com/a/60028282/4988010
for w in QtWidgets.qApp.allWidgets():
if w.objectName() and not w.objectName().startswith("qt_"):
# 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 settings_value_is_valid(val) and prop.isValid() and prop.isWritable():
settings.setValue(key, w.property(name))
class MyApp(QtWidgets.QMainWindow):
def __init__(self, app_name='DefaultAppName'):
super(MyApp, self).__init__()
self.settings = QSettings("./temp/gui_settings-{}.ini".format(app_name), QSettings.IniFormat)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# Set window titlte and qlineedit to indicate the name of app instance
self.ui.app_instance_name.setText(app_name)
self.setWindowTitle(app_name)
# Load the saved config file saved from previous app usage
self.config_widgets_load_settings()
self.ui.action_save_current_config.triggered.connect(self.config_widgets_save_settings)
self.ui.action_load_config.triggered.connect(self.config_widgets_load_settings)
self.ui.action_clear_config_file.triggered.connect(self.config_clear_settings)
def config_widgets_save_settings(self):
# Write current state to the settings config file
settings_save(self.settings)
def config_widgets_load_settings(self):
# Load settings config file
settings_restore(self.settings)
def config_clear_settings(self):
# Clear the settings config file
self.settings.clear()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
# First instance of the MyApp
app_one = MyApp(app_name='App#1')
app_one.show()
# Second instance of the MyApp
app_two = MyApp(app_name='App#2')
app_two.show()
app.exec_()
ui_mainwindow.py
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(330, 202)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayoutWidget = QtWidgets.QWidget(self.centralwidget)
self.gridLayoutWidget.setGeometry(QtCore.QRect(20, 80, 291, 51))
self.gridLayoutWidget.setObjectName("gridLayoutWidget")
self.gridLayout = QtWidgets.QGridLayout(self.gridLayoutWidget)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setObjectName("gridLayout")
self.lineEdit1 = QtWidgets.QLineEdit(self.gridLayoutWidget)
self.lineEdit1.setObjectName("lineEdit1")
self.gridLayout.addWidget(self.lineEdit1, 2, 0, 1, 1)
self.pushButton1 = QtWidgets.QPushButton(self.gridLayoutWidget)
self.pushButton1.setCheckable(True)
self.pushButton1.setObjectName("pushButton1")
self.gridLayout.addWidget(self.pushButton1, 2, 1, 1, 1)
self.spinBox1 = QtWidgets.QSpinBox(self.gridLayoutWidget)
self.spinBox1.setMaximum(10000)
self.spinBox1.setProperty("value", 37)
self.spinBox1.setObjectName("spinBox1")
self.gridLayout.addWidget(self.spinBox1, 2, 2, 1, 1)
self.label = QtWidgets.QLabel(self.gridLayoutWidget)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 1, 0, 1, 3)
self.horizontalLayoutWidget = QtWidgets.QWidget(self.centralwidget)
self.horizontalLayoutWidget.setGeometry(QtCore.QRect(50, 20, 241, 31))
self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.label_2 = QtWidgets.QLabel(self.horizontalLayoutWidget)
self.label_2.setObjectName("label_2")
self.horizontalLayout.addWidget(self.label_2)
self.app_instance_name = QtWidgets.QLineEdit(self.horizontalLayoutWidget)
self.app_instance_name.setObjectName("app_instance_name")
self.horizontalLayout.addWidget(self.app_instance_name)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 330, 21))
self.menubar.setObjectName("menubar")
self.menuFile = QtWidgets.QMenu(self.menubar)
self.menuFile.setObjectName("menuFile")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.action_save_current_config = QtWidgets.QAction(MainWindow)
self.action_save_current_config.setObjectName("action_save_current_config")
self.action_load_config = QtWidgets.QAction(MainWindow)
self.action_load_config.setObjectName("action_load_config")
self.action_clear_config_file = QtWidgets.QAction(MainWindow)
self.action_clear_config_file.setObjectName("action_clear_config_file")
self.menuFile.addAction(self.action_save_current_config)
self.menuFile.addAction(self.action_load_config)
self.menuFile.addAction(self.action_clear_config_file)
self.menubar.addAction(self.menuFile.menuAction())
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.lineEdit1.setText(_translate("MainWindow", "This is default text"))
self.pushButton1.setText(_translate("MainWindow", "Push me"))
self.label.setText(_translate("MainWindow", "My simple app with various widgets"))
self.label_2.setText(_translate("MainWindow", "App instance name"))
self.menuFile.setTitle(_translate("MainWindow", "File"))
self.action_save_current_config.setText(_translate("MainWindow", "Save current config"))
self.action_load_config.setText(_translate("MainWindow", "Load config"))
self.action_clear_config_file.setText(_translate("MainWindow", "Clear config file"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
我在進一步挖掘 Qt 文檔后找到了解決方案。 每個QWidget都是QObject類型,它有一個名為findChildren()的方法。 此方法可用於指定要為其保存/恢復設置的父小部件。
以下是創建的有助於保存/恢復設置的方法。 我的貢獻是創建了函數settings_get_all_widgets()
。 保存/恢復功能最初由@eyllanesc創建,我對其進行了修改以解決此問題。
def settings_get_all_widgets(parent):
# Possible fix to the issue:
# https://stackoverflow.com/questions/64202927/how-to-save-and-restore-widget-properties-that-is-unique-for-each-instance-of-th
if parent:
# Find all children inside the given parent that is of type QWidget
all_widgets = parent.findChildren(QtWidgets.QWidget)
if parent.isWidgetType():
# If parent is of type QWidget, add the parent itself to the list
all_widgets.append(parent)
else:
# If no parent is given then get all the widgets from all the PyQt applications
all_widgets = QtWidgets.qApp.allWidgets()
return all_widgets
def settings_value_is_valid(val):
# Originally adapted from:
# https://stackoverflow.com/a/60028282/4988010
# https://github.com/eyllanesc/stackoverflow/issues/26#issuecomment-703184281
if isinstance(val, QtGui.QPixmap):
return not val.isNull()
return True
def settings_restore(settings, parent=None):
# Originally adapted from:
# https://stackoverflow.com/a/60028282/4988010
# https://github.com/eyllanesc/stackoverflow/issues/26#issuecomment-703184281
if not settings:
return
all_widgets = settings_get_all_widgets(parent)
finfo = QtCore.QFileInfo(settings.fileName())
if finfo.exists() and finfo.isFile():
for w in all_widgets:
if w.objectName() and not w.objectName().startswith("qt_"):
# 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 settings_value_is_valid(val)
and prop.isValid()
and prop.isWritable()
):
w.setProperty(name, val)
def settings_save(settings, parent=None):
# Originally adapted from:
# https://stackoverflow.com/a/60028282/4988010
# https://github.com/eyllanesc/stackoverflow/issues/26#issuecomment-703184281
if not settings:
return
all_widgets = settings_get_all_widgets(parent)
for w in all_widgets:
if w.objectName() and not w.objectName().startswith("qt_"):
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 settings_value_is_valid(val) and prop.isValid() and prop.isWritable():
settings.setValue(key, w.property(name))
以下是自包含的工作代碼,其中包含原始問題的解決方案。
import sys
from PyQt5 import QtWidgets, uic, QtCore, QtGui
from PyQt5.QtCore import QFileInfo, QSettings, QObject
from PyQt5.QtWidgets import qApp
# from ui_mainwindow import Ui_MainWindow
def settings_get_all_widgets(parent):
# Possible fix to the issue:
# https://stackoverflow.com/questions/64202927/how-to-save-and-restore-widget-properties-that-is-unique-for-each-instance-of-th
if parent:
# Find all children inside the given parent that is of type QWidget
all_widgets = parent.findChildren(QtWidgets.QWidget)
if parent.isWidgetType():
# If parent is of type QWidget, add the parent itself to the list
all_widgets.append(parent)
else:
# If no parent is given then get all the widgets from all the PyQt applications
all_widgets = QtWidgets.qApp.allWidgets()
return all_widgets
def settings_value_is_valid(val):
# Originally adapted from:
# https://stackoverflow.com/a/60028282/4988010
# https://github.com/eyllanesc/stackoverflow/issues/26#issuecomment-703184281
if isinstance(val, QtGui.QPixmap):
return not val.isNull()
return True
def settings_restore(settings, parent=None):
# Originally adapted from:
# https://stackoverflow.com/a/60028282/4988010
# https://github.com/eyllanesc/stackoverflow/issues/26#issuecomment-703184281
if not settings:
return
all_widgets = settings_get_all_widgets(parent)
finfo = QtCore.QFileInfo(settings.fileName())
if finfo.exists() and finfo.isFile():
for w in all_widgets:
if w.objectName() and not w.objectName().startswith("qt_"):
# 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 settings_value_is_valid(val)
and prop.isValid()
and prop.isWritable()
):
w.setProperty(name, val)
def settings_save(settings, parent=None):
# Originally adapted from:
# https://stackoverflow.com/a/60028282/4988010
# https://github.com/eyllanesc/stackoverflow/issues/26#issuecomment-703184281
if not settings:
return
all_widgets = settings_get_all_widgets(parent)
for w in all_widgets:
if w.objectName() and not w.objectName().startswith("qt_"):
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 settings_value_is_valid(val) and prop.isValid() and prop.isWritable():
settings.setValue(key, w.property(name))
class MyApp(QtWidgets.QMainWindow):
def __init__(self, app_name='DefaultAppName'):
super(MyApp, self).__init__()
self.settings = QSettings("./temp/gui_settings-{}.ini".format(app_name), QSettings.IniFormat)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# Set window titlte and qlineedit to indicate the name of app instance
self.ui.app_instance_name.setText(app_name)
self.setWindowTitle(app_name)
# Load the saved config file saved from previous app usage
self.config_widgets_load_settings()
self.ui.action_save_current_config.triggered.connect(self.config_widgets_save_settings)
self.ui.action_load_config.triggered.connect(self.config_widgets_load_settings)
self.ui.action_clear_config_file.triggered.connect(self.config_clear_settings)
def config_widgets_save_settings(self):
# Write current state to the settings config file
settings_save(self.settings, self)
def config_widgets_load_settings(self):
# Load settings config file
settings_restore(self.settings, self)
def config_clear_settings(self):
# Clear the settings config file
self.settings.clear()
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(330, 202)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayoutWidget = QtWidgets.QWidget(self.centralwidget)
self.gridLayoutWidget.setGeometry(QtCore.QRect(20, 80, 291, 51))
self.gridLayoutWidget.setObjectName("gridLayoutWidget")
self.gridLayout = QtWidgets.QGridLayout(self.gridLayoutWidget)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setObjectName("gridLayout")
self.lineEdit1 = QtWidgets.QLineEdit(self.gridLayoutWidget)
self.lineEdit1.setObjectName("lineEdit1")
self.gridLayout.addWidget(self.lineEdit1, 2, 0, 1, 1)
self.pushButton1 = QtWidgets.QPushButton(self.gridLayoutWidget)
self.pushButton1.setCheckable(True)
self.pushButton1.setObjectName("pushButton1")
self.gridLayout.addWidget(self.pushButton1, 2, 1, 1, 1)
self.spinBox1 = QtWidgets.QSpinBox(self.gridLayoutWidget)
self.spinBox1.setMaximum(10000)
self.spinBox1.setProperty("value", 37)
self.spinBox1.setObjectName("spinBox1")
self.gridLayout.addWidget(self.spinBox1, 2, 2, 1, 1)
self.label = QtWidgets.QLabel(self.gridLayoutWidget)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 1, 0, 1, 3)
self.horizontalLayoutWidget = QtWidgets.QWidget(self.centralwidget)
self.horizontalLayoutWidget.setGeometry(QtCore.QRect(50, 20, 241, 31))
self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.label_2 = QtWidgets.QLabel(self.horizontalLayoutWidget)
self.label_2.setObjectName("label_2")
self.horizontalLayout.addWidget(self.label_2)
self.app_instance_name = QtWidgets.QLineEdit(self.horizontalLayoutWidget)
self.app_instance_name.setObjectName("app_instance_name")
self.horizontalLayout.addWidget(self.app_instance_name)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 330, 21))
self.menubar.setObjectName("menubar")
self.menuFile = QtWidgets.QMenu(self.menubar)
self.menuFile.setObjectName("menuFile")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.action_save_current_config = QtWidgets.QAction(MainWindow)
self.action_save_current_config.setObjectName("action_save_current_config")
self.action_load_config = QtWidgets.QAction(MainWindow)
self.action_load_config.setObjectName("action_load_config")
self.action_clear_config_file = QtWidgets.QAction(MainWindow)
self.action_clear_config_file.setObjectName("action_clear_config_file")
self.menuFile.addAction(self.action_save_current_config)
self.menuFile.addAction(self.action_load_config)
self.menuFile.addAction(self.action_clear_config_file)
self.menubar.addAction(self.menuFile.menuAction())
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.lineEdit1.setText(_translate("MainWindow", "This is default text"))
self.pushButton1.setText(_translate("MainWindow", "Push me"))
self.label.setText(_translate("MainWindow", "My simple app with various widgets"))
self.label_2.setText(_translate("MainWindow", "App instance name"))
self.menuFile.setTitle(_translate("MainWindow", "File"))
self.action_save_current_config.setText(_translate("MainWindow", "Save current config"))
self.action_load_config.setText(_translate("MainWindow", "Load config"))
self.action_clear_config_file.setText(_translate("MainWindow", "Clear config file"))
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
# First instance of the MyApp
app_one = MyApp(app_name='App#1')
app_one.show()
# Second instance of the MyApp
app_two = MyApp(app_name='App#2')
app_two.show()
app.exec_()
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.