本文介绍了tkinter监视剪贴板GetMessage没有返回值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想为win10监控剪贴板应用程序。

如:当我们从notepad.exe复制文本00-22-33-11-22 Mac地址时,tk的窗口获取文本并将mac地址转换为机器名称。

i want to Monitoring Clipboard app for win10.
like: when we copy text 00-22-33-11-22 Mac Address from notepad.exe,tk's window get text and translate mac address to machine name.

但是tkinter没有剪贴板事件。

所以我叫win32api

i搜索pywin32文档,发现win32clipboard.SetClipboardViewer

,但是创建剪贴板查看器窗口非常复杂

i搜索MSDN,发现建议使用AddClipboardFormatListener。此方法比SetClipboardViewer更简单。

i使用了它,但GetMessage始终被阻止

but tkinter have no clipboard events.
so i call win32api
i search pywin32 document , found win32clipboard.SetClipboardViewer
but Creating a Clipboard Viewer Window is very complex
i search MSDN , found AddClipboardFormatListener is recommended 。this method is simpler to SetClipboardViewer. MSDN Creating a Clipboard Format Listener
i used it ,but GetMessage always be blocked

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消息?

how to get WM_CLIPBOARDUPDATE message?

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

my english is very poor.run result:

begin
thrd start
done= 1
begin GetMessage

我复制任何内容,GetMessage始终被阻止,无法返回。

AddClipboardFormatListener是成功的。
GetMessage(hwnd或None,0,0)

结果是相同的。

i copy anything , GetMessage are always blocked ,no return.
AddClipboardFormatListener is successful.GetMessage(hwnd or None,0,0)
The results are the same.

推荐答案

我在主线程中研究了 GetMessage ,并使用使用 AddClipboardFormatListener 进行了注册。 GetMessage 是正常的,但是在新线程中, GetMessage 始终没有返回值。

我已经回顾了许多论坛帖子,基本上提到tk多线程存在问题。

@stovfl提到了该帖子,我读过。我认为之后不是一个好主意。

之后会消耗主线程性能并影响UI显示。
使用事件在vb.net的页面上进行通信。所以我搜索了tk文档,发现 event_generate

测试发现 event_generate 并不似乎受到多线程的影响。

通过论坛帖子,我完成了 event_generate 的一些缺陷,并给出了解决方案。

I have studied GetMessage, in the main thread, using AddClipboardFormatListener to register, using GetMessage is normal, but in the new thread, GetMessage always has no return value.
I have reviewed many of the forum posts, and basically mentioned that there are problems with multithreading of tk.
@stovfl mentioned the post, I read. I think that after is not a good idea.
after consumes main thread performance and affects UI display.Use event to communicate on the page in vb.net. So I searched the tk documentation and found event_generate.
The test found that event_generate does not seem to be affected by multithreading.
Through the forum posts, I have completed some of the defects of event_generate and given my solution.

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

My code demonstrates monitoring the clipboard and launching a multi-threaded task with the button (traversing all the files in the path directory, finding the total number of files), the UI display is not affected by the task blocking.

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,以及讨论中的所有人。您给了我解决这个问题的灵感。

如果您觉得此解决方案有一些缺陷,请告诉我。

Python 3.7.2, os win10 1151, the test passed. (Continuous click on the button, open 12 worker threads, no problems found, UI thread is smooth)
If the code has an unexpected error, check tk*.dll in the python installation directory.
There is information that tk86t.dll supports multithreading, tk86.dll is not supported.
Thanks to @FabienAndre, @BryanOakley ,@stovfl and everyone in the discussion. You gave me the inspiration to solve this problem.
If you feel that this solution has some flaws, please let me know.

这篇关于tkinter监视剪贴板GetMessage没有返回值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-16 08:58