0

I cannot get x and y scrollbars to work in Tkinter with Python, although I have followed multiple examples:

  1. How to add 2 scrollbars with tkinter in python 3.4
  2. Adding a scrollbar to a group of widgets in tkinter
  3. How to make a proper double scrollbar frame in tkinter
  4. Vertical and Horizontal Scrollbars on Tkinter Widget
  5. Scrolling a Canvas widget horizontally and vertically

The scrollbars appear, but do not activate when the window is smaller than the frame. How can I get this to work (see image below)?

Problem 1

Below is the minimal code that is producing my problem (Python 3.7)

import tkinter as tk
from tkinter import ttk

big_font = ("Arial", 50)

class DoubleScrollbarFrame(ttk.Frame):

    def __init__(self,
                 parent,
                 *args,
                 **kwargs):
        self.parent = parent

        super().__init__(self.parent,
                         *args,
                         **kwargs)

        #Set widgets to fill main window such that they are
        #all the same size
        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)

        self.create_widgets()
        self.position_widgets()

    def create_widgets(self):
        self.canvas = tk.Canvas(self)
        self.frame = ttk.Frame(self.canvas)
        self.scroll_x = ttk.Scrollbar(self,
                                      orient = tk.HORIZONTAL,
                                      command = self.canvas.xview)
        self.scroll_y = ttk.Scrollbar(self,
                                      orient = tk.VERTICAL,
                                      command = self.canvas.yview)
        self.canvas.config(xscrollcommand = self.scroll_x.set,
                           yscrollcommand = self.scroll_y.set)
        self.canvas.create_window((0,0),
                                  window = self.frame,
                                  anchor = 'nw')
        self.frame.bind('<Configure>',
                        self.set_scrollregion)
        self.sizegrip = ttk.Sizegrip(self)

        #Test
        self.test1 = tk.Label(self.frame,
                              text = 'Test 1',
                              font = big_font)
        self.test2 = tk.Label(self.frame,
                              text = 'Test 2',
                              font = big_font)
        self.test3 = tk.Label(self.frame,
                              text = 'Test 3',
                              font = big_font)

    def position_widgets(self,
                         **kwargs):
        self.test1.grid(row = 0,
                        column = 0,
                        sticky = 'w')
        self.test2.grid(row = 1,
                        column = 0,
                        sticky = 'w')
        self.test3.grid(row = 2,
                        column = 0,
                        sticky = 'w')
        
        self.scroll_x.grid(row = 1,
                           column = 0,
                           sticky = 'ew')

        self.scroll_y.grid(row = 0,
                           column = 1,
                           sticky = 'ns')

        self.canvas.grid(row = 0,
                         column = 0,
                         sticky = 'nsew')
        
        self.frame.grid(row = 0,
                        column = 0,
                        sticky = 'nsew')

        self.sizegrip.grid(row = 1,
                           column = 1,
                           sticky = 'se')

    def set_scrollregion(self,
                         event):
        print('Frame Dimensions: {} x {}'.format(self.frame.winfo_width(),
                                                 self.frame.winfo_height()))
        print('Canvas Dimensions: {} x {}'.format(self.canvas.winfo_width(),
                                                  self.canvas.winfo_height()))
        self.canvas.configure(scrollregion = self.canvas.bbox('all'))

class MainApp(tk.Tk):

    def __init__(self,
                 *args,
                 **kwargs):

        super().__init__(*args,
                         **kwargs)

        #Set widgets to fill main window such that they are
        #all the same size
        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)

        self.create_widgets()
        self.position_widgets()

    def create_widgets(self):
        self.frame = DoubleScrollbarFrame(self)

    def position_widgets(self):
        self.frame.grid(row = 0,
                        column = 0,
                        sticky = 'nsew')

    def exit(self):
        self.destroy()

if __name__ == '__main__':

    #Create GUI
    root = MainApp()

    #Run program
    root.mainloop()
8
  • What have you done to debug this? For example, have you verified that the scrollregion is being updated to the correct values? Commented Jul 19, 2020 at 18:49
  • Please don't ask "bonus" questions - a good question asks about a single problem. Commented Jul 19, 2020 at 18:56
  • @BryanOakley I removed the bonus question, added references to links I've reviewed, and added a printout in set_scrollregion. The method only outputs: Frame Dimensions: 193 x 243 Canvas Dimensions: 1 x 1 and does not reprint when I resize the window. Commented Jul 19, 2020 at 20:36
  • You didn't answer the question of whether you've verified that scrollregion is being updated correctly. Commented Jul 19, 2020 at 21:31
  • @BryanOakley How do I verify scrollregion is updated correctly? Please tell me how to do this. Commented Jul 19, 2020 at 21:38

1 Answer 1

1

The problem is in these lines of code inside DoubleScrollbarFrame.position_widgets:

self.frame.grid(row = 0,
                column = 0,
                sticky = 'nsew')

This removes control of the widget from the canvas and gives control to grid. It is no longer a canvas object, so self.canvas.bbox("all") is returning (0, 0, 1, 1). If the scrollregion is set incorrectly, the scrollbars don't know how much to scroll.

The solution is simple: don't call grid on self.frame.

Sign up to request clarification or add additional context in comments.

1 Comment

Brilliant! Thank you! This works perfectly now. I read Tkinter GUI Application Development Cookbook by Alejandro Rodas and he states "...we did not call any geometry manager to draw the frame because the create_window() method does this for us." I had no idea that calling the geometry manager would pass control from the canvas to the grid! I wish someone had mentioned this in a book or tutorial!

Your Answer

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.