Skip to content
Advertisement

Adding a header line to a scrollable canvas with weights

I’m trying get a list of .xlsm files from a folder, and generate a scrollable canvas from which the tabs needed for import can be selected manually using the check buttons (all having the same tab format e.g. tab1, tab2, tab3, tab4).

The major issue I’m having is getting weights to work correctly for the headers in relation to their canvas columns, as longer file names distorts the weight.

Files with short names

Longer file names distorts the column weights

I’ve tried playing with the weights and can’t seem to figure out a workaround. I also attempted using treeview as an alternative but this seems to introduce far bigger issues with using checkbuttons. Would it possible to freeze the top row if the headers were placed inside the canvas itself, or could I implement something like a bind so that the header frames individual columns align with the width of the columns of the canvas frame?

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


class MainFrame:
    def __init__(self, master):
        master.geometry('1000x200')

        self.master_tab = ttk.Notebook(master)
        self.master_tab.grid(row=0, column=0, sticky='nsew')

        # Sub-Classes
        self.file_select = FileSelect(self.master_tab, main=self)


class FileSelect:
    def __init__(self, master, main):
        self.main = main

        # ================== Primary Frame ==================
        self.primary_frame = tk.Frame(master)
        self.primary_frame.grid(row=0, column=0, sticky='NSEW')
        master.add(self.primary_frame, text='Import Selection')
        self.primary_frame.columnconfigure(0, weight=1)
        self.primary_frame.rowconfigure(1, weight=1)

        # ================== File Selection Frame ==================
        self.selection_frame = tk.Frame(self.primary_frame)
        self.selection_frame.grid(row=0, column=0, sticky='EW')
        # Button - Select Directory
        self.fp_button = tk.Button(self.selection_frame, text='Open:', command=self.directory_path)
        self.fp_button.grid(row=0, column=0, sticky='W')
        # Label - Display Directory
        self.fp_text = tk.StringVar(value='Select Import Directory')
        self.fp_label = tk.Label(self.selection_frame, textvariable=self.fp_text, anchor='w')
        self.fp_label.grid(row=0, column=1, sticky='W')

        # ================== Canvas Frame ==================
        self.canvas_frame = tk.Frame(self.primary_frame)
        self.canvas_frame.grid(row=1, column=0, sticky='NSEW')
        self.canvas_frame.rowconfigure(1, weight=1)
        # Canvas Header Labels
        for header_name, x in zip(['File Name', 'Tab 1', 'Tab 2', 'Tab 3', 'Tab 4'], range(5)):
            tk.Label(self.canvas_frame, text=header_name, anchor='w').grid(row=0, column=x, sticky='EW')
            self.canvas_frame.columnconfigure(x, weight=1)
        # Scroll Canvas
        self.canvas = tk.Canvas(self.canvas_frame, bg='#BDCDFF')
        self.canvas.grid(row=1, column=0, columnspan=5, sticky='NSEW')
        self.canvas.bind('<Configure>', self.frame_width)
        # Scrollbar
        self.scroll_y = tk.Scrollbar(self.canvas_frame, orient="vertical", command=self.canvas.yview)
        self.scroll_y.grid(row=1, column=5, sticky='NS')
        # Canvas Sub-Frame
        self.canvas_sub_frame = tk.Frame(self.canvas)
        for x in range(5):
            self.canvas_sub_frame.columnconfigure(x, weight=1)
        self.canvas_frame_window = self.canvas.create_window(0, 0, anchor='nw', window=self.canvas_sub_frame)
        self.canvas_sub_frame.bind('<Configure>', self.config_frame)

    def config_frame(self, event):
        self.canvas.configure(scrollregion=self.canvas.bbox('all'), yscrollcommand=self.scroll_y.set)

    def frame_width(self, event):
        canvas_width = event.width
        event.widget.itemconfigure(self.canvas_frame_window, width=canvas_width)

    def directory_path(self):
        try:
            # Select file path
            directory = filedialog.askdirectory(initialdir='/', title='Select a directory')
            self.fp_text.set(str(directory))
            os.chdir(directory)

            # Updates GUI with .xlsm file list & checkboxes
            if len(os.listdir(directory)) != 0:
                y = -1
                for tb in os.listdir(directory):
                    if not tb.endswith('.xlsm'):
                        print(str(tb) + ' does not have ;.xlsm file extension')
                    else:
                        y += 1
                        file_name = tk.Label(self.canvas_sub_frame, text=tb, anchor='w', bg='#96ADF3')
                        file_name.grid(row=y, column=0, sticky='EW')
                        for x in range(4):
                            tb_period = tk.Checkbutton(self.canvas_sub_frame, anchor='w', bg='#C2D0F9')
                            tb_period.grid(row=y, column=x+1, sticky='EW')
            else:
                print('No files in directory')

        # Filepath error handling exception
        except os.error:
            print('OS ERROR')


if __name__ == '__main__':
    root = tk.Tk()
    root.rowconfigure(0, weight=1)
    root.columnconfigure(0, weight=1)
    MainFrame(root)
    root.mainloop()

Advertisement

Answer

The simplest solution is to use two canvases, and then set up a binding so that whenever the size of the inner frame changes, you update the headers to match the columns.

It might look something like this:

def config_frame(self, event):
    self.canvas.configure(scrollregion=self.canvas.bbox('all'), yscrollcommand=self.scroll_y.set)
    self.canvas.after_idle(self.reset_headers)

def reset_headers(self):
    for column in range(self.canvas_sub_frame.grid_size()[0]):
        bbox = self.canvas_sub_frame.grid_bbox(column, 0)
        self.canvas_frame.columnconfigure(column, minsize = bbox[2])
User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement