Moving Around A Python Canvas Without Using Scrollbars
Question: **Background:**  
I have a program using Tkinter as the basis of the GUI. The program has a
canvas which is populated with a large number of objects. Currently, in order
to move all objects on the screen, I am simply binding a movement function to
the tag 'all' which of course moves all objects on the screen. However, it is
vital for me to keep track of _all_ canvas object positions- i.e. after every
move I log the new position, which seems unnecessarily complicated.

**Question:**  
What is the best way to effectively scroll/drag around the whole canvas
(several times the size of the screen) using only the mouse (not using
scrollbars)?

**My Attempts:**  
I have implemented scrollbars and found several guides to setting up
scrollbars, but none that deal with this particular requirement.

**Example of disused scrollbar method:**

    
    
    from Tkinter import *
    
    class Canvas_On:    
        def __init__(self, master):
    
            self.master=master
            self.master.title( "Example")
            self.c=Canvas(self.master, width=find_res_width-20, height=find_res_height, bg='black', scrollregion=(0,0,5000,5000))
            self.c.grid(row=0, rowspan=25, column=0)
            self.c.tag_bind('bg', '<Control-Button-1>', self.click)
            self.c.tag_bind('bg', '<Control-B1-Motion>', self.drag)
    
            self.c.tag_bind('dot', '<Button-1>', self.click_item)
            self.c.tag_bind('dot', '<B1-Motion>', self.drag_item)
    
            draw=Drawing_Utility(self.c)
            draw.drawer(self.c)
    
        def click(self, event):
            self.c.scan_mark(event.x, event.y)
    
        def drag(self, event):
            self.c.scan_dragto(event.x, event.y)
    
        def click_item(self, event):
            self.c.itemconfigure('dot 1 text', text=(event.x, event.y))
            self.drag_item = self.c.find_closest(event.x, event.y)
            self.drag_x, self.drag_y = event.x, event.y
            self.c.tag_raise('dot')
            self.c.tag_raise('dot 1 text')
    
        def drag_item(self, event):
            self.c.move(self.drag_item, event.x-self.drag_x, event.y-self.drag_y)
            self.drag_x, self.drag_y = event.x, event.y
    
    class Drawing_Utility:
        def __init__(self, canvas):
            self.canvas=canvas
            self.canvas.focus_set()
    
    
        def drawer(self, canvas):
            self.canvas.create_rectangle(0, 0, 5000, 5000, 
                                fill='black', tags='bg')
            self.canvas.create_text(450,450, text='', fill='black', activefill='red', tags=('draggable', 'dot', 'dot 1 text'))
            self.canvas.create_oval(400,400,500,500, fill='orange', activefill='red', tags=('draggable', 'dot', 'dot 2'))
            self.canvas.tag_raise(("dot"))
    
    root=Tk()
    find_res_width=root.winfo_screenwidth()
    find_res_height=root.winfo_screenheight()
    run_it=Canvas_On(root)  
    root.mainloop() 
    

**My Particular Issue**  
  
My program generates all canvas object coordinates and then draws them. The
objects are arranged in various patterns, but critically they must 'know'
where each other is. When moving around the canvas using the method @abarnert
kindly supplied, and a similar method I wrote that moved all canvas objects,
the issue arises that each object 'thinks' it is at the canvas coordinates
generated before the objects were drawn. For example if I drag the canvas 50
pixels to the left and clicked on an object in my program, it jumps 50 pixels
back to the right to it's original position. My solution to this was to write
some code that, upon release of the mouse button, logged the last position and
updated the coordinate data of each object. However, I'm looking for a way to
remove this last step- I was hoping there was a way to move the canvas such
that the object positions were absolute, and assumed a function similar to a
'scroll' function would do this. I realise I've rambled here, but I've added a
couple of lines to the example above which highlights my issue- by moving the
canvas you can see that the coordinates change. Thank you again.


Answer: I'll give you the code for the simplest version first, then explain it so you
can expand it as needed.

    
    
    class Canvas_On:    
        def __init__(self, master):
            # ... your original code here ...
            self.c.bind('<Button-1>', self.click)
            self.c.bind('<B1-Motion>', self.drag)
    
        def click(self, event):
            self.c.scan_mark(event.x, event.y)
    
        def drag(self, event):
            self.c.scan_dragto(event.x, event.y)
    

First, the easy part: scrolling the canvas manually. As the
[documentation](http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.xview-
method) explains, you use the `xview` and `yview` methods, exactly as your
scrollbar `command`s do. Or you can just directly call `xview_moveto` and
`yview_moveto` (or the `foo_scroll` methods, but they don't seem to be what
you want here). You can see that I didn't actually use these; I'll explain
below.

Next, to capture click-and-drag events on the canvas, you just bind
`<B1-Motion>`, as you would for a normal drag-and-drop.

The tricky bit here is that the drag event gives you screen pixel coordinates,
while the `xview_moveto` and `yview_moveto` methods take a fraction from 0.0
for the top/left to 1.0 for the bottom/right. So, you'll need to capture the
coordinates of the original click (by binding `<Button-1>`; with that, the
coordinates of the drag event, and the canvas's bbox, you can calculate the
`moveto` fractions. If you're using the `scale` method and want to drag
appropriately while zoomed in/out, you'll need to account for that as well.

But unless you want to do something unusual, the `scan` helper methods do
exactly that calculation for you, so it's simpler to just call them.

* * *

Note that this will also capture click-and-drag events on the items on the
canvas, not just the background. That's probably what you want, unless you
were planning to make the items draggable within the canvas. In the latter
case, add a background rectangle item (either transparent, or with whatever
background you intended for the canvas itself) below all of your other items,
and `tag_bind` that instead of `bind`ing the canvas itself. (IIRC, with older
versions of Tk, you'll have to create a tag for the background item and
`tag_bind` that… but if so, you presumably already had to do that to bind all
your other items, so it's the same here. Anyway, I'll do that even though it
shouldn't be necessary, because tags are a handy way to create groups of items
that can all be bound together.)

So:

    
    
    class Canvas_On:    
        def __init__(self, master):
            # ... your original code here ...
            self.c.tag_bind('bg', '<Button-1>', self.click)
            self.c.tag_bind('bg', '<B1-Motion>', self.drag)
            self.c.tag_bind('draggable', '<Button-1>', self.click_item)
            self.c.tag_bind('draggable', '<B1-Motion>', self.drag_item)
        # ... etc. ...
        def click_item(self, event):
            x, y = self.c.canvasx(event.x), self.c.canvasy(event.y)
            self.drag_item = self.c.find_closest(x, y)
            self.drag_x, self.drag_y = x, y
            self.tag_raise(item)
        def drag_item(self, event):
            x, y = self.c.canvasx(event.x), self.c.canvasy(event.y)
            self.c.move(self.drag_item, x-self.drag_x, y-self.drag_y)
            self.drag_x, self.drag_y = x, y
    
    class Drawing_Utility:
        # ...
        def drawer(self, canvas):
            self.c.create_rectangle(0, 0, 5000, 5000, 
                                    fill='black', tags='bg')
            self.c.create_oval(50,50,150,150, fill='orange', tags='draggable')
            self.c.create_oval(1000,1000,1100,1100, fill='orange', tags='draggable')
    

Now you can drag the whole canvas around by its background, but dragging other
items (the ones marked as 'draggable') will do whatever else you want instead.

* * *

If I understand your comments correctly, your remaining problem is that you're
trying to use window coordinates when you want canvas coordinates. The section
Coordinate Systems in the docs explains the distinction.

So, let's say you've got an item that you placed at 500, 500, and the origin
is at 0, 0. Now, you scroll the canvas to 500, 0. The window coordinates of
the item are now 0, 500, but its canvas coordinates are still 500, 500. As the
docs say:

> To convert from window coordinates to canvas coordinates, use the `canvasx`
> and `canvasy` methods

