Tkinter: Fully functional scrolledFrame with autohiding scrollbar, except for border

491 Views Asked by At

I put together many part of code found to make a fully functional scrolled Frame in tkinter (class ScrolledWindow). It's working good.

Still remaining a little problem when scrolling with mousewheel (when no scrollbar present). I supposed a problem of border or something like that but can't find the problem or the solution (quite new to tkinter). Some help will be appreciate to perfect this tkinter scrolledFrame.

The code sample:

# -*- coding: utf8 -*-
'''
Created on 16 juin 2016

@author: Chevalier Thierry
'''
import tkinter, tkinter.scrolledtext
import tkinter.ttk

class ScrolledWindow(tkinter.ttk.Frame):
    """
    Parent = master of scrolled window
    """
    def __init__(self, parent, *args, **kwargs):
        """Parent = master of scrolled window
       """
        super().__init__(parent, *args, **kwargs)
        self.parent = parent
        # creating a scrollbars and canvas
        self.xScrollbar = tkinter.ttk.Scrollbar(self, orient = 'horizontal')
        self.yScrollbar = tkinter.ttk.Scrollbar(self)
        self.scrollCanvas = tkinter.Canvas(self)
        ##self.scrollCanvas.config(relief = 'flat', width = 100, heigh = 100, bd = 0)
        self.scrollCanvas.config(relief = 'flat', bd = 0)
        # placing scrollbar and  canvas into frame
        self.xScrollbar.grid(column = 0, row = 1, sticky="NESW", columnspan = 2)
        self.yScrollbar.grid(column = 1, row = 0, sticky="NESW")
        self.scrollCanvas.grid(column = 0, row = 0, columnspan=1,rowspan=1,sticky="NESW",padx=0,pady=0,ipadx=0,ipady=0)
        self.grid_columnconfigure(0,weight=1)
        self.grid_columnconfigure(1,weight=0)
        self.grid_rowconfigure(0,weight=1)
        self.grid_rowconfigure(1,weight=0)
        # accociating scrollbar comands to canvas scroling
        self.xScrollbar.config(command = self.scrollCanvas.xview)
        self.yScrollbar.config(command = self.scrollCanvas.yview)
        self.scrollCanvas.config(xscrollcommand = self.xScrollbar.set, yscrollcommand = self.yScrollbar.set)
        # creating a frame to inserto to canvas
        self.scrollWindow = tkinter.ttk.Frame(self.scrollCanvas)
        self.scrollWindowItemId = self.scrollCanvas.create_window(0, 0, window = self.scrollWindow, anchor = 'nw')

        self.scrollWindow.bind('<Configure>', self._configure_scrollWindow)
        self.scrollCanvas.bind('<Configure>', self._configure_scrollCanvas)
        self.scrollWindow.bind('<Enter>', self._bound_to_mousewheel)
        self.scrollWindow.bind('<Leave>', self._unbound_to_mousewheel)

    def _bound_to_mousewheel(self, event):
        self.scrollCanvas.bind_all("<MouseWheel>", self._on_mousewheel)
    def _unbound_to_mousewheel(self, event):
        self.scrollCanvas.unbind_all("<MouseWheel>")
    def _on_mousewheel(self, event):
        self.scrollCanvas.yview_scroll(int(-1*(event.delta/120)), "units")

    def _configure_scrollWindow(self, event):
        print("_configure_scrollWindow:")
        print("    scrollWindow", "w=", event.width, "h", event.height)
        size = (self.scrollWindow.winfo_reqwidth(), self.scrollWindow.winfo_reqheight())
        print("    scrollWindow reqwidth and reqheight", "w=", size[0], ", h=", size[1])
        print("    scrollCanvas", "w=", self.scrollCanvas.winfo_width(), ", h=", self.scrollCanvas.winfo_height())

    def _configure_scrollCanvas(self, event):
        print("_configure_scrollCanvas:")
        print("    scrollCanvas", "w=", event.width, self.scrollCanvas.winfo_width(), ", h=", event.height, self.scrollCanvas.winfo_height())
        #=======================================================================
        # if self.scrollWindow.winfo_reqwidth() != self.scrollCanvas.winfo_width():
        #     # update the inner frame's width to fill the canvas
        #     self.scrollCanvas.itemconfig(self.scrollWindowItemId, width=self.scrollCanvas.winfo_width())
        # if self.scrollWindow.winfo_reqheight() != self.scrollCanvas.winfo_height():
        #     # update the inner frame's width to fill the canvas
        #     self.scrollCanvas.itemconfig(self.scrollWindowItemId, height=self.scrollCanvas.winfo_height())
        #=======================================================================
        canvasWidth, canvasHeight = (self.scrollCanvas.winfo_width(), self.scrollCanvas.winfo_height())
        canvasWidth, canvasHeight = (event.width, event.height)
        windowReqWidth, windowReqHeight = (self.scrollWindow.winfo_reqwidth(), self.scrollWindow.winfo_reqheight())
        if windowReqWidth < canvasWidth:
            if windowReqHeight < canvasHeight:
                # windowReqWidth < canvasWidth and windowReqHeight < canvasHeight
                self.scrollCanvas.itemconfig(self.scrollWindowItemId, width=canvasWidth, height=canvasHeight)
                ##self.scrollCanvas.config(scrollregion='0 0 %s %s' % (canvasWidth, canvasHeight))
                self.scrollCanvas.config(scrollregion=self.scrollCanvas.bbox("all"))
                self.tk.call("grid", "remove", self.xScrollbar)
                self.tk.call("grid", "remove", self.yScrollbar)
            else:
                # windowReqWidth < canvasWidth and windowReqHeight > canvasHeight
                self.scrollCanvas.config(width = canvasWidth, height = windowReqHeight)
                self.scrollCanvas.itemconfig(self.scrollWindowItemId, width=canvasWidth, height=windowReqHeight)
                ##self.scrollCanvas.config(scrollregion='0 0 %s %s' % (canvasWidth, windowReqHeight))
                self.scrollCanvas.config(scrollregion=self.scrollCanvas.bbox("all"))
                self.tk.call("grid", "remove", self.xScrollbar)
                self.yScrollbar.grid()
        else: # windowReqWidth >  canvasWidth
            if windowReqHeight < canvasHeight:
                # windowReqWidth > canvasWidth and windowReqHeight < canvasHeight
                self.scrollCanvas.config(width = windowReqWidth, height = canvasHeight)
                self.scrollCanvas.itemconfig(self.scrollWindowItemId, width=windowReqWidth, height=canvasHeight)
                ##self.scrollCanvas.config(scrollregion='0 0 %s %s' % (windowReqWidth, canvasHeight))
                self.scrollCanvas.config(scrollregion=self.scrollCanvas.bbox("all"))
                self.xScrollbar.grid()
                self.tk.call("grid", "remove", self.yScrollbar)
            else:
                # windowReqWidth > canvasWidth and windowReqHeight > canvasHeight
                self.scrollCanvas.config(width = windowReqWidth, height = windowReqHeight)
                self.scrollCanvas.itemconfig(self.scrollWindowItemId, width=windowReqWidth, height=windowReqHeight)
                ##self.scrollCanvas.config(scrollregion='0 0 %s %s' % (windowReqWidth, windowReqHeight))
                self.scrollCanvas.config(scrollregion=self.scrollCanvas.bbox("all"))
                self.xScrollbar.grid()
                self.yScrollbar.grid()

    def getScrollWindow(self):
        return self.scrollWindow

class tkTestingGUI(tkinter.ttk.Frame):
    def __init__(self, master):
        super().__init__(master)
        self.initialize()
        self.pack(fill="both", expand=1)
        self.master.resizable(True,True)

    def initialize(self):
               #
        # main frame definition
        #
        leftFrame=tkinter.ttk.Frame(self, borderwidth=0)
        rightFrame=tkinter.ttk.Frame(self, borderwidth=0)
        leftFrame.grid(column=0,row=0,columnspan=1,rowspan=1,sticky=tkinter.N+tkinter.S+tkinter.E+tkinter.W,padx=1,pady=1,ipadx=0,ipady=0)
        rightFrame.grid(column=1,row=0,columnspan=1,rowspan=1,sticky='NESW',padx=1,pady=1,ipadx=0,ipady=0)
        self.grid_columnconfigure(0,weight=1)
        self.grid_columnconfigure(1,weight=1)
        self.grid_rowconfigure(0,weight=1)

        scrolledLeftFrame = ScrolledWindow(self, borderwidth=0)
        rightFrame=tkinter.ttk.Frame(self, borderwidth=0)
        scrolledLeftFrame.grid(column=0,row=0,columnspan=1,rowspan=1,sticky=tkinter.N+tkinter.S+tkinter.E+tkinter.W,padx=1,pady=1,ipadx=0,ipady=0)
        rightFrame.grid(column=1,row=0,columnspan=1,rowspan=1,sticky="NESW",padx=1,pady=1,ipadx=0,ipady=0)
        self.grid_columnconfigure(0,weight=1)
        self.grid_columnconfigure(1,weight=1)
        self.grid_rowconfigure(0,weight=1)

        # get real inside left frame
        leftFrame = scrolledLeftFrame.getScrollWindow() # real inside window to put widgets

        #
        # left frame definition
        #
        self.idFrame = tkinter.ttk.Frame(leftFrame, borderwidth=0)
        ##self.idFrame.grid(column=0,row=0,columnspan=1,rowspan=1,sticky='NESW', padx=1,pady=1,ipadx=0,ipady=0)
        self.idFrame.grid(column=0,row=0,sticky='NESW')
        label = tkinter.ttk.Label(self.idFrame, text="Test").grid(column=0, row=0, sticky="NESW")
        label = tkinter.ttk.Label(self.idFrame, text="Test").grid(column=1, row=0, sticky="NESW")
        label = tkinter.ttk.Label(self.idFrame, text="Test").grid(column=2, row=0, sticky="NESW")

        label = tkinter.ttk.Label(self.idFrame, text="Test").grid(column=0, row=1, sticky="NESW")
        label = tkinter.ttk.Label(self.idFrame, text="Test").grid(column=1, row=1, sticky="NESW")
        label = tkinter.ttk.Label(self.idFrame, text="Test").grid(column=2, row=1, sticky="NESW")
        label = tkinter.ttk.Label(self.idFrame, text="Test", anchor="e").grid(column=3, row=1, sticky=tkinter.E)
        entry2 = tkinter.ttk.Entry(self.idFrame,text="A")
        entry2.grid(column=4,row=0,columnspan=1,rowspan=1,sticky='EW',padx=1,pady=1,ipadx=0,ipady=0)
        self.idFrame.grid_columnconfigure(0,weight=1)
        label = tkinter.ttk.Label(self.idFrame, text="Test").grid(column=0, row=2, sticky="NESW")
        label = tkinter.ttk.Label(self.idFrame, text="Test").grid(column=1, row=2, sticky="NESW")
        label = tkinter.ttk.Label(self.idFrame, text="Test").grid(column=2, row=2, sticky="NESW")
        label = tkinter.ttk.Label(self.idFrame, text="Test").grid(column=0, row=3, sticky="NESW")
        label = tkinter.ttk.Label(self.idFrame, text="Test").grid(column=1, row=3, sticky="NESW")
        label = tkinter.ttk.Label(self.idFrame, text="Test").grid(column=2, row=3, sticky="NESW")

        #
        self.activeEventsFrame = tkinter.ttk.Frame(leftFrame, borderwidth=0)
        self.activeEventsFrame.grid(column=0,row=1,columnspan=1,rowspan=1,sticky='NESW',padx=1,pady=1,ipadx=0,ipady=0)
        label = tkinter.ttk.Label(self.activeEventsFrame, text="Test").grid(column=0, row=0, sticky="NESW")
        label = tkinter.ttk.Label(self.activeEventsFrame, text="Test").grid(column=1, row=0, sticky="NESW")
        label = tkinter.ttk.Label(self.activeEventsFrame, text="Test long").grid(column=2, row=0, sticky="NESW")


        #
        leftFrame.grid_columnconfigure(0,weight=1)
        leftFrame.grid_rowconfigure(0,weight=1)
        leftFrame.grid_rowconfigure(1,weight=1)

        #
        # right frame definition
        #
        self.inputText = tkinter.scrolledtext.ScrolledText(rightFrame)
        self.inputText.insert(tkinter.END, "Paste your text here.....")
        self.inputText.grid(column=0,row=0,columnspan=1,rowspan=1,sticky="NESW",padx=1,pady=1,ipadx=0,ipady=0)
        #
        rightFrame.grid_columnconfigure(0,weight=1)
        rightFrame.grid_rowconfigure(0,weight=1)

        #
        # update main frame
        #
        self.update()

    def quit(self):
        pass

if __name__ == "__main__":
    tkinterTk = tkinter.Tk()
    app = tkTestingGUI(master=tkinterTk)
    app.master.title('Testing TK')
    app.mainloop()
1

There are 1 best solutions below

0
T. Knight On

I found the solution, finally, just need to add highlightthickness=0 option to canvas because default is not null.

Find below the fully functionnal scrolledFrame with autohiding scrollbars and mousewheel scrolling (little modifications added for better behaviour)

# -*- coding: utf8 -*-
'''
Created on 16 juin 2016

@author: Chevalier Thierry
'''
import tkinter
import tkinter.ttk

class ScrolledWindow(tkinter.ttk.Frame):
    """

    Parent = master of scrolled window

    """

    def __init__(self, parent, *args, **kwargs):
        """Parent = master of scrolled window
       """
        super().__init__(parent, *args, **kwargs)
        self.parent = parent
        # creating scrollbars and canvas and window to contain future widgets of the scrolling area
        self.xScrollbar = tkinter.ttk.Scrollbar(self, orient = 'horizontal')
        self.yScrollbar = tkinter.ttk.Scrollbar(self)
        self.scrollCanvas = tkinter.Canvas(self)
        self.scrollCanvas.config(relief = 'flat', borderwidth=0, highlightthickness=0)
        self.scrollWindow = tkinter.ttk.Frame(self.scrollCanvas, borderwidth=0)
        # placing scrollbar and  canvas into frame and the scrollFrame into canvas
        self.xScrollbar.grid(column = 0, row = 1, sticky="NESW", columnspan = 2) 
        self.yScrollbar.grid(column = 1, row = 0, sticky="NESW")     
        self.scrollCanvas.grid(column = 0, row = 0, columnspan=1,rowspan=1,sticky="NESW",padx=0,pady=0,ipadx=0,ipady=0)
        self.scrollWindowItemId = self.scrollCanvas.create_window(0, 0, window = self.scrollWindow, anchor = 'nw')
        self.grid_columnconfigure(0,weight=1)
        self.grid_columnconfigure(1,weight=0)
        self.grid_rowconfigure(0,weight=1)
        self.grid_rowconfigure(1,weight=0)
        # accociating scrollbar comands to canvas scroling
        self.xScrollbar.config(command = self.scrollCanvas.xview)
        self.yScrollbar.config(command = self.scrollCanvas.yview)
        self.scrollCanvas.config(xscrollcommand = self.xScrollbar.set, yscrollcommand = self.yScrollbar.set)
        ##self.yScrollbar.lift(self.scrollWindow) # put to first plan     
        ##self.xScrollbar.lift(self.scrollWindow)
        self.scrollCanvas.bind('<Configure>', self._configure_scrollCanvas)
        ##self.scrollWindow.bind('<Configure>', self._configure_scrollWindow)
        self.scrollWindow.bind('<Enter>', self._bound_to_mousewheel)
        self.scrollWindow.bind('<Leave>', self._unbound_to_mousewheel)

    def _bound_to_mousewheel(self, event):
        self.scrollCanvas.bind_all("<MouseWheel>", self._on_mousewheel)   
    def _unbound_to_mousewheel(self, event):
        self.scrollCanvas.unbind_all("<MouseWheel>") 
    def _on_mousewheel(self, event):
        self.scrollCanvas.yview_scroll(int(-1*(event.delta/120)), "units")  

    def _configure_scrollWindow(self, event):
        print("_configure_scrollWindow:")
        print("    event", "w=", event.width, "h", event.height)
        print("    scrollWindow", "w=", self.scrollWindow.winfo_width(), ", h=", self.scrollWindow.winfo_height())
        print("    scrollWindow reqwidth and reqheight", "w=", self.scrollWindow.winfo_reqwidth(), ", h=", self.scrollWindow.winfo_reqheight())
        print("    scrollCanvas", "w=", self.scrollCanvas.winfo_width(), ", h=", self.scrollCanvas.winfo_height())
    def _configure_scrollCanvas(self, event):
        #=======================================================================
        # print("_configure_scrollCanvas:")
        # print("    event", "w=", event.width, "h", event.height)
        # print("    scrollCanvas", "w=", self.scrollCanvas.winfo_width(), ", h=", self.scrollCanvas.winfo_height())
        # print("    scrollWindow", "w=", self.scrollWindow.winfo_width(), ", h=", self.scrollWindow.winfo_height())
        # print("    scrollWindow reqwidth and reqheight", "w=", self.scrollWindow.winfo_reqwidth(), ", h=", self.scrollWindow.winfo_reqheight())
        #=======================================================================
        ##canvasWidth, canvasHeight = (self.scrollCanvas.winfo_width(), self.scrollCanvas.winfo_height())
        canvasWidth, canvasHeight = (event.width, event.height)
        windowReqWidth, windowReqHeight = (self.scrollWindow.winfo_reqwidth(), self.scrollWindow.winfo_reqheight())   
        if canvasWidth < windowReqWidth:
            if  canvasHeight < windowReqHeight:
                # canvasWidth < windowReqWidth and canvasHeight < windowReqHeight
                self.scrollCanvas.config(width = windowReqWidth, height = windowReqHeight)
                self.scrollCanvas.itemconfig(self.scrollWindowItemId, width=windowReqWidth, height=windowReqHeight)
                ##self.scrollCanvas.config(scrollregion='0 0 %s %s' % (windowReqWidth, windowReqHeight))
                self.scrollCanvas.config(scrollregion=self.scrollCanvas.bbox("all"))
                self.xScrollbar.grid()
                self.yScrollbar.grid()

            else:
                # canvasWidth < windowReqWidth and windowReqHeight <= canvasHeight
                self.scrollCanvas.config(width = windowReqWidth)
                self.scrollCanvas.itemconfig(self.scrollWindowItemId, width=windowReqWidth, height=canvasHeight)
                ##self.scrollCanvas.config(scrollregion='0 0 %s %s' % (windowReqWidth, canvasHeight))
                self.scrollCanvas.config(scrollregion=self.scrollCanvas.bbox("all"))
                self.xScrollbar.grid()
                self.tk.call("grid", "remove", self.yScrollbar)
        else: #  windowReqWidth <= canvasWidth
            if canvasHeight < windowReqHeight:
                # windowReqWidth <= canvasWidth and canvasHeight < windowReqHeigh
                self.scrollCanvas.config(height = windowReqHeight)
                self.scrollCanvas.itemconfig(self.scrollWindowItemId, width=canvasWidth, height=windowReqHeight)
                ##self.scrollCanvas.config(scrollregion='0 0 %s %s' % (canvasWidth, windowReqHeight))
                self.scrollCanvas.config(scrollregion=self.scrollCanvas.bbox("all"))
                self.tk.call("grid", "remove", self.xScrollbar)
                self.yScrollbar.grid()
            else:
                # windowReqWidth <= canvasWidth and windowReqHeight <= canvasHeight
                self.scrollCanvas.itemconfig(self.scrollWindowItemId, width=canvasWidth, height=canvasHeight)
                self.scrollCanvas.config(scrollregion=self.scrollCanvas.bbox("all"))
                self.tk.call("grid", "remove", self.xScrollbar)
                self.tk.call("grid", "remove", self.yScrollbar)

    def getScrollWindow(self):
        return self.scrollWindow