一键复制粘贴工具


当你查网课答案时,当你修改文章时,有没有经常使用Ctrl+C,Ctrl+V。几轮下来小拇指已经失去知觉。或许这个工具能够帮你解脱。

一键复制粘贴工具

  1. 目的是将Ctrl+C键用一个C键来代替,只需要点击请设置“复制操作”热键后的文本框,切换英文输入法,按下C即可。
  2. 再在请设置“粘贴操作”热键中按下V键即可。
  3. 点击右下角的应用热键,就完成啦。
    设置完成
  • 完成设置后会在软件同一目录下产生KeyConfig.dat,里面记录的是刚刚设置的键位,软件用完可以将其删除,下次是用再重新设置即可。
  • 现在打开网课试题界面,在电脑端登陆微信,打开与自助查题助手公众号的聊天界面。然后享受解放小拇指带来的快感吧
    一键复制与粘贴

如果你只是来拿个工具教程,上面这么多已经够了。如果你想进一步了解编程实现,可以往下看

python实现

1.HotKey类

# 创建一个Thread.threading扩展类,实例用来监听热键
class Hotkey(threading.Thread):
    def run(self):
        self.id1 = 105              # 注册热键的唯一id,用来区分热键
        self.id2 = 106
        self.user32 = ctypes.windll.user32                                # 加载user32.dll
        if not self.user32.RegisterHotKey(None, self.id1, 0, ord("C")):   # 注册快捷键C并判断是否成功
            my_pyqt_form.label4.setText("无法注册热键C")                  # 在Label_4显示错误信息
        if not self.user32.RegisterHotKey(None, self.id2, 0, ord("V")):   # 注册快捷键V并判断是否成功
            my_pyqt_form.label4.setText("无法注册热键V")
        # 检测热键是否被按下,并在出现异常时释放热键
        try:  
            msg = ctypes.wintypes.MSG()
            # 循环读取消息
            while True:
                if self.user32.GetMessageA(ctypes.byref(msg), None, 0, 0) != 0:
                    if msg.message == win32con.WM_HOTKEY:
                        # 按下了热键C
                        if msg.wParam == self.id1:
                            # 调用copy()函数
                            self.copy()
                        # 按下了热键V
                        elif msg.wParam == self.id2:
                            # 调用paste()函数
                            self.paste()
                    self.user32.TranslateMessage(ctypes.byref(msg))
                    self.user32.DispatchMessageA(ctypes.byref(msg))
        # 释放热键,否则下次会注册失败
        finally:
            self.user32.UnregisterHotKey(None, self.id1)
            self.user32.UnregisterHotKey(None, self.id2)

    def copy(self):
        # 模拟按下Ctrl+C
        pyautogui.hotkey('ctrl', 'c')

    def paste(self):
        # 模拟按下Ctrl+V
        pyautogui.hotkey('ctrl', 'v')
        # 模拟按下Ctrl+V,如果启用了换行,则在模拟按下Enter
        if my_pyqt_form.checkBox.isChecked(): pyautogui.press('enter')
  • RegisterHotKey()这个函数用来注册热键,包含在user32.dll中,通过ctypes库来调用。可以参考微软的官方文档
  • 由于user32.GetMessageA(ctypes.byref(msg), None, 0, 0)这个函数会阻塞线程,即使已经发送异常通知线程退出后,线程还是会等待一个热键消息,因此在停用热键后,需要多发送一个热键字符(如注册的C,V)来协助结束进程。

2.Ui_Form类

  • UI设计如下:
    UI设计
  • 生成.ui文件,再使用pyuic生成.py文件,命名为UIDesign.py,里面包含类Ui_Form。如果不清楚如何生成,参考PyQt5基础
  • 启用按钮绑定到函数start(),停用按钮绑定到函数stop()

3.MyPyQT_Form类

这个类继承自UIDesign.py中的Ui_Form类。

# 窗口子类
class MyPyQT_Form(QtWidgets.QWidget, Ui_Form):
    def __init__(self):
        super(MyPyQT_Form, self).__init__()
        self.setupUi(self)
        self.hotkey = None

    # “启用”按钮绑定的函数
    def start(self):
        self.hotkey = Hotkey()
        self.hotkey.start()
        self.label.setText("已启用")

    # “停用”按钮绑定的函数
    def stop(self):
        if self.hotkey:
            if self.hotkey.is_alive():
                stop_thread(self.hotkey)
                pyautogui.press('c')
                self.label.setText("未启用")

    # 窗口关闭事件
    def closeEvent(self, event):
        if self.hotkey:
            if self.hotkey.is_alive():
                stop_thread(self.hotkey)
                pyautogui.press('c')
        sys.exit(0)
  • start()函数中实例化一个Hotkey线程并运行,stop()函数判断Hotkey是否已经实例化,若是,则检测该线程是否在运行。满足条件则引起线程异常,使其退出,再模拟按下C键。若更改需要注册的热键,这里模拟按下的键也要修改为注册的热键之一。
  • 由于正常退出窗口,会由于线程未结束而使程序仍在后台执行,因此通过closeEvent捕获窗口关闭事件,退出线程再退出窗口。

4.线程结束

def _async_raise(tid, exctype):
    """Raises an exception in the threads with id tid"""
    if not inspect.isclass(exctype):
        raise TypeError("Only types can be raised (not instances)")
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), ctypes.py_object(exctype))
    if res == 0:
        raise ValueError("invalid thread id")
    elif res != 1:
        # """if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"""
        ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")


def stop_thread(thread):
    _async_raise(thread.ident, SystemExit)
  • 尝试了好多方法来结束线程,但或多或少都有些问题,最后选择了通过Python内置Api使线程抛出异常从而结束线程。
  • 使用这个方法需要导入包inspectctypes。调用方式为stop_thread(self.hotkey)

5.完整代码

  • 已经整合到一个文件中
# _*_ coding:UTF-8 _*_  
import win32con
import ctypes
import ctypes.wintypes
import threading
import pyautogui
from PyQt5 import QtCore, QtGui, QtWidgets
import inspect
import ctypes


# 窗口父类
class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.setEnabled(True)
        Form.resize(420, 271)
        self.textBrowser = QtWidgets.QTextBrowser(Form)
        self.textBrowser.setEnabled(False)
        self.textBrowser.setGeometry(QtCore.QRect(40, 20, 321, 31))
        self.textBrowser.setObjectName("textBrowser")
        self.pushButton = QtWidgets.QPushButton(Form)
        self.pushButton.setGeometry(QtCore.QRect(70, 160, 93, 28))
        self.pushButton.setObjectName("pushButton")
        self.checkBox = QtWidgets.QCheckBox(Form)
        self.checkBox.setGeometry(QtCore.QRect(40, 70, 341, 19))
        self.checkBox.setObjectName("checkBox")
        self.pushButton_2 = QtWidgets.QPushButton(Form)
        self.pushButton_2.setGeometry(QtCore.QRect(230, 160, 93, 28))
        self.pushButton_2.setObjectName("pushButton_2")
        self.label = QtWidgets.QLabel(Form)
        self.label.setGeometry(QtCore.QRect(200, 120, 72, 15))
        self.label.setObjectName("label")
        self.label_2 = QtWidgets.QLabel(Form)
        self.label_2.setGeometry(QtCore.QRect(120, 120, 41, 16))
        self.label_2.setObjectName("label_2")
        self.label_3 = QtWidgets.QLabel(Form)
        self.label_3.setGeometry(QtCore.QRect(80, 230, 41, 16))
        self.label_3.setObjectName("label_3")
        self.label_4 = QtWidgets.QLabel(Form)
        self.label_4.setGeometry(QtCore.QRect(140, 230, 231, 16))
        self.label_4.setText("暂无日志")
        self.label_4.setObjectName("label_4")

        self.retranslateUi(Form)
        self.pushButton.clicked.connect(Form.start)
        self.pushButton_2.clicked.connect(Form.stop)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "一键复制粘贴"))
        self.textBrowser.setHtml(_translate("Form", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'SimSun\'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">使用C键来代替Ctrl+C,使用V键来代替Ctrl+V</p></body></html>"))
        self.pushButton.setText(_translate("Form", "启用"))
        self.checkBox.setText(_translate("Form", "启用换行(粘贴之后自动按下Enter发送消息)"))
        self.pushButton_2.setText(_translate("Form", "停用"))
        self.label.setText(_translate("Form", "未启用"))
        self.label_2.setText(_translate("Form", "状态:"))
        self.label_3.setText(_translate("Form", "Log:"))


# 窗口子类
class MyPyQT_Form(QtWidgets.QWidget, Ui_Form):
    def __init__(self):
        super(MyPyQT_Form, self).__init__()
        self.setupUi(self)
        self.hotkey = None

    # “启用”按钮绑定的函数
    def start(self):
        self.hotkey = Hotkey()
        self.hotkey.start()
        self.label.setText("已启用")

    # “停用”按钮绑定的函数
    def stop(self):
        if self.hotkey:
            if self.hotkey.is_alive():
                stop_thread(self.hotkey)
                pyautogui.press('c')
                self.label.setText("未启用")

    # 窗口关闭事件
    def closeEvent(self, event):
        if self.hotkey:
            if self.hotkey.is_alive():
                stop_thread(self.hotkey)
                pyautogui.press('c')
        sys.exit(0)


# 创建一个Thread.threading扩展类,实例用来监听热键
class Hotkey(threading.Thread):
    def run(self):
        self.id1 = 105              # 注册热键的唯一id,用来区分热键
        self.id2 = 106
        self.user32 = ctypes.windll.user32                                # 加载user32.dll
        if not self.user32.RegisterHotKey(None, self.id1, 0, ord("C")):   # 注册快捷键C并判断是否成功
            my_pyqt_form.label4.setText("无法注册热键C")                  # 在Label_4显示错误信息
        if not self.user32.RegisterHotKey(None, self.id2, 0, ord("V")):   # 注册快捷键V并判断是否成功
            my_pyqt_form.label4.setText("无法注册热键V")
        # 检测热键是否被按下,并在出现异常时释放热键
        try:
            msg = ctypes.wintypes.MSG()
            # 循环读取消息
            while True:
                if self.user32.GetMessageA(ctypes.byref(msg), None, 0, 0) != 0:
                    if msg.message == win32con.WM_HOTKEY:
                        # 按下了热键C
                        if msg.wParam == self.id1:
                            # 调用copy()函数
                            self.copy()
                        # 按下了热键V
                        elif msg.wParam == self.id2:
                            # 调用paste()函数
                            self.paste()
                    self.user32.TranslateMessage(ctypes.byref(msg))
                    self.user32.DispatchMessageA(ctypes.byref(msg))
        # 释放热键,否则下次会注册失败
        finally:
            self.user32.UnregisterHotKey(None, self.id1)
            self.user32.UnregisterHotKey(None, self.id2)

    def copy(self):
        # 模拟按下Ctrl+C
        pyautogui.hotkey('ctrl', 'c')

    def paste(self):
        # 模拟按下Ctrl+V
        pyautogui.hotkey('ctrl', 'v')
        # 模拟按下Ctrl+V,如果启用了换行,则在模拟按下Enter
        if my_pyqt_form.checkBox.isChecked(): pyautogui.press('enter')


def _async_raise(tid, exctype):
    """Raises an exception in the threads with id tid"""
    if not inspect.isclass(exctype):
        raise TypeError("Only types can be raised (not instances)")
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), ctypes.py_object(exctype))
    if res == 0:
        raise ValueError("invalid thread id")
    elif res != 1:
        # """if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"""
        ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")


def stop_thread(thread):
    _async_raise(thread.ident, SystemExit)


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    my_pyqt_form = MyPyQT_Form()
    my_pyqt_form.show()
    sys.exit(app.exec_())

文章作者: Tqraf
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Tqraf !
评论
  目录