将 header 行添加到具有权重的可滚动 canvas

Adding a header line to a scrollable canvas with weights

我正在尝试从文件夹中获取 .xlsm 文件的列表,并生成一个可滚动的 canvas,可以使用复选按钮从中手动选择导入所需的选项卡(所有选项卡都具有相同的选项卡格式,例如 tab1、tab2、tab3、tab4)。

我遇到的主要问题是让 header 的权重相对于它们的 canvas 列正确工作,因为较长的文件名会扭曲权重。

我试过使用权重,但似乎无法找到解决方法。我也尝试使用 treeview 作为替代方案,但这似乎会引入使用复选按钮的更大问题。如果 header 被放置在 canvas 本身内,是否可以冻结顶行,或者我可以实现类似绑定的东西,以便 header 帧的各个列与宽度对齐canvas 框架的列数?

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()

最简单的解决方案是使用两个画布,然后设置一个绑定,以便每当内部框架的大小发生变化时,您都会更新 headers 以匹配列。

它可能看起来像这样:

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])