0

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.

2
  • I'm not an expert in Tkinter, but this might help Commented Jun 27, 2011 at 15:41
  • I could find this helpful, except that the variable ws is not defined. By the context, I assume that it is the coordinates Commented Oct 6, 2017 at 17:28

3 Answers 3

0

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.

Sign up to request clarification or add additional context in comments.

3 Comments

I think I understood your suggestion, but after playing with this concept (and others) I still can't figure out how to do it. I've made a function that calls itself, but it keeps calling itself after the mouse leaves the scroll region. I'm still trying it, but, could you show me an example or some pseudo code? Thanks in advance.
I didn't know the after method, which I think better suits in this case because the Timer class involves threads.
I think I've figured out, at least, it's working. I've updated the question with the working code. Please let me know if I've done it right (if the code is good).
0

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:

TIMEOUT = 0.5 timer = None def _on_timeout(event): global timer scroll_xy(event) timer = TimerReset(TIMEOUT, _on_timeout, [event]) timer.start() def xy_motion(event): global timer if is_in_scrollable_area(event): if timer is None: timer = TimerReset(TIMEOUT, _on_timeout, [event]) timer.start() else: timer.reset() scroll_xy(event) elif timer is not None: timer.cancel() timer = None 

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.

Comments

-1

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 moment x becomes less than 30 and from that thread control the scrolling. The moment x becomes more than or equal to 30, you kill the thread.

5 Comments

why the downvote? it might be heavyweight solution but it is not incorrect. Atleast I made him figure out why his program is getting stuck.
@Guanidene, actually I didn't understand your suggestion, because I can control the mouse, it's just that the program doesn't respond anymore (still, I think you were right about the while condition being satisfied forever). And because of the suggestion I made a search about "threads", that I've never heard about before.
@Marcos: if you've never heard of threads before, this solution is definitely not the right solution for you. Threads add a considerable amount of complexity, some of which is very subtle.
@Guanidene: I dowvoted for the same reason I would downvote a suggestion to cut your hair with a lawn mower. I feel the answer is less than useful becaus it sends one down the wrong path. Threads are good for what threads are good for, and terrible for everything else. This problem falls into the "everything else" category IMHO.
@Macros - what I mean by you not being able to control the mouse is, your mouse move events won't be accepted by your program unless it exits the while loop...

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.