包含可滚动 canvas 的框架未占用剩余的分配 Tk window

Frame containing a scrollable canvas is not taking up the rest of the allotted Tk window

我编写了一个应用程序,允许用户select 一个目录并加载目录中的信息。然后,用户可以 select 将文件的哪些方面显示在图中。该图放置在用户可以滚动的 canvas window 内的 Tkinter-matplotlib canvas 中。我遇到的问题是包含可滚动框架的框架(StartPage 中的 canvas_frame)没有采用 Tkinter window.

中分配的 space

下面的代码重现了这个问题,图像就是应用程序的样子。可滚动框架的大部分代码取自 ex. 1 and ex. 2。应用程序的图像在这里:

https://imgur.com/ELXmehG

from tkinter import Tk, Frame, Canvas
from tkinter.ttk import Scrollbar
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg as FigCanvas
from matplotlib.figure import Figure

class Scrollable(Frame):    
    def __init__(self, frame, fig, width=16):

        # Base class initialization
        Frame.__init__(self, frame)

        # Instance variable for tkinter canvas
        self.tk_cnv = Canvas(frame, highlightthickness=0)
        self.tk_cnv.pack(side='left', fill='both', expand=True)

        # Instance variable for the scroll-bar
        v_scroll = Scrollbar(frame, width=width)
        v_scroll.pack(side="right", fill="y", expand=False)
        v_scroll.config(command=self.tk_cnv.yview)
        v_scroll.activate(" ")

        # Instance variable for the matplotlib canvas
        self.mpl_cnv = FigCanvas(fig, frame)
        self.cnv_widget = self.mpl_cnv.get_tk_widget()

        self.cnv_widget.config(yscrollcommand=v_scroll.set)
        self.cnv_widget.bind("<Configure>", self.__fill_canvas)

        # Assign frame generated by the class to the canvas
        # and create a scrollable window for it.
        self.windows_item = \
            self.tk_cnv.create_window((0, 900), window=self.cnv_widget, anchor='e',
                                      tag='self.canvas')

        self.tk_cnv.config(scrollregion=self.tk_cnv.bbox("all"))

    def __fill_canvas(self, event):
        # Enlarge the windows item to the canvas width
        canvas_width = event.width
        canvas_height = event.height
        self.tk_cnv.itemconfig(self.windows_item, width=canvas_width,
                               height=canvas_height)

class StartPage(Frame):
    """ Tkinter based class for single frame upon which widgets
    such as buttons, check-buttons, and entry are used as a
    simple graphical user interface.
    """
    LARGE_FONT = ("Veranda", 12)

    def __init__(self, parent, controller):
        Frame.__init__(self, parent)

        # Instance variables with page/window info of current frame
        self.window = parent

        # Instance variable for third row of widgets
        self.canvas_frame = Frame(self.window, relief="sunken")
        self.canvas_frame.grid(row=0, column=0, pady=5, sticky="news")

        # Instance variables for the figure
        self.plot_fig = Figure(figsize=[14.0, 18.0])

        # Instance variable for the frame with scrolling functionality
        self.canvas_body = Scrollable(self.canvas_frame, self.plot_fig)
        self.canvas = self.canvas_body.mpl_cnv

        self.canvas_setup()

    def canvas_setup(self):
        self.canvas_frame.grid_rowconfigure(2, weight=1)
        self.canvas_frame.grid_columnconfigure(0, weight=1)

class MainContainer(Tk):
    """ Tkinter based class used to generate a single window
    and contain a single frame. The frame contains multiple
    widgets for user choice and visualization.
    """

    def __init__(self, *args, **kwargs):
        Tk.__init__(self, *args, **kwargs)

        Tk.wm_title(self, "Sequence Viewer")

        Tk.wm_resizable(self, width=True, height=True)

        container = Frame(self)
        container.grid_configure(row=0, column=0, sticky="nsew")
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.frames = {}

        frame = StartPage(container, self)

        self.frames[StartPage] = frame

        self.show_frame(StartPage)

        self.center_window()

    def show_frame(self, frame_to_add):
        frame = self.frames[frame_to_add]
        frame.tkraise()

    def center_window(self):
        w = 1100
        h = 900
        sw = self.winfo_screenwidth()
        sh = self.winfo_screenheight()
        x = (sw - w) / 2
        y = (sh - h) / 2

        self.geometry('%dx%d+%d+%d' % (w, h, x, y))

if __name__ == "__main__":
    app = MainContainer()
    app.mainloop()

问题的主要原因是使用了 MainContainer class 中的初始 Frame 对象来实例化包含 Scrollable class 的 Frame。 MainContainer的对象应该是传过来的,这是因为它继承自Tk class.

其次,window 经理的混合导致了问题。因此,我转而专门打包。解决方案如下。

from tkinter import Tk, Frame, Canvas
from tkinter.ttk import Scrollbar
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg as FigCanvas
from matplotlib.figure import Figure

class Scrollable(Frame):
    def __init__(self, frame, fig, width=16):

        # Base class initialization
        Frame.__init__(self, frame)

        # Instance variable for tkinter canvas
        self.tk_cnv = Canvas(frame, highlightthickness=0)
        self.tk_cnv.pack(side="left", anchor="nw", fill="both",
                         expand=True)

        # Instance variable for the scroll-bar
        v_scroll = Scrollbar(frame)
        v_scroll.pack(side="right", fill="y", expand=False)
        v_scroll.config(command=self.tk_cnv.yview, width=width)
        v_scroll.activate("slider")

        # Instance variable for the matplotlib canvas
        self.mpl_cnv = FigCanvas(fig, frame)
        self.cnv_widget = self.mpl_cnv.get_tk_widget()

        self.tk_cnv.config(yscrollcommand=v_scroll.set)
        self.tk_cnv.bind("<Configure>", self.__fill_canvas)

        # Assign frame generated by the class to the canvas
        # and create a scrollable window for it.
        self.windows_item = \
            self.tk_cnv.create_window((0, 900), window=self.cnv_widget, anchor='e',
                                      tag='self.canvas')

        self.tk_cnv.config(scrollregion=self.tk_cnv.bbox("all"))

    def __fill_canvas(self, event):
        # Enlarge the windows item to the canvas width
        canvas_width = event.width
        canvas_height = event.height * 2.825
        self.tk_cnv.itemconfig(self.windows_item, width=canvas_width,
                               height=canvas_height)


class StartPage(Frame):
    """ Tkinter based class for single frame upon which widgets
    such as buttons, check-buttons, and entry are used as a
    simple graphical user interface.
    """
    LARGE_FONT = ("Veranda", 12)

    def __init__(self, parent, controller):
        Frame.__init__(self, parent)

        self.controller = controller

        # Instance variable for third row of widgets
        self.canvas_frame = Frame(self.controller, relief="sunken")
        self.canvas_frame.pack(side="top", anchor="nw", fill="both",
                               expand=True)

        # Instance variables for the figure
        self.plot_fig = Figure(figsize=[14.0, 18.0])

        # Instance variable for the frame with scrolling functionality
        self.canvas_body = Scrollable(self.canvas_frame, self.plot_fig)
        self.canvas = self.canvas_body.mpl_cnv

        # Instance variable for third row of widgets
        self.control_frame = Frame(self.controller, relief="sunken")
        self.control_frame.pack(side="right", anchor="ne", fill="y",
                                expand=True)


class MainContainer(Tk):
    """ Tkinter based class used to generate a single window
    and contain a single frame. The frame contains multiple
    widgets for user choice and visualization.
    """

    def __init__(self, *args, **kwargs):
        Tk.__init__(self, *args, **kwargs)

        Tk.wm_title(self, "Sequence Viewer")

        Tk.wm_resizable(self, width=True, height=True)

        container = Frame(self)
        container.pack_configure(side="top", anchor="nw", fill="both")

        self.frames = {}

        frame = StartPage(container, self)

        self.frames[StartPage] = frame

        self.show_frame(StartPage)

        self.center_window()

    def show_frame(self, frame_to_add):
        frame = self.frames[frame_to_add]
        frame.tkraise()

    def center_window(self):
        w = 1100
        h = 900
        sw = self.winfo_screenwidth()
        sh = self.winfo_screenheight()
        x = (sw - w) / 2
        y = (sh - h) / 2

        self.geometry('%dx%d+%d+%d' % (w, h, x, y))

if __name__ == "__main__":
    app = MainContainer()
    app.mainloop()