1
\$\begingroup\$

Below is an example on how to scroll two canvasses in two separate frames simultaneously. The trick is on how to pass on *args from the bindings to function onscroll for scrolling and onresize for changing the dimensions of the rectangle using lambda in the commands: Scrollbar(frame3, orient='vertical', command=lambda *args: onscroll('axis', *args)) and Button(frame3, text='reduce ', command=lambda *args: onresize(False, *args)).

Is this the best way to handle this?

import tkinter as tk
from tkinter import Frame, Canvas, Scrollbar, Button
global size_x, size_y, canvas1, canvas2, block1, block2
size_x = size_y = 150
block1 = block2 = ''
def onscroll(axis, *args):
 global canvas1, canvas2
 print(f'axis: {axis} and args are {[*args]} ',
 end='\r')
 if axis == 'x-axis':
 canvas1.xview(*args)
 canvas2.xview(*args)
 elif axis == 'y-axis':
 canvas1.yview(*args)
 canvas2.yview(*args)
 else:
 assert False, f"axis {axis} is incorrect, use 'x-axis' or 'y-axis'"
def onresize(enlarge, *args):
 global size_x, size_y
 print(f'enlarge: {enlarge} and args are {[*args]} ',
 end='\r')
 if enlarge:
 size_x *= 1.1
 size_y *= 1.1
 else:
 size_x /= 1.1
 size_y /= 1.1
def main():
 global canvas1, canvas2, block1, block2
 root = tk.Tk()
 frame1 = Frame(root, bg='grey')
 frame1.grid(row=0, column=0)
 frame2 = Frame(root, bg='grey')
 frame2.grid(row=0, column=1)
 frame3 = Frame(root, bg='grey')
 frame3.grid(row=0, column=2)
 yscrollbar = Scrollbar(frame3, orient='vertical',
 command=lambda *args: onscroll('y-axis', *args))
 yscrollbar.pack(side='right', fill='y', expand='yes')
 xscrollbar = Scrollbar(frame3, orient='horizontal',
 command=lambda *args: onscroll('x-axis', *args))
 xscrollbar.pack(side='bottom', fill='x', expand='yes')
 reduce_button = Button(frame3, text='reduce ',
 command=lambda *args: onresize(False, *args))
 reduce_button.pack(side='right', anchor='ne')
 enlarge_button = Button(frame3, text='enlarge',
 command=lambda *args: onresize(True, *args))
 enlarge_button.pack(side='right', anchor='ne')
 canvas1 = Canvas(frame1, width=200, height=200, bg="blue",
 scrollregion=(0, 0, 500, 500),
 yscrollcommand=yscrollbar.set,
 xscrollcommand=xscrollbar.set)
 canvas1.pack(side='right')
 canvas2 = Canvas(frame2, width=200, height=200, bg="yellow",
 scrollregion=(0, 0, 500, 500),
 yscrollcommand=yscrollbar.set,
 xscrollcommand=xscrollbar.set)
 canvas2.pack()
 canvas3 = Canvas(frame3, width=200, height=200, bg='pink')
 canvas3.pack()
 while True:
 canvas1.delete(block1)
 canvas2.delete(block2)
 block1 = canvas1.create_rectangle(100, 100, size_x, size_y,
 fill='orange')
 block2 = canvas2.create_rectangle(100, 100, size_x, size_y,
 fill='blue')
 root.update()
 root.mainloop()
if __name__ == "__main__":
 main()
asked Aug 20, 2018 at 12:38
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

Remove unnecessary imports

You're importing parts of tkinter twice, Remove this line:

from tkinter import Frame, Canvas, Scrollbar, Button

Then, whenever you need to use Frame, Canvas, etc, use tk.Frame, tk.Canvas, etc.

Don't create your own event loop

You have code that looks like this:

while True:
 <some code>
 root.update()

Instead, call mainloop once, move <some code> into a function, and call it with after if you need it to run in a loop.

def redraw():
 <some code>
 root.after(50, redraw)
root.mainloop()

Don't continually delete and recreate items on a canvas

You're continually deleting and recreating identical items on a canvas. This is inefficient and will eventually call tkinter to crash. The canvas widget does not re-use canvas ids, so each time you create a new object you get a new id. There is a limited number of ids that tkinter can manage.

Instead, create the items once, and then if you need to change them, change the existing items

def draw():
 global block1, block2
 block1 = canvas1.create_rectangle(100, 100, size_x, size_y,
 fill='orange')
 block2 = canvas2.create_rectangle(100, 100, size_x, size_y,
 fill='blue')
def redraw():
 global block1, block2
 canvas1.itemconfigure(block1, ...)
 canvas2.itemconfigure(block2, ...)

Group your layout code together

Intermixing widget creation and widget layout makes the code hard to read.

Consider this:

frame1 = Frame(root, bg='grey')
frame1.grid(row=0, column=0)
frame2 = Frame(root, bg='grey')
frame2.grid(row=0, column=1)
frame3 = Frame(root, bg='grey')
frame3.grid(row=0, column=2)

I recommend splitting this into two blocks:

frame1 = Frame(root, bg='grey')
frame2 = Frame(root, bg='grey')
frame3 = Frame(root, bg='grey')
frame1.grid(row=0, column=0)
frame2.grid(row=0, column=1)
frame3.grid(row=0, column=2)

I think this makes the code much easier to read because I can see at a glance that you're creating three frames, and that each frame is going into a separate column. In the original code it's not nearly as easy to see this without closely reading the code.

Use separate functions for scrolling in x and y direction

Your use of an argument to specify x-axis or y-axis makes the code a little bit harder to understand. Instead, I recommend using two separate functions.

For example:

def scroll_x(*args):
 global canvas1, canvas2
 canvas1.xview(*args)
 canvas2.xview(*args)
def scroll_y(*args):
 global canvas1, canvas2
 canvas1.yview(*args)
 canvas2.yview(*args)
...
yscrollbar = Scrollbar(frame3, orient='vertical', command=scroll_y)
xscrollbar = Scrollbar(frame3, orient='horizontal', command=scroll_x)

Notice how it makes the code much easier to read, since we can replace the complicated lambda with a much simpler command.

answered Aug 20, 2018 at 14:06
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Thanks for the very useful tips. Three questions: 1) as I am changing the dimensions of the rectangle (100, 100, size_x, size_y) I am not sure how to implement this with itemconfigure as it seems only the options can be used as input and not the positional paramters? 2) am I correct that redraw is a recursive function, i.e. the function calls itself? 3) what is a graceful way to exit the redraw function, do I use root.destroy on some condition? \$\endgroup\$ Commented Aug 21, 2018 at 3:05

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.