Найти - Пользователи
Полная версия: Проблема с движением виджетов в Canvas tkinter
Начало » Python для новичков » Проблема с движением виджетов в Canvas tkinter
1
Konstantin1984+
Я написал программу, которая обеспечивает создание и перемещение объектов по экрану (планировщик помещения). Каждый объект активируется для движения нажатием левой кнопки мыши на него. Есть несколько проблем. После активации объекта двигаться начинает не он, а объект, созданный перед ним (имеющий порядковый номер, который я сделал тегом, на единицу меньше), а если сохранить положение фигур и снова открыть файл, то смещение произведенное предметом с меньшим тегом, будет отображено как смещение предмета, который я и активировал. Есть еще проблема с функцией изменения положения объекта в пространстве (с горизонтального в вертикальное и обратно), в данном случае виджет может как не двигаться, так и совершить одно действие и прекратить в принципе реагировать на команды, при этом перестают реагировать все виджеты. Код указан ниже. В чем здесь проблема?

  from tkinter import *
import tkinter.ttk as ttk
from time import sleep
from tkinter.filedialog import *
import fileinput
from tkinter.messagebox import *
import os
import sqlite3
#список с параметрами виджетов
spisok_widget=[]
#имя файла для автоматического сохранения
name_save=''
tag=0
zvet='white'
figura='прямоугольник'
#открытие базы данных с сохраненными параметрами объектов
def _open():
    okno_sozd=Tk()
    okno_sozd.title('')
    okno_sozd.geometry('+0+0')    
    l = Listbox(okno_sozd,height=9,width=33)
    scroll=Scrollbar(okno_sozd, command=l.yview)
    l.configure(yscrollcommand=scroll.set)
    l.grid(row=0,column=0)
    scroll.grid(row=0,column=1)
    for i in os.listdir():
        if i[len(i)-2:len(i)]=='db':
            l.insert(END, i)
    def zakritie(event):
        okno_sozd.destroy()
        
    def obnovlen(event):
        index=l.curselection()
        dannie_widget=l.get(index)
        okno_sozd.destroy()
        #определяем имя файла для автоматического сохранения
        global name_save
        name_save=dannie_widget
        con=sqlite3.connect(dannie_widget)
        cur=con.cursor()
        cur.execute('SELECT*FROM perechen')
        b=cur.fetchall()
        con.commit()
        cur.close()
        con.close()
        global spisok_widget
        for elem in b:
            spisok_widget.append(list(elem))
            if elem[6]=='прямоугольник':
                widget=c.create_rectangle(elem[1],elem[2],elem[1]+elem[3],
                elem[2]+elem[4],fill=elem[5],outline='black',tag=elem[0])
            if elem[6]=='овал':
                widget=c.create_oval(elem[1],elem[2],elem[1]+elem[3],
                elem[2]+elem[4],fill=elem[5],outline='black',tag=elem[0])  
            
            c.tag_bind(widget,'<Button-1>',identifikacia)
    
    ramka=Frame(okno_sozd)
    ramka.grid(row=1,column=0)
    knopka_da=Button(ramka,text='Открыть')
    knopka_net=Button(ramka,text='Отменить')
    knopka_da.pack(side=LEFT)
    knopka_net.pack(side=RIGHT)
    
    knopka_da.bind('<Button-1>',obnovlen)
    knopka_net.bind('<Button-1>',zakritie)  
#сохранение параметров объектов в создаваемом файле
def _save_kak():
    okno_sozd=Tk()
    okno_sozd.title('')
    okno_sozd.geometry('+0+0')
    #okno_sozd.overrideredirect(1)
    vvod_text=Label(okno_sozd,text='Введите наименование файла')
    st=StringVar()
    pole=Entry(okno_sozd,textvariable=st)
    ramka=Frame(okno_sozd)
    vvod_text.pack()
    pole.pack()
    ramka.pack()
    knopka_da=Button(ramka,text='Сохранить')
    knopka_net=Button(ramka,text='Отменить')
    knopka_da.pack(side=LEFT)
    knopka_net.pack(side=RIGHT)
            
    def zakritie(event):
        okno_sozd.destroy()
        
    def obnovlen(event):
        name=pole.get()+'.db'
        #определяем имя файла для автоматического сохранения
        global name_save
        name_save=name
        okno_sozd.destroy()
        con = sqlite3.connect(name)
        cur = con.cursor()
        sql='''CREATE TABLE perechen(tag INTEGER PRIMARY KEY AUTOINCREMENT,
x INTEGER, y INTEGER, razmer1 INTEGER,razmer2 INTEGER,zvet TEXT,figura TEXT)'''
        cur.executescript(sql)
        cur.close()
        con.close()
        
        con=sqlite3.connect(name)
        cur=con.cursor()
        global spisok_widget
        for elem in spisok_widget:
            a=tuple(elem)           
            sql_a='INSERT INTO perechen VALUES(?,?,?,?,?,?,?)'
            cur.execute(sql_a,a)
            con.commit()
        cur.close()
        con.close()
        
    knopka_da.bind('<Button-1>',obnovlen)
    knopka_net.bind('<Button-1>',zakritie)  
        
#сохранение параметров объектов в последний используемый файл
def _save():
    if name_save!='':
        con=sqlite3.connect(name_save)
        cur=con.cursor()
        cur.execute('SELECT*FROM perechen')
        b=cur.fetchall()
        global spisok_widget
        for elem in spisok_widget:
            a=tuple(elem)
            n=0
            for elem in b:
                if a[0]==elem[0]:
                    n=1
            if n!=1:
                sql_a='INSERT INTO perechen VALUES(?,?,?,?,?,?,?)'
                cur.execute(sql_a,a)
            elif n==1:
                cur.execute('UPDATE perechen SET x=? WHERE tag=?',\
                            (a[1],a[0]))
                cur.execute('UPDATE perechen SET y=? WHERE tag=?',\
                            (a[2],a[0]))
                cur.execute('UPDATE perechen SET razmer1=? WHERE tag=?',\
                            (a[3],a[0]))
                cur.execute('UPDATE perechen SET razmer2=? WHERE tag=?',\
                            (a[4],a[0]))
                cur.execute('UPDATE perechen SET zvet=? WHERE tag=?',\
                            (a[5],a[0]))
                cur.execute('UPDATE perechen SET figura=? WHERE tag=?',\
                            (a[6],a[0]))
            con.commit()
        cur.close()
        con.close()
#создание функций меню                   
def close_win ():
    #def quit():
        if askyesno("Выход", "Желаете выйти"):
            root.destroy()
def new_proekt():
    if askyesno("Новый проект", "Желаете создать новый проект"):
        root.destroy()
        
            
def about():
    showinfo("О программе", '''Программа предоставляет возможность
спроектировать расстановку различных предметов(в том числе мебели)
в помещении''')   
#создание окна, холста, меню, виджетов для ввода информации
root=Tk()
root.title('Планировка помещения')
m = Menu(root)
root.config(menu=m)
fm = Menu(m)
m.add_cascade(label="Меню",menu=fm)
fm.add_command(label="Открыть",command=_open)
fm.add_command(label="Сохранить",command=_save)
fm.add_command(label="Сохранить как...",command=_save_kak)
fm.add_command(label="Новый проект",command=new_proekt)
fm.add_command(label="Выход",command=close_win)
hm = Menu(m)
m.add_cascade(label="Помощь",menu=hm)
hm.add_command(label="О программе",command=about)
#масштаб 1 пиксель - 1 см
c = Canvas(width=1000,height=700,bg='white')
ramka=Frame(root,width=200,height=700)
c.pack(side=LEFT)
ramka.pack(side=RIGHT)
c.focus_set()
nadp_predmet=Label(ramka,text='Выбор объекта')
nadp_predmet.pack()
spisok_predmet=ttk.Combobox(ramka,height=5,values=['комната','окно','дверь',
                                               'мебель','другой предмет'])
forma_predmet=ttk.Combobox(ramka,height=2,values=['прямоугольник','овал'])
spisok_predmet.pack()
forma_predmet.pack()
spisok_predmet.set('комната')
forma_predmet.set('прямоугольник')
nadp_predmet=Label(ramka,text='Размеры объекта')
nadp_predmet.pack()
ramka_razmer=Frame(ramka)
ramka_razmer.pack()
dk=IntVar()
razmer1_komnata=Entry(ramka_razmer,width=4,textvariable=dk)
nadp_razmer1=Label(ramka_razmer,text='см',width=2)
sk=IntVar()
razmer2_komnata=Entry(ramka_razmer,width=4,textvariable=sk)
nadp_razmer2=Label(ramka_razmer,text='см',width=2)
knopka_vvoda=Button(ramka,text='Ввод данных')
knopka_vvoda.pack()
razmer1_komnata.grid(row=0,column=0)
nadp_razmer1.grid(row=0,column=1)
razmer2_komnata.grid(row=0,column=2)
nadp_razmer2.grid(row=0,column=3)
def obnovl_predmet(e=None):
    global tag
    global zvet
    global figura
    global spisok_widget
    predmet=spisok_predmet.get()
    figura=forma_predmet.get()
    if predmet=='комната':
        tag=0;zvet='white'
    else:
        tag=spisok_widget[-1][0]+1
        if predmet=='окно':
            zvet='gray80'
        elif predmet=='дверь' or predmet=='другой предмет':
            zvet='black'
        elif predmet=='мебель':
            zvet='gray'
        
spisok_predmet.bind('<<ComboboxSelected>>', obnovl_predmet)
forma_predmet.bind('<<ComboboxSelected>>', obnovl_predmet)        
#создание объектов
x=20
y=20
def dannie_komnata(event):
    global tag
    global zvet
    global figura
    global spisok_widget
    razmer1=float(razmer1_komnata.get())
    razmer2=float(razmer2_komnata.get())
    if figura=='прямоугольник': 
        widget_komnata=c.create_rectangle(x,y,x+razmer1,y+razmer2,
                               fill=zvet,outline='black',tag=tag)
    elif figura=='овал':
        widget_komnata=c.create_oval(x,y,x+razmer1,y+razmer2,
                               fill=zvet,outline='black',tag=tag)
    
    elem_spisok_widget=[tag,x,y,razmer1,razmer2,zvet,figura]
    spisok_widget.append(elem_spisok_widget)
    c.tag_bind(widget_komnata,'<Button-1>',identifikacia)
    
knopka_vvoda.bind('<Button-1>',dannie_komnata)
#создание функций движения объектов
def sdvig_vverch(event):
    global tag
    global spisok_widget
    c.move(tag,0,-2)
    for elem in spisok_widget:
        if elem[0]==tag:
            elem[2]=elem[2]-2
    c.update()
    sleep(0.02)
def sdvig_vniz(event):
    global tag
    global spisok_widget
    c.move(tag,0,2)
    for elem in spisok_widget:
        if elem[0]==tag:
            elem[2]=elem[2]+2
    c.update()
    sleep(0.02)
def sdvig_vpravo(event):
    global tag
    global spisok_widget
    c.move(tag,2,0)
    for elem in spisok_widget:
        if elem[0]==tag:
            elem[1]=elem[1]+2
    c.update()
    sleep(0.02)
def sdvig_vlevo(event):
    global tag
    global spisok_widget
    c.move(tag,-2,0)
    for elem in spisok_widget:
        if elem[0]==tag:
            elem[1]=elem[1]-2
    c.update()
    sleep(0.02)
#функция изменения положения фигуры с горизонтального в вертикальное и обратно
def izmen_polozhenie(event):
    global tag
    global spisok_widget
    c.delete(tag)
    for elem in spisok_widget:
        if elem[0]==tag:
            elem[3],elem[4]=elem[4],elem[3]
        if elem[6]=='прямоугольник':
            widget=c.create_rectangle(elem[1],elem[2],elem[1]+elem[3],
            elem[2]+elem[4],fill=elem[5],outline='black',tag=elem[0])   
        elif elem[6]=='овал':
            widget=c.create_oval(elem[1],elem[2],elem[1]+elem[3],
            elem[2]+elem[4],fill=elem[5],outline='black',tag=elem[0]) 
#идентификация виджета по тегу                
def identifikacia(event):
    c.focus_set()
    global tag
    item=c.find_closest(event.x,event.y)
    tag=int(c.gettags(item)[0])
    
c.bind('<Up>',sdvig_vverch)
c.bind('<Down>',sdvig_vniz)
c.bind('<Right>',sdvig_vpravo)
c.bind('<Left>',sdvig_vlevo)
c.bind('s',izmen_polozhenie)
  
root.mainloop()

FishHook
Konstantin1984+
В чем здесь проблема?
Вашим кодом можно восхищаться бесконечно, но мне кажется одна мааааленькая проблемка у вас налицо - декомпозиция. У вас есть проблемы очень базовые, например “После активации объекта двигаться начинает не он, а объект, созданный перед ним”. Это значит, что у вас нет понимания принципиальной логики работы программы. Вы нафигачили 350 строк кода - тут у вас и запросы к БД и менюшки какие-то, вот это всё - второстепенное, это надстройка над базовым функционалом. Если у вас есть список неких объектов, то вовсе не имеет значения откуда этот список взялся - из БД или вы его захардкодили. Сначала научитесь работать с одним объектом. Потом со списком объектов. Вот уже потом можно придумать хоть сколько способов заполнять этот список. Программа пишется ровно так же. Сначала вы делаете простейший рабочий прототип. Потом постепенно вносите улучшения. Смысл вашей программы, насколько я понял, заставить предсказуемо двигаться некие объекты. Если вам не удалось этого достичь - зачем нужны все тонны остального бреда?

Никому не интересно разбираться в ваших, простите, каракулях. Дайте минимальный пример вашей проблемы, декомпозируйте задачу, уберите всё ненужное и неважное. Тогда, вероятно, вам и самому будет проще разобраться.
Konstantin1984+
Вот урезанная версия, минимум кода, чтобы проверить программу.

 from tkinter import *
from time import sleep
#список с параметрами виджетов
spisok_widget=[]
#создание окна, холста, меню, виджетов для ввода информации
root=Tk()
root.title('Движение объектов')
c=Canvas(width=1000,height=700,bg='white')
knopka_vvoda=Button(root,text='Создать фигуру')
c.pack(side=LEFT)
knopka_vvoda.pack(side=RIGHT)
      
#создание объектов
x=20
y=20
def dannie_komnata(event):
    global tag
    global spisok_widget
    if len(spisok_widget)==0:
        tag=0
    else:
        tag=spisok_widget[-1][0]+1
    razmer1=400
    razmer2=100
    widget_komnata=c.create_rectangle(x,y,x+razmer1,y+razmer2,
                                      fill='gray',outline='black',tag=tag)
    
    elem_spisok_widget=[tag,x,y,razmer1,razmer2]
    spisok_widget.append(elem_spisok_widget)
    c.tag_bind(widget_komnata,'<Button-1>',identifikacia)
knopka_vvoda.bind('<Button-1>',dannie_komnata)
#идентификация виджета по тегу                
def identifikacia(event):
    c.focus_set()
    global tag
    item=c.find_closest(event.x,event.y)
    tag=int(c.gettags(item)[0])
#создание функций движения объектов
def sdvig_vverch(event):
    global tag
    global spisok_widget
    c.move(tag,0,-2)
    for elem in spisok_widget:
        if elem[0]==tag:
            elem[2]=elem[2]-2
    c.update()
    sleep(0.02)
def sdvig_vniz(event):
    global tag
    global spisok_widget
    c.move(tag,0,2)
    for elem in spisok_widget:
        if elem[0]==tag:
            elem[2]=elem[2]+2
    c.update()
    sleep(0.02)
def sdvig_vpravo(event):
    global tag
    global spisok_widget
    c.move(tag,2,0)
    for elem in spisok_widget:
        if elem[0]==tag:
            elem[1]=elem[1]+2
    c.update()
    sleep(0.02)
def sdvig_vlevo(event):
    global tag
    global spisok_widget
    c.move(tag,-2,0)
    for elem in spisok_widget:
        if elem[0]==tag:
            elem[1]=elem[1]-2
    c.update()
    sleep(0.02)
#функция изменения положения фигуры с горизонтального в вертикальное и обратно
def izmen_polozhenie(event):
    global tag
    global spisok_widget
    for elem in spisok_widget:
        if elem[0]==tag:
            elem[3],elem[4]=elem[4],elem[3]
            c.coords(tag,elem[1],elem[2],elem[1]+elem[3],elem[2]+elem[4])  
    
c.bind('<Up>',sdvig_vverch)
c.bind('<Down>',sdvig_vniz)
c.bind('<Right>',sdvig_vpravo)
c.bind('<Left>',sdvig_vlevo)
c.bind('s',izmen_polozhenie)
root.mainloop()

Самое интересное, что при замене в методах “c.move” и “с.coords” аргумента “tag” на “tag+1” программа начинает работать.
rami
Konstantin1984+
Вот урезанная версия, минимум кода, чтобы проверить программу.
Минимум кода это как-то так:
 from tkinter import Tk, Variable, Canvas, Button
from time import sleep
 
 
STEP = 15      #Шаг перемещения фигур (можно изменить)
DIRECTION = {'Down': (0, STEP), 'Left': (-STEP, 0), 'Right': (STEP, 0), 'Up': (0, -STEP)}  #Не изменять!!!
X, Y, W, H = 70, 20, 400, 100      #Начальное положение и размер новых фигур
 
 
def dannie_komnata():
    '''Создание новой фигуры.'''
    item = c.create_rectangle(X, Y, X+W, Y+H,  fill='gray', outline='black')
    c.tag_bind(item, '<Button-1>', identifikacia)
 
def identifikacia(event):
    '''Идентификация выбранного виджета.'''
    c.focus_set()
    item = c.find_closest(event.x, event.y)
    item_selected.set(item[0])
 
def sdvig(e):
    '''Нажатие на клавиши со стрелками перемещает выбранную фигуру в направлении стрелки.'''
    c.move(item_selected.get(), *DIRECTION.get(e.keysym, (0, 0)))
 
def izmen_polozhenie(event):
    '''Функция изменения положения фигуры с горизонтального в вертикальное и обратно.'''
    x1, y1, x2, y2 = c.coords(item_selected.get())
    c.coords(item_selected.get(), x1, y1, y2-y1+x1, x2-x1+y1)
 
 
#создание окна,  холста,  меню,  виджетов для ввода информации
root = Tk()
root.title('Движение объектов')
item_selected = Variable()
c = Canvas(root, width=1000, height=600, bg='white')
c.pack(side='left')
c.bind('<Key>', sdvig)
c.bind('s', izmen_polozhenie)
Button(root, text='Создать фигуру', command=dannie_komnata).pack(side='right')
 
root.mainloop()
PEHDOM
Konstantin1984+ вы упорно отказываетесь читать документацию?

coords(item, *coords)

Returns the coordinates for an item.

item
Item specifier (tag or id).
….
The tags option takes either a single tag string, or a tuple of strings.

https://effbot.org/tkinterbook/canvas.htm
Как по вашему программа должна отличить tag от id, если и то и то у вас int?

попробуйте для разнообразия сделать tag строкой хотябы из одной буквы, и вас приятно удивит результат.
Konstantin1984+
Самое интересное, что при замене в методах “c.move” и “с.coords” аргумента “tag” на “tag+1” программа начинает работать.
естественно, у вас ИД обьекта 1, а тег 0, предполагаю что обьект с ИД 0 это сам канвас(или ХЗ что еще), вот оно и берет что первое находит, а именно обьект с ИД=0, а когда вы делаете tag+1 то оно ищет обьект с ИД =1 ну и находит вашу фигуру.
Konstantin1984+
Всем спасибо!
Konstantin1984+
Уважаемый PEHDOM, я не очень понял фразу про отличия tag от id.
id - это ведь идентификатор? А идентификатор в моем случае - это целое выражение “c.create_rectangle(x,y,x+razmer1,y+razmer2, fill='gray',outline='black',tag=tag)”,
а tag принимает конкретное цифровое значение. Какая тут может быть путаница между id и tag?
PEHDOM
Konstantin1984+
Уважаемый PEHDOM, я не очень понял фразу про отличия tag от id.
id - это ведь идентификатор? А идентификатор в моем случае - это целое выражение “c.create_rectangle(x,y,x+razmer1,y+razmer2, fill='gray',outline='black',tag=tag)”.
Черт, я даже не знаю что вам сказать. Все дело в том что программа работает так как запрограмировали прграмисты а не так как вы хотите чтобы она рабтала. И идентификатор в данном случае это то что решили програмисты ткинтера, а не вы. Обратимся к документации, которую вы почемуто упорно игнорируете:
create_rectangle(bbox, **options)

Draws a rectangle on the canvas.

….
Returns:
The item id.

когда вы пишете :“c.create_rectangle(x,y,x+razmer1,y+razmer2, fill='gray',outline='black',tag=tag)” программа создает некий обьект, помещает его на холст и возвращает его id. Заметте, не вы ей говорите какой id назначить, а программа сама выбирает по заложеному алгоритму и говорит вам:“ вот я создала прямоугольник , с id=ХХХ” Конктетно у ткинтер.канвас это просто целое число, начиная с 1 и заканчивая дохрена дохрельонов.
Konstantin1984+
tag принимает конкретное цифровое значение. Какая тут может быть путаница между id и tag?
tag же это просто метка , вы можете назначить один и тот же тег нескольким обьектам, или у одного объекта может быть несколько тегов.

А теперь у нас есть метод coords() котороый в качестве аргумента принимает id или tag. но что будет если у вас tag и id просто целые числа? Как вообще понять что вы ввели tag или id? Какой из обектов должна выбрат программа, с id=1 или с tag=1?. Вот програмисты ткинтера и решили что если вы скрмливаете число то это id и программа выберет объект с этим id, а если аргументом выступает строка то это тег и нужно выбрать все объекты с заданым тегом.


FishHook
Konstantin1984+
Какая тут может быть путаница между id и tag?
У вас есть имя и фамилия, а есть номер паспорта. Разница существенна.
Konstantin1984+
Спасибо! Что-то я упустил при прочтении материалов.
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB