я делаю приложение для рисования на Python. в нем есть функции для создания фигур и рисования с помощью щелчка и перетаскивания.
я пытался реализовать кнопку отмены в своем приложении. он отлично работает, чтобы отменить фигуры, но не так хорошо, чтобы отменить щелчок и перетаскивание линий.
моя функция:
def undo ():
if la[len(la)-1] =='s' :
turtle.undo()
del(la[len(la)-1])
while(turtle.isdown()==True) :
turtle.undo()
turtle.undo()
turtle.undo()
if la[len(la)-1]=='d' :
turtle.undo()
del(la[len(la)-1])
while turtle.xcor()!=xp[len(xp)-1] and turtle.ycor()!=yp[len(yp)-1] :
turtle.undo()
del(xp[len(xp)-1])
del(yp[len(yp)-1])
la — это массив, который запоминает, было ли последнее действие рисованием линии или фигуры.
если это фигура («s»), она отменяется, пока перо опущено (поскольку фигуры создаются с помощью действия черепахи, перо не поднимается между этими действиями).
если это линия ('d'), ее следует отменить до тех пор, пока координаты черепахи не станут такими же, как координаты начала строки (запоминаемые в xp и yp).
ну, иногда он отменяет больше, чем нужно, а иногда не отменяет вообще. дело в том, что в большинстве случаев все работает идеально, поэтому я не знаю, что вызывает проблемы. чаще всего возникают проблемы, когда перед линией стоит фигура, часть фигуры тоже отменяется. и, конечно, если вы пересечете начало линии несколько раз, она перестанет отменяться, но это можно исправить, подсчитав, сколько раз вы ее пересекли.
редактировать :
минимальная рабочая программа:
from tkinter import *
from functools import partial
from turtle import TurtleScreen, RawTurtle, Shape
menu = Tk()
frame=Frame(menu, bd=1, bg='#d1c7c7')
frame.grid(row=1, column=6, rowspan=26 )
canvas=Canvas(frame,width=1000, height=700)
canvas.grid(column=6,row=1, rowspan=26)
size=5
la=['n']
xp=[]
yp=[]
# turtle actions
def draw(x, y):
turtle.ondrag(None)
turtle.down()
turtle.goto(x, y)
turtle.up()
screen.update()
turtle.ondrag(draw)
def move(x, y):
global xp
global yp
global la
xp.append(turtle.xcor())
yp.append(turtle.ycor())
la.append('d')
screen.onscreenclick(None)
turtle.goto(x, y)
screen.onclick(move)
screen.update()
def main():
turtle.shape("circle")
polygon = turtle.get_shapepoly()
fixed_color_turtle = Shape("compound")
fixed_color_turtle.addcomponent(polygon, "", "")
screen.register_shape('fixed', fixed_color_turtle)
turtle.shape("fixed")
turtle.penup()
turtle.pensize(5)
turtle.turtlesize(2000,2000)
turtle.ondrag(draw)
screen.onscreenclick(move)
screen.update()
def setcolor (color) :
turtle.pencolor(color)
turtle.fillcolor(color)
bblack = Button(
menu,
bg='black',
width=10,
command=partial(setcolor,'black')
).grid(column=1, row=2)
bred = Button(
menu,
bg='red',
width=10,
command=partial(setcolor,'red')
).grid(column=1, row=3)
bdraw = Button(
menu,
text='pen',
width=10,
command=main
).grid(column=1, row=4)
def square(x, y):
global la
la.append('s')
turtle.turtlesize(1,1)
screen.onclick(None)
turtle.ondrag(None)
turtle.goto(x-size*8.3, y-size*8.3)
turtle.pendown()
turtle.begin_fill()
for i in range(4):
turtle.forward(size*18)
turtle.left(360 / 4)
turtle.end_fill()
turtle.penup()
possqr()
def possqr():
screen.onclick(square)
bsquare = Button(
menu,
text='square',
width=10,
command=possqr
).grid(column=1, row=5)
def triangle(x, y):
global la
la.append('s')
turtle.turtlesize(1,1)
screen.onclick(None)
turtle.ondrag(None)
turtle.goto(x-size*8.5, y-size*6)
turtle.pendown()
turtle.begin_fill()
for i in range(3):
turtle.forward(size*18)
turtle.left(360 / 3)
turtle.end_fill()
turtle.penup()
postriangle()
def postriangle():
screen.onclick(triangle)
btriangle = Button(
menu,
text='triangle',
width=10,
command=postriangle
).grid(column=1, row=6)
Label(menu, text='COLORS').grid(column=1, row=1)
def undo ():
if la[len(la)-1] =='s' :
turtle.undo()
del(la[len(la)-1])
while(turtle.isdown()==True) :
turtle.undo()
turtle.undo()
turtle.undo()
if la[len(la)-1]=='d' :
turtle.undo()
del(la[len(la)-1])
while turtle.xcor()!=xp[len(xp)-1] and turtle.ycor()!=yp[len(yp)-1] :
turtle.undo()
del(xp[len(xp)-1])
del(yp[len(yp)-1])
bundo = Button(
menu,
text='undo',
width=10,
command=undo
).grid(column=2, row=1)
screen = TurtleScreen(canvas)
screen.tracer(False)
pen_color = 'black'
turtle = RawTurtle(screen)
main()
mainloop()
В вашем коде есть одна проблема, которую вы можете исправить, чтобы устранить ошибку в логике функции undo()
:
elif la[len(la)-1]=='d' : ### <<< ###
При использовании одного if
вы рискуете, что оба блока if
будут выполнены за одно undo()
действие, что приведет к неожиданному поведению.
Чтобы улучшить поведение с неправильно обновленной графикой, вы можете добавить:
screen.update()
screen.getcanvas().update_idletasks()
в конце функции undo()
.
При этом следует отметить, что модуль turtle
спроектирован для простоты, поэтому надежность обновления графики после операций undo()
может в некоторых сложных сценариях привести к неправильному обновлению экрана.
Обратите внимание, что установленный по умолчанию предел операций отмены в случае рисования от руки или добавления огромного количества квадратов и треугольников может привести к невозможности удалить все, что видно на экране.
В случае RawTurtle вам необходимо установить более высокое значение «частной» переменной, если вы хотите большее количество уровней отмены, чем уровень по умолчанию, который легко исчерпывается в таком сценарии рисования, например:
turtle._undobuffersize=1_000_000
Вы также можете попробовать заменить часть tkinter
кнопками на основе turtle
, где вы нажимаете на черепах, которые вы используете в качестве кнопок. Таким образом вы упростите обновление экрана, что может привести к более надежному обновлению всей графики после отмены действий.
Приведенный ниже код использует модуль turtle
только так, как предложено в другом ответе.
Обратите внимание, что в коде используется другая логика для ограничения шагов отмены, что делает списки с типом нарисованной фигуры и координатами устаревшими, что делает код короче и проще. Вы уже используете используемый механизм в своем коде, но не заметили, что его можно использовать для обозначения того, где необходимо остановить отмену. Другими словами, перед каждым шагом рисования, независимо от его типа, черепаха перемещается в позицию маркера, которую затем можно использовать для остановки цикла команд отмены. Соответствующий стартовый маркер предотвращает отмену первоначальной настройки, если пользователь не выполняет никаких действий для отмены. Вы можете переписать свой модуль tkinter/turtle, используя этот подход, чтобы сделать его короче и проще.
Еще одно упрощение — пропустить сложную вложенную структуру вызовов функций и определить кнопки, на которые нужно нажимать, как черепах. На черепахах-кнопках нужно щелкать правой кнопкой мыши, чтобы не мешать действиям левой кнопки мыши.
Я оставляю расположение «кнопок» и написание текстов рядом с ними, объясняющих их функции (не над ними, так как это вызовет проблемы с нажатием) в качестве упражнения, если вы решите использовать этот чистый подход Turtle вместо tkinter/turtle. один.
Обратите внимание: чтобы перетаскивание работало из любого положения щелчка, я сделал фигуру черепахи достаточно большой, чтобы она всегда покрывала всю область рисования, поэтому независимо от того, где был сделан щелчок, черепаха будет поражена этим щелчком и отреагирует на ее перетаскивание.
Вы можете отслеживать, что делает код, просматривая его вывод в командной строке Терминала — в код встроены соответствующие операторы печати:
import turtle
size = 5
fig = "draw"
def move(x, y):
tT.penup(); tT.goto(4096,0)
tT.goto(x, y)
drawfig(x,y)
def drawfig(x=None, y=None):
global fig
if fig= = "triangle":
tT.ondrag(None)
print("drawfig:triangle")
triangle(x,y)
elif fig= = "square":
tT.ondrag(None)
print("drawfig:square")
square(x,y)
elif fig= = "draw":
tT.ondrag(draw)
print("drawfig:draw")
draw(x, y)
def square(x, y):
print(f"square")
tT.pendown()
tT.begin_fill()
for i in range(4):
tT.forward(size*18)
tT.left(360 / 4)
tT.end_fill()
screen.update()
def triangle(x, y):
print(f"triangle")
tT.pendown()
tT.begin_fill()
for i in range(3):
tT.forward(size*18)
tT.left(360 / 3)
tT.end_fill()
screen.update()
def draw(x, y) :
tT.pendown()
tT.ondrag(None)
tT.goto(x,y)
screen.update()
tT.ondrag(draw)
def undo (x=None, y=None):
print(f"UNDO:", end='', flush=True)
while tT.undobufferentries() and tT.xcor() != 4096 and tT.xcor() != 8192:
print(f"{tT.xcor()}! = ", end = "")
tT.undo()
while tT.undobufferentries() and tT.xcor() == 4096 and tT.xcor() != 8192 :
print(f"{tT.xcor()}= = ", end = "")
tT.undo()
print()
screen.update()
screen.getcanvas().update_idletasks()
def setblack(x=None,y=None) :
print(f"setblack")
tT.pencolor("black")
tT.fillcolor("black")
def setred(x=None,y=None) :
print(f"setred")
tT.pencolor("red")
tT.fillcolor("red")
def setdraw(x=None,y=None):
global fig
print("setdraw")
fig = "draw"
def setsquare(x=None, y=None):
print("setsquare")
global fig
fig = "square"
def settriangle(x=None, y=None):
print("settriangle")
global fig
fig = "triangle"
screen = turtle.Screen()
screen.tracer(False)
tT=turtle.Turtle()
tT.shape("square")
tT.shapesize(stretch_wid=2000, stretch_len=2000)
tT.pensize(5)
pen_color = 'black'
tT.penup();tT.goto(8192,0)
screen.onscreenclick(move)
tT.ondrag(draw)
dx,dy=30, 30
x,y=20,20
tt = turtle.Turtle(); x+=dx; y+=dy;
tt.color("black");tt.goto(x,y);tt.shape("square");tt.shapesize(stretch_wid=1, stretch_len=1)
tt.onclick(setblack, 3 )
bblack=tt
tt = turtle.Turtle(); x+=dx; y+=dy;
tt.color("red");tt.goto(x,y);tt.shape("square");tt.shapesize(stretch_wid=1, stretch_len=1)
tt.onclick(setred , 3)
bred=tt
tt = turtle.Turtle(); x+=dx; y+=dy;
tt.color("gray");tt.goto(x,y);tt.shape("square");tt.shapesize(stretch_wid=1, stretch_len=1)
tt.onclick(setdraw, 3 )
bdraw = tt
tt = turtle.Turtle(); x+=dx; y+=dy;
tt.color("green");tt.goto(x,y);tt.shape("square");tt.shapesize(stretch_wid=1, stretch_len=1)
tt.onclick(setsquare , 3)
bsquare = tt
tt = turtle.Turtle(); x+=dx; y+=dy;
tt.color("pink");tt.goto(x,y);tt.shape("square");tt.shapesize(stretch_wid=1, stretch_len=1)
tt.onclick(settriangle, 3 )
btriangle = tt
tt = turtle.Turtle(); x+=dx; y+=dy;
tt.color("blue");tt.goto(x,y);tt.shape("square");tt.shapesize(stretch_wid=1, stretch_len=1)
tt.onclick(undo , 3)
bundo = tt
screen.update()
screen.mainloop()
Отмена состояния со многими объектами:
правильно удаляет их все по одному до стартового экрана:
Целью кода является полный, работоспособный минимально воспроизводимый пример . Так что да, вам не следует публиковать слишком много кода, но он также должен быть полным и работоспособным. Таким образом, это предполагает удаление некоторых ненужных функций, чтобы добраться до сути проблемы, сохраняя при этом полную работоспособность приложения. Кроме того, я предлагаю отформатировать ваш код так, чтобы его было легко читать другим.