Nick
Nick

Reputation: 49

Tkinter Scrollbar with Grid: Scrolls past frame and scrollbar gray

I'm trying to implement a scrollbar into my Python program with Tkinter. The problem is that the scrollbar can scroll above the top and below the bottom of the frame. Moreover, the scrollbar looks deactivated, even though the buttons and mouse wheel work.

Most questions answered before use the .pack method, which I can't/don't want to use. Moreover, in most examples the objects are added to the frame in the same scope where the scrollbar code is, but I want to do that through the loadColumnNames method instead.

    import tkinter as tk
from tkinter import ttk
from tkinter import filedialog


class MainWindow(tk.Frame):

    def build_widgets(self):

        # Create a notebook to later embed the tabs into it.
        self.nb = ttk.Notebook(self)
        self.nb.grid(row=1, column=0, columnspan=5, rowspan=5, sticky="NESW")

        tabStyle = ttk.Style()
        tabStyle.configure("new.TFrame", background="white")

        #---------------------------------------------------
        # Column renaming tab.
        #---------------------------------------------------

        tab2 = ttk.Frame(self.nb, style="New.TFrame")
        self.nb.add(tab2, text="Column Renaming")

        frame = tk.Frame(tab2)
        frame.grid(row=1, column=2, sticky="nesw")

        button = tk.Button(frame, text="Load Column Names", command=self.loadColumnNames)
        button.grid(row=1, column=0, sticky="nesw", padx=self.PadLeft)

        wrapperFrame = tk.LabelFrame(tab2)

        self.columnNamesCanvas = tk.Canvas(wrapperFrame)
        self.columnNamesCanvas.grid(row=0, column=0, sticky="nesw")

        yscrollbar = ttk.Scrollbar(wrapperFrame, orient="vertical", command=self.columnNamesCanvas.yview)
        yscrollbar.grid(row=0, column=4, sticky="ns")

        self.columnNamesCanvas.configure(yscrollcommand=yscrollbar.set)

        self.columnNamesFrame = tk.Frame(self.columnNamesCanvas)
        self.columnNamesCanvas.create_window((0,0), window=self.columnNamesFrame, anchor="nw")

        wrapperFrame.grid(row=1, column=1, sticky="nesw")

        self.columnNamesFrame.bind("<Enter>", self._bound_to_mousewheel)
        self.columnNamesFrame.bind("<Leave>", self._unbound_to_mousewheel)

        return

    def __init__(self, master=None):
        self.PadLeft = 10
        self.columNamesFrameVars = {}

        tk.Frame.__init__(self, master, bg=BG)
        self.pack()
        self.build_widgets()

        return

    def _bound_to_mousewheel(self, event):
        self.columnNamesCanvas.bind_all("<MouseWheel>", self._on_mousewheel)

    def _unbound_to_mousewheel(self, event):
        self.columnNamesCanvas.unbind_all("<MouseWheel>")

    def _on_mousewheel(self, event):
        self.columnNamesCanvas.yview_scroll(int(-1*(event.delta/120)), "units")

    def loadColumnNames(self):

        self.columNamesFrameVars = {}
        
        columns = ["a", "b", "c", "d", "e", "f", "h", "i", "j", "k", "l", "m", "n"]

        i = 0
        for column in columns:

            self.columNamesFrameVars[column] = []

            # New column name.
            newname = tk.Entry(self.columnNamesFrame)
            newname.grid(row=i, column=3, padx=self.PadLeft)
            newname.insert(0, str(column))
            self.columNamesFrameVars[column].append(newname)

            i += 1

        self.columnNamesCanvas.bind('<Configure>', lambda e: self.columnNamesCanvas.configure(scrollregion = self.columnNamesCanvas.bbox("all")))

        return


if __name__ == '__main__':
    BG = "white"

    root = tk.Tk()
    root.wm_title("")
    MainWindow(root)
    root.mainloop()

How can I "activate" the scrollbar? How can I limit the scrolling to the first and last element in the frame?

Upvotes: 0

Views: 322

Answers (1)

Novel
Novel

Reputation: 13729

You have the <Configure> event bound to the Canvas, when it should be bound to the Frame. You also need to move that into build_widgets method.

For cleanliness sake, I also recommend you move away from lambda and into a real method. So add this to end of build_widgets:

self.columnNamesFrame.bind('<Configure>', self._on_configure)
#     ^ note this is the Frame, not the Canvas

And add this method:

def _on_configure(self, event=None):
    self.columnNamesCanvas.configure(scrollregion = self.columnNamesCanvas.bbox("all"))

Upvotes: 2

Related Questions