Python как остановить цикл по клику пункта меню?
Всем привет! В универе по лабораторной работе задали следующую задачу:
На пространстве формы изображена матрица случайных чисел от 0 до 99 размером 6х6. Метка в виде овала исходно находится в левом верхнем углу, а после запуска циклически движется по периметру матрицы по часовой стрелке с дискретом времени 0.8 сек. После окончания полного цикла рамка движется против часовой стрелки, а затем снова по часовой. Запуск движения – двойной щелчок левой клавиши мыши по форме, остановка – команда главного меню, что приводит к установке рамки в исходное положение.
Думаю, здесь с рамкой опечатка, и имелся в виду овал.
Не могу никак разобраться с остановкой цикла по щелчку пункта меню. Принцип выполнения задачи в коде у меня такой: по двойному клику по канвасу вызывается функция start, которая вызывает функцию move_clockwise. Та, отработав полный цикл, вызывает move_counterclockwise. И в итоге они вызывают друг друга по очереди. Остался нерешённым вопрос про остановку. Может кто-нибудь подсказать, как её можно реализовать?
from tkinter import *
import random
import time
#from PIL import ImageTk, Image
root = Tk()
root.title("Лабораторная работа №4")
root.geometry('360x360')
width=360
height=360
sleep=0.01
step=15
work=True
c = Canvas(root, width=width, height=height, bg="white")
c.place(x = 0, y = 0)
oval = c.create_oval(0, 0, 30, 30, fill='green')
def move_clockwise():
for i in range((width - 30) // step):
c.move(oval, step, 0)
root.update()
time.sleep(sleep)
for i in range((height - 30) // step):
c.move(oval, 0, step)
root.update()
time.sleep(sleep)
for i in range((width - 30) // step):
c.move(oval, -step, 0)
root.update()
time.sleep(sleep)
for i in range((height - 30) // step):
c.move(oval, 0, -step)
root.update()
time.sleep(sleep)
move_counterclockwise()
def move_counterclockwise():
for i in range((height - 30) // step):
c.move(oval, 0, step)
root.update()
time.sleep(sleep)
for i in range((width - 30) // step):
c.move(oval, step, 0)
root.update()
time.sleep(sleep)
for i in range((height - 30) // step):
c.move(oval, 0, -step)
root.update()
time.sleep(sleep)
for i in range((width - 30) // step):
c.move(oval, -step, 0)
root.update()
time.sleep(sleep)
move_clockwise()
def start(event):
global work
while work:
move_clockwise()
def stop():
global work
if work:
work==False
#Заполнение матрицы
for x in range(6):
for y in range(6):
text=c.create_text(30+60*x, 30+60*y, text=str(random.randint(0,99)))
c.bind('<Double-Button-1>', start)
menu=Menu(root)
root.config(menu=menu)
menu.add_command(label="Stop", command=stop)
root.mainloop()
P.S. Пожалуйста, по возможности ООП не предлагать, преподаватель его не поддерживает...
Ответы (1 шт):
Одна опечатка и одна принципиальная ошибка.
Опечатка в присваивании:
if work:
work==False # должно быть просто =
А ошибка в том, что вы снова вызываете move_clockwise() в конце функции def move_counterclockwise(). В результате функции уходят в рекурсию и никогда не возвращаются в цикл проверки условия на останов:
while work:
move_clockwise()
Ну и work = True надо бы добавить в начало def start(event):. Чтобы можно было снова запустить движение овала.
Ну а вообще, код можно подсократить. И останавливаться мгновенно.
from tkinter import *
import random
import time
#from PIL import ImageTk, Image
root = Tk()
root.title("Лабораторная работа №4")
root.geometry('360x360')
width=360
height=360
sleep=0.02
step=15
work=False
c = Canvas(root, width=width, height=height, bg="white")
c.place(x = 0, y = 0)
oval = c.create_oval(0, 0, 30, 30, fill='green')
def move(figure):
while True:
for vertical, increment in ((False, +1), (True , +1), (False, -1), (True , -1), # по часовой
(True , +1), (False, +1), (True , -1), (False, -1)): # против часовой
for i in range(((height if vertical else width) - 30) // step):
if not work:
c.coords(figure, 0, 0, 30, 30)
return
c.move(figure, (0 if vertical else 1) * step * increment, (1 if vertical else 0) * step * increment)
root.update()
time.sleep(sleep)
def start(event):
global work
if not work:
work = True
move(oval)
def stop():
global work
work = False
#Заполнение матрицы
for x in range(6):
for y in range(6):
text=c.create_text(30+60*x, 30+60*y, text=str(random.randint(0,99)))
c.bind('<Double-Button-1>', start)
menu=Menu(root)
root.config(menu=menu)
menu.add_command(label="Stop", command=stop)
root.mainloop()
К тому же задание довольно невнятное. С учётом того, что движение должно происходить с дискретом 0.8 секунды (а это довольно большая пауза), возможно имелось ввиду, что овал должен прыгать по элементам матрицы. И в таком случае time.sleep() вообще применять нельзя, т.к. он будет фризить интерфейс на это время. Нужно использовать функцию after() для отложенного запуска следующего перемещения. Например, с использованием генератора, код может выглядеть так:
from tkinter import *
import random
import time
#from PIL import ImageTk, Image
root = Tk()
root.title("Лабораторная работа №4")
sleep = 0.8
step = 60
oval_size = 40
matrix_size = {"W":6, "H":6} # width, height
offset = (step - oval_size) // 2
work = False
root.geometry(f'{matrix_size["W"] * step}x{matrix_size["H"] * step}')
c = Canvas(root, width=matrix_size["W"] * step, height=matrix_size["H"] * step, bg="white")
c.place(x = 0, y = 0)
oval = c.create_oval(offset, offset, offset + oval_size, offset + oval_size)
def move(gen=None):
if work:
next(gen)
root.after(int(sleep*1000), lambda: move(gen))
def move_generator():
while True:
for vertical, increment in ((False, +1), (True , +1), (False, -1), (True , -1), # по часовой
(True , +1), (False, +1), (True , -1), (False, -1)): # против часовой
for i in range(matrix_size["H" if vertical else "W"] - 1):
c.move(oval, (0 if vertical else 1) * step * increment, (1 if vertical else 0) * step * increment)
yield
def start(event):
global work
if not work:
work = True
move(move_generator())
def stop():
global work
work = False
c.coords(oval, offset, offset, offset + oval_size, offset + oval_size)
#Заполнение матрицы
for x in range(6):
for y in range(6):
text=c.create_text(30+60*x, 30+60*y, text=str(random.randint(0,99)))
c.bind('<Double-Button-1>', start)
menu=Menu(root)
root.config(menu=menu)
menu.add_command(label="Stop", command=stop)
root.mainloop()
Оно же без использования генератора.
from tkinter import *
import random
import time
root = Tk()
root.title("Лабораторная работа №4")
sleep = 0.8
step = 60
oval_size = 40
matrix_size = {"W":6, "H":6} # width, height
offset = (step - oval_size) // 2
work = False
root.geometry(f'{matrix_size["W"] * step}x{matrix_size["H"] * step}')
c = Canvas(root, width=matrix_size["W"] * step, height=matrix_size["H"] * step, bg="white")
c.place(x = 0, y = 0)
oval = c.create_oval(offset, offset, offset + oval_size, offset + oval_size)
movements = []
for vertical, increment in ((False, +1), (True , +1), (False, -1), (True , -1), # по часовой
(True , +1), (False, +1), (True , -1), (False, -1)): # против часовой
for i in range(matrix_size["H" if vertical else "W"] - 1):
movements.append(((0 if vertical else 1) * step * increment, (1 if vertical else 0) * step * increment))
def move(pos=0):
if work:
c.move(oval, *movements[pos])
root.after(int(sleep*1000), lambda: move( (pos+1) % len(movements) ))
def start(event):
global work
if not work:
work = True
move()
def stop():
global work
work = False
c.coords(oval, offset, offset, offset + oval_size, offset + oval_size)
#Заполнение матрицы
for x in range(6):
for y in range(6):
text=c.create_text(30+60*x, 30+60*y, text=str(random.randint(0,99)))
c.bind('<Double-Button-1>', start)
menu=Menu(root)
root.config(menu=menu)
menu.add_command(label="Stop", command=stop)
root.mainloop()