I think this is a very common question, but I couldn't find the answer.
I'm trying to make a window that scrolls depending on the mouse position: if the mouse is close to top of the screen, it scrolls to the top, if it is close to the right border, it scrolls to the right and so on. Here is the code:
from tkinter import *
from tkinter import ttk
root = Tk()
h = ttk.Scrollbar(root, orient = HORIZONTAL)
v = ttk.Scrollbar(root, orient = VERTICAL)
canvas = Canvas(root, scrollregion = (0, 0, 2000, 2000), width = 600, height = 600, yscrollcommand = v.set, xscrollcommand = h.set)
h['command'] = canvas.xview
v['command'] = canvas.yview
ttk.Sizegrip(root).grid(column=1, row=1, sticky=(S,E))
canvas.grid(column = 0, row = 0, sticky = (N,W,E,S))
h.grid(column = 0, row = 1, sticky = (W,E))
v.grid(column = 1, row = 0, sticky = (N,S))
root.grid_columnconfigure(0, weight = 1)
root.grid_rowconfigure(0, weight = 1)
canvas.create_rectangle((0, 0, 50, 50), fill = 'black')
canvas.create_rectangle((500, 500, 550, 550), fill = 'black')
canvas.create_rectangle((1500, 1500, 1550, 1550), fill = 'black')
canvas.create_rectangle((1000, 1000, 1050, 1050), fill = 'black')
def xy_motion(event):
x, y = event.x, event.y
if x < 30:
delta = -1
canvas.xview('scroll', delta, 'units')
if x > (600 - 30):
delta = 1
canvas.xview('scroll', delta, 'units')
if y < 30:
delta = -1
canvas.yview('scroll', delta, 'units')
if y > (600 - 30):
delta = 1
canvas.yview('scroll', delta, 'units')
canvas.bind('<Motion>', xy_motion)
root.mainloop()
The problem is that the scrolling movement is in the motion function that only works if there is mouse movement (if you stop moving the mouse, the scrolling stops too). I would like to make it a way that even if the mouse is not moving (but still in the "scrolling area") the window would keep scrolling until it reaches the end.
I thought the obvious way would be changing the if statement (from line 30 for example) to a while statement, like this:
while x < 30:
But then when the mouse reaches this position the program freezes (waiting for the while loop to finish I think).
Any suggestion?
Thanks in advance.
UPDATE
Here is the working code with an (or one of the possible) answer. I don't know if it is right to update the question itself with the answer, but I think it can be useful to others.
x, y = 0, 0
def scroll():
global x, y
if x < 30:
delta = - 1
canvas.xview('scroll', delta, 'units')
elif x > (ws - 30):
delta = 1
canvas.xview('scroll', delta, 'units')
elif y < 30:
delta = -1
canvas.yview('scroll', delta, 'units')
elif y > (ws - 30):
delta = 1
canvas.yview('scroll', delta, 'units')
canvas.after(100, scroll)
def xy_motion(event):
global x, y
x, y = event.x, event.y
scroll()
canvas.bind('<Motion>', xy_motion)
Please let me know if it is correct. Thanks to everyone for the discussion and suggestions. These links were useful too.
First, set up a method that scrolls the window by a tiny amount, then calls itself again after some fixed period of time (eg 100ms) if the mouse is in the region. You can use the method "after" todo this. With this, the canvas will continuously scroll as long as the mouse is in the scroll region.
Next, create a binding that calls this function when the cursor enters the scroll zone for the first time.
And that's all you need. Just make sure you only have one of tbese scroll jobs running at any one time.
The obvious reason for your program getting stuck is the moment
x is less than 30
it goes into the loop and unless it gets out of the loop, you are not going to be able to control the mouse and unless you are able to control the mouse, the position will always be < 30 so your loop will be forever satisfied and will never end.So, what you need to do is run this check
while x < 30
in a separate thread. You can initialize the thread the momentx becomes less than 30
and from that thread control the scrolling. The momentx becomes more than or equal to 30
, you kill the thread.As you said this works only if mouse is in movement, otherwise the
<Motion>
event is not triggered. You can use a timer that is triggered after a timeout and only if the mouse is in the scrolling area. The following is just a pseudo-code which uses a resettable timer I found in ActiveState:Beware that these are just thoughts, I didn't check the code and probably there is a race condition on the
timer
variable and you should use a lock.