Python Tkinter - прокрутка холста с помощью мышки - PullRequest
0 голосов
/ 27 июня 2011

Я думаю, что это очень распространенный вопрос, но я не смог найти ответ.

Я пытаюсь сделать окно, которое прокручивается в зависимости от положения мыши: если мышь находится близко к верхней части экрана, она прокручивается к вершине, если она находится близко к правой границе, она прокручивается вправо. и так далее. Вот код:

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()

Проблема в том, что движение прокрутки находится в функции движения, которая работает только при наличии движения мыши (если вы перестанете двигать мышь, прокрутка тоже остановится). Я хотел бы сделать так, чтобы даже если мышь не двигалась (но все еще находилась в «области прокрутки»), окно продолжало бы прокручиваться, пока не достигло конца.

Я думал, что очевидным способом будет изменение оператора if (например, из строки 30) на оператор while, например:

while x < 30:

Но затем, когда мышь достигает этой позиции, программа останавливается (думаю, что я ожидаю завершения цикла while).

Есть предложения?

Заранее спасибо.

UPDATE

Вот рабочий код с (или одним из возможных) ответом. Я не знаю, правильно ли обновлять сам вопрос с ответом, но я думаю, что он может быть полезен для других.

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)

Пожалуйста, дайте мне знать, если это правильно. Спасибо всем за обсуждение и предложения. Эти ссылки были полезными тоже.

Ответы [ 3 ]

0 голосов
/ 27 июня 2011

Как вы сказали, это работает, только если мышь находится в движении, в противном случае событие <Motion> не вызывается.Вы можете использовать таймер, который срабатывает после тайм-аута и только если мышь находится в области прокрутки.Ниже приведен только псевдокод, который использует сбрасываемый таймер , который я обнаружил в 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

Остерегайтесь, что это всего лишь мысли, я не проверял код и, вероятно, тамявляется условием гонки для переменной timer, и вам следует использовать блокировку.

0 голосов
/ 27 июня 2011

Сначала настройте метод, который прокручивает окно на небольшую величину, а затем снова вызывает себя через некоторый фиксированный промежуток времени (например, 100 мс), если мышь находится в регионе.Вы можете использовать метод "после", чтобы сделать это.При этом холст будет непрерывно прокручиваться, пока мышь находится в области прокрутки.

Затем создайте привязку, которая вызывает эту функцию, когда курсор впервые входит в зону прокрутки.

И это все, что вам нужно.Просто убедитесь, что у вас одновременно запущено только одно из заданий прокрутки.

0 голосов
/ 27 июня 2011

Очевидная причина зависания вашей программы - это момент, когда x is less than 30 она входит в цикл, и если она не выйдет из цикла, вы не сможете управлять мышью и, если не сможете контролировать мыши, позиция всегда будет <30, так что ваш цикл будет навсегда удовлетворен и никогда не закончится. </p>

Итак, вам нужно выполнить эту проверку while x < 30 в отдельном потоке. Вы можете инициализировать поток в момент x becomes less than 30 и из этого потока управлять прокруткой. В момент x becomes more than or equal to 30 вы убиваете нить.

...