Jason W
Jason W

Reputation: 33

Python Tkinter Scrollbar Shaky Scrolling

Introduction: My Python Tkinter application is designed to have a scrollbar on the side so that if the window is resized, the application can still be viewed via the scrollbar. I do this by putting a frame with all my content inside a canvas, with a scrollbar controlling the canvas. On window resize, I have a function called resizeCanvas which resizes the canvas.

Problem: After I resize the window, the scrollbar sort of works but it seems to jump around and fidget like its having a seizure. However, at initialization the scroll bar works smoothly. So resizing the window seems to be the problem. Any suggestions on why the scroll bar is behaving like this?

Application Hierarchy:

NOTE: Python 2.7.2

Code Snippit:

    myframe=Frame(root,width=winx,height=winy)
    myframe.place(x=0,y=0)
    canvas = Tkinter.Canvas(myframe,width=winx,height=winy)
    frame = Frame(canvas,width=winx,height=winy)
    myscrollbar=Scrollbar(myframe,orient="vertical")
    myscrollbar.configure(command=canvas.yview)
    canvas.configure(yscrollcommand=myscrollbar.set)

    myscrollbar.pack(side="left",fill="y")
    canvas.pack(side="left")
    canvas.create_window((0,0),window=frame,anchor='nw')
    frame.bind("<Configure>", initializeCanvas)
    bind("<Configure>", resizeCanvas)

def initializeCanvas(self, event):
   canvas.configure(scrollregion=self.canvas.bbox("all"),width=winx,height=winy)

def resizeCanvas(self, event):
    update()
    cwidth = self.winfo_width()
    cheight = self.winfo_height()
    canvas.config(width=cwidth, height=cheight)

Entire Code:

import sys
#external python files are in the includes folder
sys.path.insert(0, 'includes')

import webbrowser
import os
import Tkinter
import tkFileDialog
import tkMessageBox
import ConfigParser
from ttk import *
import tkFont
import Tix

# configurations held in this variable
config = ConfigParser.RawConfigParser()
# pady padding for create buttons
py = 15
# padx padding for create buttons
px = 5
# padding on left side for indenting elements
indentx = 25
winx = 815
winy = 515
# array to hold features
FEATURES =['']


# wrapper class for GUI
class AppTk(Tkinter.Tk):
    def __init__(self, parent):
        Tkinter.Tk.__init__(self, parent)
        self.parent = parent
        self.settings = "./settings/settings.cfg"
        self.initialize_gui()
        self.minsize(winx, 100)
        sizex = winx
        sizey = winy
        posx  = 100
        posy  = 100
        self.wm_geometry("%dx%d+%d+%d" % (sizex, sizey, posx, posy))

        try:
            self.iconbitmap('./imgs/favicon.ico')
        except Exception, e:
            print "\n****Error occurred (GUI_MBD_File_Creator.init):  favicon not found!"

    # Setup grid of elements in GUI
    # action: on start of application
    def initialize_gui(self):

        # ----------------------------------------------------
        # START Settings frame initialization
        # ----------------------------------------------------
        self.myframe=Frame(self,width=winx,height=winy)
        self.myframe.place(x=0,y=0)
        self.canvas = Tkinter.Canvas(self.myframe,width=winx,height=winy)
        self.frame = Frame(self.canvas,width=winx,height=winy)
        self.myscrollbar=Scrollbar(self.myframe,orient="vertical")
        self.myscrollbar.configure(command=self.canvas.yview)
        self.canvas.configure(yscrollcommand=self.myscrollbar.set)

        self.myscrollbar.pack(side="left",fill="y")
        self.canvas.pack(side="left")
        self.canvas.create_window((0,0),window=self.frame,anchor='nw')
        self.frame.bind("<Configure>",self.initializeCanvas)
        self.bind("<Configure>",self.resizeCanvas)


        frameFont = tkFont.Font(size=13, weight=tkFont.BOLD)

        self.frameSettings = Tkinter.LabelFrame(self.frame, text="Settings: fill these out first", relief="groove", borderwidth="3", font=frameFont)
        self.frameSettings.grid(sticky="EW", padx=px, pady=(5,15))

        labelSpreadsheet = Label(self.frameSettings, text="1. Spreadsheet Path:")
        labelSpreadsheet.grid(row=0, column=0, sticky='W', padx=(indentx,0))
        variableSpreadsheet = Tkinter.StringVar()
        self.entrySpreadsheet = Entry(self.frameSettings, textvariable=variableSpreadsheet, width=90, state=Tkinter.DISABLED)
        self.entrySpreadsheet.grid(row=0, column=1, sticky='W', padx=px, pady=5)
        self.entrySpreadsheet.svar = variableSpreadsheet
        buttonSpreadsheet = Button(self.frameSettings, text="Browse...")
        buttonSpreadsheet.grid(row=0, column=2, sticky='W', padx=(0,10))

        labelPath = Label(self.frameSettings, text="2. Root Save Path:")
        labelPath.grid(row=1, column=0, sticky='W', padx=(indentx,0))
        variablePath = Tkinter.StringVar()
        self.entryPath = Entry(self.frameSettings, textvariable=variablePath, width=90, state=Tkinter.DISABLED)
        self.entryPath.grid(row=1, column=1, sticky='W', padx=px, pady=(5,10))
        self.entryPath.svar = variablePath
        buttonPath = Button(self.frameSettings, text="Browse...")
        buttonPath.grid(row=1, column=2, sticky='W', padx=(0,10), pady=(0,5))

        # ----------------------------------------------------
        # START Creation Menu frame initialization
        # ----------------------------------------------------

        self.frameCreationIndividual = Tkinter.LabelFrame(self.frame, text="Feature Files Creation Menu", relief="groove", borderwidth="3", font=frameFont)
        self.frameCreationIndividual.grid(sticky="EW", padx=px, pady=(5,15))

        labelReq = Label(self.frameCreationIndividual, text="3. Feature(s):")
        labelReq.grid(row=0, column=0, sticky='NW', pady=(5,0), padx=(indentx,15))
        self.scrollbarReq = Scrollbar(self.frameCreationIndividual)
        self.scrollbarReq.grid(row=1, column=3, rowspan=16, sticky="NSW", pady=(0,15),padx=(0,20))

        variableSelectAll = Tkinter.IntVar()
        self.checkSelectAll = Checkbutton(self.frameCreationIndividual, text = "Select All", variable = variableSelectAll, onvalue = 1, offvalue = 0)
        self.checkSelectAll.grid(row=0, column=1, columnspan=2, sticky='NE', padx=px, pady=(5,0))
        self.checkSelectAll.svar = variableSelectAll

        labelReq = Label(self.frameCreationIndividual, text="4. Files:")
        labelReq.grid(row=0, column=5, sticky='NW', pady=(5,0), padx=15)

        variableIndividualFFS = Tkinter.IntVar()
        self.checkIndividualFFS = Checkbutton(self.frameCreationIndividual, text = "Create Feature File", variable = variableIndividualFFS, onvalue = 1, offvalue = 0)
        self.checkIndividualFFS.grid(row=1, column=5, sticky='NW', padx=15)
        self.checkIndividualFFS.svar = variableIndividualFFS

        variableIndividualSFS = Tkinter.IntVar()
        self.checkIndividualSFS = Checkbutton(self.frameCreationIndividual, text = "Create SubFeature Files", variable = variableIndividualSFS, onvalue = 1, offvalue = 0)
        self.checkIndividualSFS.grid(row=2, column=5, sticky='NW', padx=15)
        self.checkIndividualSFS.svar = variableIndividualSFS

        variableIndividualDO = Tkinter.IntVar()
        self.checkIndividualDO = Checkbutton(self.frameCreationIndividual, text = "Create Doc Outline", variable = variableIndividualDO, onvalue = 1, offvalue = 0)
        self.checkIndividualDO.grid(row=3, column=5, sticky='NW', padx=15)
        self.checkIndividualDO.svar = variableIndividualDO

        variableIndividualDWR = Tkinter.IntVar()
        self.checkIndividualDWR = Checkbutton(self.frameCreationIndividual, text = "Create Doc With Requirements", variable = variableIndividualDWR, onvalue = 1, offvalue = 0)
        self.checkIndividualDWR.grid(row=4, column=5, sticky='NW', padx=(15,30))
        self.checkIndividualDWR.svar = variableIndividualDWR

        self.buttonIndividualAll = Button(self.frameCreationIndividual, text="Create...", width=43)
        self.buttonIndividualAll.grid(row=1, column=6, rowspan=4, sticky='NESW', padx=px)

        # ----------------------------------------------------
        # START Entire System Creation frame initialization
        # ----------------------------------------------------

        self.frameCreationSystem = Tkinter.LabelFrame(self.frame, text="System Creation Menu", relief="groove", borderwidth="3", font=frameFont)
        self.frameCreationSystem.grid(sticky="EW", padx=px, pady=15)

        self.buttonLAIF = Button(self.frameCreationSystem, text="Create Layers/App Integration Files", width=35)
        self.buttonLAIF.grid(row=11, column=0, sticky='NESW', ipady=5, padx=(indentx,0), pady=(16,8))

        self.buttonDO = Button(self.frameCreationSystem, text="Create Entire Doc Outline")
        self.buttonDO.grid(row=12, column=0, sticky='NESW', ipady=5, padx=(indentx,0), pady=(8,10))

        # ----------------------------------------------------
        # START Feature Tab Creation Frame initialization
        # ----------------------------------------------------

        self.frameCreationNew = Tkinter.LabelFrame(self.frame, text="Feature Tab Creation Menu", relief="groove", borderwidth="3", font=frameFont)
        self.frameCreationNew.grid(sticky="EW", padx=px, pady=(15,5))

        labelIssueSpreadsheet = Label(self.frameCreationNew, text="2. Feature Spreadsheet Path:")
        labelIssueSpreadsheet.grid(row=0, column=0, sticky='W', padx=(indentx,0))
        variableIssueSpreadsheet = Tkinter.StringVar()
        self.entryIssueSpreadsheet = Entry(self.frameCreationNew, textvariable=variableIssueSpreadsheet, width=83, state=Tkinter.DISABLED)
        self.entryIssueSpreadsheet.grid(row=0, column=1, sticky='W', padx=px, pady=5)
        self.entryIssueSpreadsheet.svar = variableIssueSpreadsheet
        buttonIssueSpreadsheet = Button(self.frameCreationNew, text="Browse...")
        buttonIssueSpreadsheet.grid(row=0, column=2, sticky='W')

        labelFeatureTab = Label(self.frameCreationNew, text="3. Feature Name:")
        labelFeatureTab.grid(row=1, column=0, sticky='W', padx=(indentx,0))
        variableFeatureTab = Tkinter.StringVar()
        self.entryFeatureTab = Entry(self.frameCreationNew, textvariable=variableFeatureTab, width=83)
        self.entryFeatureTab.grid(row=1, column=1, sticky='W', padx=px, pady=5)
        self.entryFeatureTab.svar = variableFeatureTab

        labelFeatureAbbrv = Label(self.frameCreationNew, text="4. Feature Abbreviation:")
        labelFeatureAbbrv.grid(row=2, column=0, sticky='W', padx=(indentx,0))
        variableFeatureAbbrv = Tkinter.StringVar()
        self.entryFeatureAbbrv = Entry(self.frameCreationNew, textvariable=variableFeatureAbbrv, width=83)
        self.entryFeatureAbbrv.grid(row=2, column=1, sticky='W', padx=px, pady=5)
        self.entryFeatureAbbrv.svar = variableFeatureAbbrv

        self.buttonNewFeature = Button(self.frameCreationNew, text="Create Feature Tab", width=35)
        self.buttonNewFeature.grid(row=3, column=0, columnspan =2, sticky='NWS', ipady=5, pady=(8,10), padx=(indentx,0))

    # ----------------------------------------------------
    # START general purpose methods
    # ----------------------------------------------------

    def initializeCanvas(self, event):
        self.canvas.configure(scrollregion=self.canvas.bbox("all"),width=winx,height=winy)

    def resizeCanvas(self, event):
        self.update()
        cwidth = self.winfo_width()
        cheight = self.winfo_height()
        self.canvas.config(scrollregion=self.canvas.bbox("all"), width=cwidth, height=cheight)

# ----------------------------------------------------
# Initialize application
# ----------------------------------------------------
if __name__ == "__main__":
    app = AppTk(None)
    app.title("MBD File Creator")
    app.mainloop()

Upvotes: 3

Views: 2596

Answers (2)

Pontios
Pontios

Reputation: 2394

Try first to save the canvas.create_window to a variable as following:

self.windows_item = canvas.create_window((0,0),window=frame,anchor='nw')

then at the end of resizeCanvas to call the following method:

def update(self):
    "Update the canvas and the scrollregion"
    self.update_idletasks()
    canvas.config(scrollregion=canvas.bbox(self.windows_item))

Hope it helps!

Most of it found here: https://stackoverflow.com/a/47985165/2160507

Upvotes: -1

Bryan Oakley
Bryan Oakley

Reputation: 385870

While there may be other errors, you have one very critical flaw: you are creating a binding for the <Configure> event to self. Because self is the instance of the root window, every widget you create inherits this binding. Your resizeCanvas method is literally being called hundreds of times at startup, and hundreds of times when the window is resized.

You also have the problem where you are calling update in an event handler. As a general rule, this is bad. In effect, it's like calling mainloop in that it will not return until all events are processed. If your code causes more events to be processed (such as reconfiguring a window and causing the <configure> event to fire, you end up in a recursive loop.

You probably need to remove the calls to self.update() and/or replace them with the less dangerous self.update_idletasks(). You also need to either remove the <Configure> binding on self, or in the method you need to check for which widget caused the event to fire (ie: check that the event.widget is the root window).

Upvotes: 1

Related Questions