python:写个简陋的按键精灵

本文介绍了如何使用Python开发一个简单的按键精灵,该程序能够执行重复的按键、文本输入和鼠标点击操作。程序包含三个线程:主线程用于UI界面,监听线程用于全局热键监听,自动化线程负责执行自动化任务。开发中使用了pyhooked进行全局热键监听,pyautogui实现自动化输入。此外,还分享了打包过程中遇到的问题及解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.介绍说明

本程序类似于按键精灵,代替用户执行重复性的的按键、输入文本、鼠标点击功能,可设置时间延迟。

示例如图:


使用流程:
1.共有ABCDEF六套方案,用户编辑任意方案
2.点击打开,可打开之前保存的方案
  点击保存,可保存方案。
  点击生成之后将不可编辑,并且开始监听热键,启动热键执行方案,结束热键均为F12
  点击修改之后可以修改,结束监听按键,按键将无法执行,修改完成点击生成
3.按键框可输入普通按键,如N,1,t等,执行时会按压此键
  按键框可输入文本,如:adsaf,123234等,执行时会输入此文本    
  按键框激活时,按压TAB 或者回车键,按键框会相应,,执行时照此执行
  按键框激活时,在按键框以外点击鼠标左键或者右键,按键框会相应显示,执行时照此执行
  延迟框可输入上次按键后需要间隔的时间,单位是秒,如:1  或者  1.3   等,输入汉字会崩溃

2、开发构思

三个功能同时工作,互相影响,所以需要三个线程,分别是

1.主线程,ui界面需要ui.show,所以一直占用一个线程,如果其他动作时间过长,会无响应

       使用qtdesigner.exe设置ui界面,然后调用,尽量多的在qtdesigner完成设置

2.监听线程,持续监听全局热键,以便控制方案的开启和关闭

        使用QThread多线程, pyqtSignal线程间传输信号.

        使用pyhooked import Hook, KeyboardEvent全局监听热键,本打算用PYHK,但是安装不了,听说pyhk也是有大佬打包好的,看来还有其他方法可以

3.自动化线程,收到接听到的信号之后,开始执行,自动化输入

         使用pyautogui#实现自动化

3.代码部分

# -*- coding:utf-8 -*-

import sys#读取数据文件用
import time#控制按键的延迟

import pyautogui#实现自动化
from PyQt5.QtCore import Qt, QThread, pyqtSignal# QThread多线程, pyqtSignal线程间传输信号
from PyQt5.QtGui import QDoubleValidator#延迟框只能输入数字
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5 import sip#打包时显示WARNING: Hidden import "sip" not found!,加上也没啥用
from pyhooked import Hook, KeyboardEvent#全局监听热键,本打算用PYHK,但是安装不了

from anjian_ui_main import Ui_MainWindow  # 调用qtdesigner.exe生成的主窗口,好省事


# 子线程1_执行命令,自动化线程
class BackendThread(QThread):
    def __init__(self, e=0):
        super().__init__()
        self.e = e  # 传入的按键值,F6=0,使用方案A
    def run(self):
        while 1:  # 默认一直循环
            # 执行A方案的文本框内容
            for index, step in enumerate(ui.dataALL[self.e]):
                if step:  # 若不为空
                    if index % 2 != 1:  # 除2无余数,也是就是偶数,按键信息框
                        if len(step) == 1:  # 只是1个按键,那就是按键,按压它
                            pyautogui.press(step)
                        elif step == "左键":#那就点击左键
                            pyautogui.leftClick()
                        elif step == "右键":#那就是右键
                            pyautogui.rightClick()
                        elif step == "tab":#那就按压tab键
                            print(f"输入{step}")
                            pyautogui.press("tab")
                        elif step == "Enter":
                            pyautogui.press("Enter")
                            print(f"输入{step}")
                        else:
                            pyautogui.write(step)  # 否则就是输入文本
                            print(f"输入{step}")
                    else:  # 是时间框体
                        time.sleep(float(step))#延迟数秒
                        print(f"延迟{step}秒")
            #  若是执行一次,索引为1,退出循环
            if ui.comboBoxall[self.e].currentIndex() == 0:
                print("仅执行一次")
                break
            print("再循环一次")


# 子线程2_hotkey
class hotkeynew(QThread):
    sinOut = pyqtSignal(int)  # 监听到热键后,传出的信号
    def run(self):
        def handle_events(args):
            if isinstance(args, KeyboardEvent):
                if args.current_key == "F5" and args.event_type == "key down":  # 如果F5按下了,传出0,也就是方案A
                    self.sinOut.emit(0)
                if args.current_key == "F6" and args.event_type == "key down":
                    self.sinOut.emit(1)
                if args.current_key == "F7" and args.event_type == "key down":
                    self.sinOut.emit(2)
                if args.current_key == "F8" and args.event_type == "key down":
                    self.sinOut.emit(3)
                if args.current_key == "F9" and args.event_type == "key down":
                    self.sinOut.emit(4)
                if args.current_key == "F10" and args.event_type == "key down":
                    self.sinOut.emit(5)
                if args.current_key == "F12" and args.event_type == "key down":# 如果F12按下了,传出12,关闭线程
                    self.sinOut.emit(12)
        hk = Hook()#实例化线程
        hk.handler = handle_events
        hk.hook()#启动热键监控


class MainWindow(QMainWindow, Ui_MainWindow):  # 多重继承QMainWindow和Ui_MainWindow

    def __init__(self):
        super(MainWindow, self).__init__()  # 先调用父类QMainWindow的初始化方法
        self.setupUi(self)  # 再调用setupUi方法

        # 实例化热键线程
        self.treadhotkey = hotkeynew()
        self.treadhotkey.sinOut.connect(self.doit)  # 热键后发送信号给执行
        # 实例化执行线程
        self.treaddoit = BackendThread()

        self.dataALL = [[], [], [], [], [], []]  # 用来存储6种方案
        self.anjianlei = [self.anjianA, self.anjianB, self.anjianC, self.anjianD, self.anjianE, self.anjianF]
        self.deleylei = [self.deleyA, self.deleyB, self.deleyC, self.deleyD, self.deleyE, self.deleyF]
        self.comboBoxall = [self.comboBox1, self.comboBox2, self.comboBox3, self.comboBox4, self.comboBox5,
                            self.comboBox6]

        self.saveaction.triggered.connect(self.savenow)  # 保存连接到savenow
        self.openaction.triggered.connect(self.opennow)  # 打开连接到opennow
        self.finishwriteaction.triggered.connect(self.finishwrite)  # 生成按键连接到finishwrite
        self.rewriteaction.triggered.connect(self.rewrite)  # 修改按键连接到rewrite
        #重新定义主页面的按键,去掉焦点,否则不能输入TAB。其实可以在qtdesigner.exe设置
        for u in range(6):  # 6种方案
            for i in range(8):  # 8个按键
                self.anjianlei[u].itemAt(i).widget().setFocusPolicy(Qt.ClickFocus)  # 单击成为焦点,为了tab能用
                self.deleylei[u].itemAt(i).widget().setFocusPolicy(Qt.ClickFocus)  # 单击成为焦点
                self.deleylei[u].itemAt(i).widget().setValidator(QDoubleValidator())  # 时间框体只能输入数字

    def savenow(self):  # 保存的动作
        self.dataALL = [[], [], [], [], [], []]  # 6种方案,之前的清理掉,重新保存现在的
        #现在的方案都保存进去
        for u in range(6):  # 6种方案
            for i in range(8):  # 8个按键
                self.dataALL[u].append(self.anjianlei[u].itemAt(i).widget().text())
                self.dataALL[u].append(self.deleylei[u].itemAt(i).widget().text())


        file = open("anjiandata.txt", "w")#打开txt。数据较少,为了方便,习惯用txt保存数据
        file.write(str(self.dataALL))#写入
        print("写入成功")
        file.close()#关闭

    def opennow(self):  # 打开之前保存的数据
        with open("anjiandata.txt", "r") as file:
            self.dataALL = eval(file.read())  # 之前保存的数据,file.read()是str格式,需要转化为list,用eval,字典转化用ast
        #把打开的输入输入到各个文本框
        for u in range(6):  # 6种方案
            for i in range(8):  # 8个按键
                self.anjianlei[u].itemAt(i).widget().setText(self.dataALL[u][i * 2])
                self.deleylei[u].itemAt(i).widget().setText(self.dataALL[u][i * 2 + 1])

    def finishwrite(self):  # 生成之后不可写
        if self.focusWidget():  # 保存以后有焦点就清除
            self.focusWidget().clearFocus()
        for u in range(6):  # 6种方案
            for i in range(8):  # 8个按键
                self.anjianlei[u].itemAt(i).widget().setReadOnly(1)  # 设置不可写
                self.deleylei[u].itemAt(i).widget().setReadOnly(1)# 设置不可写
                self.anjianlei[u].itemAt(i).widget().setFocusPolicy(Qt.NoFocus)  # 不能设为焦点
                self.deleylei[u].itemAt(i).widget().setFocusPolicy(Qt.NoFocus)  # 不能设为焦点
                self.dataALL[u].append(self.anjianlei[u].itemAt(i).widget().text())  # 储存下文本
                self.dataALL[u].append(self.deleylei[u].itemAt(i).widget().text())  # 储存下文本
        print("生成之后开启监控热键线程")
        self.treadhotkey.start()#生成之后开启监控热键线程

    def rewrite(self):  # 重写之后可以写
        for u in range(6):  # 6种方案
            for i in range(8):  # 8个按键
                self.anjianlei[u].itemAt(i).widget().setReadOnly(0)#设置可以写
                self.deleylei[u].itemAt(i).widget().setReadOnly(0)#设置可以写
                self.anjianlei[u].itemAt(i).widget().setFocusPolicy(Qt.ClickFocus)  # 设只能单击为焦点
                self.deleylei[u].itemAt(i).widget().setFocusPolicy(Qt.ClickFocus)  # 设只能单击为焦点
        # 重写之后关闭监控热键线程

        self.treadhotkey.terminate()
        # 如过还有执行线程,关闭他
        if self.treaddoit.isRunning():
            self.treaddoit.terminate()
            print("关闭线程")

    # 重写鼠标事件
    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton and self.focusWidget():  # 如果左键并且有焦点
            self.focusWidget().setText("左键")#这个框体的文本设为左键
        if event.button() == Qt.RightButton and self.focusWidget():
            self.focusWidget().setText("右键")

    # 执行热键的方案,重写事件
    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Tab and self.focusWidget():#如果按了tab并且有焦点
            self.focusWidget().setText("tab")#这个框体的文本设为tab
        if event.key() == 16777220 and self.focusWidget():  #如果按了回车并且有焦点
            self.focusWidget().setText("Enter")

    def doit(self, e):  # e是按键的表示,F6用6表示
        if e == 12:  # 按了F12
            if self.treaddoit.isRunning():#若有线程就关闭他
                self.treaddoit.terminate()
                print("关闭线程")
            else:
                pass
        else:
            # 实例化线程
            self.treaddoit = BackendThread(e)
            self.treaddoit.start()
            print(f"启动线程{e}")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ui = MainWindow()
    ui.show()
    sys.exit(app.exec_())

附赠qtdesigner.exe生成的代码,诸君可以拿去打包,这个不需要看,打包用

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'anjian_ui_main.ui'
#
# Created by: PyQt5 UI code generator 5.15.6
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1298, 791)
        MainWindow.setLocale(QtCore.QLocale(QtCore.QLocale.Chinese, QtCore.QLocale.China))
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName("gridLayout")
        self.verticalLayoutA = QtWidgets.QVBoxLayout()
        self.verticalLayoutA.setObjectName("verticalLayoutA")
        self.label_15 = QtWidgets.QLabel(self.centralwidget)
        self.label_15.setObjectName("label_15")
        self.verticalLayoutA.addWidget(self.label_15)
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.verticalLayout_19 = QtWidgets.QVBoxLayout()
        self.verticalLayout_19.setObjectName("verticalLayout_19")
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setObjectName("label")
        self.verticalLayout_19.addWidget(self.label)
        self.label_3 = QtWidgets.QLabel(self.centralwidget)
        self.label_3.setObjectName("label_3")
        self.verticalLayout_19.addWidget(self.label_3)
        self.label_5 = QtWidgets.QLabel(self.centralwidget)
        self.label_5.setObjectName("label_5")
        self.verticalLayout_19.addWidget(self.label_5)
        self.label_7 = QtWidgets.QLabel(self.centralwidget)
        self.label_7.setObjectName("label_7")
        self.verticalLayout_19.addWidget(self.label_7)
        self.label_9 = QtWidgets.QLabel(self.centralwidget)
        self.label_9.setObjectName("label_9")
        self.verticalLayout_19.addWidget(self.label_9)
        self.label_18 = QtWidgets.QLabel(self.centralwidget)
        self.label_18.setObjectName("label_18")
        self.verticalLayout_19.addWidget(self.label_18)
        self.label_17 = QtWidgets.QLabel(self.centralwidget)
        self.label_17.setObjectName("label_17")
        self.verticalLayout_19.addWidget(self.label_17)
        self.label_21 = QtWidgets.QLabel(self.centralwidget)
        self.label_21.setObjectName("label_21")
        self.verticalLayout_19.addWidget(self.label_21)
        self.horizontalLayout.addLayout(self.verticalLayout_19)
        self.anjianA = QtWidgets.QVBoxLayout()
        self.anjianA.setObjectName("anjianA")
        self.anjianA1 = QtWidgets.QLineEdit(self.centralwidget)
        self.anjianA1.setText("")
        self.anjianA1.setObjectName("anjianA1")
        self.anjianA.addWidget(self.anjianA1)
        self.anjianA2 = QtWidgets.QLineEdit(self.centralwidget)
        self.anjianA2.setObjectName("anjianA2")
        self.anjianA.addWidget(self.anjianA2)
        self.anjianA3 = QtWidgets.QLineEdit(self.centralwidget)
        self.anjianA3.setObjectName("anjianA3")
        self.anjianA.addWidget(self.anjianA3)
        self.anjianA4 = QtWidgets.QLineEdit(self.centralwidget)
        self.anjianA4.setObjectName("anjianA4")
        self.anjianA.addWidget(self.anjianA4)
        self.anjianA5 = QtWidgets.QLineEdit(self.centralwidget)
        self.anjianA5.setObjectName("anjianA5")
        self.anjianA.addWidget(self.anjianA5)
        self.anjianA6 = QtWidgets.QLineEdit(self.centralwidget)
        self.anjianA6.
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值