qt ui 转python

发布于:2025-06-06 ⋅ 阅读:(20) ⋅ 点赞:(0)

系统 没有 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.py

import 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 = False



def 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 True
    except subprocess.CalledProcessError:
        display(f"{BG_RED}{COLOR_BLACK}", f"编译失败:{input_file}")
        return False

def modify_python_file(output_file, input_file, md5, class_info, pyside_version, first_charer="Ui_"):
    """修改生成的Python文件(新增input_file参数)"""
    class_name, widget_class = class_info
    base_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 = True
            return 'force'
        elif choice == 'q':
            global_skip = True
            return '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}")
                    return
                elif 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
    
    # 计算MD5
    md5 = 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_skip
    first_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 = True
        sys.argv.remove('--force')
    elif '--skip' in sys.argv:
        global_skip = True
        sys.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选项需要指定输出路径")
            return
        input_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中添加自动生成

 


网站公告

今日签到

点亮在社区的每一天
去签到