2017-05-08 73 views
-1

我試圖做一個自定義小部件,以提供一個可調整大小以適應其內容或窗口的可滾動框架,例如,內部框架的內容小於畫布的視圖,然後內容可以被拉伸以適合視圖,或者如果它們是可以被啓用的更小的滾動條,但是爲了使得使用更簡單,我希望能夠在內部框架內覆蓋需要打包或網格小部件所需的任何方法。例如,而不是: 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__在內部框架methodswithout成功

回答

1

你正在尋找的屬性是_w點。然而,只是使用它不會得到你想要的。問題是你需要將一些屬性傳遞給外框,其他屬性傳遞給內框。您可以通過與未初始化的Widget進行比較來理清所發生的情況。然而,由於您需要覆蓋屬性,因此很難從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,它解決了諸如跨平臺鼠標滾輪綁定等更多問題。

+0

謝謝,我喜歡這樣,看起來像是一個儘可能優雅的解決方案,我製作另一個滾動框架類的主要原因是(我知道互聯網上有幾個這樣的框架類)是因爲它們似乎都缺乏某種方式,例如滾動錯誤(如果內部內容小於畫布),或者如果需要,它們不允許內部內容被拉伸以適應畫布框。我目前的版本已經爲鼠標添加了綁定,包括linux,mac和windows,但是我留下了一個簡短的例子。 –

+0

同樣,如果你感興趣我的更新代碼現在在這裏:https://github.com/JamesGKent/python-tkwidgets/blob/master/scrollableframe.py –