Home > Back-end >  Set number of clickable and draggable objects
Set number of clickable and draggable objects

Time:01-13

I'm wanting to click two spots on an image/window, draw a horizontal line for each, both of which are movable independently, and then use the two points/lines to get their Y position.

The way I'm thinking about this is currently to just draw two lines that are draggable during creation. Once both lines are drawn no more lines should be drawable. It should also be possible to click one line or the other to drag and modify its location. Then I simply get the y coordinate off each one using their tag name once a confirm button is pressed.

So far I can get lines draggable on creation, but not afterward.

lines = []

def click(e):
    lines.append(canvas.create_line(0, e.y, width, e.y))

def drag(e):
    canvas.coords(lines[-1], 0, e.y, width, e.y,)

canvas.bind("<ButtonPress-1>", click)
canvas.bind("<B1-Motion>", drag)

This allows you to create and drag as many lines as you want and none are moveable after creation. I was playing around with keeping track of the index and only using index 0, 1, but couldn't get that to work out.

I can also make it where premade lines with tags are draggable with this

canvas.create_line(0, 150, width, 150, tag="first_line")
canvas.create_line(0, 300, width, 300, tag="second_line")


def find_shape(e):
    item = canvas.find_closest(e.x, e.y)

    tags = canvas.gettags(item)
    print(tags[0])
    canvas.bind("<B1-Motion>", lambda event, arg=tags[0]: drag(event, arg))

def drag(e, tag):

    canvas.coords(tag, 0, e.y, width, e.y)


canvas.bind("<ButtonPress-1>", find_shape)

def print_y():
    print(f"line1 y: {canvas.coords(canvas.find_withtag('first_line'))[1]}")

button = tk.Button(text="print tag y coord", width=30, height=3,
                command=print_y)
button.pack()

My big problem comes with trying to combine them. I'm not sure how to keep track of if the first line has been created or not, and how to avoid the user from creating more than 2 lines. Also dealing with the overlapping <ButtonPress-1> prompts is confusing me a bit.

CodePudding user response:

This code uses canvas.move for nice object movement control.

The select function enables line drawing via key-d and limits total number of objects.

The work function performs line drawing and object movement.

In draw mode, mouse key 1 will draw line segments while mouse key 3 will finish drawing.

In movement mode, mouse key 1 will cut canvas object (pick up) and mouse key 3 will paste object.

I've inserted remarks into the code that help explain the various actions.

import tkinter as tk

root = tk.Tk()

labelframe = tk.LabelFrame(root, labelanchor = tk.S, text = '0|0')
labelframe.grid(sticky = tk.NSEW)
canvas = tk.Canvas(labelframe, width = 640, height = 480)
canvas.grid(sticky = tk.NSEW)
# Variables
item = xd = yd = 0
# Preset to draw
flag = True

def select(event):
    global flag
    # limit number of canvas objects or allow object editing
    flag = (len(canvas.find_all()) < 2 or item)

def work(event):
    global item, xd, yd, flag
    # capture mouse pointer position in canvas
    xc, yc = canvas.canvasx(event.x), canvas.canvasx(event.y)
    if not flag:
        # 5 = mouse button release
        if int(event.type) == 5: 
            # cut and move object
            if event.num == 1:
                item = canvas.find_closest(xc, yc)
                if item:
                    item = item[0]
                else:
                    item = 0
            # paste object
            elif event.num == 3:
                item = 0
        if item:
            # Use previous and current mouse positions to move item
            canvas.move(item, xc - xd, yc - yd)
    elif item:
        # draw line
        bbox = canvas.coords(item)
        if int(event.type) == 5:
            # mouse button 1 release
            if event.num == 1:
                bbox.extend([xc, yc])
                canvas.coords(item, *bbox)
            # line drawing complete
            elif event.num == 3:
                flag = False
        else:
            # mouse movement controls line display
            canvas.coords(item, bbox[:~1]   [xc, yc])
    # Make new line object
    elif int(event.type) == 5:
        if event.num == 1:
            item = canvas.create_line(
                xd, yd, xc, yc, fill = 'black', tag = 'piece')

    labelframe['text'] = f'{xc} | {yc}'
    # keep track of previous and current mouse positions
    xd, yd = xc, yc

# Press 'd' key to Draw
canvas.bind('<KeyRelease-d>', select)
# handle many inputs via event_add
canvas.event_add('<<PICK>>', '<Motion>', '<ButtonPress>', '<ButtonRelease>')
canvas.bind('<<PICK>>', work)
# make sure canvas has the focus
canvas.focus_force()
root.mainloop()

CodePudding user response:

You can check the size of lines to determine whether new line should be created. Also use a global variable selected to refer to the current selected line:

lines = []
selected = None  # item ID of selected line

def click(e):
    global selected
    if len(lines) < 2:
        # create new line
        selected = canvas.create_line(0, e.y, width, e.y)
        lines.append(selected)
    else:
        # check whether a line is under current mouse position
        selected = canvas.find_withtag(tk.CURRENT)

def drag(e):
    if selected:
        canvas.coords(selected, 0, e.y, width, e.y)
  •  Tags:  
  • Related