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.
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()
how to get WM_CLIPBOARDUPDATE message?
my english is very poor. run result:
begin
thrd start
done= 1
begin GetMessage
i copy anything , GetMessage are always blocked ,no return.
AddClipboardFormatListener is successful.
GetMessage(hwnd or None,0,0)
The results are the same.
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.
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, 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.
来源:https://stackoverflow.com/questions/54665708/tkinter-watch-clipboard-getmessage-no-return-value