Tkinter拖拽应用:拖动文本时绑定鼠标滚轮至对应滚动条
解决方案
问题核心是拖拽过程中鼠标悬浮在自定义的Toplevel拖拽窗口上,原有的canvas进入/离开事件无法触发,导致滚轮绑定不会自动切换。需要在拖拽过程中实时检测鼠标位置,动态切换滚轮的滚动目标。
以下是修改后的完整代码:
import tkinter as tk from tkinter import ttk class MainApplication(tk.Frame): def __init__(self, parent, *args, **kwargs): tk.Frame.__init__(self, parent, *args, **kwargs) self.first_frame = tk.LabelFrame(root, text="") self.first_frame.pack(padx=15, pady=15, fill="both", expand="true", side="left", anchor="w") self.first_canvas = tk.Canvas(self.first_frame, highlightthickness=0) self.first_canvas.pack(side="left", fill="both", expand="true") # 移除原有的Enter/Leave绑定 # self.first_canvas.bind("<Enter>", lambda _: self.first_canvas.bind_all('<MouseWheel>', self.on_mousewheel_left)) # self.first_canvas.bind("<Leave>", lambda _: self.first_canvas.unbind_all('<MouseWheel>')) self.second_frame = tk.LabelFrame(root, text="") self.second_frame.pack(padx=15, pady=15, fill="both", expand="true", side="left") self.second_canvas = tk.Canvas(self.second_frame, highlightthickness=0) self.second_canvas.pack(side="left", fill="both", expand="true") # 移除原有的Enter/Leave绑定 # self.second_canvas.bind("<Enter>", lambda _: self.second_canvas.bind_all('<MouseWheel>', self.on_mousewheel_right)) # self.second_canvas.bind("<Leave>", lambda _: self.second_canvas.unbind_all('<MouseWheel>')) self.upload_one() self.upload_two() def get_canvas_under_mouse(self, x_root, y_root): # 递归查找鼠标下方的目标canvas widget = root.winfo_containing(x_root, y_root) while widget: if widget in (self.first_canvas, self.second_canvas): return widget widget = widget.winfo_parent() return None def upload_one(self): self.first_list = ["11:00", "03:43", "22:04", "17:21", "22:35", "07:01", "16:11", "09:00"] self.first_scrollbar = ttk.Scrollbar(self.first_frame, orient="vertical", command=self.first_canvas.yview) self.first_scrollbar.pack(side="right", fill="y") self.first_canvas.configure(yscrollcommand=self.first_scrollbar.set) self.first_canvas.bind('<Configure>', lambda e: self.first_canvas.configure(scrollregion = self.first_canvas.bbox("all"))) self.first_list_frame = tk.Frame(self.first_canvas) self.first_canvas.create_window((0,0), window=self.first_list_frame, anchor="nw") for index, cit in enumerate(sorted(self.first_list)): self.one_frame = tk.LabelFrame(self.first_list_frame) self.one_frame.grid(row=index, column=0, padx=10, pady=(20,0)) self.one_text = tk.Text(self.one_frame, width=77, height=4) self.one_text.grid(row=1, column=1, rowspan=2, sticky="nw") self.dragged_text = tk.Text(self.one_frame, wrap="word", width=68, height=3, highlightthickness=1, highlightcolor= "DodgerBlue3",) self.dragged_text.grid(row=3, column=1, rowspan=2, columnspan=2, pady=(10,20), ipady=7, sticky="nw") def upload_two(self): self.second_list = ["morning\nmorning\nmorning", "afternoon\nafternoon\nafternoon", "evening\nevening\nevening", "night\nnight\nnight"] self.second_scrollbar = ttk.Scrollbar(self.second_frame, orient="vertical", command=self.second_canvas.yview) self.second_scrollbar.pack(side="right", fill="y") self.second_canvas.configure(yscrollcommand=self.second_scrollbar.set) self.second_canvas.bind('<Configure>', lambda e: self.second_canvas.configure(scrollregion = self.second_canvas.bbox("all"))) self.two_list_frame = tk.Frame(self.second_canvas) self.second_canvas.create_window((0,0), window=self.two_list_frame, anchor="nw") for index, two in enumerate(sorted(self.second_list)): self.two_frame = tk.Frame(self.two_list_frame) self.two_frame.pack(pady=(15,0), fill="x", expand="yes", side="top") self.two_text = tk.Text(self.two_frame, font=("Times New Roman", 11), wrap="word", highlightthickness=1, highlightcolor= "DodgerBlue3", cursor="hand2", width=81, height=4) self.two_text.grid(row=0, column=0, padx=15) self.two_text.insert("end", str(two.strip()), "normal") self.two_text.config(state="disabled") self.two_text.bind('<Button-1>', self.click) self.two_text.bind('<B1-Motion>',self.drag) self.two_text.bind('<ButtonRelease-1>',self.release) def on_mousewheel_left(self, event): # 直接操作左侧canvas,无需额外获取widget self.first_canvas.yview_scroll(int(-1*(event.delta//120)), 'units') def on_mousewheel_right(self, event): # 直接操作右侧canvas self.second_canvas.yview_scroll(int(-1*(event.delta//120)), 'units') def click(self, event): self.text = event.widget.get("1.0",'end-1c') root.clipboard_clear() root.clipboard_append(self.text) self.top = tk.Toplevel(root) self.top.attributes('-alpha', 0.7) self.top.overrideredirect(True) self.top._offsetx = event.x self.top._offsety = event.y x = self.top.winfo_pointerx() - self.top._offsetx - 30 y = self.top.winfo_pointery() - self.top._offsety - 30 self.top.geometry('+{x}+{y}'.format(x=x,y=y)) two_toplevel_label = tk.Label(self.top, text=self.text, wraplength=571, justify="left", bg="SystemButtonFace") two_toplevel_label.grid(row=0, column=0) def drag(self, event): x = self.top.winfo_pointerx() - self.top._offsetx y = self.top.winfo_pointery() - self.top._offsety self.two_text.tag_remove("sel", "1.0", "end") self.top.geometry(f"+{x}+{y}") # 实时检测鼠标下方的canvas current_canvas = self.get_canvas_under_mouse(event.x_root, event.y_root) if current_canvas == self.first_canvas: # 绑定左侧滚轮事件,解绑右侧 root.bind_all('<MouseWheel>', self.on_mousewheel_left) elif current_canvas == self.second_canvas: # 绑定右侧滚轮事件,解绑左侧 root.bind_all('<MouseWheel>', self.on_mousewheel_right) else: # 不在任何canvas上,解绑所有滚轮事件 root.unbind_all('<MouseWheel>') def release(self, event): widget = event.widget.winfo_containing(event.x_root, event.y_root) self.top.destroy() # 释放鼠标后解绑所有滚轮绑定,恢复默认 root.unbind_all('<MouseWheel>') if __name__ == "__main__": root = tk.Tk() MainApplication(root).pack(side="top", fill="both", expand=True) root.mainloop()
关键修改说明
- 移除原有绑定:删除了canvas的
<Enter>和<Leave>事件绑定,因为拖拽时鼠标不在canvas上,这些事件不会触发。 - 新增位置检测函数:
get_canvas_under_mouse函数递归查找鼠标当前位置下方的目标canvas,解决Toplevel窗口遮挡导致直接获取不到canvas的问题。 - 拖拽时动态切换绑定:在
drag方法中,实时检测鼠标所在的canvas,动态绑定对应的滚轮滚动方法,实现拖拽时滚轮跟随鼠标位置切换滚动目标。 - 释放鼠标清理绑定:在
release方法中解绑所有滚轮绑定,避免影响后续正常操作。 - 简化滚轮方法:直接操作对应的canvas,去掉了不必要的widget父级查找逻辑。
内容的提问来源于stack exchange,提问作者Diana Mele
相关产品推荐
相关产品推荐

