tkinter 观看剪贴板 GetMessage 没有 return 值
tkinter watch clipboard GetMessage no return value
我想监控 win10 的剪贴板应用程序。
比如:当我们从 notepad.exe 复制文本 00-22-33-11-22 Mac 地址时,tk 的 window 获取文本并将 mac 地址翻译成 mac你的名字。
但是 tkinter 没有剪贴板事件。
所以我调用 win32api
我搜索 pywin32 文档,发现 win32clipboard.SetClipboardViewer
但是创建剪贴板查看器 Window 非常复杂
我搜索MSDN,发现推荐AddClipboardFormatListener。这个方法比SetClipboardViewer简单。 MSDN 创建剪贴板格式侦听器
我用过它,但 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 总是被阻止,没有 return。
AddClipboardFormatListener 成功。
GetMessage(hwnd 或 None,0,0)
结果是一样的
我研究过GetMessage
,在主线程中,使用AddClipboardFormatListener
注册,使用GetMessage
是正常的,但是在新线程中,GetMessage
总是有没有 return 值。
看了很多论坛posts,基本都提到tk的多线程有问题
@stovfl 提到了 post,我读过。我认为 after
不是一个好主意。
after
消耗主线程性能,影响UI显示。
在vb.net的页面上使用事件进行通信。所以我搜索了 tk 文档并找到 event_generate
.
测试发现event_generate
似乎不受多线程影响
通过论坛posts,我已经完成了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()
Python3.7.2,oswin10 1151,测试通过。 (连续点击按钮,开启12个工作线程,没发现问题,UI线程流畅)
如果代码出现意外错误,检查python安装目录下的tk*.dll。
有资料说tk86t.dll支持多线程,tk86.dll不支持
感谢@FabienAndre、@BryanOakley、@stovfl 和讨论中的每个人。你给了我解决这个问题的灵感。
如果您觉得此解决方案有一些缺陷,请告诉我。
我想监控 win10 的剪贴板应用程序。
比如:当我们从 notepad.exe 复制文本 00-22-33-11-22 Mac 地址时,tk 的 window 获取文本并将 mac 地址翻译成 mac你的名字。
但是 tkinter 没有剪贴板事件。
所以我调用 win32api
我搜索 pywin32 文档,发现 win32clipboard.SetClipboardViewer
但是创建剪贴板查看器 Window 非常复杂
我搜索MSDN,发现推荐AddClipboardFormatListener。这个方法比SetClipboardViewer简单。 MSDN 创建剪贴板格式侦听器
我用过它,但 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 总是被阻止,没有 return。
AddClipboardFormatListener 成功。
GetMessage(hwnd 或 None,0,0)
结果是一样的
我研究过GetMessage
,在主线程中,使用AddClipboardFormatListener
注册,使用GetMessage
是正常的,但是在新线程中,GetMessage
总是有没有 return 值。
看了很多论坛posts,基本都提到tk的多线程有问题
@stovfl 提到了 post,我读过。我认为 after
不是一个好主意。
after
消耗主线程性能,影响UI显示。
在vb.net的页面上使用事件进行通信。所以我搜索了 tk 文档并找到 event_generate
.
测试发现event_generate
似乎不受多线程影响
通过论坛posts,我已经完成了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()
Python3.7.2,oswin10 1151,测试通过。 (连续点击按钮,开启12个工作线程,没发现问题,UI线程流畅)
如果代码出现意外错误,检查python安装目录下的tk*.dll。
有资料说tk86t.dll支持多线程,tk86.dll不支持
感谢@FabienAndre、@BryanOakley、@stovfl 和讨论中的每个人。你给了我解决这个问题的灵感。
如果您觉得此解决方案有一些缺陷,请告诉我。