1

Thus far I have:

  1. Repurposed code from Drag and drop image object to allow multiple custom widgets to be draggable.

  2. Repurposing the code has gone well with the exception of attempting to enforce draggable widgets to only move within the canvas dimensions.

  3. If a single widget is added to the canvas the widget is draggable and bound to the canvas dimensions.

  4. The Problem: If any number of widgets greater than one are added to the canvas, then only the last widget added is draggable and bound to the canvas dimensions. All other widgets fail to move due to logic in the mouse motion callback where the detected mouse motion is unusually large.

  5. The draggable widgets are object-oriented, thus the logic for making a single draggable widget is identical to multiple other instantiations of the same widget type.

  6. The manipulated code that I have thus far is:

import os
import tkinter as tk

APP_TITLE = "Drag & Drop Tk Canvas Images"
APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 300
APP_HEIGHT = 200

class CreateCanvasObject(object):    

    def __init__(self, canvas, block_name):
        self.canvas = canvas

        self.block_width = 250
        self.block_height = 250

        self.name = block_name

        self.max_speed = 30

        self.win_width = self.canvas.winfo_width()
        self.win_height = self.canvas.winfo_height()

        self.block_main = tk.Frame(self.canvas, bd=10, relief=tk.RAISED)
        self.block_main.pack(side=tk.TOP, fill=tk.BOTH, expand=False, padx=0, pady=0)

        self.block_name = tk.Label(self.block_main, text=block_name, anchor=tk.CENTER, font='Helvetica 10 bold', cursor='fleur')
        self.block_name.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        self.image_obj = self.canvas.create_window( (0, 0), window=self.block_main, anchor="nw", width=self.block_width, height=self.block_height)

        self.block_name.bind( '<Button1-Motion>', self.move)
        self.block_name.bind( '<ButtonRelease-1>', self.release)
        self.move_flag = False    

        self.canvas.bind("<Configure>", self.configure)


    def configure(self, event):
        self.win_width = event.width
        self.win_height = event.height        


    def move(self, event):

        print('Moving [%s]'%self.name)
        print(event)

        if self.move_flag:

            dx, dy = event.x, event.y

            abs_coord_x, abs_coord_y = self.canvas.coords( self.image_obj )

            cond_1 = abs_coord_x + dx >= 0
            cond_2 = abs_coord_y + dy >= 0
            cond_3 = (abs_coord_x + self.block_width + dx) <= self.win_width
            cond_4 = (abs_coord_y + self.block_height + dy) <= self.win_height

            print('abs_coord_x = %3.2f ; abs_coord_y = %3.2f'%(abs_coord_x, abs_coord_y))
            print('dx = %3.2f ; dy = %3.2f'%(dx, dy))
            print('self.block_width = %3.2f'%(self.block_width))
            print('self.block_height = %3.2f'%(self.block_height))
            print('Cond 1 = %s; Cond 2 = %s; Cond 3 = %s; Cond 4 = %s\n\n'%(cond_1, cond_2, cond_3, cond_4))

            if cond_1 and cond_2 and cond_3 and cond_4:
                self.canvas.move(self.image_obj, dx, dy)

        else:
            self.move_flag = True
            self.canvas.tag_raise(self.image_obj)


    def release(self, event):
        self.move_flag = False



class Application(tk.Frame):

    def __init__(self, master):
        self.master = master
        self.master.protocol("WM_DELETE_WINDOW", self.close)
        tk.Frame.__init__(self, master)

        self.canvas = tk.Canvas(self, width=800, height=800, bg='steelblue', highlightthickness=0)
        self.canvas.pack(fill=tk.BOTH, expand=True)

        self.block_1 = CreateCanvasObject(canvas=self.canvas, block_name='Thing 1')
        self.block_2 = CreateCanvasObject(canvas=self.canvas, block_name='Thing 2')

    def close(self):
        print("Application-Shutdown")
        self.master.destroy()

def main():
    app_win = tk.Tk()
    app_win.title(APP_TITLE)
    app_win.geometry("+{}+{}".format(APP_XPOS, APP_YPOS))

    Application(app_win).pack(fill='both', expand=True)

    app_win.mainloop()


if __name__ == '__main__':
    main()
Tony A
  • 98
  • 1
  • 6
  • 1
    I recommend that you refactor the example so that it doesn't require images. You can drag rectangles around just as easily as images, and rectangles make it easier for us to run your code. Also, there seems to be an awful lot of frames. If the question is about dragging items on a canvas, all we need is the canvas. – Bryan Oakley May 28 '20 at 21:02
  • See this answer for an example of dragging one of several items on a canvas: https://stackoverflow.com/a/6789351/7432 – Bryan Oakley May 28 '20 at 21:04
  • Please simplify your code and make it a [mre] that others can easily run. – martineau May 28 '20 at 21:20
  • To all, as requested, updates have been made to remove dependencies on images and to be more a kin to a minimal reproducible example. – Tony A May 28 '20 at 21:40

1 Answers1

1

The root of the problem is this line of code:

self.canvas.bind("<Configure>", self.configure)

Whenever you call self.canvas.bind, you replace any previous binding on the widget. Thus, when the binding is triggered, only the last CreateCanvasObject will see the event because it will have replaced the bindings created by previous CreateCanvasObject objects.

Because of that, you only ever update self.win_width and self.win_height of the last object created. And because you use those values to compute self.cond3 and self.cond4, those conditions are always false and thus the object never moves.

A simple solution is to remove the binding and remove the configure method, and instead compute the width and height inside your move function. That, or make self.win_width and self.win_height class variables so when you update one you update all. Those aren't the only ways to solve the problem, but they are the simplest.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Genius, thanks! I went the route of setting self.win_width and self.win_height inside of the move function via self.canvas.winfo_reqwidth() and self.canvas.winfo_reqheight(). Everything is working beautifully now. – Tony A May 29 '20 at 00:39
  • Bryan, in your first comment to this post, you made a comment about an awful lot of frames. Surely, it's a bad habit and something I do regularly to control layout of widgets. Admittedly, I really don't know any better and am just comfortable with the options. Would you suggest some better method for layout management. – Tony A May 29 '20 at 00:43
  • @TonyA: there's nothing wrong with using a lot of frames. They exist precisely to make layout easier. The point is, they were unnecessary to reproduce the problem in this question. The examples in questions should be as short as possible. – Bryan Oakley May 29 '20 at 01:06