自用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()
相关推荐
西岸行者4 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意4 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码4 天前
嵌入式学习路线
学习
毛小茛4 天前
计算机系统概论——校验码
学习
babe小鑫4 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms4 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下4 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。4 天前
2026.2.25监控学习
学习
im_AMBER4 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J4 天前
从“Hello World“ 开始 C++
c语言·c++·学习