-1

I am using Tkinter to import images with Openslide. I would like to integrate a manual annotation module into my program like this:

class ResizableCanvas(Canvas):

    def __init__(self, parent, **kwargs):
        Canvas.__init__(self, parent, **kwargs)
        self.bind("<Configure>", self.on_resize)
        self.height = self.winfo_reqheight()
        self.width = self.winfo_reqwidth()

    def on_resize(self, event):
        wscale = float(event.width) / self.width
        hscale = float(event.height) / self.height
        self.width = event.width
        self.height = event.height
        self.config(width=self.width, height=self.height)


    class ViewerTab:

    def __init__(self, master, model, dim=800):

        self.sideFrame = ttk.Frame(self.master, width=100)


        self.coords = {"x":0,"y":0,"x2":0,"y2":0}

        self.lines = []
    
    def click(self):

        self.coords["x"] = self.x
        self.coords["y"] = self.y
    
        self.lines.append(self.canvas.create_line(self.coords["x"],self.coords["y"],self.coords["x"],self.coords["y"]))
    
    def drag(self):
        # update the coordinates from the event
        self.coords["x2"] = self.x
        self.coords["y2"] = self.y
    

        self.canvas.coords(self.lines[-1], self.coords["x"],self.coords["y"],self.coords["x2"],self.coords["y2"])
    
        #self.canvas.bind("<Button-1>", self.dirbutton)
        #self.canvas.bind("<B1-Motion>", self.move)
        self.canvas.bind("<ButtonRelease-1>", self.nomove)
        self.canvas.bind("<Button-2>", self.get_position)
        self.canvas.bind("<ButtonPress-1>", self.click)
        self.canvas.bind("<B1-Motion>", self.drag) 
M-Chen-3
  • 2,036
  • 5
  • 13
  • 34
Turing
  • 13
  • 7
  • 1
    Please be more specific about what you want to do: is it to allow the user to draw lines on top of an image? What have you tried? I see only code about creating the image, not about adding annotations. It would also be more helpful to have a [Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example) instead of a piece of code we cannot understand without looking into your github repository. – j_4321 Jan 05 '21 at 09:56
  • If you want to draw lines on a Canvas with the mouse, there are already some posts on SO that should help you: https://stackoverflow.com/questions/50126720/how-do-i-make-lines-by-clicking-dragging-and-releasing-the-mouse-on-tkinter, https://stackoverflow.com/questions/47996285/how-to-draw-a-line-following-your-mouse-coordinates-with-tkinter, https://stackoverflow.com/questions/40877429/drawing-straight-lines-on-a-tkinter-canvas-with-grid-on-python-with-mouse – j_4321 Jan 05 '21 at 10:03
  • Thank you for your reply. I have seen these posts. My problem is that I already have this in my code: # canvas bind events self.canvas.bind ("", self.dirbutton) self.canvas.bind ("", self.move) self.canvas.bind ("", self.nomove) self.canvas.bind ("", self.get_position) – Turing Jan 05 '21 at 13:44
  • One solution is to use Radiobuttons to make the user choose a mode, e.g. "Pan" and "Annotate". Or you can use different bindings, e.g. to pan, the user needs to press Ctrl at the same time. – j_4321 Jan 05 '21 at 14:01

1 Answers1

0

So if I got it right from the comments, the issue is to be able to both pan the slide and draw on it using binding to mouse clicks and motion. There are several way to do that, for instance:

  1. Use radiobuttons so that the user selects the "mode": either pan or annotate. Here is a small example based on https://stackoverflow.com/a/50129744/6415268 for the drawing part. The click() and drag() functions do different actions depending on the selected mode (stored a the StringVar).

     import tkinter as tk
    
     coords = {"x": 0, "y": 0, "x2": 0, "y2": 0}
     # keep a reference to all lines by keeping them in a list
     lines = []
    
     def click(event):
         if mode.get() == "pan":
             canvas.scan_mark(event.x, event.y)
         else:
             # define start point for line
             coords["x"] = canvas.canvasx(event.x)
             coords["y"] = canvas.canvasy(event.y)
    
             # create a line on this point and store it in the list
             lines.append(canvas.create_line(coords["x"], coords["y"], coords["x"], coords["y"]))
    
     def drag(event):
         if mode.get() == "pan":
             canvas.scan_dragto(event.x, event.y, gain=1)
         else:
             # update the coordinates from the event
             coords["x2"] = canvas.canvasx(event.x)
             coords["y2"] = canvas.canvasy(event.y)
    
             # Change the coordinates of the last created line to the new coordinates
             canvas.coords(lines[-1], coords["x"], coords["y"], coords["x2"], coords["y2"])
    
    
     root = tk.Tk()
     mode = tk.StringVar(root, "pan")
     toolbar = tk.Frame(root)
     toolbar.pack(fill='x')
    
     tk.Radiobutton(toolbar, text="Pan",
                    variable=mode, value="pan").pack(side='left')
     tk.Radiobutton(toolbar, text="Annotate",
                    variable=mode, value="annotate").pack(side='left')
    
     canvas = tk.Canvas(root, bg="white")
     canvas.create_rectangle(0, 0, 50, 50, fill='red')
     canvas.create_rectangle(400, 400, 450, 450, fill='blue')
     canvas.pack(fill='both')
    
     canvas.bind("<ButtonPress-1>", click)
     canvas.bind("<B1-Motion>", drag)
    
     root.mainloop()
    
  2. Another possibility is to use different bindings for the two kinds of actions using event modifiers (e.g. pressing the Ctrl, Shift or Alt key). For instance, the panning can be bound to Ctrl + mouse events while the drawing happens on simple mouse clicks and motion.

     import tkinter as tk
    
     coords = {"x": 0, "y": 0, "x2": 0, "y2": 0}
     # keep a reference to all lines by keeping them in a list
     lines = []
    
     def draw_click(event):
         # define start point for line
         coords["x"] = canvas.canvasx(event.x)
         coords["y"] = canvas.canvasy(event.y)
    
         # create a line on this point and store it in the list
         lines.append(canvas.create_line(coords["x"], coords["y"], coords["x"], coords["y"]))
    
     def draw_drag(event):
         # update the coordinates from the event
         coords["x2"] = canvas.canvasx(event.x)
         coords["y2"] = canvas.canvasy(event.y)
    
         # Change the coordinates of the last created line to the new coordinates
         canvas.coords(lines[-1], coords["x"], coords["y"], coords["x2"], coords["y2"])
    
    
     root = tk.Tk()
     toolbar = tk.Frame(root)
     toolbar.pack(fill='x')
    
     canvas = tk.Canvas(root, bg="white")
     canvas.create_rectangle(0, 0, 50, 50, fill='red')
     canvas.create_rectangle(400, 400, 450, 450, fill='blue')
     canvas.pack(fill='both')
    
     canvas.bind("<ButtonPress-1>", draw_click)
     canvas.bind("<B1-Motion>", draw_drag)
     canvas.bind('<Control-ButtonPress-1>', lambda event: canvas.scan_mark(event.x, event.y))
     canvas.bind("<Control-B1-Motion>", lambda event: canvas.scan_dragto(event.x, event.y, gain=1))
    
     root.mainloop()    
    
j_4321
  • 15,431
  • 3
  • 34
  • 61
  • Thank you very much for your reply, it helps me. My code is here: https://pastebin.com/n8KRATH5 and I have this error: Exception in Tkinter callback Traceback (most recent call last): File "C: \ Users \ acer \ anaconda3 \ lib \ tkinter \ __ init__.py", line 1705, in __call__ return self.func (* args) TypeError: drag () takes 1 positional argument but 2 were given – Turing Jan 05 '21 at 16:30
  • @Turing Your drag function need to take an `event` argument if you want to use it as a binding: `drag(self, event)` (even if you don't use it in the function). – j_4321 Jan 05 '21 at 16:43
  • Thank you very much it worked. I have a question: can the design be flexible? Because it's a triangle, but I would like it to be flexible like a pencil. The last problem I have is that I have the button move which erases everything. def move (self, event): if self.isSlideOn: if self.tool == "slide": dpx = (event.x - self.xref) dpy = (event.y - self.yref) self.canvas.delete ("image") self.canvas.create_image (-self.canvas.width + dpx, -self.canvas.height + dpy, anchor = NW, image = self.photoimage, tags = "image") – Turing Jan 05 '21 at 21:23
  • @Turing I don't understand what you mean by flexible, do you mean free hand drawing? For the second problem, what is this function supposed to achieve? Because if you want to move the image you don't need to delete it, you can use `canvas.coords` or `canvas.move`. – j_4321 Jan 06 '21 at 08:32
  • Yes free hand drawing and color choices – Turing Jan 06 '21 at 09:36
  • @Turing For the free hand drawing see https://stackoverflow.com/questions/47996285/how-to-draw-a-line-following-your-mouse-coordinates-with-tkinter and for the color change you can use an OptionMenu with a StringVar and get the color of the line from the StringVar when drawing. If you want to do something more elaborate I am sure you will find projects of Paint like app in tkinter on the Internet. – j_4321 Jan 06 '21 at 09:40
  • @Turing What's the precise problem? – j_4321 Feb 01 '21 at 20:24
  • I have two problems: When I add the colors, the stroke does not take into account the color change. The second problem, when I move the screen, the color change screen moves with the screen – Turing Feb 01 '21 at 20:33
  • @Turing I cannot reproduce the first issue, the color changes when I click on a different color square. For the second issue, the color palette is part of the Canvas so of course it moves when the canvas is scrolled, so put your palette in a different Canvas. Your initial question is too broad, so it would be better if you post separate questions when you encounter further issues in your project (1 question = 1 specific problem). – j_4321 Feb 01 '21 at 21:20