0
votes

I'm trying to create a GUI for timeseries data. It has 2 scrollable canvas and a frame within each. The top frame contains other frames within. The main_frame seems to expand with the canvas but catmainframes don't seem to do that. catmainframe is used to generate frames dynamically.

import os
import tkinter as tk
#from tkinter import ttk
from tkinter import filedialog as fd
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

def on_configure(event):
    # update scrollregion after starting 'mainloop'
    canvas1.configure(scrollregion=canvas1.bbox('all'))
    canvas2.configure(scrollregion=canvas2.bbox('all'))

def enterCategory():
    global cat
    global catmainframe
    global rownum
    global add_file
    cat = category.get()
    catmainframe = tk.Frame(main_frame, borderwidth=2, relief="solid")
    catmainframe.grid(row=rownum,column=0,sticky='nsew', padx=3, pady=3)
    #canvas1.create_window((0,0), window=catmainframe,anchor='nw')

    catmainframe.grid_rowconfigure(rownum, weight=1)
    catmainframe.grid_columnconfigure(0, weight=1)     
    catframe = tk.Frame(catmainframe, borderwidth=2, relief="solid")
    catframe.grid(row=0,column=0, sticky='nsew', padx=1, pady=1)

    catlabel = tk.Label(catframe, text=cat)
    catlabel.grid(row=0, column=0, sticky='nsew')

    add_file = tk.Button(catframe,text="Add File",command=openFile)
    add_file.grid(row=0, column=1, sticky='nsew')

    global catchildframe
    catchildframe = tk.Frame(catmainframe, borderwidth=2, relief="solid")
    catchildframe.grid(row=1,column=0,sticky='nsew', padx=1, pady=1)
    catchildframe.grid_rowconfigure(1, weight=1)
    catchildframe.grid_columnconfigure(1, weight=1)  
    global box1, box2, box3
    box1 = tk.Frame(catchildframe, borderwidth=2, relief="solid")
    box2 = tk.Frame(catchildframe, borderwidth=2, relief="solid")
    box3 = tk.Frame(catchildframe, borderwidth=2, relief="solid")
    box1.grid(row=1, column=0, sticky='nsew', padx=10, pady=10)
    box2.grid(row=1, column=1, sticky='nsew', padx=10, pady=10)
    box3.grid(row=1, column=2, sticky='nsew', padx=10, pady=10)
    box1.propagate(1)
    box2.propagate(1)
    box3.propagate(1)

    rownum =  rownum +1

def openFile():
    global fname
    global mindatetime
    global maxdatetime
    parentname = catmainframe.winfo_parent()
    parent = catmainframe._nametowidget(parentname)

    #childname = catchildframe.winfo_parent()
    #child = catchildframe._nametowidget(childname)
    child = add_file.master

    print("Catmainframe parent:"+parentname)
    #print("Catchildframe parent:"+child)

    file_path=fd.askopenfilename()
    #print(file_path)

    file_name = os.path.basename(file_path)
    print(file_name)

    file_list = []

    file_list.append(file_name)

    df = pd.read_csv(file_path)
    names = list(df.columns[0:])
    indexcol = names[0]
    #print(indexcol)
    df = df.rename(columns = {indexcol:'datetime'})
    names = list(df.columns[1:])
    #print(names)
    df.datetime = pd.to_datetime(df.datetime)
    df.set_index('datetime',inplace=True)

    if mindatetime == pd.to_datetime('1900-01-01 00:00:00'):
        mindatetime = df.index.min()
    elif mindatetime > df.index.min():
        mindatetime = df.index.min()

    if maxdatetime == pd.to_datetime('1900-01-01 00:00:00'):
        maxdatetime = df.index.max()
    elif maxdatetime < df.index.max():
        maxdatetime = df.index.max()

    print(mindatetime)
    print(maxdatetime)    
    global unique_dates
    unique_dates = []
    unique_dates = df.index.map(pd.Timestamp.date).unique()

    for x in range(len(names)):    
        if(len(names)==1):
            l = tk.Checkbutton(box1, text=names[x], variable=names[x],state='disabled')
            l.select()
            l.pack(anchor = 'w')
        else:
            l = tk.Checkbutton(box1, text=names[x], variable=names[x])
            l.select()
            l.pack(anchor = 'w')

    figure = plt.Figure(figsize=(4,3), dpi=100)
    ax2 = figure.add_subplot(111)
    line = FigureCanvasTkAgg(figure, box2)
    line.get_tk_widget().grid(row=1,column=1,sticky='nsew')
    df.plot(kind='line', legend=False, ax=ax2, fontsize=10)
    ax2.set_title(cat)
    ax2.set_xlim(mindatetime,maxdatetime)

    for x in range(len(unique_dates)):
        d = tk.Checkbutton(box3, text=unique_dates[x], variable=unique_dates[x])
        d.select()
        d.pack(anchor = 'w')

root = tk.Tk()
root.geometry('{}x{}'.format(800, 600))

# layout all of the main containers
root.grid_rowconfigure(1, weight=1)
root.grid_columnconfigure(0, weight=1)


#Global variables
category = tk.StringVar()
global rownum
rownum =0
mindatetime = pd.to_datetime('1900-01-01 00:00:00')
maxdatetime = pd.to_datetime('1900-01-01 00:00:00')

#Top frame
top_frame = tk.Frame(root)
top_frame.grid(row=0, column=0, sticky='nsew')

category_name = tk.Label(top_frame, text='Category:')
category_name.grid(row=0, column=0, sticky='nsew')

entry_category = tk.Entry(top_frame, background="pink",textvariable = category)
entry_category.grid(row=0, column=1, sticky='nsew')
entry_category.focus()

ok_button = tk.Button(top_frame, text="OK", command=enterCategory)
ok_button.grid(row=0, column=2, sticky='nsew')

xscrollbar = tk.Scrollbar(root, orient='horizontal')
xscrollbar.grid(row=3, column=0, sticky='ew')

yscrollbar = tk.Scrollbar(root)
yscrollbar.grid(row=1, column=1, sticky='ns')

canvas1 = tk.Canvas(root, bd=0,#scrollregion=(0, 0, 1000, 1000),
                yscrollcommand=yscrollbar.set)
canvas1.grid(row=1, column=0, sticky='nsew')

# create the center widgets
canvas1.grid_rowconfigure(1, weight=1)
canvas1.grid_columnconfigure(0, weight=1)

canvas2 = tk.Canvas(root, bd=0,#scrollregion=(0, 0, 1000, 1000),
                xscrollcommand=xscrollbar.set)
canvas2.grid(row=2, column=0, sticky='nsew')

xscrollbar.config(command=canvas2.xview)
yscrollbar.config(command=canvas1.yview)


canvas1.config(scrollregion=canvas1.bbox("all"))

canvas2.config(scrollregion=canvas2.bbox("all"))

main_frame = tk.Frame(canvas1)

canvas1.create_window((0,0), window=main_frame,anchor='nw')
#main_frame.grid(row=0,column=0,stick='nsew')

time_box = tk.Frame(canvas2)
canvas2.create_window((0,0), window=time_box,anchor='nw')

root.bind('<Configure>', on_configure) 
root.mainloop()

Below is the error even though the application seems to run fine.

Traceback (most recent call last):
  File "C:\Users\ranji\Anaconda3\lib\tkinter\__init__.py", line 1702, in __call__
    return self.func(*args)
  File "C:\Users\ranji\Anaconda3\lib\tkinter\__init__.py", line 3069, in set
    self.tk.call(self._w, 'set', first, last)
_tkinter.TclError: invalid command name ".!scrollbar2"
Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\ranji\Anaconda3\lib\tkinter\__init__.py", line 1702, in __call__
    return self.func(*args)
  File "C:\Users\ranji\Anaconda3\lib\tkinter\__init__.py", line 3069, in set
    self.tk.call(self._w, 'set', first, last)
_tkinter.TclError: invalid command name ".!scrollbar"
1
Please try to reduce this down to a minimal reproducible example. There seems to be a lot of code there that isn't directly related to the question. - Bryan Oakley

1 Answers

0
votes

You have to manually manage the size of an inner frame yourself. You can do this by explicitly setting the width of the inner window to be the width of the canvas (minus any desired borders or margins) whenever the canvas changes size. You can get a callback when the window changes size by binding to the <Configure> event.

I've pared your code down to just the issue being asked about. Notice in the following code that I added a tag to the embedded window object so that it can be referenced in the on_configure function. I've also colorized the inner frame to make it easier to visualize, and given it a height since there are no widgets inside.

The important part of this example is the call to canvas1.itemconfigure in on_configure:

import tkinter as tk

def on_configure(event):
    width = canvas1.winfo_width()
    canvas1.itemconfigure("main_frame", width=width)

root = tk.Tk()
root.grid_rowconfigure(1, weight=1)
root.grid_columnconfigure(0, weight=1)

yscrollbar = tk.Scrollbar(root)
canvas1 = tk.Canvas(root, bd=0,yscrollcommand=yscrollbar.set)

canvas1.grid(row=1, column=0, sticky='nsew')
yscrollbar.grid(row=1, column=1, sticky='ns')

main_frame = tk.Frame(canvas1, background="bisque", height=200)
canvas1.create_window((0,0), window=main_frame,anchor='nw', tags=("main_frame",))

root.bind('<Configure>', on_configure)
root.mainloop()