自用pyside6项目模板

在下文基础上添加了一些自动生成的脚本:

https://blog.csdn.net/xulibo5828/article/details/145801919


模板简述:

  • 目录结构:
python 复制代码
pyside6_project 
├── DataBase/
├── Job/
├── Settings/
├── UI/
│   ├── SRC/
│   ├── UI_forms/
│   │   ├── UI_process.py
│   │   ├── __init__.py
│   │   ├── base_form.ui
│   │   ├── main_window/
│   │   │   ├── main_window.ui
│   │   │   ├── main_window_func.py
│   │   │   ├── main_window_ui.py
│   │   │   └── 本目录说明.md
│   │   ├── window_1/
│   │   │   ├── window_1.ui
│   │   │   ├── window_1_func.py
│   │   │   └── window_1_ui.py
│   │   └── 本目录说明.md
│   ├── UI_functions/
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   └── 本目录说明.md
│   ├── __init__.py
│   └── settings/
├── main.py
├── tmp/
└── UI_project.py

UI/UI_forms/目录下保存有界面文件,按照文件同名目录存放。每个目录下有三个主要文件:

  • xxx.ui:使用designer组态的界面文件,这里只设计画面的的图像特征,亦即只设计前端的显示特征,不牵涉后端任何的逻辑和功能,为前后端分离打下基础。
  • xxx_ui.py:使用pyuic将xxx.ui转换为xxx_ui.py,这就是界面的前端模块脚本,这个文件纯自动生成,严禁手动编辑,严格遵守前后端分离的原则。
  • **xxx_func.py:**后端代码,使用自编脚本自动创建后端代码的基础框架,然后手动补充完成所有的功能函数和逻辑处理。

使用流程:

1、创建项目的文件目录。

2、使用designer组态界面文件,保存在同名目录下。

3、运行UI_process.py,一键生成前端模块和后端基本框架的脚本。

4、完成后端代码,在main.py中调用。

当界面文件发生增减、编辑、改变名称,再运行一次UI_process.py,半自动更新项目。界面的各个文件名称是由它所在的文件夹名用UI_process.py自动更新的。


主要脚本内容:

python 复制代码
# -*- coding: utf-8 -*-
import os
import sys

from PySide6.QtWidgets import QApplication

from UI import UI_functions   # 导入所有功能函数

current_dir = os.path.dirname(os.path.abspath(__file__)) # 获取当前脚本所在目录



def main():
    UI_functions.add_forms() # 添加所有组态好的窗口到uiproj对象中

if __name__ == "__main__":
    app = QApplication(sys.argv)  # 创建一个QApplication对象
    main()  # 执行主函数
    sys.exit(app.exec())  # 结束主循环后退出程序
  • UI_project.py:创建一个项目实体(QObject),用来管理和协调各个画面以及各种变量、信号、操控、反馈等。对于本项目而言,这个项目实体(QObject)的直系成员都是全局成员。这样,通过这个项目实体,就很容易在各个画面和功能作业函数之间进行数据交互。
python 复制代码
# 这里定义项目级别的信号和变量、参数等
from PySide6.QtCore import Signal, QObject, Slot


# # 获取当前脚本所在目录
# current_dir = os.path.dirname(os.path.abspath(__file__))
# # 构建上一级目录的路径
# parent_dir = os.path.dirname(current_dir)
# # 构建数据库路径
# db_file_path = os.path.join(parent_dir, "DataBase", "project_db.db")



# 定义全局的ui项目类
class UiProj(QObject):
    # 在这里定义全局信号
    global_signal_1 = Signal()   # 一个全局信号示例
    def __init__(self):
        super(UiProj, self).__init__()
        self.forms = []
        self.run()

    def run(self):
        self.slot_signal()  # 连接全局的信号和槽函数
        self.num = 0  # 一个全局变量示例

    # 信号和槽函数
    def slot_signal(self):
        # 全局的一个槽函数示例
        @Slot()
        def slot_1():
            self.num += 2
            self.form_window_1.label.setText(f"全局信号的槽函数{self.num}")  # 使用全局信号跨窗口传递

        self.global_signal_1.connect(slot_1)  # 连接信号和槽函数,尽可能使槽函数和信号连接成对出现,方便管理。


uiproj = UiProj()  # 生成全局的ui项目对象
  • UI_process.py:读取UI/UI_forms/目录下各个子文件夹下的.ui文件,并自动生成前端模块和后端基本框架的脚本。
python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
import xml.etree.ElementTree as ET
import re
from pathlib import Path

# Windows终端编码设置,避免中文乱码
if sys.platform == "win32":
    os.system("chcp 65001 > nul")

# 定义UI根目录(脚本运行目录)
UI_ROOT = Path.cwd()

def get_conda_scripts_path():
    """自动推导当前Python环境的Scripts目录,无需硬编码"""
    python_exe = Path(sys.executable)
    scripts_path = python_exe.parent / "Scripts"
    pyside6_uic_path = scripts_path / "pyside6-uic.exe"
    if pyside6_uic_path.exists():
        return str(pyside6_uic_path)
    raise FileNotFoundError(
        f"未找到pyside6-uic.exe,请检查PySide6是否安装!\n查找路径:{scripts_path}"
    )

def get_ui_widget_class(ui_file_path: Path) -> str:
    """解析ui文件,获取第一个widget class属性值"""
    try:
        tree = ET.parse(ui_file_path)
        root = tree.getroot()
        for elem in root.iter():
            if elem.tag.endswith('widget') and 'class' in elem.attrib:
                return elem.attrib['class']
        return 'QWidget'
    except Exception as e:
        print(f"解析UI文件失败: {e}")
        return 'QWidget'

def get_ui_frontend_class(ui_py_path: Path) -> str:
    """从生成的_ui.py文件中获取前端类名"""
    try:
        with open(ui_py_path, 'r', encoding='utf-8') as f:
            content = f.read()
            match = re.search(r'class Ui_(\w+)\(object\):', content)
            if match:
                return f"Ui_{match.group(1)}"
        return 'Ui_Form'
    except Exception as e:
        print(f"解析前端类名失败: {e}")
        return 'Ui_Form'

def update_func_file_content(func_file: Path, subdir_name: str, widget_class: str, frontend_class: str):
    """
    检查并更新_func.py文件的关键代码行,保留其他内容不变
    :param func_file: 目标func文件路径
    :param subdir_name: 子目录名
    :param widget_class: UI文件解析出的widget类名(如QMainWindow/QWidget)
    :param frontend_class: _ui.py中的前端类名(如Ui_Form)
    """
    try:
        with open(func_file, 'r', encoding='utf-8') as f:
            lines = f.readlines()

        updated_lines = []
        # 标记是否已处理过目标行,避免重复添加
        imported_widget = False
        imported_ui = False
        class_defined = False

        for line in lines:
            # 1. 检查并修正:from PySide6.QtWidgets import XXXX
            if re.match(r'^\s*from PySide6.QtWidgets import ', line.strip()):
                updated_line = f"from PySide6.QtWidgets import {widget_class}\n"
                updated_lines.append(updated_line)
                imported_widget = True
                print(f"  修正widget导入行: {line.strip()} -> {updated_line.strip()}")
            # 2. 检查并修正:from . import XXXX_ui
            elif re.match(r'^\s*from \. import .*_ui', line.strip()):
                updated_line = f"from . import {subdir_name}_ui\n"
                updated_lines.append(updated_line)
                imported_ui = True
                print(f"  修正UI模块导入行: {line.strip()} -> {updated_line.strip()}")
            # 3. 检查并修正:class form_XXX(XXX, XXX_ui.XXX):
            elif re.match(r'^\s*class form_\w+\(', line.strip()):
                updated_line = f"class form_{subdir_name}({widget_class}, {subdir_name}_ui.{frontend_class}):\n"
                updated_lines.append(updated_line)
                class_defined = True
                print(f"  修正类定义行: {line.strip()} -> {updated_line.strip()}")
            # 其他行保持不变
            else:
                updated_lines.append(line)

        # 若关键行缺失,补充到文件开头(防止文件内容异常)
        if not imported_widget:
            updated_lines.insert(0, f"from PySide6.QtWidgets import {widget_class}\n")
        if not imported_ui:
            updated_lines.insert(1, f"from . import {subdir_name}_ui\n")
        if not class_defined:
            # 找合适位置插入类定义(简单处理:插入到导入行后)
            insert_pos = 2
            while insert_pos < len(updated_lines) and updated_lines[insert_pos].strip().startswith('from '):
                insert_pos += 1
            class_def = f"\n# 定义窗口类\nclass form_{subdir_name}({widget_class}, {subdir_name}_ui.{frontend_class}):\n"
            class_def += "    def __init__(self):\n        super().__init__()\n        self.setupUi(self)\n        self.run()\n\n    def run(self):\n        pass\n"
            updated_lines.insert(insert_pos, class_def)

        # 写入更新后的内容
        with open(func_file, 'w', encoding='utf-8') as f:
            f.writelines(updated_lines)
        print(f"  完成{func_file.name}内容检查与修正")

    except Exception as e:
        print(f"  警告:更新{func_file.name}内容失败 - {e}")

def process_subdirectory(subdir: Path):
    """处理单个子目录"""
    # 自动获取pyside6-uic路径
    try:
        pyside6_uic_path = get_conda_scripts_path()
    except FileNotFoundError as e:
        print(f"\n处理目录 {subdir.name} 失败: {e}")
        return

    subdir_name = subdir.name
    print(f"\n处理目录: {subdir_name}")

    # 步骤1: 查找并处理.ui文件
    ui_files = list(subdir.glob('*.ui'))
    if not ui_files:
        print(f"  无UI文件,跳过")
        return

    # 重命名UI文件为目录同名
    target_ui_file = subdir / f"{subdir_name}.ui"
    if len(ui_files) > 1:
        print(f"  警告: 发现多个UI文件,仅保留{target_ui_file.name}")
    for ui_file in ui_files:
        if ui_file != target_ui_file:
            if target_ui_file.exists():
                target_ui_file.unlink()
            ui_file.rename(target_ui_file)
            print(f"  重命名UI文件: {ui_file.name} -> {target_ui_file.name}")

    # 步骤2: 删除旧_ui.py并重新生成
    for py_file in subdir.glob('*_ui.py'):
        py_file.unlink()
        print(f"  删除旧文件: {py_file.name}")

    target_ui_py = subdir / f"{subdir_name}_ui.py"
    try:
        # 路径加双引号,兼容空格/特殊字符
        cmd = f"{pyside6_uic_path} {target_ui_file} -o {target_ui_py}"
        print(f"  执行命令: {cmd}")
        exit_code = os.system(cmd)
        if exit_code != 0:
            raise Exception(f"命令执行失败(退出码:{exit_code})")
        print(f"  生成新文件: {target_ui_py.name}")
    except Exception as e:
        print(f"  错误: 生成UI文件失败 - {e}")
        return

    # 步骤3: 处理_func.py文件
    target_func_file = subdir / f"{subdir_name}_func.py"
    func_files = list(subdir.glob('*_func.py'))

    # 重命名非目标func文件
    for func_file in func_files:
        if func_file != target_func_file:
            if target_func_file.exists():
                target_func_file.unlink()
            func_file.rename(target_func_file)
            print(f"  重命名功能文件: {func_file.name} -> {target_func_file.name}")

    # 获取必要的类名(用于新建/更新func文件)
    widget_class = get_ui_widget_class(target_ui_file)
    frontend_class = get_ui_frontend_class(target_ui_py)

    # 如果目标func文件不存在则新建
    if not target_func_file.exists():
        print(f"  创建新文件: {target_func_file.name}")
        func_content = f'''from PySide6.QtCore import Slot, Qt
from PySide6.QtWidgets import {widget_class}   # 导入本界面所属的widget类,在UI文件中也可以查到它的定义

from . import {subdir_name}_ui  # 导入使用pyuic生成的前端UI模块


# 定义窗口类
class form_{subdir_name}({widget_class}, {subdir_name}_ui.{frontend_class}):  # 继承自widget类和前端UI类
    def __init__(self):
        super().__init__()
        self.setupUi(self)   # 加载窗口
        self.run()   # 实例化以后的初始化运行窗口

     # 初始化运行窗口,继续完成参数的设定
    def run(self):
        # self.setWindowFlag(Qt.WindowStaysOnTopHint)  # 可以单独设置窗口是否置顶
        # self.setWindowFlag(Qt.FramelessWindowHint)  # 可以单独设置窗口是否无边框
        # self.setWindowFlag(Qt.WindowMinimizeButtonHint)  # 可以单独设置窗口是否显示最小化按钮
        # self.setWindowFlag(Qt.WindowMaximizeButtonHint)  # 可以单独设置窗口是否显示最大化按钮
        # self.setWindowFlag(Qt.WindowCloseButtonHint)  # 可以单独设置窗口是否显示关闭按钮
        # self.setWindowFlag(Qt.WindowContextHelpButtonHint)  # 可以单独设置窗口是否显示帮助按钮
        # self.setWindowFlag(Qt.WindowFullscreenButtonHint)  # 可以单独设置窗口是否显示全屏按钮
        # self.setWindowFlag(Qt.WindowSystemMenuHint)  # 可以单独设置窗口是否显示系统菜单
        # self.setWindowFlag(Qt.WindowTitleHint)  # 可以单独设置窗口是否显示标题栏
        # 以及其他窗口设置
        self.show()  # 执行一次显示窗口,以便完成窗口的所有初始化
        self.hide()  # 如果是起始主窗口,请注释掉本行代码,使其能在程序启动后显示

    @Slot()
    def on_Object_handled(self):   # 槽函数,Object是UI文件中定义的对象名,handled是事件名,
                                    # 由于pyuic在生成前端模块时包含了QMetaObject.connectSlotsByName()语句,
                                    # 因此这里可以直接使用对象名和事件名来连接槽函数
        pass
'''
        with open(target_func_file, 'w', encoding='utf-8') as f:
            f.write(func_content)
        print(f"  写入预置内容到: {target_func_file.name}")
    else:
        # 文件存在时,检查并更新关键代码行
        print(f"  检查{target_func_file.name}内容并修正...")
        update_func_file_content(target_func_file, subdir_name, widget_class, frontend_class)

def generate_initFile(root_dir):
    """
    自动生成目录下的__init__.py文件
    :param root_dir: __init__.py所在目录
    """
    # 1. 定义初始化内容的模板开头
    init_content = []
    # 2. 收集需要导出的变量名
    all_list = []

    # 3. 扫描root_dir下的目录
    for dir_name in os.listdir(root_dir):
        dir_path = os.path.join(root_dir, dir_name)
        # 只处理目录,且符合windowN命名规则
        if os.path.isdir(dir_path) and not dir_name.startswith('__'):
            # 构造导入语句:from . windowN.windowN import form_windowN
            import_line = f'from . {dir_name}.{dir_name}_func import form_{dir_name}'
            init_content.append(import_line)
            # 添加到__all__列表
            all_list.append(f'form_{dir_name}')

    # 4. 拼接完整的__init__.py内容
    init_content.append('')  # 空行分隔
    init_content.append(f'__all__ = {all_list}')
    final_content = '\n'.join(init_content)

    # 5. 写入__init__.py文件
    init_file_path = os.path.join(root_dir, '__init__.py')
    with open(init_file_path, 'w', encoding='utf-8') as f:
        f.write(final_content)

    print(f"成功生成__init__.py:\n{final_content}")
    print(f"文件路径:{init_file_path}")

def main():
    """主函数"""
    print(f"开始处理UI目录: {UI_ROOT}")
    print("=" * 50)

    # 遍历第一层子目录(过滤隐藏目录)
    for item in UI_ROOT.iterdir():
        if item.is_dir() and not item.name.startswith('__'):
            process_subdirectory(item)

    print("\n" + "=" * 50)
    print("处理完成!")
    generate_initFile(UI_ROOT)

if __name__ == "__main__":
    # 检查运行目录
    if not UI_ROOT.name == "UI_forms":
        print("警告: 建议在UI_forms目录下运行此脚本!")
    main()
相关推荐
YJlio20 小时前
VolumeID 学习笔记(13.10):卷序列号修改与资产标识管理实战
windows·笔记·学习
小龙20 小时前
【学习笔记】多标签交叉熵损失的原理
笔记·学习·多标签交叉熵损失
知识分享小能手21 小时前
Ubuntu入门学习教程,从入门到精通,Ubuntu 22.04的Linux网络配置(14)
linux·学习·ubuntu
手揽回忆怎么睡21 小时前
Streamlit学习实战教程级,一个交互式的机器学习实验平台!
人工智能·学习·机器学习
xiaoxiaoxiaolll1 天前
《Advanced Materials》基于MXene的复合纤维实现智能纺织品多模态功能集成
学习
db_murphy1 天前
学习篇 | 英方i2Active和i2Stream工具了解
学习
强子感冒了1 天前
Java学习笔记:String、StringBuilder与StringBuffer
java·开发语言·笔记·学习
BullSmall1 天前
Doris的备份及恢复方案
学习
小李子不吃李子1 天前
人工智能与创新第二章练习题
人工智能·学习
NULL指向我1 天前
STM32F407VET6学习笔记14:Bootloader程序笔记
笔记·stm32·学习