5

I would like to display an image using tkinter (Python 3). The image is very long. Therefore, I would like to add a vertical scrollbar. This is what I have tried:

(based on this question: Scrollbars for a .jpg image on a Tkinter Canvas in Python)

import tkinter
import PIL.Image, PIL.ImageTk

# Create a window
window = tkinter.Tk()

frame = tkinter.Frame(window, bd=2) # relief=SUNKEN)

frame.grid_rowconfigure(0, weight=1)
frame.grid_columnconfigure(0, weight=1)

xscrollbar = tkinter.Scrollbar(frame, orient=tkinter.HORIZONTAL)
xscrollbar.grid(row=1, column=0, sticky=tkinter.E+tkinter.W)

yscrollbar = tkinter.Scrollbar(frame)
yscrollbar.grid(row=0, column=1, sticky=tkinter.N+tkinter.S)

canvas = tkinter.Canvas(frame, bd=0, xscrollcommand=xscrollbar.set, yscrollcommand=yscrollbar.set)
canvas.grid(row=0, column=0, sticky=tkinter.N+tkinter.S+tkinter.E+tkinter.W)
canvas.config(scrollregion=canvas.bbox(tkinter.ALL))

File = "FILEPATH"
img = PIL.ImageTk.PhotoImage(PIL.Image.open(File))
canvas.create_image(0,0,image=img, anchor="nw")

xscrollbar.config(command=canvas.xview)
yscrollbar.config(command=canvas.yview)

frame.pack()
window.mainloop()

I get the following:

I am able to plot the image, but the scrollbars are not working. They are simply grey and non-functioning.

enter image description here

halfer
  • 19,824
  • 17
  • 99
  • 186
henry
  • 875
  • 1
  • 18
  • 48

3 Answers3

9

If you want a Scrollable image widget then the best way would be to create a class of Scrollable Image that'll arrange and look nicer to your code. So I created a class for the same and also added bind <MouseWheel> so one can scroll through their mouse to view the image more conveniently.


Here is the code sample

import tkinter

class ScrollableImage(tkinter.Frame):
    def __init__(self, master=None, **kw):
        self.image = kw.pop('image', None)
        sw = kw.pop('scrollbarwidth', 10)
        super(ScrollableImage, self).__init__(master=master, **kw)
        self.cnvs = tkinter.Canvas(self, highlightthickness=0, **kw)
        self.cnvs.create_image(0, 0, anchor='nw', image=self.image)
        # Vertical and Horizontal scrollbars
        self.v_scroll = tkinter.Scrollbar(self, orient='vertical', width=sw)
        self.h_scroll = tkinter.Scrollbar(self, orient='horizontal', width=sw)
        # Grid and configure weight.
        self.cnvs.grid(row=0, column=0,  sticky='nsew')
        self.h_scroll.grid(row=1, column=0, sticky='ew')
        self.v_scroll.grid(row=0, column=1, sticky='ns')
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)
        # Set the scrollbars to the canvas
        self.cnvs.config(xscrollcommand=self.h_scroll.set, 
                           yscrollcommand=self.v_scroll.set)
        # Set canvas view to the scrollbars
        self.v_scroll.config(command=self.cnvs.yview)
        self.h_scroll.config(command=self.cnvs.xview)
        # Assign the region to be scrolled 
        self.cnvs.config(scrollregion=self.cnvs.bbox('all'))
        self.cnvs.bind_class(self.cnvs, "<MouseWheel>", self.mouse_scroll)

    def mouse_scroll(self, evt):
        if evt.state == 0 :
            self.cnvs.yview_scroll(-1*(evt.delta), 'units') # For MacOS
            self.cnvs.yview_scroll(int(-1*(evt.delta/120)), 'units') # For windows
        if evt.state == 1:
            self.cnvs.xview_scroll(-1*(evt.delta), 'units') # For MacOS
            self.cnvs.xview_scroll(int(-1*(evt.delta/120)), 'units') # For windows

How to use the class?

Treat ScrollableImage class as a widget of Tkinter and use just like you use any other Tkinter widget as every other widget is a class on their own if you see the source code of tkinter.

There are different ways through which you can use ScrollableImage.

  1. Save the above code to a new <name>.py( for ex: "scrollimage.py" ) file as a package in the same directory and then import into your main class like from scrollimage import ScrollableImage and then use it as a normal widget.

  2. Or you can keep the ScrollableImage class at the top after the imports of your main file and use it just like normal.

Example:

import tkinter as tk
# Import the package if saved in a different .py file else paste 
# the ScrollableImage class right after your imports.
from scrollimage import ScrollableImage   

root = tk.Tk()

# PhotoImage from tkinter only supports:- PGM, PPM, GIF, PNG format.
# To use more formats use PIL ImageTk.PhotoImage
img = tk.PhotoImage(file="logo.png")

image_window = ScrollableImage(root, image=img, scrollbarwidth=6, 
                               width=200, height=200)
image_window.pack()

root.mainloop()

The bind <MouseWheel> needs some modifications on windows like divide event.delta by 120. On macOS no modifications needed. And on X11 you need to bind both <Button-4>, <Button-5> and also divide event.delta by 120.

  • Tkinter Mousewheel Binding

    For more details on bind <MouseWheel> and how it works on different platforms can check the above link.

  • Best way to structure Tkinter application.

    You can refer to the above link to read in detail about how you can apply oops in Tkinter properly. When you use OOPs you can always inherit any widget of Tkinter and modify it to make new widgets and useful classes that'll help you to make an application with Tkinter better.

Community
  • 1
  • 1
Saad
  • 3,340
  • 2
  • 10
  • 32
  • Wow ! Thank you very much for your class ! Seems to be very useful. I am quite new to tkinter. How would you use it ? Would you mind adding a small example ? Thanks. – henry May 08 '19 at 21:19
  • @Henry: I added more useful information which will help you to learn better. – Saad May 08 '19 at 23:23
  • 1
    Thank you so much ! This is very kind of you and very helpful ! – henry May 09 '19 at 06:59
  • 1
    I get the following error: Exception in Tkinter callback Traceback (most recent call last): File "C:\Users\Anaconda3\lib\tkinter\__init__.py", line 1702, in __call__ return self.func(*args) File "", line 31, in mouse_scroll self.yview_scroll(-1*(evt.delta/120), 'units') # For windows File "C:\Users\Anaconda3\lib\tkinter\__init__.py", line 1748, in yview_scroll self.tk.call(self._w, 'yview', 'scroll', number, what) _tkinter.TclError: expected integer but got "1.0" – henry May 09 '19 at 07:18
  • @henry: it's because `scroll` takes integer value and "1.0" is a float value, the fix is simple just convert it to integer. I edited my post and fixed it though. I couldn't test that out as I'm on Macos. Hopefully it'll works now. – Saad May 09 '19 at 07:26
  • Hmm.. how would you change the size of the window ? – henry May 09 '19 at 08:33
  • Just like you change the size of a widget with `width` and `height` parameters, see the code at line 42 `ScrollableImage(root, image=img, width=200, height=200).pack()` – Saad May 09 '19 at 08:37
  • Thank you so much for your patience and help ! Really appreciate it. Maybe, if you have the time, would you mind having a look at my other question: https://stackoverflow.com/questions/56056054/add-check-boxes-to-scrollable-image-in-python – henry May 09 '19 at 09:14
  • No problem, and sure I'll look into it later. – Saad May 09 '19 at 09:17
  • @Saad Is it possible to modify your class in such a way that image will appear not in the left top corner of the canvas but at some other coordinate ? I tried to achive this by modifying the line `self.cnvs.create_image(0, 0, anchor='nw', image=self.image)` but nothing has changed. – Юрій Ярош Dec 13 '21 at 19:57
4

Put the line:

canvas.config(scrollregion=canvas.bbox(tkinter.ALL))

after:

canvas.create_image(0,0,image=img, anchor="nw")

or canvas does not include image in scrollregion.

figbeam
  • 7,001
  • 2
  • 12
  • 18
4

Set the scroll region of the canvas after you draw the image:

canvas.create_image(0, 0, image=img, anchor="nw")
canvas.config(scrollregion=canvas.bbox(tkinter.ALL))
Maximouse
  • 4,170
  • 1
  • 14
  • 28