zhiyiYo / PyQt-Fluent-Widgets

A fluent design widgets library based on C++ Qt/PyQt/PySide. Make Qt Great Again.
https://qfluentwidgets.com
GNU General Public License v3.0
5.61k stars 541 forks source link

qconfig.set一个值为list的configItem时,出现没有写入config.json的情况 #471

Closed AlphaLiu closed 1 year ago

AlphaLiu commented 1 year ago

Describe the bug 有一个config的值是list,在使用qconfig.set改变时,只有程序启动后的第一次qconfiig.set有实时写入config.json,之后同样的qconfig.set都没有写入。如果是一个值为str的config,则在每次textChanged时调用qconfig.set,都会实时写入config.json。不清楚这是否是bug。我是参考了example 的setting里面的musicFolderList的config用法。

Environment 环境信息

To Reproduce GIF 2023-8-25 17-00-48

Code 最小复现代码

# coding:utf-8
import sys
from PySide6.QtCore import Qt
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import (
    QApplication,
    QCheckBox,
    QLineEdit,
    QVBoxLayout,
)
from qfluentwidgets import (
    MSFluentTitleBar,
    ConfigItem,
    QConfig,
    qconfig,
)
from qframelesswindow import FramelessWindow as Window
class Config(QConfig):
    """Config of application"""
    checkList = ConfigItem(
        "Config",
        "checklist",
        [],
    )
    name = ConfigItem(
        "Config",
        "name",
        "",
    )
cfg = Config()
qconfig.load('config.json', cfg)
class Demo(Window):
    def __init__(self):
        super().__init__()
        self.setTitleBar(MSFluentTitleBar(self))
        self.setWindowTitle('Save config List Demo')
        self.setWindowIcon(QIcon(':/qfluentwidgets/images/logo.png'))
        self.vBoxLayout = QVBoxLayout(self)
        self.vBoxLayout.setContentsMargins(34, 40, 34, 20)
        self.vBoxLayout.setSpacing(12)
        self.checkList = qconfig.get(cfg.checkList).copy()
        for i in range(3):
            name = f'CheckBox {i}'
            checkbox = QCheckBox(name, self)
            checkbox.stateChanged.connect(
                lambda checked, name=name: self.checkStateChanged(name, checked)
            )
            self.vBoxLayout.addWidget(checkbox)
            if name in self.checkList:
                checkbox.setCheckState(Qt.Checked)
        self.inputName = QLineEdit(self)
        self.inputName.setPlaceholderText('Input name')
        self.inputName.setText(qconfig.get(cfg.name))
        self.inputName.textChanged.connect(self.inputChanged)
        self.vBoxLayout.addWidget(self.inputName)
        self.setLayout(self.vBoxLayout)
        self.resize(280, 100)
        self.titleBar.raise_()
    def checkStateChanged(self, name, checked):
        print(f'{name} is {"checked" if checked else "unchecked"}')
        if checked:
            if name not in self.checkList:
                self.checkList.append(name)
        else:
            if name in self.checkList:
                self.checkList.remove(name)

        print(f"checkList: {self.checkList}")
        qconfig.set(cfg.checkList, self.checkList)
        print(f"cfg.checkList: {qconfig.get(cfg.checkList)}")

    def inputChanged(self, text):
        print(f'input changed: {text}')
        qconfig.set(cfg.name, text)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = Demo()
    w.show()
    app.exec()

Expected behavior qconfig.set能实时把list写入config.json

kingmo888 commented 1 year ago

在本地用PyQt5简单复现了下,确实存在该问题。经查,作者代码不存在问题,是提问者的代码处理上与作者代码存在隐含冲突。

解决方案:

qconfig.set(cfg.checkList, self.checkList)

改为:

import copy
qconfig.set(cfg.checkList, copy.copy(self.checkList))   

或者:

qconfig.set(cfg.checkList, [x for x in self.checkList])

原因:很多人写py的时候会忘记py中不同类型赋值给不同变量到底是引用还是赋值,在这里,字符串赋值给不同变量时它是复制一个值,而list则是将地址给其他变量,类似的还有dict等。set方法部分代码:

    def set(self, item, value, save=True):
        """ set the value of config item

        Parameters
        ----------
        item: ConfigItem
            config item

        value:
            the new value of config item

        save: bool
            whether to save the change to config file
        """
        if item.value == value:
            return

如果是list的话,则在最后两行判断地址未发生变化,直接返回,不会保存。

@zhiyiYo 可以关了