Python PyQt4函数以保存和恢复UI小部件的值?

25

在我尝试编写自己的Python PyQt4模块函数之前...我想问一下是否有人有这样的函数可以分享。

在许多使用PyQt4和qtDesigner构建GUI的Python程序中,我使用QSettings方法保存和恢复所有小部件的UI状态和值,以便在关闭和启动时使用。

以下示例展示了我如何保存和恢复一些lineEdit、checkBox和radioButton字段。

是否有一个可以遍历UI并查找所有小部件/控件及其状态并将它们保存(例如guisave())以及另一个可以恢复它们(例如guirestore())的函数呢?

我的closeEvent看起来像这样:

#---------------------------------------------
# close by x OR call to self.close
#---------------------------------------------

def closeEvent(self, event):      # user clicked the x or pressed alt-F4...

    UI_VERSION = 1   # increment this whenever the UI changes significantly

    programname = os.path.basename(__file__)
    programbase, ext = os.path.splitext(programname)  # extract basename and ext from filename
    settings = QtCore.QSettings("company", programbase)    
    settings.setValue("geometry", self.saveGeometry())  # save window geometry
    settings.setValue("state", self.saveState(UI_VERSION))   # save settings (UI_VERSION is a constant you should increment when your UI changes significantly to prevent attempts to restore an invalid state.)

    # save ui values, so they can be restored next time
    settings.setValue("lineEditUser", self.lineEditUser.text());
    settings.setValue("lineEditPass", self.lineEditPass.text());

    settings.setValue("checkBoxReplace", self.checkBoxReplace.checkState());
    settings.setValue("checkBoxFirst", self.checkBoxFirst.checkState());

    settings.setValue("radioButton1", self.radioButton1.isChecked());

    sys.exit()  # prevents second call

我的MainWindow初始化大致如下:

def __init__(self, parent = None):
    # initialization of the superclass
    super(QtDesignerMainWindow, self).__init__(parent)
    # setup the GUI --> function generated by pyuic4
    self.setupUi(self)

    #---------------------------------------------
    # restore gui position and restore fields
    #---------------------------------------------

    UI_VERSION = 1

    settings = QtCore.QSettings("company", programbase)    # http://pyqt.sourceforge.net/Docs/PyQt4/pyqt_qsettings.html

    self.restoreGeometry(settings.value("geometry"))
    self.restoreState(settings.value("state"),UI_VERSION) 

    self.lineEditUser.setText(str(settings.value("lineEditUser")))  # restore lineEditFile
    self.lineEditPass.setText(str(settings.value("lineEditPass")))  # restore lineEditFile

    if settings.value("checkBoxReplace") != None:
        self.checkBoxReplace.setCheckState(settings.value("checkBoxReplace"))   # restore checkbox
    if settings.value("checkBoxFirst") != None:
        self.checkBoxFirst.setCheckState(settings.value("checkBoxFirst"))   # restore checkbox

    value = settings.value("radioButton1").toBool()
    self.ui.radioButton1.setChecked(value)
7个回答

30

好的,我编写了一个具有两个函数的模块来完成我所要求的任务。一旦我弄清楚了,它并不是很复杂,但每当你创建新的pyqt gui程序时,想要在会话之间保存小部件字段值时,它确实可以节省大量时间。目前,我只编码了lineEdit、checkBox和comboBox字段。如果有人想要添加或改进(例如:radio buttons...等等),我相信其他人,包括我自己,都会感激。

#===================================================================
# Module with functions to save & restore qt widget values
# Written by: Alan Lilly 
# Website: http://panofish.net
#===================================================================

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import inspect

#===================================================================
# save "ui" controls and values to registry "setting"
# currently only handles comboboxes editlines & checkboxes
# ui = qmainwindow object
# settings = qsettings object
#===================================================================

def guisave(ui, settings):

    #for child in ui.children():  # works like getmembers, but because it traverses the hierarachy, you would have to call guisave recursively to traverse down the tree

    for name, obj in inspect.getmembers(ui):
        #if type(obj) is QComboBox:  # this works similar to isinstance, but missed some field... not sure why?
        if isinstance(obj, QComboBox):
            name   = obj.objectName()      # get combobox name
            index  = obj.currentIndex()    # get current index from combobox
            text   = obj.itemText(index)   # get the text for current index
            settings.setValue(name, text)   # save combobox selection to registry

        if isinstance(obj, QLineEdit):
            name = obj.objectName()
            value = obj.text()
            settings.setValue(name, value)    # save ui values, so they can be restored next time

        if isinstance(obj, QCheckBox):
            name = obj.objectName()
            state = obj.checkState()
            settings.setValue(name, state)

#===================================================================
# restore "ui" controls with values stored in registry "settings"
# currently only handles comboboxes, editlines &checkboxes
# ui = QMainWindow object
# settings = QSettings object
#===================================================================

def guirestore(ui, settings):

    for name, obj in inspect.getmembers(ui):
        if isinstance(obj, QComboBox):
            index  = obj.currentIndex()    # get current region from combobox
            #text   = obj.itemText(index)   # get the text for new selected index
            name   = obj.objectName()

            value = unicode(settings.value(name))  

            if value == "":
                continue

            index = obj.findText(value)   # get the corresponding index for specified string in combobox

            if index == -1:  # add to list if not found
                obj.insertItems(0,[value])
                index = obj.findText(value)
                obj.setCurrentIndex(index)
            else:
                obj.setCurrentIndex(index)   # preselect a combobox value by index    

        if isinstance(obj, QLineEdit):
            name = obj.objectName()
            value = unicode(settings.value(name))  # get stored value from registry
            obj.setText(value)  # restore lineEditFile

        if isinstance(obj, QCheckBox):
            name = obj.objectName()
            value = settings.value(name)   # get stored value from registry
            if value != None:
                obj.setCheckState(value)   # restore checkbox

        #if isinstance(obj, QRadioButton):                

################################################################

if __name__ == "__main__":

    # execute when run directly, but not when called as a module.
    # therefore this section allows for testing this module!

    #print "running directly, not as a module!"

    sys.exit() 

非常有用的小模块。谢谢!当将QSettings保存到ini文件时,它可以很好地工作,例如guisave(self.ui, QtCore.QSettings('saved.ini', QtCore.QSettings.IniFormat)),这将创建一个可以在用户之间共享的文件。 - Snorfalorpagus
我不得不更改几行代码为 value = unicode(settings.value(name).toString()),因为 settings.value 返回的是 QVariant。 - Snorfalorpagus
酷炫的改进,Snorfalorpagus!我喜欢 :) - panofish
刚刚注意到对于 QCheckBox 对象,使用 obj.setCheckState(value) 会启用三态复选框,这可能并非期望的行为。我知道我从来没有想过要用三态复选框,因此我使用了 obj.setChecked(value) 。我不确定如何在加载时检测它,并为两种情况正确地恢复。 - Snorfalorpagus
挽救了我的一天 :) - limonik

11

这里是一个更新的代码片段,最初由Panofish先生分享。这些伟大的函数仍然相同,但现在可以在较新的PyQt和Python版本上进行使用,如果需要,只需进行微小的更改。感谢Panofish先生,开源万岁!:)

更改:

  • 已经更新为Python3和PyQt5
  • 添加了几何保存\还原
  • 添加了QRadioButton保存\还原
  • SetCheckState()被替换为SetChecked()以避免三态

def guisave(self):

  # Save geometry
    self.settings.setValue('size', self.size())
    self.settings.setValue('pos', self.pos())

    for name, obj in inspect.getmembers(ui):
      # if type(obj) is QComboBox:  # this works similar to isinstance, but missed some field... not sure why?
      if isinstance(obj, QComboBox):
          name = obj.objectName()  # get combobox name
          index = obj.currentIndex()  # get current index from combobox
          text = obj.itemText(index)  # get the text for current index
          settings.setValue(name, text)  # save combobox selection to registry

      if isinstance(obj, QLineEdit):
          name = obj.objectName()
          value = obj.text()
          settings.setValue(name, value)  # save ui values, so they can be restored next time

      if isinstance(obj, QCheckBox):
          name = obj.objectName()
          state = obj.isChecked()
          settings.setValue(name, state)

      if isinstance(obj, QRadioButton):
          name = obj.objectName()
          value = obj.isChecked()  # get stored value from registry
          settings.setValue(name, value)


def guirestore(self):

  # Restore geometry  
  self.resize(self.settings.value('size', QtCore.QSize(500, 500)))
  self.move(self.settings.value('pos', QtCore.QPoint(60, 60)))

  for name, obj in inspect.getmembers(ui):
      if isinstance(obj, QComboBox):
          index = obj.currentIndex()  # get current region from combobox
          # text   = obj.itemText(index)   # get the text for new selected index
          name = obj.objectName()

          value = (settings.value(name))

          if value == "":
              continue

          index = obj.findText(value)  # get the corresponding index for specified string in combobox

            if index == -1:  # add to list if not found
                obj.insertItems(0, [value])
                index = obj.findText(value)
                obj.setCurrentIndex(index)
            else:
                obj.setCurrentIndex(index)  # preselect a combobox value by index

      if isinstance(obj, QLineEdit):
          name = obj.objectName()
          value = (settings.value(name).decode('utf-8'))  # get stored value from registry
          obj.setText(value)  # restore lineEditFile

      if isinstance(obj, QCheckBox):
          name = obj.objectName()
          value = settings.value(name)  # get stored value from registry
          if value != None:
              obj.setChecked(strtobool(value))  # restore checkbox

      if isinstance(obj, QRadioButton):
         name = obj.objectName()
         value = settings.value(name)  # get stored value from registry
         if value != None:
             obj.setChecked(strtobool(value))

5

谢谢Panofish和大家。我为QSlider/QSpinBox添加了一些更新,这是小而简单的。

在guisave中,你可以添加:

if isinstance(obj, QSpinBox):
    name  = obj.objectName()
    value = obj.value()             # get stored value from registry
    settings.setValue(name, value)

if isinstance(obj, QSlider):
    name  = obj.objectName()
    value = obj.value()             # get stored value from registry
    settings.setValue(name, value)

在Guirestore中,您可以添加以下内容:
if isinstance(obj, QSlider):
    name = obj.objectName()
    value = settings.value(name)    # get stored value from registry
    if value != None:           
        obj. setValue(int(value))   # restore value from registry

if isinstance(obj, QSpinBox):
    name = obj.objectName()
    value = settings.value(name)    # get stored value from registry
    if value != None:
        obj. setValue(int(value))   # restore value from registry

4

I'm adding update for QListWidget.

In guisave:

if isinstance(obj, QListWidget):
    name = obj.objectName()
    settings.beginWriteArray(name)
    for i in range(obj.count()):
        settings.setArrayIndex(i)
        settings.setValue(name, obj.item(i).text())
    settings.endArray()

在Firestore中:

if isinstance(obj, QListWidget):
    name = obj.objectName()
    size = settings.beginReadArray(name)
    for i in range(size):
        settings.setArrayIndex(i)
        value = settings.value(name)  # get stored value from registry
        if value != None:
            obj.addItem(value)
    settings.endArray()

4

这里是另一个版本的出色代码,包含QTabWidget,并将所有内容封装到一个类中以便更易于使用:

qt_utils.py:

from PyQt5 import QtGui
from PyQt5.QtWidgets import QComboBox, QCheckBox, QLineEdit,\
    QRadioButton, QSpinBox, QSlider, QListWidget, QTabWidget
from PyQt5.QtCore import QSettings
from distutils.util import strtobool

import inspect


class QMainWindow(QtGui.QMainWindow):
    companie_name = 'CompanieName'
    software_name = 'SoftwareName'
    settings_ui_name = 'defaultUiwidget'
    settings_ui_user_name = 'user'
    _names_to_avoid = {}

    def __init__(self, parent=None):
        super(QMainWindow, self).__init__(parent)
        self.settings = QSettings(self.companie_name, self.software_name)

    def closeEvent(self, e):
        self._gui_save()

    @classmethod
    def _get_handled_types(cls):
        return QComboBox, QLineEdit, QCheckBox, QRadioButton, QSpinBox, QSlider, QListWidget, QTabWidget

    @classmethod
    def _is_handled_type(cls, widget):
        return any(isinstance(widget, t) for t in cls._get_handled_types())

    def _gui_save(self):
        """
        save "ui" controls and values to registry "setting"
        :return:
        """
        name_prefix = f"{self.settings_ui_name}/"
        self.settings.setValue(name_prefix + "geometry", self.saveGeometry())

        for name, obj in inspect.getmembers(self):
            if not self._is_handled_type(obj):
                continue

            name = obj.objectName()
            value = None
            if isinstance(obj, QComboBox):
                index = obj.currentIndex()  # get current index from combobox
                value = obj.itemText(index)  # get the text for current index

            elif isinstance(obj, QTabWidget):
                value = obj.currentIndex()

            elif isinstance(obj, QLineEdit):
                value = obj.text()

            elif isinstance(obj, QCheckBox):
                value = obj.isChecked()

            elif isinstance(obj, QRadioButton):
                value = obj.isChecked()

            elif isinstance(obj, QSpinBox):
                value = obj.value()

            elif isinstance(obj, QSlider):
                value = obj.value()

            elif isinstance(obj, QListWidget):
                self.settings.beginWriteArray(name)
                for i in range(obj.count()):
                    self.settings.setArrayIndex(i)
                    self.settings.setValue(name_prefix + name, obj.item(i).text())
                self.settings.endArray()

            if value is not None:
                self.settings.setValue(name_prefix + name, value)

    def _gui_restore(self):
        """
        restore "ui" controls with values stored in registry "settings"
        :return:
        """

        name_prefix = f"{self.settings_ui_name}/"
        geometry_value = self.settings.value(name_prefix + "geometry")
        if geometry_value:
            self.restoreGeometry(geometry_value)

        for name, obj in inspect.getmembers(self):
            if not self._is_handled_type(obj):
                continue
            if name in self._names_to_avoid:
                continue

            name = obj.objectName()
            value = None
            if not isinstance(obj, QListWidget):
                value = self.settings.value(name_prefix + name)
                if value is None:
                    continue

            if isinstance(obj, QComboBox):
                index = obj.findText(value)  # get the corresponding index for specified string in combobox

                if index == -1:  # add to list if not found
                    obj.insertItems(0, [value])
                    index = obj.findText(value)
                    obj.setCurrentIndex(index)
                else:
                    obj.setCurrentIndex(index)  # preselect a combobox value by index

            elif isinstance(obj, QTabWidget):
                try:
                    value = int(value)
                except ValueError:
                    value = 0
                obj.setCurrentIndex(value)

            elif isinstance(obj, QLineEdit):
                obj.setText(value)

            elif isinstance(obj, QCheckBox):
                obj.setChecked(strtobool(value))

            elif isinstance(obj, QRadioButton):
                obj.setChecked(strtobool(value))

            elif isinstance(obj, QSlider):
                obj.setValue(int(value))

            elif isinstance(obj, QSpinBox):
                obj.setValue(int(value))

            elif isinstance(obj, QListWidget):
                size = self.settings.beginReadArray(name_prefix + name)
                for i in range(size):
                    self.settings.setArrayIndex(i)
                    value = self.settings.value(name_prefix + name)
                    if value is not None:
                        obj.addItem(value)
                self.settings.endArray()

    def _add_setting(self, name, value):
        name_prefix = f"{self.settings_ui_user_name}/"
        self.settings.setValue(name_prefix + name, value)

    def _get_setting(self, name):
        name_prefix = f"{self.settings_ui_user_name}/"
        return self.settings.value(name_prefix + name)

以下是使用示例:

import qt_utils


class MyMaine(qt_utils.QMainWindow, Ui_MainWindow):
    companie_name = 'Name'
    software_name = 'softName'
    _names_to_avoid = {'my_widget_name_not_to_save'}

    def __init__(self, parent=None):
        super(MyMaine, self).__init__(parent)

        self.setupUi(self)

        self._gui_restore()

3

我发现这些答案很有用,所以我想把它们整合起来发布一个版本(对于PySide2/Qt5),去除一些重复并提供一个名称将设置分组。

from PySide2.QtWidgets import *
import inspect

def GetHandledTypes():
    return (QComboBox, QLineEdit, QCheckBox, QRadioButton, QSpinBox, QSlider, QListWidget)

def IsHandledType(widget):
    return any(isinstance(widget, t) for t in GetHandledTypes())

#===================================================================
# save "ui" controls and values to registry "setting"
#===================================================================

def GuiSave(ui : QWidget, settings : QSettings, uiName="uiwidget"):
    namePrefix = f"{uiName}/"
    settings.setValue(namePrefix + "geometry", ui.saveGeometry())

    for name, obj in inspect.getmembers(ui):
        if not IsHandledType(obj):
            continue

        name = obj.objectName()
        value = None
        if isinstance(obj, QComboBox):
            index = obj.currentIndex()  # get current index from combobox
            value = obj.itemText(index)  # get the text for current index

        if isinstance(obj, QLineEdit):
            value = obj.text()

        if isinstance(obj, QCheckBox):
            value = obj.isChecked()

        if isinstance(obj, QRadioButton):
            value = obj.isChecked()

        if isinstance(obj, QSpinBox):
            value = obj.value()

        if isinstance(obj, QSlider):
            value = obj.value()

        if isinstance(obj, QListWidget):
            settings.beginWriteArray(name)
            for i in range(obj.count()):
                settings.setArrayIndex(i)
                settings.setValue(namePrefix + name, obj.item(i).text())
            settings.endArray()
        elif value is not None:
            settings.setValue(namePrefix + name, value)

#===================================================================
# restore "ui" controls with values stored in registry "settings"
#===================================================================

def GuiRestore(ui : QWidget, settings : QSettings, uiName="uiwidget"):
    from distutils.util import strtobool

    namePrefix = f"{uiName}/"
    geometryValue = settings.value(namePrefix + "geometry")
    if geometryValue:
        ui.restoreGeometry(geometryValue)

    for name, obj in inspect.getmembers(ui):
        if not IsHandledType(obj):
            continue

        name = obj.objectName()
        value = None
        if not isinstance(obj, QListWidget):
            value = settings.value(namePrefix + name)
            if value is None:
                continue

        if isinstance(obj, QComboBox):
            index = obj.findText(value)  # get the corresponding index for specified string in combobox

            if index == -1:  # add to list if not found
                obj.insertItems(0, [value])
                index = obj.findText(value)
                obj.setCurrentIndex(index)
            else:
                obj.setCurrentIndex(index)  # preselect a combobox value by index

        if isinstance(obj, QLineEdit):
            obj.setText(value)

        if isinstance(obj, QCheckBox):
            obj.setChecked(strtobool(value))

        if isinstance(obj, QRadioButton):
            obj.setChecked(strtobool(value))

        if isinstance(obj, QSlider):
            obj.setValue(int(value))

        if isinstance(obj, QSpinBox):
            obj.setValue(int(value))

        if isinstance(obj, QListWidget):
            size = settings.beginReadArray(namePrefix + name)
            for i in range(size):
                settings.setArrayIndex(i)
                value = settings.value(namePrefix + name)
                if value is not None:
                    obj.addItem(value)
            settings.endArray()```

0

我是一名初学者程序员,所以我不确定我哪里出了问题,请原谅我的术语/技术理解不足,但我的应用程序有多个QWidget类,用于不同的应用程序“状态”,每个状态加载/卸载不同的UI(和相关函数)- 全部在单个主“QMainWindow”中进行

这是使用@beesleep类实现的,但看起来上述所有示例的代码都是相同的

我遇到了一些问题 -

def _is_handled_type(cls, widget):
    return any(isinstance(widget, t) for t in cls._get_handled_types())


for name, obj in inspect.getmembers(self)
    # print(name) <------------------------------
    if not self._is_handled_type...

返回自身“QMainWindow”的方法,而不是所有活动子窗口小部件。

.

我的解决方法是这样的。我删除了 - ***这部分不必要,代码已经不再使用
@classmethod
def _is_handled_type(cls, widget):
    return any(isinstance(widget, t) fort in cls._get_handled_types())

然后在_gui_save()和_gui_restore()中进行更改-

for name, obj in inspect.getmembers(self):
    if not self._is_handled_type(obj):
        continue
    if name in self._names_to_avoid: # _gui_restore()
        continue                     # _gui_restore()
        name = obj.objectName()
        value = None

关于 -

for child in self._get_handled_types():

    for obj in self.findChildren(child):

        if obj:

            name = obj.objectName()
            if name in self._names_to_avoid: # _gui_restore()
                continue                     # _gui_restore()
            value = None

最后一步是给你想要保存的每个对象分配一个名称。

self.q_list = QListWidget()
self.q_list.setObjectName("List")
...
self.q_text = QLineEdit('enter text')
self.q_text.setObjectName("Text")

.

for child in self._get_handled_types():
    for obj in self.findChildren(child):
        if obj:
            name = obj.objectName()

            print(name, obj) <-------------

返回

Text <PyQt5.QtWidgets.QLineEdit object at 0x0000023DD43E2708>
List <PyQt5.QtWidgets.QListWidget object at 0x0000023DD42530D8>

最后注意事项... 如果你想查看设置文件保存的位置,那么在init下添加- print(self.settings.fileName()) 干杯!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接