为什么在我使用异步服务进行检查时,右键菜单没有显示?
我正在使用一个线程,并将语言工具的请求排队:
import threading
import queue
import language_tool_python
class SpellCheckService:
def __init__(self, pure_english:bool=False):
self.tool_en = language_tool_python.LanguageTool("en_US")
self.tool_el = language_tool_python.LanguageTool("auto")
self.queue = queue.Queue()
self.thread = threading.Thread(target=self._worker, daemon=True)
self.thread.start()
def request(self, text, locale, callback):
"""Send text for spellcheck"""
self.queue.put((text,locale, callback))
def _worker(self):
while True:
text, language, callback = self.queue.get()
if(language == "en_US"):
matches = self.tool_en.check(text)
else:
matches = self.tool_el.check(text)
callback(text,matches)
self.queue.task_done()
接着我做了一个tk.Entry,它使用Spellcheck:
import tkinter as tk
from lib.widgets.text.pastable_entry import PasteableEntry
from lib.spellcheck_service import SpellCheckService
class SpellCheckEntry(tk.Entry):
"""
NOTE use get_spellchecked_value andf provide a valid callback upon value_callback
"""
def __init__(self,spellcheck_service:SpellCheckService, name:str="url",value_callback=None, locale="en_US",master=None, **kwargs):
super().__init__(master, **kwargs)
self.__spellcheck_service = spellcheck_service
self.__value_callback = value_callback
self.__name = name
self.__locale = locale
self.__spelling_errors=[]
self.menu = tk.Menu(master, tearoff=0)
def __spellcheck_callback(self,text,matches):
self.__spelling_errors = []
for m in matches:
print(m)
if ("EL_GR" in (m.rule_id) or "EN_US" in (m.rule_id)):
self.__spelling_errors.append(m)
print("Matching Rule", m.rule_id)
self.configure(highlightbackground="gray")
self.unbind("<Button-3>")
if self.__spelling_errors:
self.configure(highlightbackground="red", highlightcolor="red")
for error in self.__spelling_errors:
for suggestion in error.replacements:
self.menu.add_command(label=suggestion,command=lambda s=suggestion: self._apply_suggestion(s))
print("Bind Menu")
self.menu.bind("<Button-3>",self._show_suggestions)
self.__value_callback(False,self.__name,text)
elif self.__value_callback:
self.__value_callback(True,self.__name,text)
def get_spellchecked_value(self):
self.__spellcheck_service.request(self.get().strip(),self.__locale,self.__spellcheck_callback)
def _apply_suggestion(self,text):
self.delete(0, tk.END)
self.insert(0, text)
self.__spelling_errors.clear()
self.configure(highlightbackground="gray")
self.unbind("<Button-3>")
def _show_suggestions(self,event):
print("Showing Suggestions")
try:
self.menu.tk_popup(event.x_root, event.y_root)
finally:
self.menu.grab_release()
并将它应用到这个UI:
import tkinter as tk
from tkinter import ttk
import sys
import os
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..', '..')))
from lib.dto.button_dto import ButtonFinalValueDTO
from lib.url import is_test_of,make_test
from lib.widgets.text.spellcheck_entry import SpellCheckEntry
from lib.widgets.url.url_entry import UrlEntry
from lib.spellcheck_service import SpellCheckService
class UrlInput(ttk.Frame):
def __init__(self, root, button: ButtonFinalValueDTO, button_value_callback):
super().__init__(root)
self.button = button
self.__language_tool = SpellCheckService()
self.__greek_value=None
self.__en_value=None
self.__button_value_callback=button_value_callback
self.__draw()
def trigger_get_value(self):
self.__greek_value = None
self.__en_value = None
self.label_en.get_spellchecked_value()
self.label_el.get_spellchecked_value()
def __check_url(self) -> bool:
# Run all checks and store results
test_url_ok = self.url_test_input.check(self.test_url_error)
test_example_ok = self.example_test_input.check(self.test_example_error)
prod_example_ok = self.example_prod_input.check(self.prod_example_error)
prod_url_ok = self.url_prod_input.check(self.prod_url_error)
all_ok = test_url_ok and test_example_ok and prod_example_ok and prod_url_ok
if not all_ok:
return False
test_url = self.url_test_input.get()
prod_url = self.url_prod_input.get()
if not is_test_of(prod_url,test_url):
self.url_prod_input.mark_as_error()
self.prod_url_error.config(text=f"{prod_url} is not valid it should be "+make_test(prod_url))
return False
return all_ok
def __value_callback(self,value_correct:bool,name:str,text:str):
print("Value callback",value_correct,name,text)
if name == "label_el" and value_correct:
self.__greek_value = text
elif name == "label_en" and value_correct:
self.__en_value = text
if self.__greek_value is not None and self.__en_value is not None and self.__check_url():
self.__get_button_values()
def __get_button_values(self):
dto = ButtonFinalValueDTO(
label_en=self.__greek_value,
label_el=self.__en_value,
url_test=self.url_test_input.get().strip(),
example_test=self.example_test_input.get().strip(),
url_prod=self.url_prod_input.get().strip(),
example_prod=self.example_prod_input.get().strip()
)
self.__button_value_callback(dto)
def __draw(self):
# URL
label_frame = ttk.Frame(self)
ttk.Label(label_frame, text="Label Config:").grid(row=0, column=0, sticky="e", padx=(0, 5))
ttk.Label(label_frame, text="EN:").grid(row=1, column=0, sticky="e", padx=(0, 5))
self.label_en= SpellCheckEntry(master=label_frame,
spellcheck_service=self.__language_tool,
locale="en_US",
name="label_en",
value_callback=self.__value_callback)
self.label_en.grid(row=1, column=1, sticky="w")
self.label_en.insert(tk.END, self.button.label_en)
ttk.Label(label_frame, text="EL:").grid(row=1, column=2, sticky="e", padx=(0, 5))
# Example
self.label_el = SpellCheckEntry(master=label_frame,
spellcheck_service=self.__language_tool,
name="label_el",
locale="el_GR",
value_callback=self.__value_callback)
self.label_el.grid(row=1, column=3, sticky="w")
self.label_el.insert(tk.END, self.button.label_el)
label_frame.pack()
# URL
url_frame = ttk.Frame(self)
ttk.Label(url_frame, text="Url Config:").grid(row=0, column=0, sticky="e", padx=(0, 5))
ttk.Label(url_frame, text="TEST:").grid(row=1, column=0, sticky="e", padx=(0, 5))
self.url_test_input = UrlEntry(url_frame,is_test=True,initial_value=self.button.url_test,width=50)
self.url_test_input.grid(row=1, column=1, sticky="w")
self.example_test_input = UrlEntry(url_frame,is_test=True, initial_value=self.button.example_test, width=50)
self.example_test_input.grid(row=1, column=2, sticky="w")
self.test_url_error=ttk.Label(url_frame, text="",foreground="red")
self.test_url_error.grid(row=2, column=1, sticky="e", padx=(0, 5))
self.test_example_error=ttk.Label(url_frame, text="",foreground="red")
self.test_example_error.grid(row=3, column=1, sticky="e", padx=(0, 5))
ttk.Label(url_frame, text="PROD:").grid(row=4, column=0, sticky="e", padx=(0, 5))
self.url_prod_input = UrlEntry(url_frame,initial_value=self.button.url_prod, width=50)
self.url_prod_input.grid(row=4, column=1, sticky="w")
self.example_prod_input = UrlEntry(url_frame,initial_value=self.button.example_prod, width=50)
self.example_prod_input.grid(row=4, column=2, sticky="w")
self.prod_url_error=ttk.Label(url_frame, text="",foreground="red")
self.prod_url_error.grid(row=5, column=1, sticky="e", padx=(0, 5))
self.prod_example_error=ttk.Label(url_frame, text="",foreground="red")
self.prod_example_error.grid(row=6, column=1, sticky="e", padx=(0, 5))
url_frame.pack()
def print_button(button_value):
print(button_value)
if __name__ == "__main__":
root = tk.Tk()
root.title("UrlInput Test")
dummy_button = ButtonFinalValueDTO(
label_el="Παράδειγμα",
label_en="Example",
url_prod="https://example.com",
example_prod="https://example.com/page",
url_test="https://test.example.com",
example_test="https://test.example.com/page"
)
widget = UrlInput(root, dummy_button,button_value_callback=print_button)
widget.pack(padx=20, pady=20)
ttk.Button(root, text="Check",command=widget.trigger_get_value).pack(pady=10)
root.mainloop()
但在以下任一情况下,我都无法显示任何菜单:
self.label_el = SpellCheckEntry(master=label_frame,
spellcheck_service=self.__language_tool,
name="label_el",
locale="el_GR",
value_callback=self.__value_callback)
self.label_el = SpellCheckEntry(master=label_frame,
spellcheck_service=self.__language_tool,
name="label_el",
locale="el_GR",
value_callback=self.__value_callback)
里面包含错误,如何修复?这个 问题 的主要区别在于:我使用一个异步进程来验证每个条目,而不是以同步的方式直接调用LanguageTool。
你知道为什么吗?
解决方案
问题在于Tkinter不是线程安全的。你的拼写检查在后台线程中运行,但你试图直接从该线程更新GUI(菜单、高亮)。Tkinter会忽略或错误处理这些更新,因此菜单永远不会显示。
修复方法:使用after() 将所有GUI更新调度到主线程执行:
def __spellcheck_callback(self, text, matches):
def update_gui():
# Update highlights and menu here safely
pass
self.after(0, update_gui) # runs in main thread
这会让右键的建议正确显示。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。