当前位置: 首页 > article >正文

yolo自动化项目实例解析(七)自建UI--工具栏选项

在上一章我们基本实现了关于预览窗口的显示,现在我们主要完善一下工具栏菜单按键

一、添加任务ui

先加个ui页面,不想看ui的复制完这个文件到ui目录下转下py直接从第二步开始看

vi ui/formpy.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>formpy</class>
 <widget class="QWidget" name="formpy">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>316</width>
    <height>196</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Form</string>
  </property>
  <layout class="QGridLayout" name="gridLayout">
   <item row="0" column="0" colspan="4">
    <layout class="QHBoxLayout" name="horizontalLayout">
     <item>
      <widget class="QLineEdit" name="led_name">
       <property name="minimumSize">
        <size>
         <width>0</width>
         <height>0</height>
        </size>
       </property>
       <property name="maximumSize">
        <size>
         <width>999</width>
         <height>9999</height>
        </size>
       </property>
       <property name="styleSheet">
        <string notr="true">color: rgb(255, 255, 255);</string>
       </property>
       <property name="text">
        <string>蒙德锄地</string>
       </property>
       <property name="alignment">
        <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
       </property>
      </widget>
     </item>
     <item>
      <widget class="QCheckBox" name="cb_is_checked">
       <property name="minimumSize">
        <size>
         <width>0</width>
         <height>0</height>
        </size>
       </property>
       <property name="maximumSize">
        <size>
         <width>999999</width>
         <height>9999999</height>
        </size>
       </property>
       <property name="layoutDirection">
        <enum>Qt::RightToLeft</enum>
       </property>
       <property name="styleSheet">
        <string notr="true"/>
       </property>
       <property name="text">
        <string/>
       </property>
       <property name="checked">
        <bool>true</bool>
       </property>
      </widget>
     </item>
    </layout>
   </item>
   <item row="1" column="1">
    <widget class="QPushButton" name="bt_arg">
     <property name="minimumSize">
      <size>
       <width>50</width>
       <height>50</height>
      </size>
     </property>
     <property name="text">
      <string>参数</string>
     </property>
    </widget>
   </item>
   <item row="1" column="2">
    <widget class="QPushButton" name="bt_opendir">
     <property name="minimumSize">
      <size>
       <width>50</width>
       <height>50</height>
      </size>
     </property>
     <property name="maximumSize">
      <size>
       <width>99999</width>
       <height>99999</height>
      </size>
     </property>
     <property name="text">
      <string>打开目录</string>
     </property>
    </widget>
   </item>
   <item row="1" column="3">
    <widget class="QPushButton" name="bt_start">
     <property name="minimumSize">
      <size>
       <width>50</width>
       <height>50</height>
      </size>
     </property>
     <property name="maximumSize">
      <size>
       <width>999999</width>
       <height>999999</height>
      </size>
     </property>
     <property name="text">
      <string>启动</string>
     </property>
    </widget>
   </item>
   <item row="2" column="0">
    <widget class="QPushButton" name="bt_chuansong">
     <property name="minimumSize">
      <size>
       <width>50</width>
       <height>50</height>
      </size>
     </property>
     <property name="maximumSize">
      <size>
       <width>999999</width>
       <height>999999</height>
      </size>
     </property>
     <property name="text">
      <string>传送
脚本</string>
     </property>
    </widget>
   </item>
   <item row="2" column="1">
    <widget class="QPushButton" name="bt_moban">
     <property name="minimumSize">
      <size>
       <width>50</width>
       <height>50</height>
      </size>
     </property>
     <property name="maximumSize">
      <size>
       <width>99999</width>
       <height>99999</height>
      </size>
     </property>
     <property name="text">
      <string>传送
模板</string>
     </property>
    </widget>
   </item>
   <item row="2" column="2">
    <widget class="QPushButton" name="bt_moban_maodian">
     <property name="minimumSize">
      <size>
       <width>50</width>
       <height>50</height>
      </size>
     </property>
     <property name="maximumSize">
      <size>
       <width>999</width>
       <height>9999</height>
      </size>
     </property>
     <property name="text">
      <string>锚点
模板</string>
     </property>
    </widget>
   </item>
   <item row="2" column="3">
    <widget class="QPushButton" name="bt_del">
     <property name="minimumSize">
      <size>
       <width>50</width>
       <height>50</height>
      </size>
     </property>
     <property name="maximumSize">
      <size>
       <width>99999</width>
       <height>99999</height>
      </size>
     </property>
     <property name="text">
      <string>删除
任务</string>
     </property>
    </widget>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>

1、ui手动操作

1、打开qt,新建QWidget项目  大小316*196

2、新增按钮Push button  加7个 68*55

3、添加水平布局

4、在水平布局中添加一个line edit输入框,和check box多选框

5、右键主窗口--布局--珊格布局

2、按钮按键重命名

3、类重命名

后面对接py使用

formpy
bt_arg
bt_chuansong
bt_del
bt_moban
bt_moban_maodian
bt_opendir
bt_start
horizontalLayout
cb_is_checked
led_name

感觉貌似漏了点啥,下面做不成功的话直接用一开始的xml  后面补充这个

二、添加工具栏--任务包按钮

1、配置文件读取

我们后面要改的东西越来越多了,先加个变量文件方便我们后面调用

下面我们使用的config.get意思是从./datas/setting.ini文件中读取关键字PACKS_TASK对应的路径,如果没有的话默认值为./datas/Task/

vi state.py

# -*- coding: utf-8 -*-
import configparser
import json
import os
import sys

# 创建 ConfigParser 对象
config = configparser.ConfigParser()
# 加载 INI 文件
config.read("./datas/setting.ini")


#Task任务目录路径
PACKS_TASK = config.get('seting', 'PACKS_TASK', fallback='./datas/Task/')

2、工具栏添加按钮

    #添加任务包名为按钮 
    def add_tool_item(self, dir_):


        action_item = QWidgetAction(self)
        dir_ = dir_.replace("/", "\\")

        if dir_[-1] == "\\":
            dir_ = dir_[:-1]

        #这里把路径下的目录名取出来当作按钮名称
        bt_item = QPushButton(dir_.split("\\")[-1].strip())
        bt_item.setMaximumWidth(int(80 * ratio))

        #读取项目的说明打印出来
        try:
            with open(dir_ + "/" + "使用说明.txt", "r", encoding="utf-8") as f:
                txt = f.read()
        except:
            try:
                with open(dir_ + "/" + "使用说明.txt", "r", encoding="utf-8") as f:
                    txt = f.read()
            except:
                txt = "无  \n(可以在任务包文件夹下创建一个 使用说明.txt 文件 来添加说明)"

        bt_item.setToolTip(dir_ + "\n使用说明:" + txt + "\n")
        bt_item.setStyleSheet("border: none; padding: 3px;")

        #绑定按钮的功能
        bt_item.clicked.connect(functools.partial(self.show_tool_item, dir_))

        #添加动作
        action_item.setDefaultWidget(bt_item)
        #按钮添加到工具栏
        self.toolBar.addAction(action_item)

    #按钮触发函数,先放着有个东西
    def show_tool_item(self, dir_):
        pass

3、自动触发添加

class MainWindow(QMainWindow, Ui_mainWindow):
    def __init__(self):
        

       ...


        #添加任务包
        self.load_task_packs()


 
    #取任务包的路径,循环触发工具栏添加按钮
    def load_task_packs(self):
        self.toolBar.clear()
        packs = state.PACKS_TASK.split("#@@#")
        try:
            if len(packs) >= 1:
                for pack in packs:
                    if pack != "":
                        self.add_tool_item(pack)
            else:

                self.add_tool_item(state.PACKS_TASK)
        except:
            pass

4、工具栏移动、任务删除

这里在add_tool_item中添加了关于上下移动和删除的按钮,以及对应的触发函数

    def add_tool_item(self, dir_):


        action_item = QWidgetAction(self)
        dir_ = dir_.replace("/", "\\")

        if dir_[-1] == "\\":
            dir_ = dir_[:-1]

        #这里把路径下的目录名取出来当作按钮名称
        bt_item = QPushButton(dir_.split("\\")[-1].strip())
        bt_item.setMaximumWidth(int(80 * ratio))

        #读取项目的说明打印出来
        try:
            with open(dir_ + "/" + "使用说明.txt", "r", encoding="utf-8") as f:
                txt = f.read()
        except:
            try:
                with open(dir_ + "/" + "使用说明.txt", "r", encoding="utf-8") as f:
                    txt = f.read()
            except:
                txt = "无  \n(可以在任务包文件夹下创建一个 使用说明.txt 文件 来添加说明)"

        bt_item.setToolTip(dir_ + "\n使用说明:" + txt + "\n")
        bt_item.setStyleSheet("border: none; padding: 3px;")

        #绑定按钮的功能
        bt_item.clicked.connect(functools.partial(self.show_tool_item, dir_))

        #添加动作
        action_item.setDefaultWidget(bt_item)
        #按钮添加到工具栏
        self.toolBar.addAction(action_item)



        menu = QMenu(self)

        action_menu_down = QAction('顺序 ↓', self)
        action_menu_down.triggered.connect(functools.partial(self.down_tool_item, len(self.toolBar.actions())))
        menu.addAction(action_menu_down)
        action_menu_up = QAction('顺序 ↑', self)
        action_menu_up.triggered.connect(functools.partial(self.up_tool_item, len(self.toolBar.actions())))
        menu.addAction(action_menu_up)
        action_menu_del = QAction('删除任务包', self)
        action_menu_del.triggered.connect(functools.partial(self.del_tool_item, action_item, bt_item, dir_))
        menu.addAction(action_menu_del)
        # 将菜单关联到工具栏上
        bt_item.setContextMenuPolicy(Qt.CustomContextMenu)
        bt_item.customContextMenuRequested.connect(lambda pos: menu.exec_(bt_item.mapToGlobal(pos)))
        self.change_tool_show_style(dir_)

    def del_tool_item(self, action_item, bt_item, dir_):
        self.toolBar.removeAction(action_item)
        if state.PATH_TASK.replace("/", "\\") == dir_:
            self.datas = []
            self.row = 0
            self.column = -1
            # 清空当前布局
            for i in reversed(range(self.g_box.count())):
                self.g_box.itemAt(i).widget().setParent(None)
        del bt_item, action_item  # 删除动作对象和bt对象

        txt_ = ""
        packs = state.PACKS_TASK.split("#@@#")

        if len(packs) >= 1:
            for pack in packs:

                if os.path.realpath(dir_) != os.path.realpath(pack) and pack != "":
                    txt_ = txt_ + pack + "#@@#"
        state.PACKS_TASK = txt_
        print(f"成功移除{dir_}任务包")
        self.sg.mysig_tishi.emit(f"成功移除{dir_}任务包")

    def down_tool_item(self, idex):
        task_list = state.PACKS_TASK.split("#@@#")
        if idex < len(task_list) - 1:
            # 将指定索引位置的成员与其前一个成员交换位置
            task_list[idex], task_list[idex + 1] = task_list[idex + 1], task_list[idex]

        state.PACKS_TASK = ""
        for item in task_list:
            if item != "":
                state.PACKS_TASK += "#@@#" + item

        self.load_task_packs()

    def up_tool_item(self, idex):
        task_list = state.PACKS_TASK.split("#@@#")
        if idex > 0:
            # 将指定索引位置的成员与其后一个成员交换位置
            task_list[idex], task_list[idex - 1] = task_list[idex - 1], task_list[idex]
        state.PACKS_TASK = ""
        for item in task_list:
            if item != "":
                state.PACKS_TASK += "#@@#" + item

        self.load_task_packs()

不知道按钮为什么时灵时不灵,不重要先忽略

三、任务包--设置工作目录

1、更新变量

# -*- coding: utf-8 -*-
import configparser
import json
import os
import sys

# 创建 ConfigParser 对象
config = configparser.ConfigParser()
# 加载 INI 文件
config.read("./datas/setting.ini")


#Task任务目录路径
PACKS_TASK = config.get('seting', 'PACKS_TASK', fallback='./datas/Task/')

PATH_TASK = config.get('seting', 'PATH_TASK', fallback='./datas/Task/')




PROVIDERS = config.get('seting', 'PROVIDERS', fallback="""["CUDAExecutionProvider", "CPUExecutionProvider"]""")
PROVIDERS = json.loads(PROVIDERS.replace("'", '"'))
LIANZHAOFUWU = config.get('seting', 'LIANZHAOFUWU', fallback='./datas/jiaoben/躺宝连招插件.exe')
DUANGKOUHAO = config.get('seting', 'DUANGKOUHAO', fallback='29943')
GAME_TITLE = config.get('seting', 'GAME_TITLE', fallback='原神')
LIANZHAO = config.get('seting', 'LIANZHAO', fallback='阵容1草神2久岐忍3钟离4雷神.txt')
PATH_TASK = config.get('seting', 'PATH_TASK', fallback='./datas/Task/')
PATH_JIAOBEN = config.get('seting', 'PATH_JIAOBEN', fallback='./datas/JiaoBen/')
PATH_JUESE = config.get('seting', 'PATH_JUESE', fallback='./datas/img/juese/')
PATH_ADDRESS = config.get('seting', 'PATH_ADDRESS', fallback='./datas/img/address/')
WEIGHTS = config.get('seting', 'WEIGHTS', fallback='./datas/yolov5s_320.onnx')
IMGSIZE_WIDTH = int(config.get('seting', 'IMGSIZE_WIDTH', fallback='320'))
IMGSIZE_HEIGHT = int(config.get('seting', 'IMGSIZE_HEIGHT', fallback='320'))
WINDOW_WIDTH = int(config.get('seting', 'WINDOW_WIDTH', fallback="640"))
WINDOW_HEIGHT = int(config.get('seting', 'WINDOW_HEIGHT', fallback="900"))
WINDOW_LEFT = int(config.get('seting', 'WINDOW_LEFT', fallback="0"))
WINDOW_TOP = int(config.get('seting', 'WINDOW_TOP', fallback="300"))

CMD_WIDTH = int(config.get('seting', 'CMD_WIDTH', fallback="800"))
CMD_HEIGHT = int(config.get('seting', 'CMD_HEIGHT', fallback="400"))
CMD_LEFT = int(config.get('seting', 'CMD_LEFT', fallback="500"))
CMD_TOP = int(config.get('seting', 'CMD_TOP', fallback="300"))

ON_SHUTDOWN = int(config.get('seting', 'ON_SHUTDOWN', fallback="0"))
ON_JIXING = int(config.get('seting', 'ON_JIXING', fallback="0"))
ON_NEXTPACK = int(config.get('seting', 'ON_NEXTPACK', fallback="0"))
ON_LIANZHAOBUJIANCE = int(config.get('seting', 'ON_LIANZHAOBUJIANCE', fallback="0"))
ON_STARTWITH = int(config.get('seting', 'ON_STARTWITH', fallback="0"))
TIMEOUT_DAGUAI = int(config.get('seting', 'TIMEOUT_DAGUAI', fallback='120'))

2、保存窗口配置

    def save_ini_seting(self):
        try:
            hwnd = ctypes.windll.kernel32.GetConsoleWindow()
            if hwnd:
                rect = win32gui.GetWindowRect(hwnd)
                x, y, w, h = rect[0], rect[1], rect[2] - rect[0], rect[3] - rect[1]
            else:
                x, y, w, h = state.CMD_LEFT, state.CMD_TOP, state.CMD_WIDTH, state.CMD_HEIGHT

            # 创建 ConfigParser 对象
            config = configparser.ConfigParser()
            # 添加节和键-值对
            config['seting'] = {
                "GAME_TITLE": state.GAME_TITLE,
                'LIANZHAO': state.LIANZHAO,
                'LIANZHAOFUWU': state.LIANZHAOFUWU,
                'DUANGKOUHAO': state.DUANGKOUHAO,
                'PATH_TASK': state.PATH_TASK,
                'PATH_JIAOBEN': state.PATH_JIAOBEN,
                'PACKS_TASK': state.PACKS_TASK,
                'WEIGHTS': state.WEIGHTS,
                'PROVIDERS': state.PROVIDERS,
                'IMGSIZE_WIDTH': str(state.IMGSIZE_WIDTH),
                'IMGSIZE_HEIGHT': str(state.IMGSIZE_HEIGHT),
                'WINDOW_WIDTH': str(self.width()),
                'WINDOW_HEIGHT': str(self.height()),
                'WINDOW_LEFT': str(self.x()),
                'WINDOW_TOP': str(self.y()),
                'CMD_WIDTH': str(w),
                'CMD_HEIGHT': str(h),
                'CMD_LEFT': str(x),
                'CMD_TOP': str(y),
                'ON_SHUTDOWN': str(state.ON_SHUTDOWN),
                'ON_JIXING': str(state.ON_JIXING),
                'ON_NEXTPACK': str(state.ON_NEXTPACK),
                'ON_STARTWITH': str(state.ON_STARTWITH),
                'ON_LIANZHAOBUJIANCE': str(state.ON_LIANZHAOBUJIANCE),
                'TIMEOUT_DAGUAI': str(state.TIMEOUT_DAGUAI)
            }
            # 写入配置到 INI 文件
            with open("./datas/setting.ini", 'w') as configfile:
                config.write(configfile)
        except:
            pass

3、获取工作栏动作

    def change_tool_show_style(self, dir_):
        # 获取工具栏上的所有动作
        actions_list = self.toolBar.actions()

        # 遍历处理每个动作
        for action in actions_list:
            # 在这里对每个动作进行处理

            bt_item = action.defaultWidget()
            if dir_ == bt_item.toolTip().replace("/", "\\").split("\n")[0]:
                bt_item.setStyleSheet("border-width: 1px; padding: 3px;")
            else:
                bt_item.setStyleSheet("border: none; padding: 3px;")

4、更新任务-路由

后续这里打算通过判断脚本的id来选择不同的任务,比如说我们下面注释的内容中,我们会通过工具栏中目录下的jiaoben.ini 配置文件中的type 去判断这个任务具体是要做什么

   def update_tasks(self):
        try:
            self.datas = []
            self.row = 0
            self.column = -1

            # 清空当前布局
            for i in reversed(range(self.g_box.count())):
                self.g_box.itemAt(i).widget().setParent(None)
            # 遍历文件夹下有哪些目录
            # 判断这个路径是否存在
            if not os.path.exists(state.PATH_TASK):
                state.PATH_TASK = "./datas/Task/"

            # # 创建 ConfigParser 对象
            # config_main = configparser.ConfigParser()
            # # 加载 INI 文件
            #
            # config_main.read(os.path.join(state.PATH_TASK, "细节参数.ini"))
            # self.run_times = int(config_main.get('seting', 'run_times', fallback='1'))
            # self.action_run_times.setText(f"设置:当前任务包 执行次数:{self.run_times}")
            #
            # # 获取文件夹列表
            # folders = [item for item in os.listdir(state.PATH_TASK) if
            #            os.path.isdir(os.path.join(state.PATH_TASK, item))]
            # # 将文件夹名称中的数字提取出来,并按照数字大小排序
            # sorted_folders = sorted(folders, key=lambda x: extract_number(x))
            # for item in sorted_folders:
            #     item_path = os.path.join(state.PATH_TASK, item)
            #     if os.path.isdir(item_path):
            #         # 创建 ConfigParser 对象
            #         config = configparser.ConfigParser()
            #         # 加载 INI 文件
            #         config.read(os.path.join(item_path, "jiaoben.ini"))
            #         if config.get('seting', 'type', fallback='1') == "2":
            #             # 副本任务
            #             self.add_taskfuben(item_path, config)
            #         elif config.get('seting', 'type', fallback='1') == "3":
            #             # 脚本任务
            #             self.add_taskjiaoben(item_path, config)
            #         elif config.get('seting', 'type', fallback='1') == "4":
            #             # 脚本任务
            #             self.add_xuanjue(item_path, config)
            #         elif config.get('seting', 'type', fallback='1') == "5":
            #             # 脚本任务
            #             self.add_xuanlianzhao(item_path, config)
            #         elif config.get('seting', 'type', fallback='1') == "6":
            #             self.add_taskpy(item_path, config)
            #         else:
            #             # 锄地任务
            #             self.add_task(item_path, config)
        except Exception:
            print(f"请选择任务文件夹,目前没有这个文件夹{state.PATH_TASK}")

5、保存

    def save(self):

        for idex in range(len(self.datas)):
            self.returnPressed_name(idex)
        # 重写closeEvent方法,在窗口关闭时调用quit()退出应用程序

        for i, data in enumerate(self.datas):
            dir_ = os.path.join(state.PATH_TASK, data["name"])
            self.save_ini(dir_, data)
        self.update_tasks()
        self.save_ini_seting()

6、补全触发函数

这里的show_tool在原程序上的作用是,当我们点击工具栏的任务包时,在我们预览窗口的下面去显示单个任务,下面的函数我们可以视为,先更新保存了配置,然后修改了样式

    def show_tool_item(self, dir_):

        self.save()
        state.PATH_TASK = dir_
        self.save_ini_seting()
        #self.update_tasks()   不知道为什么作者在这里又写了一遍,先注释了
        #self.change_tool_show_style(dir_)
        print(f"成功设置{state.PATH_TASK}为当前任务文件夹")

7、全量代码

import configparser
import ctypes
import functools
import os
import re
import sys

import win32gui
# 导入PyQt5模块
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

import state
# 导入UI文件生成的类
from ui.show import Ui_DockWidget
from ui.main import Ui_mainWindow


def extract_number(folder_name):
    numbers = re.findall(r'\d+', folder_name)
    return int(numbers[0]) if numbers else float('inf')


class MySignal(QObject):
    # 无参数信号,可能用于触发显示某个路径或轨迹的操作
    mysig_show_xunlu = pyqtSignal()

    # 无参数信号,可能与YOLOv模型有关,用于触发显示模型输出或其他相关操作
    mysig_show_yolov = pyqtSignal()


# 定义FormShow类,继承自QDockWidget和Ui_DockWidget
class FormShow(QDockWidget, Ui_DockWidget):
    def __init__(self, parent=None):
        super(QDockWidget, self).__init__(parent)  # 调用父类构造函数
        self.parent = parent  # 保存父窗口引用
        self.setParent(parent)  # 设置父窗口
        self.setupUi(self)  # 初始化UI界面
        self.set_ui()  # 自定义设置UI界面

        # 设置窗口标题
        self.setWindowTitle("检测预览")

        # 设置标签控件属性
        self.lb_yolov.setScaledContents(True)
        self.lb_yolov.setAlignment(Qt.AlignCenter)
        self.lb_xunlu.setAlignment(Qt.AlignCenter)

        # 移动窗口位置
        self.move(0, 0)

        # 设置窗口保持在最顶层
        self.setWindowFlags(Qt.WindowStaysOnTopHint)

        # 计算窗口大小
        self.window_height = int(270 * ratio)
        self.window_width = int(480 * ratio)

        # 设置窗口最小尺寸
        self.setMinimumSize(self.window_width, self.window_height * 2)

        # 连接按钮点击事件
        self.bt_jia.clicked.connect(self.clicked_jia)
        self.bt_jian.clicked.connect(self.clicked_jian)

    # 自定义UI设置
    def set_ui(self):
        pass  # 此处可添加更多UI设置

    # 按钮“加”点击事件处理
    def clicked_jia(self):
        # 如果窗口高度加上增量后超过屏幕高度,则返回
        if self.window_height + 108 * ratio > 1080 * ratio:
            return

        # 更新窗口大小
        self.window_height += int(108 * ratio)
        self.window_width += int(190 * ratio)

        # 设置标签固定高度
        self.lb_xunlu.setFixedHeight(self.window_height)

        # 根据窗口宽度调整标签位置
        if self.width() >= self.lb_yolov.width() + self.lb_xunlu.width():
            self.lb_yolov.move(self.lb_xunlu.width(), 0)
        else:
            self.lb_yolov.move(0, self.lb_xunlu.height())

        # 设置标签固定大小
        self.lb_yolov.setFixedHeight(self.window_height)
        self.lb_yolov.setFixedWidth(self.window_width)

    # 按钮“减”点击事件处理
    def clicked_jian(self):
        # 如果窗口高度减去增量后小于最小高度,则返回
        if self.window_height - 108 * ratio < 270 * ratio:
            return

        # 更新窗口大小
        self.window_height -= int(108 * ratio)
        self.window_width -= int(190 * ratio)

        # 设置标签固定高度
        self.lb_xunlu.setFixedHeight(self.window_height)

        # 根据窗口宽度调整标签位置
        if self.width() >= self.lb_yolov.width() + self.lb_xunlu.width():
            self.lb_yolov.move(self.lb_xunlu.width(), 0)
        else:
            self.lb_yolov.move(0, self.lb_xunlu.height())

        # 设置标签固定大小
        self.lb_yolov.setFixedHeight(self.window_height)
        self.lb_yolov.setFixedWidth(self.window_width)

    # 重写关闭事件
    def closeEvent(self, event):
        # 关闭窗口时取消主窗口上的动作选中状态
        self.parent.action_isShow.setChecked(False)

    # 重写调整大小事件
    def resizeEvent(self, event):
        # 根据窗口宽度调整标签位置
        if self.width() >= self.lb_yolov.width() + self.lb_xunlu.width():
            self.lb_yolov.move(self.lb_xunlu.width(), 0)
        else:
            self.lb_yolov.move(0, self.lb_xunlu.height())


# 定义MainWindow类,继承自QMainWindow和Ui_mainWindow
class MainWindow(QMainWindow, Ui_mainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()  # 调用父类构造函数
        self.setupUi(self)  # 设置UI布局
        self.retranslateUi(self)  # 重新翻译UI组件的文字

        # 修改窗口样式为黑灰色,高亮
        self.set_ui()

        # 设置窗口标题
        self.setWindowTitle(f"修改主页标题")

        # 设置窗口尺寸
        self.resize(640, 900)

        # 设置窗口起始位置
        self.move(0, 300)

        # 创建网格布局
        self.g_box = QGridLayout()

        # 创建一个中间部件
        self.container_widget = QWidget()

        # 将网格布局设置给中间部件
        self.container_widget.setLayout(self.g_box)

        # 设置布局左上对齐
        self.g_box.setAlignment(Qt.AlignTop | Qt.AlignLeft)

        # 设置布局边距
        self.g_box.setContentsMargins(0, 0, 0, 0)

        # 设置布局左对齐
        self.g_box.setAlignment(Qt.AlignTop)

        # 将中间部件设置为QScrollArea的子部件
        self.sa_main.setWidget(self.container_widget)

        # 获取纵向滚动条控件
        vertical_scrollbar = self.findChild(QScrollArea)
        self.v_scrollbar = vertical_scrollbar.verticalScrollBar()

        # 设置窗口保持在最顶层
        self.setWindowFlags(Qt.WindowStaysOnTopHint)

        # 初始化变量
        self.row = 0
        self.column = -1
        self.run_times = 1
        self.column_step = 310

        # 获取主窗口的状态栏对象
        self.statusbar = self.statusBar()

        # 设置状态栏文本
        self.statusbar.showMessage('欢迎使用')

        # 初始化数据列表
        self.datas = []

        # 创建一个计时器,用于延迟布局
        self.timer = QTimer(self)
        self.timer.setSingleShot(True)  # 设置为单次触发
        self.timer.timeout.connect(self.update_layout)

        # 绑定信号槽
        self.connect_set()

        # 创建定时器  用于周期显示图片
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.on_timer_timeout)
        self.timer.start(5000)  # 每5秒触发一次

        # 添加任务包
        self.load_task_packs()

        #显示默认的任务
        # self.show_tool_item(state.PATH_TASK)

    def load_task_packs(self):
        self.toolBar.clear()
        packs = state.PACKS_TASK.split("#@@#")
        try:
            if len(packs) >= 1:
                for pack in packs:
                    if pack != "":
                        self.add_tool_item(pack)
            else:

                self.add_tool_item(state.PACKS_TASK)
        except:
            pass

    def add_tool_item(self, dir_):

        action_item = QWidgetAction(self)
        dir_ = dir_.replace("/", "\\")

        if dir_[-1] == "\\":
            dir_ = dir_[:-1]

        # 这里把路径下的目录名取出来当作按钮名称
        bt_item = QPushButton(dir_.split("\\")[-1].strip())
        bt_item.setMaximumWidth(int(80 * ratio))

        # 读取项目的说明打印出来
        try:
            with open(dir_ + "/" + "使用说明.txt", "r", encoding="utf-8") as f:
                txt = f.read()
        except:
            try:
                with open(dir_ + "/" + "使用说明.txt", "r", encoding="utf-8") as f:
                    txt = f.read()
            except:
                txt = "无  \n(可以在任务包文件夹下创建一个 使用说明.txt 文件 来添加说明)"

        bt_item.setToolTip(dir_ + "\n使用说明:" + txt + "\n")
        bt_item.setStyleSheet("border: none; padding: 3px;")

        # 绑定按钮的功能
        bt_item.clicked.connect(functools.partial(self.show_tool_item, dir_))

        # 添加动作
        action_item.setDefaultWidget(bt_item)
        # 按钮添加到工具栏
        self.toolBar.addAction(action_item)

        menu = QMenu(self)

        action_menu_down = QAction('顺序 ↓', self)
        action_menu_down.triggered.connect(functools.partial(self.down_tool_item, len(self.toolBar.actions())))
        menu.addAction(action_menu_down)
        action_menu_up = QAction('顺序 ↑', self)
        action_menu_up.triggered.connect(functools.partial(self.up_tool_item, len(self.toolBar.actions())))
        menu.addAction(action_menu_up)
        action_menu_del = QAction('删除任务包', self)
        action_menu_del.triggered.connect(functools.partial(self.del_tool_item, action_item, bt_item, dir_))
        menu.addAction(action_menu_del)
        # 将菜单关联到工具栏上
        bt_item.setContextMenuPolicy(Qt.CustomContextMenu)
        bt_item.customContextMenuRequested.connect(lambda pos: menu.exec_(bt_item.mapToGlobal(pos)))
        self.change_tool_show_style(dir_)

    def del_tool_item(self, action_item, bt_item, dir_):
        self.toolBar.removeAction(action_item)
        if state.PATH_TASK.replace("/", "\\") == dir_:
            self.datas = []
            self.row = 0
            self.column = -1
            # 清空当前布局
            for i in reversed(range(self.g_box.count())):
                self.g_box.itemAt(i).widget().setParent(None)
        del bt_item, action_item  # 删除动作对象和bt对象

        txt_ = ""
        packs = state.PACKS_TASK.split("#@@#")

        if len(packs) >= 1:
            for pack in packs:

                if os.path.realpath(dir_) != os.path.realpath(pack) and pack != "":
                    txt_ = txt_ + pack + "#@@#"
        state.PACKS_TASK = txt_
        print(f"成功移除{dir_}任务包")
        self.sg.mysig_tishi.emit(f"成功移除{dir_}任务包")

    def down_tool_item(self, idex):
        task_list = state.PACKS_TASK.split("#@@#")
        if idex < len(task_list) - 1:
            # 将指定索引位置的成员与其前一个成员交换位置
            task_list[idex], task_list[idex + 1] = task_list[idex + 1], task_list[idex]

        state.PACKS_TASK = ""
        for item in task_list:
            if item != "":
                state.PACKS_TASK += "#@@#" + item

        self.load_task_packs()

    def up_tool_item(self, idex):
        task_list = state.PACKS_TASK.split("#@@#")
        if idex > 0:
            # 将指定索引位置的成员与其后一个成员交换位置
            task_list[idex], task_list[idex - 1] = task_list[idex - 1], task_list[idex]
        state.PACKS_TASK = ""
        for item in task_list:
            if item != "":
                state.PACKS_TASK += "#@@#" + item

        self.load_task_packs()

    def save_ini_seting(self):
        try:
            hwnd = ctypes.windll.kernel32.GetConsoleWindow()
            if hwnd:
                rect = win32gui.GetWindowRect(hwnd)
                x, y, w, h = rect[0], rect[1], rect[2] - rect[0], rect[3] - rect[1]
            else:
                x, y, w, h = state.CMD_LEFT, state.CMD_TOP, state.CMD_WIDTH, state.CMD_HEIGHT

            # 创建 ConfigParser 对象
            config = configparser.ConfigParser()
            # 添加节和键-值对
            config['seting'] = {
                "GAME_TITLE": state.GAME_TITLE,
                'LIANZHAO': state.LIANZHAO,
                'LIANZHAOFUWU': state.LIANZHAOFUWU,
                'DUANGKOUHAO': state.DUANGKOUHAO,
                'PATH_TASK': state.PATH_TASK,
                'PATH_JIAOBEN': state.PATH_JIAOBEN,
                'PACKS_TASK': state.PACKS_TASK,
                'WEIGHTS': state.WEIGHTS,
                'PROVIDERS': state.PROVIDERS,
                'IMGSIZE_WIDTH': str(state.IMGSIZE_WIDTH),
                'IMGSIZE_HEIGHT': str(state.IMGSIZE_HEIGHT),
                'WINDOW_WIDTH': str(self.width()),
                'WINDOW_HEIGHT': str(self.height()),
                'WINDOW_LEFT': str(self.x()),
                'WINDOW_TOP': str(self.y()),
                'CMD_WIDTH': str(w),
                'CMD_HEIGHT': str(h),
                'CMD_LEFT': str(x),
                'CMD_TOP': str(y),
                'ON_SHUTDOWN': str(state.ON_SHUTDOWN),
                'ON_JIXING': str(state.ON_JIXING),
                'ON_NEXTPACK': str(state.ON_NEXTPACK),
                'ON_STARTWITH': str(state.ON_STARTWITH),
                'ON_LIANZHAOBUJIANCE': str(state.ON_LIANZHAOBUJIANCE),
                'TIMEOUT_DAGUAI': str(state.TIMEOUT_DAGUAI)
            }
            # 写入配置到 INI 文件
            with open("./datas/setting.ini", 'w') as configfile:
                config.write(configfile)
        except:
            pass

    def change_tool_show_style(self, dir_):
        # 获取工具栏上的所有动作
        actions_list = self.toolBar.actions()

        # 遍历处理每个动作
        for action in actions_list:
            # 在这里对每个动作进行处理

            bt_item = action.defaultWidget()
            if dir_ == bt_item.toolTip().replace("/", "\\").split("\n")[0]:
                bt_item.setStyleSheet("border-width: 1px; padding: 3px;")
            else:
                bt_item.setStyleSheet("border: none; padding: 3px;")

    def show_tool_item(self, dir_):

        self.save()
        state.PATH_TASK = dir_
        self.save_ini_seting()
        #self.update_tasks()   不知道为什么作者在这里又写了一遍,先注释了
        #self.change_tool_show_style(dir_)
        print(f"成功设置{state.PATH_TASK}为当前任务文件夹")

    def save(self):

        for idex in range(len(self.datas)):
            self.returnPressed_name(idex)
        # 重写closeEvent方法,在窗口关闭时调用quit()退出应用程序

        for i, data in enumerate(self.datas):
            dir_ = os.path.join(state.PATH_TASK, data["name"])
            self.save_ini(dir_, data)
        self.update_tasks()
        self.save_ini_seting()

    def update_tasks(self):
        try:
            self.datas = []
            self.row = 0
            self.column = -1

            # 清空当前布局
            for i in reversed(range(self.g_box.count())):
                self.g_box.itemAt(i).widget().setParent(None)
            # 遍历文件夹下有哪些目录
            # 判断这个路径是否存在
            if not os.path.exists(state.PATH_TASK):
                state.PATH_TASK = "./datas/Task/"

            # # 创建 ConfigParser 对象
            # config_main = configparser.ConfigParser()
            # # 加载 INI 文件
            #
            # config_main.read(os.path.join(state.PATH_TASK, "细节参数.ini"))
            # self.run_times = int(config_main.get('seting', 'run_times', fallback='1'))
            # self.action_run_times.setText(f"设置:当前任务包 执行次数:{self.run_times}")
            #
            # # 获取文件夹列表
            # folders = [item for item in os.listdir(state.PATH_TASK) if
            #            os.path.isdir(os.path.join(state.PATH_TASK, item))]
            # # 将文件夹名称中的数字提取出来,并按照数字大小排序
            # sorted_folders = sorted(folders, key=lambda x: extract_number(x))
            # for item in sorted_folders:
            #     item_path = os.path.join(state.PATH_TASK, item)
            #     if os.path.isdir(item_path):
            #         # 创建 ConfigParser 对象
            #         config = configparser.ConfigParser()
            #         # 加载 INI 文件
            #         config.read(os.path.join(item_path, "jiaoben.ini"))
            #         if config.get('seting', 'type', fallback='1') == "2":
            #             # 副本任务
            #             self.add_taskfuben(item_path, config)
            #         elif config.get('seting', 'type', fallback='1') == "3":
            #             # 脚本任务
            #             self.add_taskjiaoben(item_path, config)
            #         elif config.get('seting', 'type', fallback='1') == "4":
            #             # 脚本任务
            #             self.add_xuanjue(item_path, config)
            #         elif config.get('seting', 'type', fallback='1') == "5":
            #             # 脚本任务
            #             self.add_xuanlianzhao(item_path, config)
            #         elif config.get('seting', 'type', fallback='1') == "6":
            #             self.add_taskpy(item_path, config)
            #         else:
            #             # 锄地任务
            #             self.add_task(item_path, config)
        except Exception:
            print(f"请选择任务文件夹,目前没有这个文件夹{state.PATH_TASK}")

    def on_timer_timeout(self):
        # 每隔一段时间自动显示图片
        self.sg.mysig_show_xunlu.emit()
        self.sg.mysig_show_yolov.emit()

    # 绑定信号槽
    def connect_set(self):
        # 创建一个顶级菜单
        self.menu = self.menuBar()
        self.menu_view = self.menu.addMenu("视图")

        # 创建一个动作
        self.action_isShow = QAction("显示检测结果窗口", self)
        self.action_isShow.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_F11))
        self.action_isShow.triggered.connect(self.hotkey_isShow)
        self.action_isShow.setCheckable(True)
        self.action_isShow.setChecked(True)
        self.menu_view.addAction(self.action_isShow)

        # 打开预测窗口
        self.hotkey_isShow()

        self.sg = MySignal()
        self.sg.mysig_show_xunlu.connect(self.show_xunlu)
        self.sg.mysig_show_yolov.connect(self.show_yolov)

    def show_xunlu(self, image_path="./datas/111.png"):
        # 加载本地图片
        pixmap = QPixmap(image_path)

        # 设置图片到 lb_xunlu 控件
        self.fromshow.lb_xunlu.setPixmap(pixmap)

        # 设置宽度固定,高度自适应
        self.fromshow.window_width = int(self.fromshow.window_height * pixmap.width() / pixmap.height())
        self.fromshow.lb_xunlu.setFixedHeight(self.fromshow.window_height)
        self.fromshow.lb_xunlu.setFixedWidth(self.fromshow.window_width)
        self.fromshow.lb_xunlu.setScaledContents(True)

    def show_yolov(self, image_path="./datas/111.png"):
        # 加载本地图片
        pixmap1 = QPixmap(image_path)

        # 设置图片到 lb_yolov 控件
        self.fromshow.lb_yolov.setPixmap(pixmap1)

        # 根据窗口宽度调整 lb_yolov 的位置
        if self.fromshow.width() >= self.fromshow.lb_yolov.width() + self.fromshow.lb_xunlu.width():
            self.fromshow.lb_yolov.move(self.fromshow.lb_xunlu.width(), 0)
        else:
            self.fromshow.lb_yolov.move(0, self.fromshow.lb_xunlu.height())

    # 打开预测窗口
    def hotkey_isShow(self):
        # 尝试创建悬浮窗口实例
        if not hasattr(self, "fromshow"):
            self.fromshow = FormShow(self)  # 创建悬浮窗口实例

            self.addDockWidget(Qt.TopDockWidgetArea, self.fromshow)  # 添加悬浮窗口到主窗口

    # 更新布局
    def update_layout(self):
        try:
            # 获取主窗口的宽度
            width = self.centralWidget().width()

            # 计算每行可以容纳的组件数量
            num_per_row = (width) // (self.column_step * ratio)
            if num_per_row < 1:
                num_per_row = 1

            # 清空当前布局
            for i in reversed(range(self.g_box.count())):
                self.g_box.itemAt(i).widget().setParent(None)

            # 重新添加组件到网格布局
            for i, data in enumerate(self.datas):
                self.row = int(i // num_per_row)
                self.column = int(i % num_per_row)
                self.g_box.addWidget(data["f_item"], self.row, self.column)
        except:
            pass

    # 设置主窗口样式
    def set_ui(self):
        # 设置主题样式为 Flatwhite
        from qt_material import apply_stylesheet
        apply_stylesheet(app, theme='dark_pink.xml')

        # 设置窗口样式表
        self.setStyleSheet("""
        QScrollBar::handle:horizontal {
            background-color: #A50F2C;  /* 设置滑块颜色 */
        }
        QScrollBar::handle:horizontal:hover {
            background-color: #FF1744;  /* 设置滑块颜色 */
        }
        QPushButton:hover {
            background-color: #DFC472;  /* 设置颜色 */
        }
        QPlainTextEdit {
            padding: 0px;
            margin: 0px;
        }
        QPushButton {
            padding: 0px;
            margin: 1px;
        }
        """ + "font-family: {}; font-size: {}pt;".format(font.family(), font.pointSize()))


# 主程序入口
if __name__ == '__main__':
    # 获取屏幕宽度和高度
    screen_width = ctypes.windll.user32.GetSystemMetrics(0)
    screen_height = ctypes.windll.user32.GetSystemMetrics(1)

    # 初始化QT应用
    app = QApplication(sys.argv)

    # 计算分辨率比例
    ratio = screen_width / 2560

    # 设置全局字体大小
    base_font_size = 13
    new_font_size = int(base_font_size * ratio)
    font = QFont("Arial", new_font_size)

    # 创建主窗口实例
    window_main = MainWindow()

    # 显示主窗口
    window_main.show()

    # 监听消息不关闭
    sys.exit(app.exec_())

8、多选项设置

我们上面具体做了个什么事呢,说白了就是给窗口的工具栏添加了一个选项,而且还没有具体的功能(づ ̄ 3 ̄)づ  ,他这个主要逻辑就在于show函数中的 state.PATH_TASK = dir_这么一条,当我们点工具栏的任意按钮时,都会去触发一次修改目录,然后保存配置文件,也就是./datas/setting.ini  ,也就是所谓他输出的设置为当前目录。

刚开始看的时候我还有个问题,他是从哪里设置的多个选项哇,stats.py只定义了一个路径(那个config.get之前没用过)想不明白了,然后在他的setting.ini中发现他是直接定义了多个路径,反过来一想确实唉,只要该过一次就行,后面他是可以直接读到的,如下

setting.ini

...
packs_task = ./datas/Task#@@#./datas/自行车#@@#./datas/摩托#@@#./datas/地铁#@@#./datas/高铁#@@#./datas/飞机#@@#

...

四、添加具体任务

前面做了ui的按钮,和相关的触发函数,但是现在点击页面还是什么都没反应,是因为我们没有做点击后显示什么的具体逻辑,搞一下

1 、添加执行次数

datas/Task/细节参数.ini

[seting]
run_times = 1

2 、查找字符串数字

注意这个不在类里,独立的函数

def extract_number(folder_name):
    numbers = re.findall(r'\d+', folder_name)
    return int(numbers[0]) if numbers else float('inf')

3、添加菜单

    def connect_set(self):

       ....


        # 创建一个顶级菜单
        self.menu_run = self.menu.addMenu("操作")
        # 创建一个动作
        self.action_run_times = QAction(f"设置:当前任务包 执行次数:{self.run_times}", self)
        self.action_run_times.triggered.connect(self.hotkey_run_times)
        self.menu_run.addAction(self.action_run_times)

4、添加函数

    def hotkey_run_times(self):
        try:
            text, ok = QInputDialog.getText(self, '执行次数', f'当前任务包:{state.PATH_TASK}\n请输入执行次数:',
                                            text=str(self.run_times))
            if not ok:
                return
            if text == "":
                return

            if int(text) < 1:
                self.run_times = 1
            else:

                self.run_times = int(text)

            self.action_run_times.setText(f"设置:当前任务包 执行次数:{self.run_times}")
            print(f"设置:当前任务包 执行次数:{self.run_times}")

            # 创建 ConfigParser 对象
            config_main = configparser.ConfigParser()
            # 添加节和键-值对
            config_main['seting'] = {
                "run_times": str(self.run_times),
            }
            # 写入配置到 INI 文件
            with open(os.path.join(state.PATH_TASK, "细节参数.ini"), 'w') as configfile:
                config_main.write(configfile)

        except:
            pass

5、读取任务包目录下的子目录配置文件

从指定路径下的子目录中读取配置文件,并根据配置文件中的特定值执行不同的操作

    def update_tasks(self):
        try:
            self.datas = []
            self.row = 0
            self.column = -1

            # 清空当前布局
            for i in reversed(range(self.g_box.count())):
                self.g_box.itemAt(i).widget().setParent(None)
            # 遍历文件夹下有哪些目录
            # 判断这个路径是否存在
            if not os.path.exists(state.PATH_TASK):
                state.PATH_TASK = "./datas/Task/"

            # 创建 ConfigParser 对象
            config_main = configparser.ConfigParser()
            # 加载 INI 文件

            config_main.read(os.path.join(state.PATH_TASK, "细节参数.ini"))
            self.run_times = int(config_main.get('seting', 'run_times', fallback='1'))
            self.action_run_times.setText(f"设置:当前任务包 执行次数:{self.run_times}")

            # 获取文件夹列表
            folders = [item for item in os.listdir(state.PATH_TASK) if
                       os.path.isdir(os.path.join(state.PATH_TASK, item))]
            # 将文件夹名称中的数字提取出来,并按照数字大小排序
            sorted_folders = sorted(folders, key=lambda x: extract_number(x))
            for item in sorted_folders:
                item_path = os.path.join(state.PATH_TASK, item)
                if os.path.isdir(item_path):
                    # 创建 ConfigParser 对象
                    config = configparser.ConfigParser()
                    # 加载 INI 文件
                    config.read(os.path.join(item_path, "jiaoben.ini"))
                    if config.get('seting', 'type', fallback='1') == "2":
                        # 副本任务
                        # self.add_taskfuben(item_path, config)
                        pass
                    elif config.get('seting', 'type', fallback='1') == "3":
                        # 脚本任务
                        # self.add_taskjiaoben(item_path, config)
                        pass
                    elif config.get('seting', 'type', fallback='1') == "4":
                        # 脚本任务
                        # self.add_xuanjue(item_path, config)
                        pass
                    elif config.get('seting', 'type', fallback='1') == "5":
                        # 脚本任务
                        # self.add_xuanlianzhao(item_path, config)
                        pass
                    elif config.get('seting', 'type', fallback='1') == "6":
                        # self.add_taskpy(item_path, config)
                        pass
                    else:
                        # 锄地任务
                        # self.add_task(item_path, config)
                        pass
        except Exception:
            print(f"请选择任务文件夹,目前没有这个文件夹{state.PATH_TASK}")

6、添加任务包配置

datas/Task/Py任务模板/jiaoben.ini

[seting]
type = 6
name = Py任务模板
arg = .\datas\Task\Py任务模板\参数.ini
opendir = .\datas\Task\Py任务模板
chuansong = 
cbb_address = 手动录制
chuansongmoban = 
maodianmoban = 
cb_is_checked = True

datas/Task/Py任务模板/参数.ini

[seting]
times = 1
arg1=123
arg2=ai123

7、添加模板任务

    def add_taskpy(self, dir_, config):
        '''
        添加任务
        :return:
        '''

        # 根据导入的文件
        # 获取主窗口的宽度
        width = self.centralWidget().width()
        # 计算每行可以容纳的组件数量
        num_per_row = (width) // (self.column_step * ratio)  # 假设每个组件的宽度为200

        if num_per_row < 1:
            num_per_row = 1
        self.column += 1
        if self.column == num_per_row:
            self.column = 0
            self.row += 1
        # 加载ini配置文件

        f_item = FramePY()

        f_item.setObjectName("frame_cont")
        f_item.raise_()
        f_item.setStyleSheet("""
                                  #frame_cont:hover {
                                      border: 1px solid red;
                                  }
                                  #frame_cont {
                                       background-color: rgba(0, 0, 0, 0);
                                  }
                                  """)
        d = {}
        dir_ = dir_.replace("/", "\\")
        if dir_[-1] == "\\":
            dir_ = dir_[:-1]
        d["type"] = "6"
        try:
            config.set('seting', 'name', dir_.split("\\")[-1].strip())
        except:
            # 添加一个名为 'seting' 的配置节
            config.add_section('seting')
            config.set('seting', 'name', dir_.split("\\")[-1].strip())
        d["name"] = config.get('seting', 'name', fallback=dir_.split("\\")[-1]).strip()
        d["arg"] = config.get('seting', 'arg', fallback=dir_ + "\\参数.ini")
        d["opendir"] = config.get('seting', 'opendir', fallback=dir_)
        d["chuansong"] = config.get('seting', 'chuansong', fallback='')
        d["chuansongmoban"] = config.get('seting', 'chuansongmoban', fallback='')
        d["maodianmoban"] = config.get('seting', 'maodianmoban', fallback='')
        d["cb_is_checked"] = config.get('seting', 'cb_is_checked', fallback='True')
        d["cbb_address"] = config.get('seting', 'cbb_address', fallback='手动录制')
        d["cb_wakuang"] = config.get('seting', 'cb_wakuang', fallback='False')
        d["cb_caiji"] = config.get('seting', 'cb_caiji', fallback='False')
        d["cb_daguai"] = config.get('seting', 'cb_daguai', fallback='True')

        f_item.bt_chuansong.setProperty("OK_chuansong", d["chuansong"] != "")
        f_item.bt_moban.setProperty("OK_chuansongmoban", d["chuansongmoban"] != "")
        f_item.bt_moban_maodian.setProperty("OK_maodianmoban", d["maodianmoban"] != "")
        f_item.bt_chuansong.setObjectName("bt_chuansong")
        f_item.bt_moban.setObjectName("bt_moban")
        f_item.bt_moban_maodian.setObjectName("bt_moban_maodian")
        f_item.led_name.setText(d["name"])

        f_item.bt_arg.setStyleSheet("""
                                       color: rgb(237,182,43);  
                                       border-color: rgb(237,182,43); 
                                                                """)
        f_item.bt_opendir.setStyleSheet("""
                                               color: rgb(237,182,43);  
                                               border-color: rgb(237,182,43); 
                                                                        """)

        f_item.bt_chuansong.setStyleSheet("""
                                                   #bt_chuansong[OK_chuansong="true"] {
                                                   color: rgb(237,182,43);  
                                                   border-color: rgb(237,182,43); 
                                                   }
                                                """)
        f_item.bt_moban.setStyleSheet("""
                                                   #bt_moban[OK_chuansongmoban="true"] {
                                                   color: rgb(237,182,43); 
                                                   border-color: rgb(237,182,43); 
                                                   }
                                                """)
        f_item.bt_moban_maodian.setStyleSheet("""
                                                           #bt_moban_maodian[OK_maodianmoban="true"] {
                                                           color: rgb(237,182,43); 
                                                           border-color: rgb(237,182,43); 
                                                           }
                                                        """)

        if d["cb_is_checked"] == "True":
            f_item.cb_is_checked.setChecked(True)
        else:
            f_item.cb_is_checked.setChecked(False)

        idex = len(self.datas)

        # f_item.led_name.editingFinished.connect(self.save)

        f_item.bt_arg.clicked.connect(lambda: subprocess.Popen(['notepad.exe', d["arg"]]))
        f_item.bt_opendir.clicked.connect(lambda: subprocess.Popen(['explorer', d["opendir"]]))
        f_item.bt_chuansong.clicked.connect(functools.partial(self.clickd_bt_chuansong, idex))
        f_item.bt_moban.clicked.connect(functools.partial(self.clickd_bt_moban, idex))
        f_item.bt_moban_maodian.clicked.connect(functools.partial(self.clickd_bt_moban_maodian, idex))
        f_item.bt_start.clicked.connect(functools.partial(self.clickd_bt_start, idex))
        f_item.bt_del.clicked.connect(functools.partial(self.clickd_bt_del, idex))

        f_item.bt_arg.setVisible(False)
        f_item.bt_opendir.setVisible(False)
        f_item.bt_chuansong.setVisible(False)
        f_item.bt_moban.setVisible(False)
        f_item.bt_moban_maodian.setVisible(False)
        f_item.bt_start.setVisible(False)
        f_item.bt_del.setVisible(False)

        f_item.bt_moban.setToolTip(self.tanChu_img(dir_ + "/" + d["chuansongmoban"]))
        f_item.bt_moban_maodian.setToolTip(self.tanChu_img(dir_ + "/" + d["maodianmoban"]))

        d["f_item"] = f_item

        self.datas.append(d)
        self.g_box.addWidget(f_item, self.row, self.column)

        # 将py模板文件复制一份
        dir_py = os.path.join(d['opendir'], 'main.py')
        dir_ini = os.path.join(d['opendir'], '参数.ini')
        try:
            if not os.path.exists(dir_py):
                shutil.copy('datas/pymode.py', dir_py)
            if not os.path.exists(dir_ini):
                shutil.copy('datas/pymode.ini', dir_ini)
        except:
            pass

8、按键函数

    def clickd_bt_chuansong(self, idex):

        # dl_chuansong = DialogChuansong(self, idex)
        # dl_chuansong.cbb_address.setCurrentText(self.datas[idex]['cbb_address'])
        # dl_chuansong.exec_()
        pass

    def clickd_bt_moban(self, idex):
        temp_path, ok = QFileDialog.getOpenFileName(self, "传送模板图片", "", "图片文件 (*.png)")
        if ok:
            dir_ = os.path.join(state.PATH_TASK, self.datas[idex]["name"])
            if not os.path.exists(dir_):
                os.makedirs(dir_)

                # 并且将jiaoben.ini文件也填入数据然后生成
            # 将图片复制到指定目录
            path = dir_ + "/传送模板.png"
            shutil.copy(temp_path, path)
            self.datas[idex]["chuansongmoban"] = "传送模板.png"
            self.datas[idex]["f_item"].bt_moban.setProperty("OK_chuansongmoban", True)
            self.datas[idex]["f_item"].bt_moban.setStyleSheet("""
                                                            #bt_moban[OK_chuansongmoban="true"] {
                                                            color: rgb(237,182,43); 
                                                            border-color: rgb(237,182,43); 
                                                            }
                                                         """)
            self.save_ini(dir_, self.datas[idex])
            self.sg.mysig_tishi.emit(f"导入传送模板成功:{path}")
            logger.info(f"导入传送模板成功:{path}")

    def clickd_bt_moban_maodian(self, idex):
        temp_path, ok = QFileDialog.getOpenFileName(self, "锚点模板图片", "", "图片文件 (*.png)")
        if ok:
            dir_ = os.path.join(state.PATH_TASK, self.datas[idex]["name"])
            if not os.path.exists(dir_):
                os.makedirs(dir_)
                # 并且将jiaoben.ini文件也填入数据然后生成
            # 将图片复制到指定目录
            path = dir_ + "/锚点模板.png"
            shutil.copy(temp_path, path)
            self.datas[idex]["maodianmoban"] = "锚点模板.png"
            self.datas[idex]["f_item"].bt_moban_maodian.setProperty("OK_maodianmoban", True)
            self.datas[idex]["f_item"].bt_moban_maodian.setStyleSheet("""
                                                            #bt_moban_maodian[OK_maodianmoban="true"] {
                                                            color: rgb(237,182,43); 
                                                            border-color: rgb(237,182,43); 
                                                            }
                                                         """)
            self.save_ini(dir_, self.datas[idex])
            logger.info(f"导入锚点模板成功:{path}")
            self.sg.mysig_tishi.emit(f"导入锚点模板成功:{path}")

    def clickd_bt_start(self, idex):

        global ysyl
        hwnd = win32gui.FindWindow("UnityWndClass", state.GAME_TITLE)  # 替换成你实际的窗口句柄
        if hwnd == 0:
            logger.info("错误:请先开游戏!")
            self.sg.mysig_tishi.emit("错误:请先开游戏!")
            return

        left, top, right, bottom = win32gui.GetClientRect(hwnd)
        width = right - left
        height = bottom - top
        if width != 1920 or height != 1080:
            logger.info("错误:游戏分辨率不对!请确保游戏分辨率是1920x1080  建议重新设置分辨率")
            self.sg.mysig_tishi.emit("错误:游戏分辨率不对!请确保游戏分辨率是1920x1080  建议重新设置分辨率")
            return
        self.save()

        def run(idex):

            ret = False
            self.sg.mysig_mouse_through.emit(True)
            state.计次_传送重试次数 = 0
            state.状态_全局暂停 = False
            self.datas[idex]['f_item'].cb_is_checked.setChecked(True)
            for rt in range(self.run_times):
                if self.action_runstartwith.isChecked():
                    logger.info(f"任务包:{state.PATH_TASK} {rt + 1}/{self.run_times}")

                while idex < len(self.datas):
                    if state.状态_检测中:
                        time.sleep(1)
                        continue
                    if self.datas[idex]['f_item'].cb_is_checked.isChecked():
                        ret, arg = self.run(idex)
                        if arg is None:
                            arg = "0"

                        if arg != "0":
                            idex = self.arg_jisuan(arg, idex)

                        if state.状态_循环开关 == False:
                            break
                        if ret == False:
                            if state.状态_需重新传送 == True:
                                state.计次_传送重试次数 += 1
                                if state.计次_传送重试次数 >= 5:
                                    state.计次_传送重试次数 = 0
                                    logger.info("传送重试次数过多,放弃该任务!")
                                else:
                                    logger.info(f"传送重试{state.计次_传送重试次数}")
                                    time.sleep(2)
                                    continue
                            else:
                                break
                    if self.action_runstartwith.isChecked() == False:
                        break
                    state.计次_传送重试次数 = 0
                    idex += 1
                if self.action_runstartwith.isChecked() == False:
                    break
                if state.状态_循环开关 == False:
                    break
                idex = 0

            if state.状态_循环开关 != False:
                if self.action_runstartwith.isChecked():
                    # 切下一个任务包 继续
                    next_tas = self.get_next_task()
                    if state.ON_NEXTPACK == 1 and next_tas != None:
                        logger.info("切下一个任务包!")
                        state.PATH_TASK = next_tas
                        self.sg.mysig_mouse_through.emit(False)
                        self.sg.mysig_next_pack.emit()
                        return
            if ret == True and state.ON_JIXING == 1:
                ysyl.run_jixing()
            if ret == True and state.ON_SHUTDOWN == 1:
                self.sg.mysig_shutdown.emit()
            self.sg.mysig_mouse_through.emit(False)
            state.状态_循环开关 = False

        t = threading.Thread(target=run, args=(idex,))
        if state.python_var > 9:
            t.daemon = True
        else:
            t.setDaemon(True)
        t.start()
    def clickd_bt_del(self, idex):

        dir_ = os.path.join(state.PATH_TASK, self.datas[idex]["name"])
        try:
            # 删除整个文件夹
            shutil.rmtree(dir_)
            logger.info(f"文件夹 {dir_} 已被删除")
            self.update_tasks()

        except Exception as e:
            self.update_tasks()
            logger.info(f"删除文件夹时出现错误:{e}")

如果要配置传送按钮,把clickd_bt_chuansong 函数打开补全对应ui及调用即可

9、添加任务ui配置

class FramePY(QFrame, Ui_formpy):
    def __init__(self, parent=None):
        super(QFrame, self).__init__(parent)
        self.setupUi(self)
        self.set_ui()
        self.is_zd = False
        w = int(item_width * ratio)
        h = int(item_height_min * ratio)
        self.setMinimumSize(w + int(1 * ratio), h)
        self.setMaximumSize(w, h)
        # self.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowCloseButtonHint | Qt.WindowStaysOnTopHint)

    def set_ui(self):
        pass

    def mousePressEvent(self, event):
        if not self.is_zd:
            self.is_zd = not self.is_zd
            w = int(item_width * ratio)
            h = int(item_height * ratio)
            self.setMinimumSize(w + int(1 * ratio), h)
            self.setMaximumSize(w, h)
            self.bt_arg.setVisible(True)
            self.bt_opendir.setVisible(True)
            self.bt_chuansong.setVisible(True)
            self.bt_moban.setVisible(True)
            self.bt_moban_maodian.setVisible(True)
            self.bt_start.setVisible(True)
            self.bt_del.setVisible(True)
        else:
            self.is_zd = not self.is_zd
            w = int(item_width * ratio)
            h = int(item_height_min * ratio)
            self.setMinimumSize(w + int(1 * ratio), h)
            self.setMaximumSize(w, h)
            self.bt_arg.setVisible(False)
            self.bt_opendir.setVisible(False)
            self.bt_chuansong.setVisible(False)
            self.bt_moban.setVisible(False)
            self.bt_moban_maodian.setVisible(False)
            self.bt_start.setVisible(False)
            self.bt_del.setVisible(False)

10、添加通用py模板

demo1/datas/pymode.ini

[seting]
times = 1
arg1=懒人
arg2=ai

demo1/datas/pymode.py

这个文件下有好多依赖前面的模块的,我们这里是单开的目录所以没有文件,这个东西我们暂时也不用,上面的代码就是把这俩文件拷贝下到自己的目录

import configparser
import time
import traceback  # 异常处理
import tangbaowss  # 躺宝通讯
from lanrenauto.findpic.findimg import *  # 找图 搜图
from lanrenauto.tools.screenshot import screenshot  # 截图
from lanrenauto.yolov.lanrenonnx import LanRenOnnxYolov

class LanRenPY():
    def __init__(self, name: str, arg: str, opendir: str, yolov: LanRenOnnxYolov):
        self.name = name # 任务名
        self.arg = arg # 用这个可以提前设定一些参数 比如 循环次数等
        self.opendir = opendir  # 用这个可以找到任务文件夹目录  注意: ./是项目根目录
        self.yolov = yolov # 全局的yolov对象

        if os.path.isfile(self.arg):
            # 创建 ConfigParser 对象
            config = configparser.ConfigParser()
            # 加载 INI 文件
            config.read(self.arg)

            logger.info(str(config.getint('seting', 'times', fallback=1)))
            logger.info(config.get('seting', 'arg1', fallback="arg1"))
            logger.info(config.get('seting', 'arg2', fallback="arg2"))
    def yolov_show(self):
        state.QT_信号.mysig_show_yolov.emit()
    def run(self):
        '''
        入口函数 这个函数格式不支持修改,参数也不要改 是模板函数
        :return: bool 必须return bool类型  True or False
        '''
        # 寻找游戏窗口句柄
        hwnd = win32gui.FindWindow("UnityWndClass", state.GAME_TITLE)
        left, top, right, bottom = 0, 0, 1920, 1080
        try:
            # 从这里开始写执行代码
            logger.info(f"py脚本开始执行代码了!")
            time.sleep(3)

            # -----------------全局暂停/停止-------------------------
            while True:

                if state.状态_循环开关 == False:
                    state.状态_是否回放中 = False
                    state.状态_检测中 = False
                    state.状态_需重新传送 = False
                    tangbaowss.send_msg("是否回放#@@#假")
                    return False, "0"
                if  not state.状态_全局暂停:
                    break
                else:
                    tangbaowss.send_msg("是否回放#@@#假")
                    time.sleep(2)

            # ----------------------------------------------------

            # ---------------------激活游戏窗口-------------------------------
            if get_window_handle_at_mouse_position() != hwnd:
                tangbaowss.send_msg("是否回放#@@#假")
                time.sleep(0.1)
                # 激活hwnd
                hwnd = win32gui.FindWindow("UnityWndClass", state.GAME_TITLE)
                set_window_activate(hwnd)
                time.sleep(0.2)
            # yolov目标检测案例
            img_big = screenshot(hwnd, left, top, right, bottom, filename=None)
            datas, state.图片_YOLOV = self.yolov.detect(img_big, plot_box=state.开关_是否展预测结果)
            #展示预测结果
            if state.开关_是否展预测结果: self.yolov_show();
            logger.info(str(datas))
            # 识图点击 案例
            img_small = cv2.imdecode(np.fromfile(file="./datas/派蒙.png", dtype=np.uint8), cv2.IMREAD_UNCHANGED)
            find_img_and_click(img_small, hwnd, left, top, right, bottom, label="点击 派蒙",is_click=True,is_show=True)
            # 完成后返回结果
            state.状态_需重新传送 = False #如果为True则会重试 最多5次
            logger.info(f"{self.name} py执行完毕!")

            #返回时可以指定 下一个任务 从1 开始 值超过总数 则为这 整个任务包 结束了
            #支持 +xxx  -xxx  比如 +1 -1
            #"0"或者不提供 则为不干扰顺序
            return True,"0"
        except:
            state.状态_需重新传送 = False
            logger.error(f"{self.name} py执行错误: {traceback.format_exc()}")
            return False,"0"

11、窗口弹出模板

    def tanChu_img(self, imgpath):
        '''
        弹出图片
        :param imgpath:
        :return:
        '''
        # ----------------------
        try:
            return f'''<img src='{imgpath}' > '''
        except Exception as err:
            return str(err)

 

12、全量代码

import configparser
import ctypes
import functools
import os
import re
import shutil
import subprocess
import sys
import threading
import time

import win32gui
# 导入PyQt5模块
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

import state
# 导入UI文件生成的类
from ui.show import Ui_DockWidget
from ui.main import Ui_mainWindow
from ui.formpy import Ui_formpy

def extract_number(folder_name):
    numbers = re.findall(r'\d+', folder_name)
    return int(numbers[0]) if numbers else float('inf')





class FramePY(QFrame, Ui_formpy):
    def __init__(self, parent=None):
        super(QFrame, self).__init__(parent)
        self.setupUi(self)
        self.set_ui()
        self.is_zd = False
        w = int(item_width * ratio)
        h = int(item_height_min * ratio)
        self.setMinimumSize(w + int(1 * ratio), h)
        self.setMaximumSize(w, h)
        # self.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowCloseButtonHint | Qt.WindowStaysOnTopHint)

    def set_ui(self):
        pass

    def mousePressEvent(self, event):
        if not self.is_zd:
            self.is_zd = not self.is_zd
            w = int(item_width * ratio)
            h = int(item_height * ratio)
            self.setMinimumSize(w + int(1 * ratio), h)
            self.setMaximumSize(w, h)
            self.bt_arg.setVisible(True)
            self.bt_opendir.setVisible(True)
            self.bt_chuansong.setVisible(True)
            self.bt_moban.setVisible(True)
            self.bt_moban_maodian.setVisible(True)
            self.bt_start.setVisible(True)
            self.bt_del.setVisible(True)
        else:
            self.is_zd = not self.is_zd
            w = int(item_width * ratio)
            h = int(item_height_min * ratio)
            self.setMinimumSize(w + int(1 * ratio), h)
            self.setMaximumSize(w, h)
            self.bt_arg.setVisible(False)
            self.bt_opendir.setVisible(False)
            self.bt_chuansong.setVisible(False)
            self.bt_moban.setVisible(False)
            self.bt_moban_maodian.setVisible(False)
            self.bt_start.setVisible(False)
            self.bt_del.setVisible(False)


class MySignal(QObject):
    # 无参数信号,可能用于触发显示某个路径或轨迹的操作
    mysig_show_xunlu = pyqtSignal()

    # 无参数信号,可能与YOLOv模型有关,用于触发显示模型输出或其他相关操作
    mysig_show_yolov = pyqtSignal()


# 定义FormShow类,继承自QDockWidget和Ui_DockWidget
class FormShow(QDockWidget, Ui_DockWidget):
    def __init__(self, parent=None):
        super(QDockWidget, self).__init__(parent)  # 调用父类构造函数
        self.parent = parent  # 保存父窗口引用
        self.setParent(parent)  # 设置父窗口
        self.setupUi(self)  # 初始化UI界面
        self.set_ui()  # 自定义设置UI界面

        # 设置窗口标题
        self.setWindowTitle("检测预览")

        # 设置标签控件属性
        self.lb_yolov.setScaledContents(True)
        self.lb_yolov.setAlignment(Qt.AlignCenter)
        self.lb_xunlu.setAlignment(Qt.AlignCenter)

        # 移动窗口位置
        self.move(0, 0)

        # 设置窗口保持在最顶层
        self.setWindowFlags(Qt.WindowStaysOnTopHint)

        # 计算窗口大小
        self.window_height = int(270 * ratio)
        self.window_width = int(480 * ratio)

        # 设置窗口最小尺寸
        self.setMinimumSize(self.window_width, self.window_height * 2)

        # 连接按钮点击事件
        self.bt_jia.clicked.connect(self.clicked_jia)
        self.bt_jian.clicked.connect(self.clicked_jian)

    # 自定义UI设置
    def set_ui(self):
        pass  # 此处可添加更多UI设置

    # 按钮“加”点击事件处理
    def clicked_jia(self):
        # 如果窗口高度加上增量后超过屏幕高度,则返回
        if self.window_height + 108 * ratio > 1080 * ratio:
            return

        # 更新窗口大小
        self.window_height += int(108 * ratio)
        self.window_width += int(190 * ratio)

        # 设置标签固定高度
        self.lb_xunlu.setFixedHeight(self.window_height)

        # 根据窗口宽度调整标签位置
        if self.width() >= self.lb_yolov.width() + self.lb_xunlu.width():
            self.lb_yolov.move(self.lb_xunlu.width(), 0)
        else:
            self.lb_yolov.move(0, self.lb_xunlu.height())

        # 设置标签固定大小
        self.lb_yolov.setFixedHeight(self.window_height)
        self.lb_yolov.setFixedWidth(self.window_width)

    # 按钮“减”点击事件处理
    def clicked_jian(self):
        # 如果窗口高度减去增量后小于最小高度,则返回
        if self.window_height - 108 * ratio < 270 * ratio:
            return

        # 更新窗口大小
        self.window_height -= int(108 * ratio)
        self.window_width -= int(190 * ratio)

        # 设置标签固定高度
        self.lb_xunlu.setFixedHeight(self.window_height)

        # 根据窗口宽度调整标签位置
        if self.width() >= self.lb_yolov.width() + self.lb_xunlu.width():
            self.lb_yolov.move(self.lb_xunlu.width(), 0)
        else:
            self.lb_yolov.move(0, self.lb_xunlu.height())

        # 设置标签固定大小
        self.lb_yolov.setFixedHeight(self.window_height)
        self.lb_yolov.setFixedWidth(self.window_width)

    # 重写关闭事件
    def closeEvent(self, event):
        # 关闭窗口时取消主窗口上的动作选中状态
        self.parent.action_isShow.setChecked(False)

    # 重写调整大小事件
    def resizeEvent(self, event):
        # 根据窗口宽度调整标签位置
        if self.width() >= self.lb_yolov.width() + self.lb_xunlu.width():
            self.lb_yolov.move(self.lb_xunlu.width(), 0)
        else:
            self.lb_yolov.move(0, self.lb_xunlu.height())


# 定义MainWindow类,继承自QMainWindow和Ui_mainWindow
class MainWindow(QMainWindow, Ui_mainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()  # 调用父类构造函数
        self.setupUi(self)  # 设置UI布局
        self.retranslateUi(self)  # 重新翻译UI组件的文字

        # 修改窗口样式为黑灰色,高亮
        self.set_ui()

        # 设置窗口标题
        self.setWindowTitle(f"修改主页标题")

        # 设置窗口尺寸
        self.resize(640, 900)

        # 设置窗口起始位置
        self.move(0, 300)

        # 创建网格布局
        self.g_box = QGridLayout()

        # 创建一个中间部件
        self.container_widget = QWidget()

        # 将网格布局设置给中间部件
        self.container_widget.setLayout(self.g_box)

        # 设置布局左上对齐
        self.g_box.setAlignment(Qt.AlignTop | Qt.AlignLeft)

        # 设置布局边距
        self.g_box.setContentsMargins(0, 0, 0, 0)

        # 设置布局左对齐
        self.g_box.setAlignment(Qt.AlignTop)

        # 将中间部件设置为QScrollArea的子部件
        self.sa_main.setWidget(self.container_widget)

        # 获取纵向滚动条控件
        vertical_scrollbar = self.findChild(QScrollArea)
        self.v_scrollbar = vertical_scrollbar.verticalScrollBar()

        # 设置窗口保持在最顶层
        self.setWindowFlags(Qt.WindowStaysOnTopHint)

        # 初始化变量
        self.row = 0
        self.column = -1
        self.run_times = 1
        self.column_step = 310

        # 获取主窗口的状态栏对象
        self.statusbar = self.statusBar()

        # 设置状态栏文本
        self.statusbar.showMessage('欢迎使用')

        # 初始化数据列表
        self.datas = []

        # 创建一个计时器,用于延迟布局
        self.timer = QTimer(self)
        self.timer.setSingleShot(True)  # 设置为单次触发
        self.timer.timeout.connect(self.update_layout)

        # 绑定信号槽
        self.connect_set()

        # 创建定时器  用于周期显示图片
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.on_timer_timeout)
        self.timer.start(5000)  # 每5秒触发一次

        # 添加任务包
        self.load_task_packs()

        #显示默认的任务
        # self.show_tool_item(state.PATH_TASK)

    def load_task_packs(self):
        self.toolBar.clear()
        packs = state.PACKS_TASK.split("#@@#")
        try:
            if len(packs) >= 1:
                for pack in packs:
                    if pack != "":
                        self.add_tool_item(pack)
            else:

                self.add_tool_item(state.PACKS_TASK)
        except:
            pass

    def add_tool_item(self, dir_):

        action_item = QWidgetAction(self)
        dir_ = dir_.replace("/", "\\")

        if dir_[-1] == "\\":
            dir_ = dir_[:-1]

        # 这里把路径下的目录名取出来当作按钮名称
        bt_item = QPushButton(dir_.split("\\")[-1].strip())
        bt_item.setMaximumWidth(int(80 * ratio))

        # 读取项目的说明打印出来
        try:
            with open(dir_ + "/" + "使用说明.txt", "r", encoding="utf-8") as f:
                txt = f.read()
        except:
            try:
                with open(dir_ + "/" + "使用说明.txt", "r", encoding="utf-8") as f:
                    txt = f.read()
            except:
                txt = "无  \n(可以在任务包文件夹下创建一个 使用说明.txt 文件 来添加说明)"

        bt_item.setToolTip(dir_ + "\n使用说明:" + txt + "\n")
        bt_item.setStyleSheet("border: none; padding: 3px;")

        # 绑定按钮的功能
        bt_item.clicked.connect(functools.partial(self.show_tool_item, dir_))

        # 添加动作
        action_item.setDefaultWidget(bt_item)
        # 按钮添加到工具栏
        self.toolBar.addAction(action_item)

        menu = QMenu(self)

        action_menu_down = QAction('顺序 ↓', self)
        action_menu_down.triggered.connect(functools.partial(self.down_tool_item, len(self.toolBar.actions())))
        menu.addAction(action_menu_down)
        action_menu_up = QAction('顺序 ↑', self)
        action_menu_up.triggered.connect(functools.partial(self.up_tool_item, len(self.toolBar.actions())))
        menu.addAction(action_menu_up)
        action_menu_del = QAction('删除任务包', self)
        action_menu_del.triggered.connect(functools.partial(self.del_tool_item, action_item, bt_item, dir_))
        menu.addAction(action_menu_del)
        # 将菜单关联到工具栏上
        bt_item.setContextMenuPolicy(Qt.CustomContextMenu)
        bt_item.customContextMenuRequested.connect(lambda pos: menu.exec_(bt_item.mapToGlobal(pos)))
        self.change_tool_show_style(dir_)

    def del_tool_item(self, action_item, bt_item, dir_):
        self.toolBar.removeAction(action_item)
        if state.PATH_TASK.replace("/", "\\") == dir_:
            self.datas = []
            self.row = 0
            self.column = -1
            # 清空当前布局
            for i in reversed(range(self.g_box.count())):
                self.g_box.itemAt(i).widget().setParent(None)
        del bt_item, action_item  # 删除动作对象和bt对象

        txt_ = ""
        packs = state.PACKS_TASK.split("#@@#")

        if len(packs) >= 1:
            for pack in packs:

                if os.path.realpath(dir_) != os.path.realpath(pack) and pack != "":
                    txt_ = txt_ + pack + "#@@#"
        state.PACKS_TASK = txt_
        print(f"成功移除{dir_}任务包")
        self.sg.mysig_tishi.emit(f"成功移除{dir_}任务包")

    def down_tool_item(self, idex):
        task_list = state.PACKS_TASK.split("#@@#")
        if idex < len(task_list) - 1:
            # 将指定索引位置的成员与其前一个成员交换位置
            task_list[idex], task_list[idex + 1] = task_list[idex + 1], task_list[idex]

        state.PACKS_TASK = ""
        for item in task_list:
            if item != "":
                state.PACKS_TASK += "#@@#" + item

        self.load_task_packs()

    def up_tool_item(self, idex):
        task_list = state.PACKS_TASK.split("#@@#")
        if idex > 0:
            # 将指定索引位置的成员与其后一个成员交换位置
            task_list[idex], task_list[idex - 1] = task_list[idex - 1], task_list[idex]
        state.PACKS_TASK = ""
        for item in task_list:
            if item != "":
                state.PACKS_TASK += "#@@#" + item

        self.load_task_packs()

    def save_ini_seting(self):
        try:
            hwnd = ctypes.windll.kernel32.GetConsoleWindow()
            if hwnd:
                rect = win32gui.GetWindowRect(hwnd)
                x, y, w, h = rect[0], rect[1], rect[2] - rect[0], rect[3] - rect[1]
            else:
                x, y, w, h = state.CMD_LEFT, state.CMD_TOP, state.CMD_WIDTH, state.CMD_HEIGHT

            # 创建 ConfigParser 对象
            config = configparser.ConfigParser()
            # 添加节和键-值对
            config['seting'] = {
                "GAME_TITLE": state.GAME_TITLE,
                'LIANZHAO': state.LIANZHAO,
                'LIANZHAOFUWU': state.LIANZHAOFUWU,
                'DUANGKOUHAO': state.DUANGKOUHAO,
                'PATH_TASK': state.PATH_TASK,
                'PATH_JIAOBEN': state.PATH_JIAOBEN,
                'PACKS_TASK': state.PACKS_TASK,
                'WEIGHTS': state.WEIGHTS,
                'PROVIDERS': state.PROVIDERS,
                'IMGSIZE_WIDTH': str(state.IMGSIZE_WIDTH),
                'IMGSIZE_HEIGHT': str(state.IMGSIZE_HEIGHT),
                'WINDOW_WIDTH': str(self.width()),
                'WINDOW_HEIGHT': str(self.height()),
                'WINDOW_LEFT': str(self.x()),
                'WINDOW_TOP': str(self.y()),
                'CMD_WIDTH': str(w),
                'CMD_HEIGHT': str(h),
                'CMD_LEFT': str(x),
                'CMD_TOP': str(y),
                'ON_SHUTDOWN': str(state.ON_SHUTDOWN),
                'ON_JIXING': str(state.ON_JIXING),
                'ON_NEXTPACK': str(state.ON_NEXTPACK),
                'ON_STARTWITH': str(state.ON_STARTWITH),
                'ON_LIANZHAOBUJIANCE': str(state.ON_LIANZHAOBUJIANCE),
                'TIMEOUT_DAGUAI': str(state.TIMEOUT_DAGUAI)
            }
            # 写入配置到 INI 文件
            with open("./datas/setting.ini", 'w') as configfile:
                config.write(configfile)
        except:
            pass

    def change_tool_show_style(self, dir_):
        # 获取工具栏上的所有动作
        actions_list = self.toolBar.actions()

        # 遍历处理每个动作
        for action in actions_list:
            # 在这里对每个动作进行处理

            bt_item = action.defaultWidget()
            if dir_ == bt_item.toolTip().replace("/", "\\").split("\n")[0]:
                bt_item.setStyleSheet("border-width: 1px; padding: 3px;")
            else:
                bt_item.setStyleSheet("border: none; padding: 3px;")

    def show_tool_item(self, dir_):

        self.save()
        state.PATH_TASK = dir_
        self.save_ini_seting()
        #self.update_tasks()   不知道为什么作者在这里又写了一遍,先注释了
        #self.change_tool_show_style(dir_)
        print(f"成功设置{state.PATH_TASK}为当前任务文件夹")

    def save(self):

        for idex in range(len(self.datas)):
            self.returnPressed_name(idex)
        # 重写closeEvent方法,在窗口关闭时调用quit()退出应用程序

        for i, data in enumerate(self.datas):
            dir_ = os.path.join(state.PATH_TASK, data["name"])
            self.save_ini(dir_, data)
        self.update_tasks()
        self.save_ini_seting()

    def update_tasks(self):
        try:
            self.datas = []
            self.row = 0
            self.column = -1

            # 清空当前布局
            for i in reversed(range(self.g_box.count())):
                self.g_box.itemAt(i).widget().setParent(None)
            # 遍历文件夹下有哪些目录
            # 判断这个路径是否存在
            if not os.path.exists(state.PATH_TASK):
                state.PATH_TASK = "./datas/Task/"

            # 创建 ConfigParser 对象
            config_main = configparser.ConfigParser()
            # 加载 INI 文件

            config_main.read(os.path.join(state.PATH_TASK, "细节参数.ini"))
            self.run_times = int(config_main.get('seting', 'run_times', fallback='1'))
            self.action_run_times.setText(f"设置:当前任务包 执行次数:{self.run_times}")

            # 获取文件夹列表
            folders = [item for item in os.listdir(state.PATH_TASK) if
                       os.path.isdir(os.path.join(state.PATH_TASK, item))]
            print(folders)
            # 将文件夹名称中的数字提取出来,并按照数字大小排序
            sorted_folders = sorted(folders, key=lambda x: extract_number(x))
            for item in sorted_folders:
                item_path = os.path.join(state.PATH_TASK, item)
                if os.path.isdir(item_path):
                    # 创建 ConfigParser 对象
                    config = configparser.ConfigParser()
                    # 加载 INI 文件
                    config.read(os.path.join(item_path, "jiaoben.ini"))
                    if config.get('seting', 'type', fallback='1') == "2":
                        # 副本任务
                        # self.add_taskfuben(item_path, config)
                        pass
                    elif config.get('seting', 'type', fallback='1') == "3":
                        # 脚本任务
                        # self.add_taskjiaoben(item_path, config)
                        pass
                    elif config.get('seting', 'type', fallback='1') == "4":
                        # 脚本任务
                        # self.add_xuanjue(item_path, config)
                        pass
                    elif config.get('seting', 'type', fallback='1') == "5":
                        # 脚本任务
                        # self.add_xuanlianzhao(item_path, config)
                        pass
                    elif config.get('seting', 'type', fallback='1') == "6":
                        self.add_taskpy(item_path, config)

                    else:
                        # 锄地任务
                        # self.add_task(item_path, config)
                        pass
        except Exception:
            print(f"请选择任务文件夹,目前没有这个文件夹{state.PATH_TASK}")


    def add_taskpy(self, dir_, config):
        '''
        添加任务
        :return:
        '''

        # 根据导入的文件
        # 获取主窗口的宽度
        width = self.centralWidget().width()
        # 计算每行可以容纳的组件数量
        num_per_row = (width) // (self.column_step * ratio)  # 假设每个组件的宽度为200

        if num_per_row < 1:
            num_per_row = 1
        self.column += 1
        if self.column == num_per_row:
            self.column = 0
            self.row += 1
        # 加载ini配置文件

        f_item = FramePY()

        f_item.setObjectName("frame_cont")
        f_item.raise_()
        f_item.setStyleSheet("""
                                  #frame_cont:hover {
                                      border: 1px solid red;
                                  }
                                  #frame_cont {
                                       background-color: rgba(0, 0, 0, 0);
                                  }
                                  """)
        d = {}
        dir_ = dir_.replace("/", "\\")
        if dir_[-1] == "\\":
            dir_ = dir_[:-1]
        d["type"] = "6"
        try:
            config.set('seting', 'name', dir_.split("\\")[-1].strip())
        except:
            # 添加一个名为 'seting' 的配置节
            config.add_section('seting')
            config.set('seting', 'name', dir_.split("\\")[-1].strip())
        d["name"] = config.get('seting', 'name', fallback=dir_.split("\\")[-1]).strip()
        d["arg"] = config.get('seting', 'arg', fallback=dir_ + "\\参数.ini")
        d["opendir"] = config.get('seting', 'opendir', fallback=dir_)
        d["chuansong"] = config.get('seting', 'chuansong', fallback='')
        d["chuansongmoban"] = config.get('seting', 'chuansongmoban', fallback='')
        d["maodianmoban"] = config.get('seting', 'maodianmoban', fallback='')
        d["cb_is_checked"] = config.get('seting', 'cb_is_checked', fallback='True')
        d["cbb_address"] = config.get('seting', 'cbb_address', fallback='手动录制')
        d["cb_wakuang"] = config.get('seting', 'cb_wakuang', fallback='False')
        d["cb_caiji"] = config.get('seting', 'cb_caiji', fallback='False')
        d["cb_daguai"] = config.get('seting', 'cb_daguai', fallback='True')

        f_item.bt_chuansong.setProperty("OK_chuansong", d["chuansong"] != "")
        f_item.bt_moban.setProperty("OK_chuansongmoban", d["chuansongmoban"] != "")
        f_item.bt_moban_maodian.setProperty("OK_maodianmoban", d["maodianmoban"] != "")
        f_item.bt_chuansong.setObjectName("bt_chuansong")
        f_item.bt_moban.setObjectName("bt_moban")
        f_item.bt_moban_maodian.setObjectName("bt_moban_maodian")
        f_item.led_name.setText(d["name"])

        f_item.bt_arg.setStyleSheet("""
                                       color: rgb(237,182,43);  
                                       border-color: rgb(237,182,43); 
                                                                """)
        f_item.bt_opendir.setStyleSheet("""
                                               color: rgb(237,182,43);  
                                               border-color: rgb(237,182,43); 
                                                                        """)

        f_item.bt_chuansong.setStyleSheet("""
                                                   #bt_chuansong[OK_chuansong="true"] {
                                                   color: rgb(237,182,43);  
                                                   border-color: rgb(237,182,43); 
                                                   }
                                                """)
        f_item.bt_moban.setStyleSheet("""
                                                   #bt_moban[OK_chuansongmoban="true"] {
                                                   color: rgb(237,182,43); 
                                                   border-color: rgb(237,182,43); 
                                                   }
                                                """)
        f_item.bt_moban_maodian.setStyleSheet("""
                                                           #bt_moban_maodian[OK_maodianmoban="true"] {
                                                           color: rgb(237,182,43); 
                                                           border-color: rgb(237,182,43); 
                                                           }
                                                        """)

        if d["cb_is_checked"] == "True":
            f_item.cb_is_checked.setChecked(True)
        else:
            f_item.cb_is_checked.setChecked(False)

        idex = len(self.datas)

        # f_item.led_name.editingFinished.connect(self.save)

        f_item.bt_arg.clicked.connect(lambda: subprocess.Popen(['notepad.exe', d["arg"]]))
        f_item.bt_opendir.clicked.connect(lambda: subprocess.Popen(['explorer', d["opendir"]]))
        f_item.bt_chuansong.clicked.connect(functools.partial(self.clickd_bt_chuansong, idex))

        f_item.bt_moban.clicked.connect(functools.partial(self.clickd_bt_moban, idex))
        f_item.bt_moban_maodian.clicked.connect(functools.partial(self.clickd_bt_moban_maodian, idex))
        f_item.bt_start.clicked.connect(functools.partial(self.clickd_bt_start, idex))
        f_item.bt_del.clicked.connect(functools.partial(self.clickd_bt_del, idex))

        f_item.bt_arg.setVisible(False)
        f_item.bt_opendir.setVisible(False)
        f_item.bt_chuansong.setVisible(False)
        f_item.bt_moban.setVisible(False)
        f_item.bt_moban_maodian.setVisible(False)
        f_item.bt_start.setVisible(False)
        f_item.bt_del.setVisible(False)

        f_item.bt_moban.setToolTip(self.tanChu_img(dir_ + "/" + d["chuansongmoban"]))
        f_item.bt_moban_maodian.setToolTip(self.tanChu_img(dir_ + "/" + d["maodianmoban"]))

        d["f_item"] = f_item

        self.datas.append(d)
        self.g_box.addWidget(f_item, self.row, self.column)

        # 将py模板文件复制一份
        dir_py = os.path.join(d['opendir'], 'main.py')
        dir_ini = os.path.join(d['opendir'], '参数.ini')
        try:
            if not os.path.exists(dir_py):
                shutil.copy('datas/pymode.py', dir_py)
            if not os.path.exists(dir_ini):
                shutil.copy('datas/pymode.ini', dir_ini)
        except:
            pass
    def tanChu_img(self, imgpath):
        '''
        弹出图片
        :param imgpath:
        :return:
        '''
        # ----------------------
        try:
            return f'''<img src='{imgpath}' > '''
        except Exception as err:
            return str(err)


    def clickd_bt_chuansong(self, idex):

        # dl_chuansong = DialogChuansong(self, idex)
        # dl_chuansong.cbb_address.setCurrentText(self.datas[idex]['cbb_address'])
        # dl_chuansong.exec_()
        pass

    def clickd_bt_moban(self, idex):
        temp_path, ok = QFileDialog.getOpenFileName(self, "传送模板图片", "", "图片文件 (*.png)")
        if ok:
            dir_ = os.path.join(state.PATH_TASK, self.datas[idex]["name"])
            if not os.path.exists(dir_):
                os.makedirs(dir_)

                # 并且将jiaoben.ini文件也填入数据然后生成
            # 将图片复制到指定目录
            path = dir_ + "/传送模板.png"
            shutil.copy(temp_path, path)
            self.datas[idex]["chuansongmoban"] = "传送模板.png"
            self.datas[idex]["f_item"].bt_moban.setProperty("OK_chuansongmoban", True)
            self.datas[idex]["f_item"].bt_moban.setStyleSheet("""
                                                            #bt_moban[OK_chuansongmoban="true"] {
                                                            color: rgb(237,182,43); 
                                                            border-color: rgb(237,182,43); 
                                                            }
                                                         """)
            self.save_ini(dir_, self.datas[idex])
            self.sg.mysig_tishi.emit(f"导入传送模板成功:{path}")
            print(f"导入传送模板成功:{path}")

    def clickd_bt_moban_maodian(self, idex):
        temp_path, ok = QFileDialog.getOpenFileName(self, "锚点模板图片", "", "图片文件 (*.png)")
        if ok:
            dir_ = os.path.join(state.PATH_TASK, self.datas[idex]["name"])
            if not os.path.exists(dir_):
                os.makedirs(dir_)
                # 并且将jiaoben.ini文件也填入数据然后生成
            # 将图片复制到指定目录
            path = dir_ + "/锚点模板.png"
            shutil.copy(temp_path, path)
            self.datas[idex]["maodianmoban"] = "锚点模板.png"
            self.datas[idex]["f_item"].bt_moban_maodian.setProperty("OK_maodianmoban", True)
            self.datas[idex]["f_item"].bt_moban_maodian.setStyleSheet("""
                                                            #bt_moban_maodian[OK_maodianmoban="true"] {
                                                            color: rgb(237,182,43); 
                                                            border-color: rgb(237,182,43); 
                                                            }
                                                         """)
            self.save_ini(dir_, self.datas[idex])
            print(f"导入锚点模板成功:{path}")
            self.sg.mysig_tishi.emit(f"导入锚点模板成功:{path}")

    def clickd_bt_start(self, idex):

        global ysyl
        hwnd = win32gui.FindWindow("UnityWndClass", state.GAME_TITLE)  # 替换成你实际的窗口句柄
        if hwnd == 0:
            print("错误:请先开游戏!")
            self.sg.mysig_tishi.emit("错误:请先开游戏!")
            return

        left, top, right, bottom = win32gui.GetClientRect(hwnd)
        width = right - left
        height = bottom - top
        if width != 1920 or height != 1080:
            print("错误:游戏分辨率不对!请确保游戏分辨率是1920x1080  建议重新设置分辨率")
            self.sg.mysig_tishi.emit("错误:游戏分辨率不对!请确保游戏分辨率是1920x1080  建议重新设置分辨率")
            return
        self.save()

        def run(idex):

            ret = False
            self.sg.mysig_mouse_through.emit(True)
            state.计次_传送重试次数 = 0
            state.状态_全局暂停 = False
            self.datas[idex]['f_item'].cb_is_checked.setChecked(True)
            for rt in range(self.run_times):
                if self.action_runstartwith.isChecked():
                    print(f"任务包:{state.PATH_TASK} {rt + 1}/{self.run_times}")

                while idex < len(self.datas):
                    if state.状态_检测中:
                        time.sleep(1)
                        continue
                    if self.datas[idex]['f_item'].cb_is_checked.isChecked():
                        ret, arg = self.run(idex)
                        if arg is None:
                            arg = "0"

                        if arg != "0":
                            idex = self.arg_jisuan(arg, idex)

                        if state.状态_循环开关 == False:
                            break
                        if ret == False:
                            if state.状态_需重新传送 == True:
                                state.计次_传送重试次数 += 1
                                if state.计次_传送重试次数 >= 5:
                                    state.计次_传送重试次数 = 0
                                    print("传送重试次数过多,放弃该任务!")
                                else:
                                    print(f"传送重试{state.计次_传送重试次数}")
                                    time.sleep(2)
                                    continue
                            else:
                                break
                    if self.action_runstartwith.isChecked() == False:
                        break
                    state.计次_传送重试次数 = 0
                    idex += 1
                if self.action_runstartwith.isChecked() == False:
                    break
                if state.状态_循环开关 == False:
                    break
                idex = 0

            if state.状态_循环开关 != False:
                if self.action_runstartwith.isChecked():
                    # 切下一个任务包 继续
                    next_tas = self.get_next_task()
                    if state.ON_NEXTPACK == 1 and next_tas != None:
                        print("切下一个任务包!")
                        state.PATH_TASK = next_tas
                        self.sg.mysig_mouse_through.emit(False)
                        self.sg.mysig_next_pack.emit()
                        return
            if ret == True and state.ON_JIXING == 1:
                ysyl.run_jixing()
            if ret == True and state.ON_SHUTDOWN == 1:
                self.sg.mysig_shutdown.emit()
            self.sg.mysig_mouse_through.emit(False)
            state.状态_循环开关 = False

        t = threading.Thread(target=run, args=(idex,))
        if state.python_var > 9:
            t.daemon = True
        else:
            t.setDaemon(True)
        t.start()
    def clickd_bt_del(self, idex):

        dir_ = os.path.join(state.PATH_TASK, self.datas[idex]["name"])
        try:
            # 删除整个文件夹
            shutil.rmtree(dir_)
            print(f"文件夹 {dir_} 已被删除")
            self.update_tasks()

        except Exception as e:
            self.update_tasks()
            print(f"删除文件夹时出现错误:{e}")



    def on_timer_timeout(self):
        # 每隔一段时间自动显示图片
        self.sg.mysig_show_xunlu.emit()
        self.sg.mysig_show_yolov.emit()

    # 绑定信号槽
    def connect_set(self):
        # 创建一个顶级菜单
        self.menu = self.menuBar()
        self.menu_view = self.menu.addMenu("视图")

        # 创建一个动作
        self.action_isShow = QAction("显示检测结果窗口", self)
        self.action_isShow.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_F11))
        self.action_isShow.triggered.connect(self.hotkey_isShow)
        self.action_isShow.setCheckable(True)
        self.action_isShow.setChecked(True)
        self.menu_view.addAction(self.action_isShow)

        # 打开预测窗口
        self.hotkey_isShow()

        self.sg = MySignal()
        self.sg.mysig_show_xunlu.connect(self.show_xunlu)
        self.sg.mysig_show_yolov.connect(self.show_yolov)





        # 创建一个顶级菜单
        self.menu_run = self.menu.addMenu("操作")
        # 创建一个动作
        self.action_run_times = QAction(f"设置:当前任务包 执行次数:{self.run_times}", self)
        self.action_run_times.triggered.connect(self.hotkey_run_times)
        self.menu_run.addAction(self.action_run_times)


    def hotkey_run_times(self):
        try:
            text, ok = QInputDialog.getText(self, '执行次数', f'当前任务包:{state.PATH_TASK}\n请输入执行次数:',
                                            text=str(self.run_times))
            if not ok:
                return
            if text == "":
                return

            if int(text) < 1:
                self.run_times = 1
            else:

                self.run_times = int(text)

            self.action_run_times.setText(f"设置:当前任务包 执行次数:{self.run_times}")
            print(f"设置:当前任务包 执行次数:{self.run_times}")

            # 创建 ConfigParser 对象
            config_main = configparser.ConfigParser()
            # 添加节和键-值对
            config_main['seting'] = {
                "run_times": str(self.run_times),
            }
            # 写入配置到 INI 文件
            with open(os.path.join(state.PATH_TASK, "细节参数.ini"), 'w') as configfile:
                config_main.write(configfile)

        except:
            pass

    def show_xunlu(self, image_path="./datas/111.png"):
        # 加载本地图片
        pixmap = QPixmap(image_path)

        # 设置图片到 lb_xunlu 控件
        self.fromshow.lb_xunlu.setPixmap(pixmap)

        # 设置宽度固定,高度自适应
        self.fromshow.window_width = int(self.fromshow.window_height * pixmap.width() / pixmap.height())
        self.fromshow.lb_xunlu.setFixedHeight(self.fromshow.window_height)
        self.fromshow.lb_xunlu.setFixedWidth(self.fromshow.window_width)
        self.fromshow.lb_xunlu.setScaledContents(True)

    def show_yolov(self, image_path="./datas/111.png"):
        # 加载本地图片
        pixmap1 = QPixmap(image_path)

        # 设置图片到 lb_yolov 控件
        self.fromshow.lb_yolov.setPixmap(pixmap1)

        # 根据窗口宽度调整 lb_yolov 的位置
        if self.fromshow.width() >= self.fromshow.lb_yolov.width() + self.fromshow.lb_xunlu.width():
            self.fromshow.lb_yolov.move(self.fromshow.lb_xunlu.width(), 0)
        else:
            self.fromshow.lb_yolov.move(0, self.fromshow.lb_xunlu.height())

    # 打开预测窗口
    def hotkey_isShow(self):
        # 尝试创建悬浮窗口实例
        if not hasattr(self, "fromshow"):
            self.fromshow = FormShow(self)  # 创建悬浮窗口实例

            self.addDockWidget(Qt.TopDockWidgetArea, self.fromshow)  # 添加悬浮窗口到主窗口

    # 更新布局
    def update_layout(self):
        try:
            # 获取主窗口的宽度
            width = self.centralWidget().width()

            # 计算每行可以容纳的组件数量
            num_per_row = (width) // (self.column_step * ratio)
            if num_per_row < 1:
                num_per_row = 1

            # 清空当前布局
            for i in reversed(range(self.g_box.count())):
                self.g_box.itemAt(i).widget().setParent(None)

            # 重新添加组件到网格布局
            for i, data in enumerate(self.datas):
                self.row = int(i // num_per_row)
                self.column = int(i % num_per_row)
                self.g_box.addWidget(data["f_item"], self.row, self.column)
        except:
            pass

    # 设置主窗口样式
    def set_ui(self):
        # 设置主题样式为 Flatwhite
        from qt_material import apply_stylesheet
        apply_stylesheet(app, theme='dark_pink.xml')

        # 设置窗口样式表
        self.setStyleSheet("""
        QScrollBar::handle:horizontal {
            background-color: #A50F2C;  /* 设置滑块颜色 */
        }
        QScrollBar::handle:horizontal:hover {
            background-color: #FF1744;  /* 设置滑块颜色 */
        }
        QPushButton:hover {
            background-color: #DFC472;  /* 设置颜色 */
        }
        QPlainTextEdit {
            padding: 0px;
            margin: 0px;
        }
        QPushButton {
            padding: 0px;
            margin: 1px;
        }
        """ + "font-family: {}; font-size: {}pt;".format(font.family(), font.pointSize()))


# 主程序入口
if __name__ == '__main__':
    # 获取屏幕宽度和高度
    screen_width = ctypes.windll.user32.GetSystemMetrics(0)
    screen_height = ctypes.windll.user32.GetSystemMetrics(1)

    # 初始化QT应用
    app = QApplication(sys.argv)

    # 计算分辨率比例
    ratio = screen_width / 2560

    # 设置全局字体大小
    base_font_size = 13
    new_font_size = int(base_font_size * ratio)
    font = QFont("Arial", new_font_size)

    item_width = 320
    item_height = 240
    item_height_min = 60

    # 创建主窗口实例
    window_main = MainWindow()

    # 显示主窗口
    window_main.show()

    # 监听消息不关闭
    sys.exit(app.exec_())

13、 关于多任务说明

首先,我们可以在同一个任务包下面定义多个任务

同一个任务包下的所有任务的类型必须一致

 

如果是多个任务包,那么type类型必须不一致,不然就串了

反面案例


http://www.kler.cn/news/325103.html

相关文章:

  • 【JavaEE初阶】深入解析单例模式中的饿汉模式,懒汉模式的实现以及线程安全问题
  • IDEA服务启动时无法输出日志
  • 用C++游戏开发
  • 【观察】华为:构筑先进AI存力底座,引领时代更创造时代
  • 企业如何提升知识产权管理效率?
  • linux StarRocks 安装
  • linux开启wol (网络唤醒)
  • MySQL Mail服务器集成:如何配置发送邮件?
  • 信号分解降噪 | Matlab实现基于TVFEMD-IMF能量熵增量的数据降噪方法
  • 华为OD机试 - 新学校选址(Python/JS/C/C++ 2024 E卷 100分)
  • 2024icpc(Ⅱ)网络赛补题E
  • C++(list的简单实现,着重点是迭代器)
  • navicat连接postgresql的ERROR: column datlastsysoid
  • 美图AI短片创作工具MOKI全面开放 支持生成配乐、细节修改
  • Pencils Protocol 成市场新宠,生态通证$DAPP价值几何
  • 鸿蒙HarmonyOS开发生态
  • 使用vite+react+ts+Ant Design开发后台管理项目(四)
  • 巧用枚举消除条件判断
  • ETH以太网资源学习
  • 【YashanDB知识库】yashandb执行包含带oracle dblink表的sql时性能差
  • 学习react小记
  • 独孤思维:又取关了一批副业博主,真相扎心了
  • 区块链媒体推广:15个数字解读未来-华媒舍
  • 代码随想录算法训练营day41
  • 周家庄智慧旅游小程序
  • Excel-统计日期内的个数1月到12月
  • 研究生如何利用ChatGPT帮助开展日常科研工作?
  • Linux---文件io
  • delphi制作漂亮的农历窗体(IntraWeb+Layui的完美结合)
  • 【设计模式-中介者模式】