【问题标题】:override pack/grid behaviour覆盖包/网格行为
【发布时间】:2017-05-08 09:07:44
【问题描述】:

我正在尝试制作一个自定义小部件,以提供可调整大小以适应其内容或窗口的可滚动框架,例如,内部框架的内容小于画布的视图,然后可以将内容拉伸到适合视图,或者如果它们是较小的滚动条,可以启用,但是为了使使用更简单,我希望能够覆盖内部框架内打包或网格小部件所需的任何方法。例如,而不是: tk.Button(scrollframe.inner, ...) 我希望能够使用:
tk.Button(scrollframe, ...)

但是我不知道该怎么做,我的代码目前是:

__all__ = ['ScrolledFrame']

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

class ScrolledFrame(tk.Frame):
    def __init__(self, master=None, *args, **kwargs):
        self.scrollbars = None
        self.scroll_shown= [False, False]
        if ('scrollbars' in kwargs):
            self.scrollbars = kwargs['scrollbars']
            del kwargs['scrollbars']

        tk.Frame.__init__(self, master, *args, **kwargs)

        self.grid_columnconfigure(1, weight=1)
        self.grid_rowconfigure(1, weight=1)

        self.vsb = tk.Scrollbar(self, orient='vertical')
        self.hsb = tk.Scrollbar(self, orient='horizontal')
        self.vsb.opts = {'column':2, 'row':1, 'sticky':'nesw'}
        self.hsb.opts = {'column':1, 'row':2, 'sticky':'nesw'}

        self.canvas = tk.Canvas(self, bd=0, highlightthickness=0,
                                yscrollcommand=self.vsb.set,
                                xscrollcommand=self.hsb.set)
        self.canvas.grid(column=1, row=1, sticky='nesw')

        self.vsb.config(command=self.canvas.yview)
        self.hsb.config(command=self.canvas.xview)

        self.canvas.xview_moveto(0)
        self.canvas.yview_moveto(0)

        self.canvas.bind('<Configure>', self._reconfigure)

        self.frame = tk.Frame(self.canvas)

        self.frame_id = self.canvas.create_window(0, 0, window=self.frame, anchor='nw')

        self.frame.bind('<Configure>', self._reconfigure)

        self.update_idletasks()
        self._showscrollbars()

    def _reconfigure(self, event=None):
        f_reqsize = (self.frame.winfo_reqwidth(), self.frame.winfo_reqheight())
        c_size = (self.canvas.winfo_width(), self.canvas.winfo_height())
        self.canvas.config(scrollregion="0 0 %s %s" % f_reqsize)
        if (f_reqsize[0] < c_size[0]):
            self.canvas.itemconfigure(self.frame_id, width=c_size[0])
        else:
            self.canvas.itemconfigure(self.frame_id, width=f_reqsize[0])
        if (f_reqsize[1] < c_size[1]):
            self.canvas.itemconfigure(self.frame_id, height=c_size[1])
        else:
            self.canvas.itemconfigure(self.frame_id, height=f_reqsize[1])

        if (self.scrollbars == 'auto'):
            self._showscrollbars()

    def _showscrollbars(self):
        if (self.scrollbars == 'both'):
            self.vsb.grid(**self.vsb.opts)
            self.hsb.grid(**self.hsb.opts)
        elif (self.scrollbars == 'x'):
            self.vsb.grid_remove()
            self.hsb.grid(**self.hsb.opts)
        elif (self.scrollbars == 'y'):
            self.vsb.grid(**self.vsb.opts)
            self.hsb.grid_remove()
        elif (self.scrollbars == 'auto'):
            f_reqsize = (self.frame.winfo_reqwidth(), self.frame.winfo_reqheight())
            c_size = (self.canvas.winfo_width(), self.canvas.winfo_height())
            # start with vertical
            if self.scroll_shown[1] == False: # not showing
                if (f_reqsize[1] > c_size[1]): # height is greater than canvas so show
                    self.canvas.configure(width=self.canvas.winfo_width() - self.vsb.winfo_reqwidth())
                    self.vsb.grid(**self.vsb.opts)
                    self.scroll_shown[1] = True
            else:
                if (f_reqsize[1] <= c_size[1]): # height is less than canvas so don't show
                    self.vsb.grid_remove()
                    self.canvas.configure(width=self.canvas.winfo_width() + self.vsb.winfo_reqwidth())
                    self.scroll_shown[1] = False

            # now horizontal
            if self.scroll_shown[0] == False: # not showing
                if (f_reqsize[0] > c_size[0]): # width is greater than canvas so show
                    self.canvas.configure(height=self.canvas.winfo_height() - self.hsb.winfo_reqheight())
                    self.hsb.grid(**self.hsb.opts)
                    self.scroll_shown[0] = True
            else:
                if (f_reqsize[0] <= c_size[0]): # width is less than canvas so don't show
                    self.hsb.grid_remove()
                    self.canvas.configure(height=self.canvas.winfo_height() + self.hsb.winfo_reqheight())
                    self.scroll_shown[0] = False

    def resize(self):
        self._reconfigure()

if __name__ == '__main__':
    frames = []
    def add_row():
        frame = tk.Frame(sf.frame)
        frame.grid_columnconfigure(1, weight=1)
        frame.grid_columnconfigure(2, weight=1)
        frame.grid_rowconfigure(1, weight=1)
        num = len(frames)
        tk.Label(frame, text='Test %i' % num).grid(column=1, row=1, sticky="nesw")
        tk.Entry(frame).grid(column=2, row=1, sticky="nesw")
        frame.grid(column=1, row=num, sticky='nesw')
        sf.frame.grid_rowconfigure(num, weight=1)
        frames.append(frame)
        sf.resize()

    def del_row():
        frame = frames.pop()
        frame.grid_forget()
        frame.destroy()
        num = len(frames)
        sf.frame.grid_rowconfigure(num, weight=0)
        sf.resize()

    def add_column():
        pass

    def del_column():
        pass

    root = tk.Tk()

    sf = ScrolledFrame(root, scrollbars='auto')
    sf.frame.grid_columnconfigure(1, weight=1)
    sf.grid(column=1, row=1, columnspan=2, rowspan=2, sticky='nesw')
    tk.Button(root, text='-', command=del_row).grid(column=3, row=1, sticky='nesw')
    tk.Button(root, text='+', command=add_row).grid(column=3, row=2, sticky='nesw')
    tk.Button(root, text='-', command=del_column).grid(column=1, row=3, sticky='nesw')
    tk.Button(root, text='+', command=add_column).grid(column=2, row=3, sticky='nesw')

    root.grid_columnconfigure(1, weight=1)
    root.grid_columnconfigure(2, weight=1)
    root.grid_rowconfigure(1, weight=1)
    root.grid_rowconfigure(2, weight=1)

    root.mainloop()

所以在我的草图底部的测试代码中,我想更改添加功能,使其frame = tk.Frame(sf) 不干扰将外框打包/网格到父窗口/框架中的能力,我该怎么做这个/我需要覆盖哪些方法/属性?

我尝试覆盖 __str____repr__ 以指向内部框架方法,但没有成功

【问题讨论】:

    标签: python python-2.7 python-3.x tkinter


    【解决方案1】:

    您要查找的属性是_w。然而,仅仅使用它不会得到你想要的。问题是您需要将一些属性传递给外部框架,而将其他属性传递给内部框架。您可以通过与未初始化的小部件进行比较来整理出什么地方。但是,很难从 Widget 子类路由它,因为您需要覆盖属性。创建一个普通类(不是子类)并从中路由属性要容易得多:

    __all__ = ['ScrolledFrame']
    
    try:
        import tkinter as tk
    except ImportError:
        import Tkinter as tk
    
    class ScrolledFrame:
        def __init__(self, master=None, *args, **kwargs):
            self.scrollbars = kwargs.pop('scrollbars', None)
            self.scroll_shown= [False, False]
    
            self.outer_attr = set(dir(tk.Widget)) # a list of attributes that the outer frame should handle
            self.outer_frame = tk.Frame(master)
    
            self.grid_columnconfigure(1, weight=1)
            self.grid_rowconfigure(1, weight=1)
    
            self.vsb = tk.Scrollbar(self.outer_frame, orient='vertical')
            self.hsb = tk.Scrollbar(self.outer_frame, orient='horizontal')
            self.vsb.opts = {'column':2, 'row':1, 'sticky':'nesw'}
            self.hsb.opts = {'column':1, 'row':2, 'sticky':'nesw'}
    
            self.canvas = tk.Canvas(self.outer_frame, bd=0, highlightthickness=0,
                                    yscrollcommand=self.vsb.set,
                                    xscrollcommand=self.hsb.set)
            self.canvas.grid(column=1, row=1, sticky='nesw')
    
            self.vsb.config(command=self.canvas.yview)
            self.hsb.config(command=self.canvas.xview)
    
            self.canvas.xview_moveto(0)
            self.canvas.yview_moveto(0)
    
            self.canvas.bind('<Configure>', self._reconfigure)
    
            self.frame = tk.Frame(self.canvas)
    
            self.frame_id = self.canvas.create_window(0, 0, window=self.frame, anchor='nw')
    
            self.frame.bind('<Configure>', self._reconfigure)
    
            #~ self.update_idletasks() # this is not needed; and should never be needed.
            self._showscrollbars()
    
        def __getattr__(self, item):
            '''when an attribute is requested, sort out which frame should provide the attribute'''
            if item in self.outer_attr:
                # geometry attributes etc (eg pack, destroy, tkraise) are passed on to self.outer
                return getattr(self.outer_frame, item)
            else:
                # all other attributes (_w, children, etc) are passed to self.inner
                return getattr(self.frame, item)
    
        def _reconfigure(self, event=None):
            f_reqsize = (self.frame.winfo_reqwidth(), self.frame.winfo_reqheight())
            c_size = (self.canvas.winfo_width(), self.canvas.winfo_height())
            self.canvas.config(scrollregion="0 0 %s %s" % f_reqsize)
            if (f_reqsize[0] < c_size[0]):
                self.canvas.itemconfigure(self.frame_id, width=c_size[0])
            else:
                self.canvas.itemconfigure(self.frame_id, width=f_reqsize[0])
            if (f_reqsize[1] < c_size[1]):
                self.canvas.itemconfigure(self.frame_id, height=c_size[1])
            else:
                self.canvas.itemconfigure(self.frame_id, height=f_reqsize[1])
    
            if (self.scrollbars == 'auto'):
                self._showscrollbars()
    
        def _showscrollbars(self):
            if (self.scrollbars == 'both'):
                self.vsb.grid(**self.vsb.opts)
                self.hsb.grid(**self.hsb.opts)
            elif (self.scrollbars == 'x'):
                self.vsb.grid_remove()
                self.hsb.grid(**self.hsb.opts)
            elif (self.scrollbars == 'y'):
                self.vsb.grid(**self.vsb.opts)
                self.hsb.grid_remove()
            elif (self.scrollbars == 'auto'):
                f_reqsize = (self.frame.winfo_reqwidth(), self.frame.winfo_reqheight())
                c_size = (self.canvas.winfo_width(), self.canvas.winfo_height())
                # start with vertical
                if self.scroll_shown[1] == False: # not showing
                    if (f_reqsize[1] > c_size[1]): # height is greater than canvas so show
                        self.canvas.configure(width=self.canvas.winfo_width() - self.vsb.winfo_reqwidth())
                        self.vsb.grid(**self.vsb.opts)
                        self.scroll_shown[1] = True
                else:
                    if (f_reqsize[1] <= c_size[1]): # height is less than canvas so don't show
                        self.vsb.grid_remove()
                        self.canvas.configure(width=self.canvas.winfo_width() + self.vsb.winfo_reqwidth())
                        self.scroll_shown[1] = False
    
                # now horizontal
                if self.scroll_shown[0] == False: # not showing
                    if (f_reqsize[0] > c_size[0]): # width is greater than canvas so show
                        self.canvas.configure(height=self.canvas.winfo_height() - self.hsb.winfo_reqheight())
                        self.hsb.grid(**self.hsb.opts)
                        self.scroll_shown[0] = True
                else:
                    if (f_reqsize[0] <= c_size[0]): # width is less than canvas so don't show
                        self.hsb.grid_remove()
                        self.canvas.configure(height=self.canvas.winfo_height() + self.hsb.winfo_reqheight())
                        self.scroll_shown[0] = False
    
        def resize(self):
            self._reconfigure()
    
    if __name__ == '__main__':
        frames = []
        def add_row():
            frame = tk.Frame(sf)
            frame.grid_columnconfigure(1, weight=1)
            frame.grid_columnconfigure(2, weight=1)
            frame.grid_rowconfigure(1, weight=1)
            num = len(frames)
            tk.Label(frame, text='Test %i' % num).grid(column=1, row=1, sticky="nesw")
            tk.Entry(frame).grid(column=2, row=1, sticky="nesw")
            frame.grid(column=1, row=num, sticky='nesw')
            sf.frame.grid_rowconfigure(num, weight=1)
            frames.append(frame)
            sf.resize()
    
        def del_row():
            frame = frames.pop()
            frame.grid_forget()
            frame.destroy()
            num = len(frames)
            sf.frame.grid_rowconfigure(num, weight=0)
            sf.resize()
    
        def add_column():
            pass
    
        def del_column():
            pass
    
        root = tk.Tk()
    
        sf = ScrolledFrame(root, scrollbars='auto')
        sf.frame.grid_columnconfigure(1, weight=1)
        sf.grid(column=1, row=1, columnspan=2, rowspan=2, sticky='nesw')
        tk.Button(root, text='-', command=del_row).grid(column=3, row=1, sticky='nesw')
        tk.Button(root, text='+', command=add_row).grid(column=3, row=2, sticky='nesw')
        tk.Button(root, text='-', command=del_column).grid(column=1, row=3, sticky='nesw')
        tk.Button(root, text='+', command=add_column).grid(column=2, row=3, sticky='nesw')
    
        root.grid_columnconfigure(1, weight=1)
        root.grid_columnconfigure(2, weight=1)
        root.grid_rowconfigure(1, weight=1)
        root.grid_rowconfigure(2, weight=1)
    
        root.mainloop()
    

    此解决方案的一大缺点是自动 self.master 不起作用。当您向这个“框架”添加一个小部件时,它的self.master 链是内框架> 画布> 外框架。这意味着如果您将其子类化,则必须显式传递实例,因为“孩子”没有内置的方式来访问它。

    在我尝试制作 ScrolledFrame 小部件之前,我已经解决了这个问题。 Here's my code, if you are interested 解决了更多问题,例如跨平台鼠标滚轮绑定。

    【讨论】:

    • 谢谢,我喜欢这样,似乎是一个尽可能优雅的解决方案,我制作另一个滚动框架类的主要原因(我知道互联网上有一些)是它们看起来缺少某些方面,例如滚动错误(如果内部内容小于画布),或者如果需要,它们不允许拉伸内部内容以适应画布框架。我当前的版本已经为鼠标添加了绑定,包括 linux、mac 和 windows,但我把它留给了一个更短的例子。
    • 同样,如果您有兴趣,我的更新代码现在在这里:github.com/JamesGKent/python-tkwidgets/blob/master/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-18
    • 2010-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-18
    相关资源
    最近更新 更多