pythontkinterscrollbars

Scrollbar not working on canvas


I'm creating a json editor in python using tkinter.

I've added a scrollbar by creating a Canvas, and putting a Frame inside it.

Then I set the Scrollbar command to canvas.yview.

Theres two things that are messing up, and I have no idea why.

  1. When I press the scroll buttons (up and down arrows) the canvas is not scrolling

  2. I am packing the scrollbar onto the window (root) right now instead of the frame, because whenever i pack it onto the frame, the tkinter application does not open, and my computer fan starts turning on... Anyone know what is going on here? (Therefore the scrollbar is tiny if you try to run the code)

Here is my code:

EDIT> Code shortened

import Tkinter as tk
import webbrowser
import os
import bjson as bj

class App:
    def __init__(self, master):
        self.window = master
        self.window.geometry("800x450")
        self.canvas = tk.Canvas(self.window, width=800, height=400)
        self.master = tk.Frame(self.canvas, width=800, height=400)
        self.canvas.pack()
        self.master.place(x=0, y=0)
        scrollbar = tk.Scrollbar(self.window)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        scrollbar.config(command=self.canvas.yview)

    def init(self):
        master = self.master
        self.frames = {
            "Home": HomeFrame(master)
        }
        self.openFrame = None
        self.loadFrame("Home")

    def loadFrame(self, frame):
        self.openFrame = self.frames[frame]
        self.openFrame.display()

    def setTitle(self, t):
        self.window.title(t)

class Frame:
    def __init__(self, master):
        self.master = master
        self.frame = tk.Frame(master)
        self.frame.grid(row=0, column=0, sticky='news')
        self.init()
        self.frame_create()

    def display(self):
        self.frame.tkraise()    #raises frame to top
        self.frame_load()       #initializes the frame

    def clear(self):
        for widget in self.frame.winfo_children():
            widget.destroy()

    def init(self): pass
    def frame_load(self): pass
    def frame_create(self): pass

class HomeFrame(Frame):
    def frame_create(self):
        p = self.frame
        for i in range(20):
            tk.Label(p, text="This is content... " + str(i)).pack()
            for j in range(2):
                LineBreak(p)

def LineBreak(p):
    tk.Label(p, text="").pack()

root = tk.Tk()
glob = {}
app = App(root)
app.init()
root.mainloop()

It is a bit long, and a bit messy, but you should see how I'm adding the scrollbar in the __init__ of App

Anyone have any idea what's going on, and how to fix it?

Thanks in advance!


Solution

  • There are many things wrong with your code. However, the problem with the scrollbar not working properly has to do with two things you are neglecting to do:

    First, scrollbars and widgets require two way communication. The canvas needs to be told about the scrollbar, and the scrollbar needs to be told about the canvas. You are doing one but not the other:

    self.canvas.configure(yscrollcommand=scrollbar.set)
    scrollbar.configure(command=self.canvas.yview)
    

    Second, you need to configure the scrollregion attribute of the canvas. This tells tkinter what part of the larger virtual canvas you want to be viewable. Typically this is done in a binding on the <Configure> method of the canvas, and usually you will want to set it to the bounding box of everything in the canvas. For the latter you can pass the string "all" to the bbox method:

    self.canvas.configure(scrollregion=self.canvas.bbox("all"))
    

    If you know the exact size of the area you want to be scrollable, you can simply set it to that value (eg: scrollregion=(0,0,1000,1000) to scroll around in a region that is 1000x1000 pixels).

    The reason for point #2 is that you can't use both pack and grid for widgets that share the same parent. When you do, you'll get the behavior you describe. That is because grid will try to layout all of the widgets. This may result in some widgets changing size. pack will notice the change in the size of one or more widgets and try to re-layout all of the widgets. This may result in some widgets changing size. grid will notice the change in the size of one or more widgets and try to re-layout all of the widgets. And so on.