You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在PyQt5中保存应用内动态添加控件的会话数据?

问题描述

我开发的PyQt5应用支持在GridLayout中动态添加LineEdit和PushButton,需要实现关闭应用时保存这些动态控件的数据,下次启动应用时自动加载的功能。我尝试过JSON、PICKLE、SQLITE3等存储方式,但均未成功。以下是示例界面与代码:

示例界面

import sys

from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QLabel, QGridLayout, QLineEdit, QPushButton, \
    QMessageBox

from Designer.designerTest import Ui_MainWindow  # 从其他模块导入Ui_MainWindow类


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

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.ui = Ui_MainWindow()  # 创建Ui_MainWindow实例
        self.ui.setupUi(self)  # 初始化UI元素

        self.layout = QVBoxLayout()
        self.ui.widget_2.setLayout(self.layout)  # 为widget_2设置QVBoxLayout布局

        self.ui.btnSaveTopic.clicked.connect(
        self.create_new_label)  # 点击btnSaveTopic按钮时触发create_new_label函数

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

    def create_new_label(self):  # 创建新标签并调用new_grid函数
        if self.ui.lineTopic.text() != "":
            text = self.ui.lineTopic.text()
            label = QLabel(text)
            label.setStyleSheet('color: white; font: 14pt;')
            self.layout.addWidget(label)

            self.new_grid()  # 创建新的grid布局

            self.ui.lineTopic.clear()
        else:
            QMessageBox.warning(None, "错误", "请输入主题")  # 显示警告消息

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

    def new_grid(self):  # 创建包含元素的新grid布局
        grid = QGridLayout()
        self.layout.addLayout(grid)

        line_edit = self.create_new_line_edit()
        grid.addWidget(line_edit, 0, 0)

        plus_right = QPushButton()
        plus_right.setFixedSize(20, 20)
        plus_right.setIcon(QIcon("icons/iconPlus.svg"))
        plus_right.setIconSize(QSize(14, 14))

        plus_down = QPushButton()
        plus_down.setFixedSize(20, 20)
        plus_down.setIcon(QIcon("icons/iconPlus.svg"))
        plus_down.setIconSize(QSize(14, 14))

        grid.addWidget(plus_right, 0, 1)
        grid.addWidget(plus_down, 1, 0)

        plus_right.clicked.connect(lambda: self.create_new_btn_right(grid))
        plus_down.clicked.connect(lambda: self.create_new_btn_down(grid))

        self.column_stretch(grid)  # 设置列和行的拉伸属性

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

    def create_new_btn_down(self, grid):  # 点击向下按钮时创建新行
        clicked_widget = self.sender()

        # 获取点击控件在网格中的位置
        for i in range(grid.count()):
            item = grid.itemAt(i)
            if item.widget() == clicked_widget:
                row, column = grid.getItemPosition(i)[:2]
                break
        else:
            return

        line_edit = self.create_new_line_edit()
        plus_right = self.create_new_button(lambda: self.create_new_btn_right(grid))
        plus_down = self.create_new_button(lambda: self.create_new_btn_down(grid))

        grid.addWidget(line_edit, row, column)
        grid.addWidget(plus_down, row + 1, column)
        grid.addWidget(plus_right, row, column + 1)

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

    def create_new_btn_right(self, grid):  # 点击向右按钮时创建新列
        clicked_widget = self.sender()

        # 获取点击控件在网格中的位置
        for i in range(grid.count()):
            item = grid.itemAt(i)
            if item.widget() == clicked_widget:
                row, column = grid.getItemPosition(i)[:2]
                break
        else:
            return

        line_edit = self.create_new_line_edit()
        plus_right = self.create_new_button(lambda: self.create_new_btn_right(grid))

        grid.addWidget(line_edit, row, column)
        grid.addWidget(plus_right, row, column + 1)

        self.column_stretch(grid)

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

    @staticmethod
    def column_stretch(grid):  # 设置网格列和行的拉伸属性
        grid.setColumnStretch(grid.columnCount(), 1)
        grid.setRowStretch(grid.rowCount(), 1)

    @staticmethod
    def create_new_line_edit():  # 创建新的QLineEdit
        line_edit = QLineEdit()
        line_edit.setFixedSize(30, 30)
        line_edit.setStyleSheet("background-color: white; color: black; font: 10pt;")
        line_edit.setAlignment(Qt.AlignCenter)
        return line_edit

    @staticmethod
    def create_new_button(callback):  # 创建新按钮
        button = QPushButton()
        button.setFixedSize(20, 20)
        button.setIcon(QIcon("icons/iconPlus.svg"))
        button.setIconSize(QSize(14, 14))
        button.clicked.connect(callback)
        return button

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

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())
解决方案

核心思路

要实现动态控件的数据保存与加载,关键是将界面结构和控件数据转化为可序列化的格式,这里推荐用JSON(可读性强、易调试),启动时反向解析格式重建界面。

步骤1:添加数据存储与读取逻辑

在MainWindow类中新增两个方法,分别负责保存数据到文件和从文件加载数据:

import json
from PyQt5.QtWidgets import QWidget

# 保存动态控件数据到JSON文件
def save_data(self):
    data = []
    # 遍历主布局中的所有元素
    for i in range(self.layout.count()):
        item = self.layout.itemAt(i)
        # 处理标签元素
        if isinstance(item.widget(), QLabel):
            data.append({
                "type": "label",
                "text": item.widget().text()
            })
        # 处理Grid布局元素
        elif isinstance(item, QGridLayout):
            grid_data = {"type": "grid", "cells": {}}
            # 遍历Grid内的所有控件,仅保存LineEdit的位置和文本
            for j in range(item.count()):
                grid_item = item.itemAt(j)
                if not grid_item.widget():
                    continue
                widget = grid_item.widget()
                row, col = item.getItemPosition(j)[:2]
                if isinstance(widget, QLineEdit):
                    grid_data["cells"][f"{row},{col}"] = widget.text()
            data.append(grid_data)
    # 写入JSON文件
    with open("app_data.json", "w", encoding="utf-8") as f:
        json.dump(data, f, indent=4)

# 从JSON文件加载数据并重建界面
def load_data(self):
    try:
        with open("app_data.json", "r", encoding="utf-8") as f:
            data = json.load(f)
        # 清空现有布局,避免重复加载
        while self.layout.count():
            item = self.layout.takeAt(0)
            if item.widget():
                item.widget().deleteLater()
            elif item.layout():
                while item.layout().count():
                    grid_item = item.layout().takeAt(0)
                    if grid_item.widget():
                        grid_item.widget().deleteLater()
                item.layout().deleteLater()
        # 按保存的数据重建界面
        idx = 0
        while idx < len(data):
            item_data = data[idx]
            if item_data["type"] == "label":
                # 重建标签
                label = QLabel(item_data["text"])
                label.setStyleSheet('color: white; font: 14pt;')
                self.layout.addWidget(label)
                # 重建对应的Grid布局
                idx += 1
                if idx >= len(data) or data[idx]["type"] != "grid":
                    grid = QGridLayout()
                    self.layout.addLayout(grid)
                    # 创建初始Grid元素
                    line_edit = self.create_new_line_edit()
                    grid.addWidget(line_edit, 0, 0)
                    plus_right = self.create_new_button(lambda g=grid: self.create_new_btn_right(g))
                    plus_down = self.create_new_button(lambda g=grid: self.create_new_btn_down(g))
                    grid.addWidget(plus_right, 0, 1)
                    grid.addWidget(plus_down, 1, 0)
                    self.column_stretch(grid)
                    continue
                # 处理有数据的Grid
                grid_data = data[idx]
                grid = QGridLayout()
                self.layout.addLayout(grid)
                # 获取所有单元格的行列信息
                positions = grid_data["cells"].keys()
                rows = set()
                cols = set()
                for pos in positions:
                    r, c = map(int, pos.split(","))
                    rows.add(r)
                    cols.add(c)
                max_row = max(rows) if rows else 0
                max_col = max(cols) if cols else 0
                # 填充Grid的LineEdit和按钮
                for r in range(max_row + 1):
                    for c in range(max_col + 1):
                        pos_key = f"{r},{c}"
                        if pos_key in grid_data["cells"]:
                            line_edit = self.create_new_line_edit()
                            line_edit.setText(grid_data["cells"][pos_key])
                            grid.addWidget(line_edit, r, c)
                            # 添加向右按钮
                            plus_right = self.create_new_button(lambda g=grid: self.create_new_btn_right(g))
                            grid.addWidget(plus_right, r, c+1)
                            # 添加向下按钮
                            plus_down = self.create_new_button(lambda g=grid: self.create_new_btn_down(g))
                            grid.addWidget(plus_down, r+1, c)
                self.column_stretch(grid)
            idx += 1
    except FileNotFoundError:
        # 首次启动无数据文件,不做处理
        pass

步骤2:绑定保存与加载时机

在初始化方法中调用加载逻辑,重写窗口关闭事件触发保存逻辑:

def __init__(self):
    super().__init__()
    self.ui = Ui_MainWindow()
    self.ui.setupUi(self)

    self.layout = QVBoxLayout()
    self.ui.widget_2.setLayout(self.layout)

    self.ui.btnSaveTopic.clicked.connect(self.create_new_label)
    
    # 启动时加载历史数据
    self.load_data()

def closeEvent(self, event):
    # 关闭窗口时自动保存数据
    self.save_data()
    event.accept()

关键说明

  • 数据结构设计:用列表存储界面元素,每个元素标记类型(标签/Grid布局),仅保存LineEdit的位置和文本,按钮无需存储(由动态生成逻辑控制)。
  • 布局清空:加载数据前清空现有布局,避免重复创建控件导致界面混乱。
  • 异常处理:处理文件不存在的情况,首次启动时保持初始界面状态。

内容的提问来源于stack exchange,提问作者Kiril D.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 13:32:31