使用VS Code Python和PYQT5实现简单的按键精灵

  1. 创建工程

创建工程目录,创建UI文件,创建Main Window

使用VS Code Python和PYQT5实现简单的按键精灵_第1张图片
  1. 需求整理

使用思维导图进行简单的需求整理

使用VS Code Python和PYQT5实现简单的按键精灵_第2张图片
  1. 简单设计UI

由于没怎么用过pyqt,简单排版一下,后续也可以再用Vertical Layout和Horizontal Layout进行排版优化,按设计,三个文本框进行-功能说明,-时间间隔填写,-状态说明的显示,一个按钮可以选择-删除文件,下方的大表格框用来选中并且显示录制的脚本文件名及其属性。

使用VS Code Python和PYQT5实现简单的按键精灵_第3张图片

逐一修改控件的objectname,便于代码调用,增加可读性,然后保存ui文件生成界面代码:

使用VS Code Python和PYQT5实现简单的按键精灵_第4张图片

生成了UI文件:

代码如下:

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

# Form implementation generated from reading ui file 'c:\learning\python\pyqt\mouse_recorder\mouse_recorder.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# 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(800, 400)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.doc_textBrowser = QtWidgets.QTextBrowser(self.centralwidget)
        self.doc_textBrowser.setGeometry(QtCore.QRect(40, 10, 391, 61))
        self.doc_textBrowser.setObjectName("doc_textBrowser")
        self.deleteFile_Btn = QtWidgets.QPushButton(self.centralwidget)
        self.deleteFile_Btn.setGeometry(QtCore.QRect(450, 50, 131, 21))
        self.deleteFile_Btn.setObjectName("deleteFile_Btn")
        self.file_tableWidget = QtWidgets.QTableWidget(self.centralwidget)
        self.file_tableWidget.setGeometry(QtCore.QRect(40, 90, 700, 300))
        self.file_tableWidget.setObjectName("file_tableWidget")
        self.file_tableWidget.setColumnCount(0)
        self.file_tableWidget.setRowCount(0)
        self.file_tableWidget.horizontalHeader().setCascadingSectionResizes(False)
        self.file_tableWidget.verticalHeader().setVisible(False)
        self.timeinterval_lineEdit = QtWidgets.QLineEdit(self.centralwidget)
        self.timeinterval_lineEdit.setGeometry(QtCore.QRect(460, 20, 113, 20))
        self.timeinterval_lineEdit.setObjectName("timeinterval_lineEdit")
        self.status_textEdit = QtWidgets.QTextEdit(self.centralwidget)
        self.status_textEdit.setGeometry(QtCore.QRect(600, 10, 141, 61))
        self.status_textEdit.setObjectName("status_textEdit")
        MainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "KeyHelper"))
        self.doc_textBrowser.setHtml(_translate("MainWindow", "\n"
"\n"
"

功能说明:

\n" "

功能1.填入时间间隔(可以是小数),按F8鼠标左键连点当前位置。

\n" "

功能2.按F9开始鼠标键盘录制,再按F9结束录制并保存在scripts目录下。

\n" "

功能3.勾选录制文件,按F10进行录制回放。

\n" "

功能4.默认加载当前目录下scripts目录的脚本(其他目录目前不支持)。

\n" "

功能6.点击“删除录制文件”,可以将当前选中的文件删除。

")) self.deleteFile_Btn.setText(_translate("MainWindow", "删除录制文件"))

后续我们需要一个文件实现界面类对界面进行操作,另外实现一个功能类进行各类功能监听及实现,简单创建两个py文件:

  1. 线程设计

为了界面和功能逻辑分开,需要简单设计一下多线程:

使用VS Code Python和PYQT5实现简单的按键精灵_第5张图片
  1. 代码实现

界面逻辑代码my_window.py:

import sys
import os
import time
import json
from PyQt5.QtCore import Qt, QObject, QThread, pyqtSignal
from PyQt5.QtWidgets import QApplication, QMainWindow, QCheckBox, QTableWidgetItem, QFileDialog
from Ui_mouse_recorder import *
from my_recorder import *

class MyWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MyWindow, self).__init__(parent)
        self.__cur_checkbox = None
        self.__filelist = []
        self.__curfilepath = []
        self.setupUi(self)
        self.inittable()
        self.getscripts()
        self.timeinterval_lineEdit.setPlaceholderText("请输入点击间隔(秒):")
        self.deleteFile_Btn.clicked.connect(self.deletefile)
        self.recorder = MyRecorder(self)
        self.recorder.refresh.connect(self.refresh_file_list)
        self.recorder.minimized.connect(self.minimized_window)
        self.recorder.active.connect(self.active_window)
        self.recorder.savefile.connect(self.savefile)
        self.recorder.showstatus.connect(self.showstatus)

    def inittable(self):
        # set row and column
        self.file_tableWidget.setColumnCount(3)
        self.file_tableWidget.setRowCount(1)
        self.file_tableWidget.setColumnWidth(0, 50)
        self.file_tableWidget.setColumnWidth(1, 325)
        self.file_tableWidget.setColumnWidth(2, 325)
        # set data title
        self.file_tableWidget.horizontalHeader().setStyleSheet("QHeaderView::section{background:skyblue;}")
        self.file_tableWidget.setHorizontalHeaderItem(0, QTableWidgetItem("序号"))
        self.file_tableWidget.setHorizontalHeaderItem(1, QTableWidgetItem("文件名"))
        self.file_tableWidget.setHorizontalHeaderItem(2, QTableWidgetItem("修改时间"))
        self.file_tableWidget.horizontalHeader().setStretchLastSection(True)
        
        # setting
        self.file_tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)


    def getscripts(self):
        scriptspath = self.get_scripts_path()
        if not os.path.exists(scriptspath):
            os.makedirs(scriptspath)
        if self.__filelist==[]: #file list is [], reinit file list
            for file in os.listdir(scriptspath):
                if file.endswith(".json"):
                    self.__filelist.append(file)
        row_num = len(self.__filelist)
        self.file_tableWidget.setRowCount(row_num)
        # set data items
        for row in range(row_num):
            # first column is checkbox
            item_checked = QCheckBox(parent=self.file_tableWidget)
            item_checked.setText(str(row + 1))
            item_checked.setCheckState(Qt.Unchecked)
            item_checked.clicked.connect(self.table_item_clicked)
            self.file_tableWidget.setCellWidget(row, 0, item_checked)
            # second column is filename
            item_name = QTableWidgetItem(self.__filelist[row])
            item_name.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
            self.file_tableWidget.setItem(row, 1, item_name)
            # third column is modification time
            filepath = os.path.join(scriptspath, self.__filelist[row])
            mtime = os.stat(filepath).st_mtime
            mtime_str = time.strftime('%Y_%m_%d %H:%M:%S', time.localtime(mtime))
            item_time = QTableWidgetItem(mtime_str)
            item_time.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
            self.file_tableWidget.setItem(row, 2, item_time)

    def refresh_file_list(self, file):
        #if file do not exist
        if not file in self.__filelist:  
            self.__filelist.append(file)
        self.getscripts()

    def deletefile(self):
        ix = self.file_tableWidget.indexAt(self.__cur_checkbox.pos())
        file = self.__filelist[ix.row()]
        scriptpath = self.get_scripts_path()
        filepath = scriptpath + file
        print("delete file", filepath)
        os.remove(filepath)
        self.__filelist.remove(file)
        self.getscripts()


    def get_scripts_path(self):
        #get current path
        cwdpath = os.getcwd()
        print("current path" + cwdpath)
        #create or enter scripts path
        scriptspath = cwdpath + "\scripts\\"
        print("scripts path" + scriptspath)
        return scriptspath

    # when select one check box,others should be not selected, everytime we only delete/excute one file
    def table_item_clicked(self):
        check_box = self.sender()
        ix = self.file_tableWidget.indexAt(check_box.pos())
        num = self.file_tableWidget.rowCount()
        print("check box number:", num)
        print(ix.row(), ix.column(), check_box.isChecked())
        self.__cur_checkbox = check_box
        file = self.__filelist[ix.row()]
        scriptpath = self.get_scripts_path()
        self.__curfilepath = scriptpath + file
        if check_box.isChecked() == True:
            for i in range(num):
                if i != ix.row():
                    ch = self.file_tableWidget.cellWidget(i,0)
                    ch.setCheckState(Qt.Unchecked)

    def get_current_filepath(self):
        return self.__curfilepath
    
    def minimized_window(self):
        self.setWindowState(Qt.WindowMinimized)
    
    def active_window(self):
        self.setWindowState(Qt.WindowActive)

    def savefile(self):
        path = self.get_scripts_path()
        filepath = QFileDialog.getSaveFileName(self, "保存文件", path, "json(*.json)")
        print("save file path", filepath[0])
        filename = os.path.basename(filepath[0])
        command_list = self.recorder.getcommandlist()
        self.tofile(command_list, filepath[0])
        self.refresh_file_list(filename)

    def tofile(self, commandlist, path):
        with open(path, "w") as f:
            f.write(json.dumps(commandlist))    #使用json格式写入

    def showstatus(self, str):
        self.status_textEdit.setText(str)
        

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

应用逻辑代码my_recorder.py:

import os, time
import threading
import json
#import pyautogui
from datetime import datetime
from pynput import mouse, keyboard
from pynput.mouse import Button, Controller as MouseController
from pynput.keyboard import Key, Controller as KeyController, KeyCode
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog
from PyQt5.QtCore import QObject, QThread, pyqtSignal
from Ui_mouse_recorder import *
from my_window import *


RECORD_KEY = Key.f9
PLAY_KEY = Key.f10
CON_CLICK_KEY = Key.f8

command_list=[]

BUTTONS = {
        "Button.left": Button.left,
        "Button.right": Button.right,
        "Button.middle": Button.middle
}

class MyRecorder(QObject):
    refresh = pyqtSignal(str)
    minimized = pyqtSignal()
    active = pyqtSignal()
    savefile = pyqtSignal()
    showstatus = pyqtSignal(str)
    def __init__(self, Window=None):
        super().__init__()
        self.__window = Window
        self.__record_flag = False
        self.__conclick_flag = False
        self.__play_flag = False
        self.__key_thread = keyboard.Listener(on_press=self.on_press, on_release=self.on_release)
        self.__key_thread.start()
        self.__mouse_thread = mouse.Listener(on_click=self.on_click)
        self.__mouse_thread.start()

    def on_click(self, x, y, button, pressed):
        if self.__record_flag:
            timestr= datetime.now().strftime("%Y_%m_%d_%H_%M_%S.%f")[:-3]
            print(timestr, x, y, pressed, button)
            command_list.append((
                "mouse", #opration object
                (x, y, pressed, str(button)),
                timestr
            ))
    
    def on_press(self, key):
        if key == CON_CLICK_KEY or key == RECORD_KEY or key == PLAY_KEY:
            print("press key", key)
        else:
            if self.__record_flag:
                self.record_key(key, True) #True or False for press or release

    def on_release(self, key):
        print("release key", key)
        if key == CON_CLICK_KEY or key == RECORD_KEY or key == PLAY_KEY:
            self.handle_key(key)#do nothing when press function keys
        else:
            if self.__record_flag:
                self.record_key(key, False)

    def handle_key(self, key):
        if key == CON_CLICK_KEY:
            self.handle_conclick()
        elif key == RECORD_KEY:
            self.handle_record()
        elif key == PLAY_KEY:
            self.handle_play()
        else:
            print("error key")

    def continuous_click(self):
        interval = self.__window.timeinterval_lineEdit.text()
        print("interval", interval)
        if interval == "": #if no numbers, default to 1s
            interval = 1
        interval = float(interval)
        #self.__window.setWindowState(Qt.WindowMinimized)
        mouse = MouseController()
        while self.__conclick_flag:
            print(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
            mouse.click(button=Button.left)
            print("continuous click key")
            time.sleep(interval)

    def handle_conclick(self):
        global mouse_click_thread
        if self.__conclick_flag:
            self.__conclick_flag = False
            mouse_click_thread.join()
            if not mouse_click_thread.is_alive():
                mouse_click_thread = None
            self.showstatus.emit("当前状态:\n鼠标左键连点结束...")
            self.active.emit()
            #self.__window.setWindowState(Qt.WindowActive)
        else:
            self.__conclick_flag = True
            #create new threading everytime
            #self.__window.setWindowState(Qt.WindowMinimized)
            mouse_click_thread = threading.Thread(target=self.continuous_click)
            mouse_click_thread.start()
            self.showstatus.emit("当前状态:\n鼠标左键连点中...")
            self.minimized.emit()

    def handle_record(self):
        print(self.__record_flag)
        if self.__record_flag:
            print("stop recording")
            self.__record_flag = False
            self.showstatus.emit("当前状态:\n鼠标键盘录制结束...")
            self.active.emit()
            self.savefile.emit()
            #self.__window.setWindowState(Qt.WindowActive)
            #get current path
            #cwdpath = os.getcwd()
            #print("current path" + cwdpath)
            #create or enter scripts path
            #scriptspath = cwdpath + "\scripts\\"
            #filename = time.strftime('%Y_%m_%d_%H_%M_%S', time.localtime(time.time())) + "_Record.json"
            #path = scriptspath + filename
            #print(path)
            #self.tofile(command_list, path)
            #self.refresh.emit(filename)
        else:
            #self.__window.setWindowState(Qt.WindowMinimized)
            self.showstatus.emit("当前状态:\n鼠标键盘录制中...")
            self.minimized.emit()
            print("start recording")
            self.__record_flag = True
            print("flag", self.__record_flag)

    def handle_play(self):
        global record_play_thread
        if self.__play_flag:
            self.__play_flag = False
            print("play flag", self.__play_flag)
            record_play_thread.join()
            if record_play_thread.is_alive():
                record_play_thread = None
            self.showstatus.emit("当前状态:\n鼠标键盘播放结束...")
            self.active.emit()
        else:
            self.__play_flag = True
            #self.__window.setWindowState(Qt.WindowMinimized)
            record_play_thread = threading.Thread(target=self.do_play)
            record_play_thread.start()
            self.showstatus.emit("当前状态:\n鼠标键盘播放中...")
            self.minimized.emit()
    
    def do_play(self):
        filepath = self.__window.get_current_filepath()
        print(filepath)
        if filepath == []:
            return
        command_read = []
        with open(filepath, encoding='utf-8-sig', errors='ignore') as f:
            command_read = json.loads(f.read())
        
        while True:
            print("do play")
            old_timestamp = 0
            timestamp = 0
            i = 0
            for command in command_read:
                print("one command start", i)
                print("oldtimestamp", old_timestamp)
                timestr = command[2]
                print("timestr", timestr)
                timestamp = datetime.strptime(timestr, "%Y_%m_%d_%H_%M_%S.%f").timestamp()
                print("timestamp", timestamp)
                if old_timestamp !=0:
                    timedelay = timestamp - old_timestamp
                    print("delay", timedelay)
                    time.sleep(timedelay)
                    if not self.__play_flag:
                        break
                old_timestamp = timestamp
                if command[0] == "mouse":
                    mouse = MouseController()
                    paser = command[1]
                    x = paser[0]
                    y = paser[1]
                    pressed = paser[2]
                    value = paser[3]
                    print("mouse play", x, y, value, pressed, timestr)
                    #pyautogui.moveTo(x,y)
                    mouse.position = (x,y)
                    if pressed:
                        mouse.press(button=BUTTONS[value])
                    else:
                        mouse.release(button=BUTTONS[value])
                elif command[0] == "keyboard":
                    keycon= KeyController()
                    paser = command[1]
                    pressed = paser[2]
                    value = paser[3]
                    if value[:3] == "Key":
                        key = eval(value, {}, {"Key": keyboard.Key})
                    else:
                        key = value
                    #value = "Key." + value.replace('\'','')
                    print(value, pressed, timestr)
                    #key = getattr(KeyCode, value)
                    if pressed:
                        keycon.pressed(key)
                    else:
                        keycon.release(key)
                    print("keyboard play", key, pressed)           
                else:
                    print("incorrect mode")
                i = i + 1
                if not self.__play_flag:
                    break
            print("self.__play_flag", self.__play_flag)
            if not self.__play_flag:
                break
            interval = self.__window.timeinterval_lineEdit.text()
            print("interval", interval)
            if interval == "": #if no numbers, default to 1s
                interval = 1
            interval = float(interval)
            time.sleep(interval)

    def record_key(self, key, pressed):
        timestr= datetime.now().strftime("%Y_%m_%d_%H_%M_%S.%f")[:-3]
        print(timestr, pressed, key)
        command_list.append((
            "keyboard", #opration object
            (-1, -1, pressed, str(key).strip("'")),
            timestr
        ))

    def getcommandlist(self):
        return command_list
  1. 打包python代码成为exe文件

首先安装pyinstaller:

pip install Pyinstaller

进入工程目录执行命令打包主函数文件:

Pyinstaller -F -w my_window.py

最终的可执行exe文件在代码目录的dist目录中,如果没有防火墙或者杀毒软件之类,双击即可使用。

PS:博主也刚开始学习python跟pyqt5,以上实现肯定有所缺陷,如发现问题请多多指正。

参考文章:

https://blog.csdn.net/sinat_33408502/article/details/121903944

https://www.freesion.com/article/2628907460/

代码传到github:

https://github.com/Sampsin/learning.git

你可能感兴趣的:(PYQT,ui,python,pyqt,pip)