tkinter监视剪贴板GetMessage没有返回值

时间:2019-02-13 08:35:48

标签: python winapi tkinter clipboard

我想使用win10的监控剪贴板应用程序。
如:当我们从notepad.exe复制文本00-22-33-11-22 Mac地址时,tk的窗口将获取文本并将mac地址转换为计算机名称。


但是tkinter没有剪贴板事件。
所以我叫win32api
我搜索pywin32文档,发现win32clipboard.SetClipboardViewer
但是创建剪贴板查看器窗口非常复杂
我搜索MSDN,发现推荐使用AddClipboardFormatListener。此方法比SetClipboardViewer更简单。 MSDN Creating a Clipboard Format Listener
我用过它,但GetMessage始终被阻止

import tkinter as tk
import time
import threading as thrd
import win32gui
import win32clipboard
import win32api
import win32con
import ctypes
from ctypes.wintypes import MSG
from ctypes import byref


def selfevent(root):
    print("thrd start")
    hwnd = int(root.frame(), 16)
    done = ctypes.windll.user32.AddClipboardFormatListener(hwnd)
    print("done=", done)
    if done:
        wmsg = None
        print("begin GetMessage")
        wmsg = win32gui.GetMessage(None, 0, 0)
        # wmsg = MSG()
        # ctypes.windll.user32.GetMessageA(byref(wmsg), 0, 0, 0)
        print("GetMessage", wmsg.message(), win32api.GetLastError())
        if wmsg:
            print("msg=", wmsg)
            print(ctypes.windll.user32.RemoveClipboardFormatListener(hwnd))


if __name__ == "__main__":
    root = tk.Tk()
    root.title("tktest")
    root.geometry("600x400")
    # root.bind("<<foo>>", vectrl)
    print("begin")
    txt = tk.Entry(root)
    txt.pack()
    bt2 = tk.Button(root, text="GetClipboardSequenceNumber", command=lambda: print("sn=", win32clipboard.GetClipboardSequenceNumber()))
    bt2.pack()
    t = thrd.Thread(target=selfevent, args=(root,))
    t.setDaemon(True)
    t.start()
    root.mainloop()

如何获取WM_CLIPBOARDUPDATE消息?


我的英语很差。 运行结果:

begin
thrd start
done= 1
begin GetMessage

我复制任何内容,GetMessage始终被阻止,无法返回。
AddClipboardFormatListener成功。 GetMessage(hwnd或None,0,0)
结果是相同的。

1 个答案:

答案 0 :(得分:0)

我研究过GetMessage,在主线程中,使用AddClipboardFormatListener进行注册,使用GetMessage是正常的,但是在新线程中,GetMessage总是没有返回值值。
我查看了许多论坛帖子,并基本上提到tk的多线程存在问题。
我读了@stovfl提到的帖子。我认为after不是一个好主意。
after消耗主线程性能并影响UI显示。 使用事件在vb.net的页面上进行通信。因此,我搜索了tk文档并发现了event_generate
测试发现event_generate似乎不受多线程影响。
通过论坛上的帖子,我已经完成了event_generate的一些缺陷并给出了解决方案。

我的代码演示了如何监视剪贴板并使用按钮启动多线程任务(遍历path目录中的所有文件,查找文件总数),UI的显示不受任务阻塞的影响。

import tkinter as tk
import tkinter.ttk as ttk
import win32clipboard
import threading as thrd
import time
import os
from queue import Queue


def watchClip(top):
    lastid = None
    print("StartWatch")
    while True:
        time.sleep(0.01)
        nowid = win32clipboard.GetClipboardSequenceNumber()
        # print(nowid, lastid)
        if not lastid or (lastid != nowid):
            lastid = nowid
            top.event_generate("<<clipUpdateEvent>>", when="tail")


def workButton(top, path, outQueue):
    allcount = 0
    print("StartSearch")
    for root, dirs, files in os.walk(path):
        allcount += len(files)
        top.clipboard_clear()
        top.clipboard_append(allcount)
    outQueue.put_nowait(allcount)
    # top.event_generate("<<searchFin>>", data={"result": allcount}, when="tail")
    top.event_generate("<<searchFin>>", data=f"result={allcount}", when="tail")


def bind_event_data(widget, sequence, func, add=None):
    def _substitute(*args):
        def evt():
            return None  # simplest object with __dict__
        try:
            evt.data = eval(args[0])
        except Exception:
            evt.data = args[0]
        evt.widget = widget
        return (evt,)

    funcid = widget._register(func, _substitute, needcleanup=1)
    cmd = '{0}if {{"[{1} %d]" == "break"}} break\n'.format('+' if add else '', funcid)
    widget.tk.call('bind', widget._w, sequence, cmd)


if __name__ == "__main__":
    top = tk.Tk()
    top.title("tktest")
    top.geometry("300x200")
    rsltQueue = Queue()
    # top.bind("<<foo>>", vectrl)
    print("begin")
    lbl = tk.Label(top, text="clipboard", width=30, height=3)
    lbl.pack()
    lblrslt = tk.Label(top, text="SearchResult", width=40, height=3)
    lblrslt.pack()
    prb = ttk.Progressbar(top, length=100, mode="indeterminate")
    prb.pack()
    txt = tk.Entry(top, width=20)
    txt.pack()
    prb.start(interval=10)
    t = thrd.Thread(target=watchClip, args=(top,), daemon=True)
    t.start()

    def searchPath():
        t = thrd.Thread(target=workButton, args=(top, "c:", rsltQueue), daemon=True)
        t.start()
    bt2 = tk.Button(top, text="SearchPath", command=searchPath)
    bt2.pack()
    clipText = ""

    def dealCUE(event):
        global clipText
        try:
            clipText = top.clipboard_get()
        except tk.TclError:
            pass
        lbl["text"] = clipText

    def dealSF(event):
        # lblrslt["text"] = f"allFileCount={rsltQueue.get()}"
        # lblrslt["text"] = event.data["result"]
        lblrslt["text"] = event.data
    top.bind("<<clipUpdateEvent>>", dealCUE)
    # top.bind("<<searchFin>>", dealSF)
    bind_event_data(top, "<<searchFin>>", dealSF)
    top.mainloop()

Python 3.7.2,操作系统win10 1151,测试通过。 (连续单击该按钮,打开12个工作线程,未发现问题,UI线程很平滑)
如果代码有意外错误,请检查python安装目录中的tk * .dll。
有信息说tk86t.dll支持多线程,不支持tk86.dll。
感谢@ FabienAndre,@ BryanOakley和@stovfl,以及所有人在讨论中。你给了我解决这个问题的灵感。
如果您认为此解决方案有一些缺陷,请告诉我。