2

I am currently trying to build my first GUI application through python's standard Tkinter. I soon came to grips with the computers coordinate system, and indeed I found I could pan things out as I so wish, but I came to the realisation that a drag and drop feature would be far superior then specifying coordinates explicitly. I am close, but I have one major problem; Whilst I can keep the value of the coords of a single widget in relation to where I dragged it last, I cannot do this for multiple widgets.

This is the code I have created so far:

from tkinter import *

root = Tk()


class Move_Shape:
    data = {'x': 0, 'y': 0}
    canvas = Canvas(width = root.winfo_screenwidth(), height = root.winfo_screenheight())
    shape_coords = open('Shape_coords.py', 'r')



    def __init__(self, shape, fill = 'White', *coords):

        new_coords = self.shape_coords.readline().split(',')

        if coords == (): coords = new_coords

        if shape == 'line': 
            tag = 'line'
            self.id = self.canvas.create_line(coords, tags = tag, fill = fill)

        elif shape == 'rectangle': 
            tag = 'rect'
            self.id = self.canvas.create_rectangle(coords, tags = tag, fill = fill)

        ... More code

        self.canvas.tag_bind(tag, '<Button-1>', self.click)
        self.canvas.tag_bind(tag, '<Button1-Motion>', self.track)
        self.canvas.tag_bind(tag, '<ButtonRelease-1>', self.release)
        self.canvas.grid()


    def click(self, event):
        self.data.update({'x': event.x, 'y': event.y})
        self.item = self.canvas.find_closest(self.data['x'], self.data['y'])

    def track(self, event):
        x, y = event.x - self.data['x'], event.y - self.data['y']
        self.canvas.move(self.item, x, y)
        self.data.update({'x': event.x, 'y': event.y})

    def release(self, event):
        self.data.update({'x': event.x, 'y': event.y})
        coords = str(self.canvas.coords(self.item))
        coords = coords[1:-1]
        shape_coords = open ('Shape_coords.py', 'a')
        shape_coords.write(coords)
        shape_coords.write('\n')
        shape_coords.close()



Move_Shape('rectangle', 'blue', 50, 50, 100, 100)
Move_Shape( 'oval', 'green', 50, 50, 100, 100)
Move_Shape( 'arc', 'red', 50, 50, 100, 100)
mainloop()

If I was to start with an initial pair of coords, I would very much like to be able to delete the coords and pick up where I left of, or rather, where the shape left of. Appending the coordinates to a file does not work, the main reason being that I cannot return the final value of an updated dictionary, after exiting the mainloop.

I did some research before hand, and looked into data persistence. So I came across the Module, pickle, for the first time. Through others examples online, I managed to 'dump' the values into another file, however, if some variable, call it a, changes multiple times, those values are all appended within the file (which leads us back to square one). I would like to know if there is a way to make it so that only the last value assigned to a object through a variable, is stored.

I would go through the pickle module myself, but it's terminology overwhelms me, and I do not know what to look up in specific when it comes to data persistence.

martineau
  • 119,623
  • 25
  • 170
  • 301
Jim Jam
  • 713
  • 4
  • 10
  • 22

1 Answers1

1

I am the OP(internet slang for original poster), and I believe I have found an answer to my question. This just serves for those whom may run into a similar situation, not to mention that others can provide better solutions, for am sure they exist, but this is one none the less.

So, my initial solution was to store the values in a dictionary, and then append that to a file to read back, however there was no way of obtaining the final value, that is, I had to update the dictionary and append it to the file, however I could not obtain the final value.

After that, I looked into data persistence, where you assign a value to a variable, which refers to an object, however, if I assigned a value to variable 'foobar', and 'pickle' it, (erm... writing it to a file, but in bytes), and then assign another value to 'foobar', you end up storing both of those values, rather than storing one constant value for the same object.

What I did then, was to combine these two approaches, of updating a dictionary, and pickling objects. I pickled a dictionary to which I could update, and since it was referring to the same object, then only one value was binded to the dictionary. I don't know why this does not work for mutable sequences, but I suppose that whilst the variable stays the same, it points to multiple objects, though I may be wrong, so if someone could add some clarification that would be much appreciated.

So now everything works, and I can move items on the canvas/widgets to my hearts desire.

Remember, if you need to write to a file where you only want to store a single value for a given object, whose value in dependent on time, use and read up an pickle, here is the link:

https://docs.python.org/3.4/library/pickle.html

Here are exapmles of pickle in action:

This one in particular describes to how save a dict using pickle:

How can I use pickle to save a dict?

Good wiki reference: https://wiki.python.org/moin/UsingPickle

Here is the latest source code, you may adapt it as you see fit, please note, the indentation may be wrong, because pasting this into here did some weird things with the indentation levels, but am sure you can handle that:

from tkinter import *
import pickle
import os
import __main__


class Drag_Shape:
    filename = os.path.basename(__main__.__file__)
    filename = filename[:filename.index('.')]+'_coords.py'
    print(__main__.__file__)


    if os.path.isfile(filename) == False:
        foorbar = open(filename, 'w')
        foorbar.close()

    if os.path.getsize(filename) == 0: coords_dict = {}
    else: 
        with open(filename, 'rb') as shape_cords:
            coords_dict = pickle.load(shape_cords)
    data = {'x': 0, 'y': 0}    

    def __init__(self, canvas, *coords, shape = 'rect', fill = 'white', outline = 'black', width = 1, activefill = '', 
        activeoutline = 'black', activewidth = 1, disabledfill = '', disabledoutline = 'black', disabledwidth = 1,
        state = ''):

        self.canvas = canvas
        self.shape = shape.lower()
        print(shape)
        print(coords)
        for i in self.coords_dict.keys():
            if shape.lower() in i: shape = i.lower()

        if coords != (): self.coords_dict.update({shape:coords})
        else: coords = self.coords_dict[shape]

        if shape in 'line': 
            tag = 'line'
            ID = canvas.create_line(coords, tags = tag, fill = fill, width = width,
            activefill = activefill, activewidth = activewidth, disabledfill = disabledfill,
            disabledwidth = disabledwidth, state = '')

        elif shape in 'rectangle': 
            tag = 'rect'
            ID = canvas.create_rectangle(coords, tags = tag, fill = fill, outline = outline, width = width,
            activefill = activefill, activeoutline = activeoutline, activewidth = activewidth, disabledfill = disabledfill,
            disabledoutline = disabledoutline, disabledwidth = disabledwidth, state = '')

        elif shape in 'oval': 
            tag = 'oval'
            ID = canvas.create_oval(coords, tags = tag, fill = fill, outline = outline, width = width,
            activefill = activefill, activeoutline = activeoutline, activewidth = activewidth, disabledfill = disabledfill,
            disabledoutline = disabledoutline, disabledwidth = disabledwidth, state = '')

        elif shape in 'arc':
            tag = 'arc'
            ID = canvas.create_arc(coords, tags = tag, fill = fill, outline = outline, width = width,
            activefill = activefill, activeoutline = activeoutline, activewidth = activewidth, disabledfill = disabledfill,
            disabledoutline = disabledoutline, disabledwidth = disabledwidth, state = '')

        elif shape in 'polygon': 
            tag = 'poly'
            ID = canvas.create_polygon(coords, tags = tag, fill = fill, outline = outline, width = width,
            activefill = activefill, activeoutline = activeoutline, activewidth = activewidth, disabledfill = disabledfill,
            disabledoutline = disabledoutline, disabledwidth = disabledwidth, state = '')

        elif shape in 'window': 
            tag = 'win'
            ID = canvas.create_window(coords, tags = tag, fill = fill)

        elif shape in 'text':
            tag = 'text'
            ID = canvas.create_text(coords, tags = tag, fill = fill)

        elif shape in 'image':
            tag = 'img'
            ID = canvas.create_image(coords, tags = tag, fill = fill)

        elif shape in 'bitmap': 
            tag = 'bitmap'
            ID = canvas.create_bitmap(coords, tags = tag, fill = fill)

        self.ID = ID
        self.tag = tag

        with open(self.filename, 'wb') as shape_coords:
            pickle.dump(self.coords_dict, shape_coords)

        canvas.tag_bind(tag, '<Button-1>', self.click)
        canvas.tag_bind(tag, '<Button1-Motion>', self.track)
        canvas.tag_bind(tag, '<ButtonRelease-1>', self.release)

    def click(self, event):
        self.data.update({'x': event.x, 'y': event.y})
        self.item = self.canvas.find_closest(self.data['x'], self.data['y'])
        return self.item

    def track(self, event):
        x, y = event.x - self.data['x'], event.y - self.data['y']
        self.canvas.move(self.item, x, y)
        self.data.update({'x': event.x, 'y': event.y})

    def release(self, event):
        self.data.update({'x': event.x, 'y': event.y})
        coords = list(self.canvas.coords(self.item))
        self.coords_dict.update({self.shape : coords})
        with open(self.filename, 'wb') as shape_coords:
            pickle.dump(self.coords_dict, shape_coords)
        return self.ID

Three important things:

  1. You must specify the coords with a list, or possibly a tuple or other containers (though I have only tested on lists)

  2. If you want to actually save the location of the shape on the canvas, delete the original coords that was used to create the shape, otherwise it will just reset back to it's original position, and so it should.

  3. A file is automagically created when you use the class outside of the file that it belong too. If the file is called 'foorbar.py', than a file called 'foorbar.coords.py', is created in the same folder. If you touch this in anyway, well just don't, it will mess things up.

  4. I know I said three, but am going to push this 'community wiki' sign and see what it does, sorry if this has an undesired effect.

Community
  • 1
  • 1
Jim Jam
  • 713
  • 4
  • 10
  • 22