0
votes

here is my code, I checked few other questions about scrollbar with tkinter, and still can not figure out why my devices (button in the frame inside a canvas can not scroll)

the scroll bar and canvas seems get the configure event, and also scrollbar and canvas yview got called correctly, but the frame inside not scroll with it. Did I missing something ?


import tkinter as tk

import tkinter.font as tkFont
from tkinter import StringVar

c20 = None
c16 = None


class DebugScrollbar(tk.Scrollbar):
   def set(self, *args):
    print ("SCROLLBAR SET", args)
    tk.Scrollbar.set(self, *args)

class DebugCanvas(tk.Canvas):
   def yview(self, *args):
    print("Canvas yview" , args)
    tk.Canvas.yview(self, *args)


class Application:
  def __init__(self):
    master = tk.Tk()
    global c20, c16
    c20 = tkFont.Font( family ='Helvetica', size = 20)
    c18 = tkFont.Font( family ='Helvetica', size = 18)
    c12 = tkFont.Font( family ='Helvetica', size = 12)

    self.c16 = c16 = tkFont.Font( family ='Helvetica', size = 16)
    self.myIp = tk.StringVar()

    self.master = master
    master.option_add('*Font', c18)
    self.c20 = c20
    self.c24 = tkFont.Font( family='Helvetica', size=24)
    ##self._geom='200x200+0+0'
    ##master.geometry("{0}x{1}+0+0".format( master.winfo_screenwidth()-pad, master.winfo_screenheight()-pad))

    master.title("Safe Home")
    #master.attributes("-fullscreen", True) 

    self.topBar = tk.Frame(master)

    self.topBar.grid(columnspan=5, sticky=tk.N)
    self.wifiStatus = tk.StringVar()
    self.wifiStatus.set("WIFI1 %d" )

    self.wifi = tk.Label(self.topBar, textvariable= self.wifiStatus, borderwidth=2, relief="ridge", height=4, width=8)
    self.wifi.grid(sticky=tk.E, row=1, column = 0 , padx=3, pady=3)



    self.mainFrame = tk.Frame(self.master, relief=tk.GROOVE, bd=1)
    self.mainFrame.grid(row=2, column=0 , columnspan=5 , rowspan=3, padx=3, pady=3)

    self.prepareDevices(self.mainFrame)

    self.devices.pack( side= tk.LEFT,  padx=3, pady=3 )

    #self.greet_button["font"]=c20

    self.close_button = tk.Button(master, text="Close", command=master.quit)
    self.close_button.grid(columnspan=1, sticky=tk.SE, row=6, column=4 )
    #self.close_button["font"]=c20
    self.addDevices(self.devices)


  def prepareDevices(self, insideFrame):
    insideFrame.grid_rowconfigure(0, weight=1)
    insideFrame.grid_columnconfigure(0, weight=1)
    vscrollbar = DebugScrollbar( insideFrame, orient=tk.VERTICAL)
    vscrollbar.grid(row=0, column=1, sticky=tk.N+tk.S)
    self.canvas = canvas = DebugCanvas( insideFrame, background = "#D2D2D2",# bd=1, relief='solid', highlightthickness=1,
                            #scrollregion=(0, 0, 1000, 1000), 
                            yscrollcommand=vscrollbar.set)

    vscrollbar.config(command = canvas.yview)
    canvas.config( yscrollcommand = vscrollbar.set )
    canvas.config(width = 100, height = 280)

    canvas.grid(row=0, column=0, sticky=tk.N+tk.S+tk.E+tk.W)

    vscrollbar.config(command = canvas.yview)
    canvas.config( yscrollcommand = vscrollbar.set )

    # reset the view
    #canvas.xview_moveto(0)
    #canvas.yview_moveto(0)

    # create a frame inside the canvas which will be scrolled with it
    self.devices = interior = tk.Frame(canvas #, bd=2 , relief='groove'
                                       )
    interior_id = canvas.create_window(0, 0, window=interior,  anchor=tk.NW)
    def _configure_interior(event):
        # update the scrollbars to match the size of the inner frame
        print("Configure interior event: %s" % (event))
        size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
        sizeStr= "0 0 %s %s" % size
        print(sizeStr)
        canvas.config( scrollregion = (0,0, interior.winfo_reqwidth(), interior.winfo_reqheight()) )
        print ("canvas adjust region")

        if interior.winfo_reqwidth() != canvas.winfo_width():
            # update the canvas's width to fit the inner frame
            canvas.config(width=interior.winfo_reqwidth())
        canvas.config(width= 180, height = 280)
    interior.bind('<Configure>', _configure_interior)

    def _configure_canvas(event):
        ##
        print("Configure canvas %s" % (event))
        if interior.winfo_reqwidth() != canvas.winfo_width():
            # update the inner frame's width to fill the canvas
            canvas.itemconfigure(interior_id, width=canvas.winfo_width())
            print ("canvas adjust width")

    canvas.bind('<Configure>', _configure_canvas)

    # track changes to the canvas and frame width and sync them,
    # also updating the scrollbar


  def addDevices(self, deviceFrame):
    for i in range(1 , 20):
        d = Device("1D:1s:%d" % i)
        l = DeviceLabel(deviceFrame, name="device %s" % i, macAddress=d.macAddress,  mainWindow = self)



class Device():
  def __init__(self, macAddress):
    self.macAddress = macAddress
    pass

class DeviceLabel( ):
  def __init__(self, parent, name, mainWindow, macAddress = None, status=None):
    global c16
    self.text = tk.StringVar()
    self.label= tk.Button( parent, textvariable = self.text, bd=1, relief="groove", height=3, width=15, command = lambda: mainWindow.deviceDetail(macAddress))
    self.macAddress = macAddress
    self.status = status
    self.name = name
    self.text.set(name)
    self.label["font"] = c16
    self.label.pack() 

my_gui = Application()
my_gui.master.mainloop()
1
to make code more readable don't put method inside method. - furas
read PEP 8 -- Style Guide for Python Code, i.e. put empty line before every def, put global as first line in method, etc. It makes code more readable. - furas
Please provide a minimal reproducible example instead of an entire code. - Nae

1 Answers

3
votes

Inner Frame (self.devices) is element on Canvas, not part of external frame nor any other widget/windows so you shouldn't do

self.devices.pack(side=tk.LEFT, padx=3, pady=3)

Remove this line and it will work correctly.