包含可滚动画布的帧没有占据分配给Tk窗口的剩余部分。
包含可滚动画布的帧没有占据分配给Tk窗口的剩余部分。
我编写了一个应用程序,允许用户选择一个目录并加载目录中的信息。然后用户可以选择在图表中显示文件的哪些方面。图表放置在一个Tkinter-matplotlib画布中,该画布位于用户可以滚动的画布窗口中。我遇到的问题是包含可滚动框架的帧(在StartPage中的canvas_frame)不会占用Tkinter窗口中的分配空间。
以下代码复制了问题,并且图片显示了应用程序的样子。可滚动框架的大部分代码来自ex. 1和ex. 2。应用程序的图片在这里:
https://i.stack.imgur.com/jle7H.jpg
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): Frame.__init__(self, frame) self.tk_cnv = Canvas(frame, highlightthickness=0) self.tk_cnv.pack(side='left', fill='both', expand=True) 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(" ") 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("", self.__fill_canvas) 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): canvas_width = event.width canvas_height = event.height self.tk_cnv.itemconfig(self.windows_item, width=canvas_width, height=canvas_height) class StartPage(Frame): LARGE_FONT = ("Veranda", 12) def __init__(self, parent, controller): Frame.__init__(self, parent) self.window = parent self.canvas_frame = Frame(self.window, relief="sunken") self.canvas_frame.grid(row=0, column=0, pady=5, sticky="news") self.plot_fig = Figure(figsize=[14.0, 18.0]) 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): 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()
问题的原因是在实例化包含Scrollable类的Frame时,使用了MainContainer类的初始Frame对象。应该传递MainContainer对象,因为它继承自Tk类。
解决方法是改用pack作为唯一的窗口管理器。以下是解决方案的代码示例:
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("", 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()