qt ui 转python
系统 没有 pyside6-uic,只能自己搞一个; 同时将 pyside 和 pyqt 合并到代码\bin\pyside-uic.py里;
用法: python pyside-uic.py [PyQt6/PyQt5/PySide6/PySide2] <输入路径> [-o <输出路径>] [--skip/--force]
可以根据自己的要求选择转为PyQt6 、PyQt5、PySide6、PySide2的python代码;
--skip:检测文件对应输出的python代码是否已生成,每个生成输出的py文件都包含ui文件的MD5,MD5用于判断ui文件是否有修改,如果没有修改就跳过;
--force:不检查ui文件是否有修改,强制将ui文件转换生成对应的py文件;
\bin\pyside6-uic
#!/bin/bash
#pyside-uic "PySide6" "$@"
exec /ucrt64/bin/python.exe /ucrt64/bin/pyside-uic.py "PySide6" "$@"
\bin\pyside2-uic
#!/bin/bash
# uic.exe -g python
#pyside-uic "PySide2" "$@"
exec /ucrt64/bin/python.exe /ucrt64/bin/pyside-uic.py "PySide2" "$@"
\bin\pyuic6
#!/bin/sh
#exec /ucrt64/bin/python.exe -m PyQt6.uic.pyuic ${1+"$@"}
#pyuic PyQt6 ${1+"$@"}
exec /ucrt64/bin/python.exe /ucrt64/bin/pyside-uic.py "PyQt6" "$@"
\bin\pyuic5
#!/bin/sh
#exec /ucrt64/bin/python.exe -m PyQt5.uic.pyuic ${1+"$@"}
#pyuic PyQt5 ${1+"$@"}
exec /ucrt64/bin/python.exe /ucrt64/bin/pyside-uic.py "PyQt5" "$@"
\bin\pyside-uic.py
#/bin/pyside-uic.pyimport sys
import os
import hashlib
import subprocess
import re# 颜色定义(使用ANSI转义码)
COLOR_RESET = '\033[0m'
COLOR_BLACK = '\033[30m'
COLOR_RED = '\033[31m'
COLOR_GREEN = '\033[32m'
COLOR_YELLOW = '\033[33m'
COLOR_BLUE = '\033[34m'
BG_BLACK = '\033[40m'
BG_RED = '\033[41m'
BG_GREEN = '\033[42m'
BG_YELLOW = '\033[43m'
BG_BLUE = '\033[44m'# 全局选项控制
global_force = False
global_skip = Falsedef display(color, message):"""带颜色的输出函数"""print(f"{color}{message}{COLOR_RESET}")def get_md5(file_path):"""计算文件MD5值"""md5_hash = hashlib.md5()with open(file_path, "rb") as f:for byte_block in iter(lambda: f.read(4096), b""):md5_hash.update(byte_block)return md5_hash.hexdigest()def parse_widget_info(ui_file):"""解析UI文件获取类信息"""class_value = ""widget_class = ""widget_name = ""with open(ui_file, "r", encoding="utf-8") as f:content = f.read()# 提取class元素值class_match = re.search(r'<class>(.*?)</class>', content)if class_match:class_value = class_match.group(1)# 提取第一个widget的class和name属性widget_match = re.search(r'<widget[^>]+class="([^"]+)"[^>]+name="([^"]+)"', content)if widget_match:widget_class = widget_match.group(1)widget_name = widget_match.group(2)return (class_value, widget_class)def convert_ui_to_python(pyside_version, input_file, output_file):"""调用uic.exe执行转换"""try:if pyside_version == "PyQt6":subprocess.run(["python.exe", "-m", "PyQt6.uic.pyuic", input_file, "-o", output_file],check=True,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)elif pyside_version == "PyQt5":subprocess.run(["python.exe", "-m", "PyQt5.uic.pyuic", input_file, "-o", output_file],check=True,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)else: subprocess.run(["uic.exe", "-g", "python", input_file, "-o", output_file],check=True,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) display(f"{BG_GREEN}{COLOR_BLACK}", f"成功编译 {input_file} \n {output_file}")return Trueexcept subprocess.CalledProcessError:display(f"{BG_RED}{COLOR_BLACK}", f"编译失败:{input_file}")return Falsedef modify_python_file(output_file, input_file, md5, class_info, pyside_version, first_charer="Ui_"):"""修改生成的Python文件(新增input_file参数)"""class_name, widget_class = class_infobase_name = os.path.splitext(os.path.basename(output_file))[0]new_class_name = f"{base_name}"if widget_class in ["QMainWindow", "QtWidgets.QMainWindow"]:widget_setupUi = "\n"else: widget_setupUi =" self.setupUi(self)\n"# 构造新类定义new_class_code = (f"class {new_class_name}({widget_class}):\n"f" def __init__(self, parent=None):\n"f" super().__init__(parent)\n"f"{widget_setupUi}")if pyside_version == "PyQt6" or pyside_version == "PyQt5":new_class_code = (f"class {new_class_name}(QtWidgets.{widget_class}):\n"f" def __init__(self, parent=None):\n"f" super().__init__(parent)\n"f"{widget_setupUi}")# 读取文件内容with open(output_file, "r", encoding="utf-8") as f:content = f.read()# 替换类定义(匹配原class定义模式)content = re.sub(r'class \w+\(object\):', new_class_code, content, count=1)# 添加MD5和源文件注释(使用传入的input_file)content = f"# md5={md5}\n# src={os.path.abspath(input_file)}\n" + content# 替换PySide版本(根据目标版本)if pyside_version == "PyQt6":content = content.replace("PyQt5", "PyQt6")elif pyside_version == "PySide6":content = content.replace("PySide2", "PySide6")# 写入修改后的内容with open(output_file, "w", encoding="utf-8") as f:f.write(content)def prompt_user_action(file_path):"""提示用户选择操作"""global global_force, global_skip# 如果已有全局选项,直接返回if global_force:return 'force'if global_skip:return 'skip'while True:display(f"{BG_YELLOW}{COLOR_BLACK}", f"文件已存在:{file_path}")choice = input("请选择操作 [r]重新处理 [s]跳过 [a]全部重新处理 [q]全部跳过 [e]退出: ").lower()if choice == 'r':return 'force'elif choice == 's':return 'skip'elif choice == 'a':global_force = Truereturn 'force'elif choice == 'q':global_skip = Truereturn 'skip'elif choice == 'e':display(f"{BG_BLUE}{COLOR_YELLOW}", "操作已取消")sys.exit(0)else:display(f"{BG_RED}{COLOR_BLACK}", "无效选择,请重试")def process_file(pyside_version, input_file, output_dir, first_charer):"""处理单个UI文件"""input_basename = os.path.splitext(os.path.basename(input_file))[0]output_file = os.path.join(output_dir, f"{first_charer}{input_basename}.py")# 检查文件是否已存在且MD5匹配if os.path.exists(output_file):current_md5 = get_md5(input_file)with open(output_file, "r", encoding="utf-8") as f:if f.readline().strip() == f"# md5={current_md5}":action = prompt_user_action(input_file)if action == 'skip':display(f"{BG_YELLOW}{COLOR_BLACK}", f"跳过已处理文件:{input_file}")returnelif action == 'force':display(f"{BG_YELLOW}{COLOR_BLACK}", f"重新处理文件:{input_file}")# 执行编译if not convert_ui_to_python(pyside_version, input_file, output_file):return# 解析UI信息class_info = parse_widget_info(input_file)if not class_info[1]:display(f"{BG_RED}{COLOR_BLACK}", f"解析失败:{input_file} 中未找到widget信息")return# 计算MD5md5 = get_md5(input_file)# 修改生成的Python文件(新增input_file参数传递)modify_python_file(output_file, input_file, md5, class_info, pyside_version, first_charer)def process_directory(pyside_version, input_dir, output_dir, first_charer):"""处理目录下的所有UI文件"""for root, _, files in os.walk(input_dir):for file in files:if file.endswith(".ui"):input_path = os.path.join(root, file)rel_path = os.path.relpath(root, input_dir)output_subdir = os.path.join(output_dir, rel_path)os.makedirs(output_subdir, exist_ok=True)process_file(pyside_version, input_path, output_subdir, first_charer)def main():global global_force, global_skipfirst_charer = "Ui_"pyside_version = "PyQt6"input_path = ""output_path = ""# 参数解析if len(sys.argv) < 2:display(f"{BG_BLUE}{COLOR_YELLOW}", "用法: python ui_converter.py [PyQt6/PyQt5/PySide6/PySide2] <输入路径> [-o <输出路径>]")return# 检查是否有全局选项if '--force' in sys.argv:global_force = Truesys.argv.remove('--force')elif '--skip' in sys.argv:global_skip = Truesys.argv.remove('--skip')# 确定PySide版本version_arg = sys.argv[1].lower()if version_arg in ["pyqt6"]:pyside_version = "PyQt6"args = sys.argv[2:]elif version_arg in ["pyqt5"]:pyside_version = "PyQt5"args = sys.argv[2:]elif version_arg in ["pyside6"]:pyside_version = "PySide6"args = sys.argv[2:]elif version_arg in ["pyside2"]:pyside_version = "PySide2"args = sys.argv[2:]else:display(f"{BG_RED}{COLOR_BLACK}", "错误:无效的PySide版本")return# 处理输入输出路径input_path = args[0] if args else ""output_path = None# 处理-o选项if "-o" in args:idx = args.index("-o")if idx + 1 < len(args):output_path = args[idx+1]else:display(f"{BG_RED}{COLOR_BLACK}", "错误:-o选项需要指定输出路径")returninput_path = args[:idx][0] if args[:idx] else ""else:output_path = args[1] if len(args) > 1 else None# 处理默认输出路径if not output_path:if os.path.isfile(input_path):output_path = os.path.dirname(input_path)else:output_path = input_path# 检查输入路径有效性if not os.path.exists(input_path):display(f"{BG_RED}{COLOR_BLACK}", f"错误:路径不存在 - {input_path}")return# 处理文件或目录if os.path.isfile(input_path) and input_path.endswith(".ui"):os.makedirs(os.path.dirname(output_path), exist_ok=True)process_file(pyside_version, input_path, os.path.dirname(output_path), first_charer)elif os.path.isdir(input_path):os.makedirs(output_path, exist_ok=True)process_directory(pyside_version, input_path, output_path, first_charer)else:display(f"{BG_RED}{COLOR_BLACK}", "错误:输入必须是UI文件或目录")if __name__ == "__main__":main()
在QCerator中添加自动生成