Я изучил GetMessage
, в основном потоке, использование AddClipboardFormatListener
для регистрации, использование GetMessage
- это нормально, но в новом потоке GetMessage
всегда не имеет возвращаемого значения.
Я рассмотрелво многих сообщениях на форуме и в основном упоминается, что есть проблемы с многопоточностью tk.
@stovfl упоминал пост, который я прочитал.Я думаю, что after
не очень хорошая идея.
after
потребляет производительность основного потока и влияет на отображение пользовательского интерфейса.Используйте событие для общения на странице в vb.net.Поэтому я искал документацию по tk и обнаружил event_generate
.
. Тест показал, что многопоточность не влияет на event_generate
.
В сообщениях на форуме я исправил некоторые дефекты event_generate
и учитывая мое решение.
Мой код демонстрирует мониторинг буфера обмена и запуск многопоточной задачи с помощью кнопки (обход всех файлов в каталоге пути, поиск общего количества файлов), отображение пользовательского интерфейса незатронуто блокирование задачи.
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, тест пройден.(Непрерывное нажатие на кнопку, открытие 12 рабочих потоков, проблем не обнаружено, поток пользовательского интерфейса работает гладко)
Если в коде произошла непредвиденная ошибка, проверьте tk * .dll в каталоге установки python.
Есть информация, чтоtk86t.dll поддерживает многопоточность, tk86.dll не поддерживается.
Спасибо @FabienAndre, @BryanOakley, @ stovfl и всем участникам обсуждения.Вы дали мне вдохновение для решения этой проблемы.
Если вы чувствуете, что у этого решения есть некоторые недостатки, пожалуйста, дайте мне знать.