tkinter:将鼠标滚轮绑定到滚动条
- 2025-02-21 08:48:00
- admin 原创
- 24
问题描述:
我有这个可滚动的框架(实际上是画布内的框架)。
import Tkinter as tk
class Scrollbarframe():
def __init__(self, parent,xsize,ysize,xcod,ycod):
def ScrollAll(event):
canvas1.configure(scrollregion=canvas1.bbox("all"),width=xsize,height=ysize,bg='white')
self.parent=parent
self.frame1=tk.Frame(parent,bg='white')
self.frame1.place(x=xcod,y=ycod)
canvas1=tk.Canvas(self.frame1)
self.frame2=tk.Frame(canvas1,bg='white',relief='groove',bd=1,width=1230,height=430)
scrollbar1=tk.Scrollbar(self.frame1,orient="vertical",command=canvas1.yview)
canvas1.configure(yscrollcommand=scrollbar1.set)
scrollbar1.pack(side="right",fill="y")
canvas1.pack(side="left")
canvas1.create_window((0,0),window=self.frame2,anchor='nw')
self.frame2.bind("<Configure>",ScrollAll)
我想将鼠标滚轮绑定到滚动条,以便用户可以向下滚动框架,而无需使用滚动条上的箭头按钮。环顾四周后,我canvas1
像这样添加了一个绑定
self.frame1.bind("<MouseWheel>", self.OnMouseWheel)
这是函数:
def OnMouseWheel(self,event):
self.scrollbar1.yview("scroll",event.delta,"units")
return "break"
但是当我使用鼠标滚轮时滚动条不会移动。有人能帮我解决这个问题吗?我想要的是当用户使用鼠标滚轮(在框架区域内/滚动条上)时,画布应该自动向上或向下滚动。
解决方案 1:
也许最简单的解决方案是为鼠标滚轮创建全局绑定。无论鼠标下有什么小部件或哪个小部件具有键盘焦点,它都会触发。然后您可以无条件滚动画布,或者您可以聪明地找出哪个窗口应该滚动。
例如,在 Windows 上你可以执行以下操作:
self.canvas = Canvas(...)
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
...
def _on_mousewheel(self, event):
self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")
请注意,这self.canvas.bind_all
有点误导——更正确的做法是调用,root.bind_all
但我不知道您定义根窗口的内容或方式。无论如何,这两个调用是同义词。
平台差异:
在 Windows 上,您绑定到
<MouseWheel>
并需要event.delta
除以 120(或根据您希望滚动的速度计算的其他因子)在 OSX 上,您绑定到并且无需修改
<MouseWheel>
即可使用event.delta
在 X11 系统上,您需要绑定到
<Button-4>
和<Button-5>
,并且需要除以event.delta
120(或根据您想要滚动的速度计算的其他因子)
还有更精致的解决方案涉及虚拟事件和确定哪个窗口具有焦点或在鼠标下,或者通过绑定传递画布窗口引用,但希望这可以帮助您入门。
编辑:在较新的 Python 版本中,canvas.yview_scroll
需要一个整数(参见:pathName yview scroll number what
解决方案 2:
根据@BryanOakley 的回答,这里有一种仅滚动焦点小部件(即鼠标光标当前位于其上的小部件)的方法。
绑定到位于画布内的可滚动框架<Enter>
并发生事件,方式如下(是画布内的框架):<Leave>
`scrollframe`
...
self.scrollframe.bind('<Enter>', self._bound_to_mousewheel)
self.scrollframe.bind('<Leave>', self._unbound_to_mousewheel)
return None
def _bound_to_mousewheel(self, event):
self.canv.bind_all("<MouseWheel>", self._on_mousewheel)
def _unbound_to_mousewheel(self, event):
self.canv.unbind_all("<MouseWheel>")
def _on_mousewheel(self, event):
self.canv.yview_scroll(int(-1*(event.delta/120)), "units")
解决方案 3:
此链接为您提供了如何使用滚轮的示例。
我希望这有帮助!
# explore the mouse wheel with the Tkinter GUI toolkit
# Windows and Linux generate different events
# tested with Python25
import Tkinter as tk
def mouse_wheel(event):
global count
# respond to Linux or Windows wheel event
if event.num == 5 or event.delta == -120:
count -= 1
if event.num == 4 or event.delta == 120:
count += 1
label['text'] = count
count = 0
root = tk.Tk()
root.title('turn mouse wheel')
root['bg'] = 'darkgreen'
# with Windows OS
root.bind("<MouseWheel>", mouse_wheel)
# with Linux OS
root.bind("<Button-4>", mouse_wheel)
root.bind("<Button-5>", mouse_wheel)
label = tk.Label(root, font=('courier', 18, 'bold'), width=10)
label.pack(padx=40, pady=40)
root.mainloop()
解决方案 4:
为了消除奇怪的因子 120,我们可以只查看 event.delta 值的符号。这使得在 Windows、Linux 和 Mac OS 下使用相同的处理程序变得容易。
# Mouse wheel handler for Mac, Windows and Linux
# Windows, Mac: Binding to <MouseWheel> is being used
# Linux: Binding to <Button-4> and <Button-5> is being used
def MouseWheelHandler(event):
global count
def delta(event):
if event.num == 5 or event.delta < 0:
return -1
return 1
count += delta(event)
print(count)
import tkinter
root = tkinter.Tk()
count = 0
root.bind("<MouseWheel>",MouseWheelHandler)
root.bind("<Button-4>",MouseWheelHandler)
root.bind("<Button-5>",MouseWheelHandler)
root.mainloop()
解决方案 5:
作为上述内容的补充,“delta”比例因子很容易计算,因为可以通过sys
和platform
模块(以及可能的其他模块)获得平台信息。
def my_mousewheel_handler(event):
if sys.platform == 'darwin': # for OS X # also, if platform.system() == 'Darwin':
delta = event.delta
else: # for Windows, Linux
delta = event.delta // 120 # event.delta is some multiple of 120
if event.widget in (widget1, widget2, ):
'do some really cool stuff...'
解决方案 6:
如果你感兴趣
如何同时滚动2个列表框
#listbox scrollbar
from tkinter import *
root = Tk()
def scrolllistbox2(event):
listbox2.yview_scroll(int(-1*(event.delta/120)), "units")
scrollbar = Scrollbar(root)
#scrollbar.pack(side=RIGHT, fill=Y)
listbox = Listbox(root)
listbox.pack()
for i in range(100):
listbox.insert(END, i)
# attach listbox to scrollbar
listbox.config(yscrollcommand=scrollbar.set)
listbox.bind("<MouseWheel>", scrolllistbox2)
listbox2 = Listbox(root)
listbox2.pack()
for i in range(100):
listbox2.insert(END, i+100)
listbox2.config(yscrollcommand=scrollbar.set)
#scrollbar.config(command=listbox.yview)
root.mainloop()
或者...
from tkinter import *
root = Tk()
root.geometry("400x400")
def scrolllistbox(event):
''' scrolling both listbox '''
listbox2.yview_scroll(int(-1*(event.delta/120)), "units")
listbox1.yview_scroll(int(-1*(event.delta/120)), "units")
def random_insert():
''' adding some numbers to the listboxes '''
for i in range(100):
listbox1.insert(END, i)
listbox2.insert(END, i + 100)
# SCROLLBAR
scrollbar = Scrollbar(root)
#scrollbar.pack(side=RIGHT, fill=Y)
# LISTBOX 1
listbox1 = Listbox(root)
listbox1.pack()
# attach listbox to scrollbar with yscrollcommand
# listbox1.config(yscrollcommand=scrollbar.set)
# The second one
listbox2 = Listbox(root)
listbox2.pack()
listbox2.config(yscrollcommand=scrollbar.set)
# scroll the first one when you're on the second one
# listbox2.bind("<MouseWheel>", scrolllistbox)
root.bind("<MouseWheel>", scrolllistbox)
# scroll also the second list when you're on the first
listbox1.bind("<MouseWheel>", scrolllistbox)
random_insert()
#scrollbar.config(command=listbox.yview)
root.mainloop()
解决方案 7:
这种方法在Linux上对我有用:
canvas.bind('<Button-4>', lambda e: canvas.yview_scroll(int(-1*e.num), 'units'))
canvas.bind('<Button-5>', lambda e: canvas.yview_scroll(int(e.num), 'units'))
解决方案 8:
Mikhail T. 的回答对我来说非常有效。以下可能是其他人可能觉得有用的更通用的设置(我真的需要开始回馈)
def _setup_mousewheel(self,frame,canvas):
frame.bind('<Enter>', lambda *args, passed=canvas: self._bound_to_mousewheel(*args,passed))
frame.bind('<Leave>', lambda *args, passed=canvas: self._unbound_to_mousewheel(*args,passed))
def _bound_to_mousewheel(self, event, canvas):
canvas.bind_all("<MouseWheel>", lambda *args, passed=canvas: self._on_mousewheel(*args,passed))
def _unbound_to_mousewheel(self, event, canvas):
canvas.unbind_all("<MouseWheel>")
def _on_mousewheel(self, event, canvas):
canvas.yview_scroll(int(-1*(event.delta/120)), "units")
然后设置一个画布/框架用于鼠标滚轮滚动只需:
self._setup_mousewheel(frame, canvas)
解决方案 9:
def onmousewheel(widget, command):
widget.bind("<Enter>", lambda _: widget.bind_all('<MouseWheel>',command ))
widget.bind("<Leave>", lambda _: widget.unbind_all('<MouseWheel>'))
onmousewheel(canvas, lambda e: canvas.yview_scroll(int(-1*(e.delta)), "units"))
只需滚动您想要的框架的紧凑解决方案。
感谢所有分享解决方案的人。
解决方案 10:
self.canvas = Canvas(...)
self.canvas.bind_all("<MouseWheel>", lambda event: self._on_mousewheel(where_to_scroll= (-1 * event.delta)))
...
def _on_mousewheel(self, where_to_scroll):
self.canvas.yview("scroll", where_to_scroll, "units")
bind_all 意味着 = <“发生时您想要针对的事情”>,然后执行某些操作()
1- 我们希望目标鼠标滚轮 = <“MouseWheel”>
2 - 我们希望滚动到使用鼠标滚轮时鼠标滚轮移动的位置 = lambda 事件:self._on_mousewheel(where_to_scroll=(-1 * event.delta))
当我们使用 bind_all 或 bind 方法时,它们会给我们一个变量。因此,我们使用 lambda 来获取它并在函数内部使用它
该变量具有属性,其中一个属性是 delta,它给出鼠标滚轮方向的整数值
我把它乘以 -1,因为 delta 给了我错误的方向,我想是因为我 macOS 系统,如果给了你错误的方向,试试像我一样把它乘以 -1
“更新”
我在画布内创建了一个框架,然后在新框架上使用 bind,而不是在画布上使用 bind_all,因为之前出现了问题,每次我使用鼠标滚轮时,它都会调用 _on_mousewheel() 方法,即使我不在画布内。因此,创建一个框架并使用 bind 为我解决了这个问题
喜欢这个教程 - https://www.youtube.com/watch?v=0WafQCaok6g
解决方案 11:
上面评论中有一个人遇到了 MouseWheelevent.delta
未填充的问题。我遇到了同样的问题,因此滚动只能以单位步长递增。虽然这确实有效,但对于我的应用程序来说太慢了。
为了解决这个问题,我不得不采取以下措施:
# mouse wheel callback for scrolling
...
self.prevtime = 0
self.sliceinc = 0
...
def mousewheel(self,event,key=None):
if key is None:
# if events are separated by < 100 msec, accumulate and return
if abs(event.time-self.prevtime) < 100:
if event.num == 4:
self.sliceinc += 1
elif event.num == 5:
self.sliceinc -= 1
self.prevtime = event.time
if abs(self.sliceinc) == 5:
self.canvas.get_tk_widget().event_generate('<<MyMouseWheel>>',when="tail",x=event.x,y=event.y)
return
# otherwise, just process the event and update gui
else:
self.prevtime = event.time
if event.num == 4:
self.sliceinc = 1
elif event.num == 5:
self.sliceinc = -1
newslice = self.currentslice.get() + self.sliceinc
self.currentslice.set(newslice)
self.doGuiUpdates()
# when the virtual event callback finally happens, re-initialize
if key == 'Key':
self.prevtime = 0
self.sliceinc = 0
绑定如下:
self.canvas.get_tk_widget().bind('<<MyMouseWheel>>',EventCallback(self.mousewheel,key='Key'))
EventCallback 是一个用于将 kwargs 传递给回调的便捷类:
class EventCallback():
def __init__(self, callback, *args, **kwargs):
self.callback = callback
self.args = args
self.kwargs = kwargs
def __call__(self,event):
return self.callback(event,*self.args, **self.kwargs)
简而言之,MouseWheel 事件是在不更新 GUI 的情况下累积的,并且在处理完所有待处理的 MouseWheel 事件后会生成一个虚拟事件来运行,该事件将累积总数作为对 GUI 的更新一次性应用。
解决方案 12:
我正在试验,找到了这个简单的方法。就像我们使用 Up 和 Down 绑定一样
SCROLL_STEP = 100
self.window.bind("<Down>", self.scroll_down)
self.window.bind("<Up>", self.scroll_up)
def scroll_down(self, e):
self.scroll += SCROLL_STEP
self.draw()
def scroll_up(self, e):
if self.scroll > 0:
self.scroll -= SCROLL_STEP
self.draw()
我们可以在 Mousescroll 的情况下使用相同的方法,像这样
# Linux: Binding to <Button-4> and <Button-5> is being used
self.canvas.bind('<Button-4>', self.scroll_up)
self.canvas.bind('<Button-5>', self.scroll_down)
def scroll_down(self, e):
self.scroll += SCROLL_STEP
self.draw()
def scroll_up(self, e):
if self.scroll > 0:
self.scroll -= SCROLL_STEP
self.draw()
开始时 self.scroll = 0。