代码之家  ›  专栏  ›  技术社区  ›  Sun Bear

克服缓慢的按钮和帧显示和补丁背景的自定义按钮框在帧调整期间。

  •  0
  • Sun Bear  · 技术社区  · 8 年前

    我写了一个算法,允许 ttk.Frame 要在其中包装多个按钮,以便当按钮水平占用太多空间时,受影响的按钮将自动重新定位到下一行。当TTK.frame调整大小时,这种按钮包装行为也会显示出来。我叫它A FrameButton 班级。

    此小工具仍有问题:

    1. 它的按钮包装行为和帧的大小调整非常滞后/延迟。需要更快的响应。
    2. 在调整大小的过程中,每当移除按钮以便于重新定位时,框架的背景色都会显示主窗口小部件的背景。
    3. 在重新定位过程中,当一行按钮超过帧宽度时,我必须删除该行上的最后一个按钮,以便将其重新定位为新行。此步骤会在框架右侧产生灰显效果。
    4. 当我调整框架的高度时,按钮的位置不应该受到影响,但是由于使用了绑定,按钮仍然会重新定位到相同的位置。这样的包装行为是多余的,我想避免它。

    如何规避/克服上述问题?谢谢您。

    框架按钮

    #!/usr/bin/python3.6.5
    # -*- coding: utf-8 -*-
    
    #Load python3 modules
    import tkinter as tk
    import tkinter.ttk as ttk
    import platform
    
    class FrameButtons(ttk.Frame):
    
        def __init__(self, master, **options):
            background  = master.winfo_toplevel().cget('background') 
            style       = options.pop( 'style', ttk.Style() )
            background  = options.pop( 'background', background )
            borderwidth = options.pop( 'borderwidth', 0 )
            relief      = options.pop( 'relief', 'flat' )
            texts       = options.pop( 'texts', ['0'] )
            textwidth   = options.pop( 'textwidth', 10 )
            debug       = options.pop( 'debug', False )
    
            master.update_idletasks()
            masterwidth = master.winfo_width()
            masterwidth2 = masterwidth - borderwidth*2
            print('masterwidth, w/border = ', masterwidth, masterwidth2)
            width =  masterwidth2
    
            super().__init__( master, style='main.TFrame', width=width,
                              borderwidth=borderwidth, relief=relief )
            self.grid( row=0, column=0, sticky='nsew' )
    
            master.rowconfigure(0, weight=1)
            master.columnconfigure(0, weight=1)
    
            self.parent = master
            self.style = style
            self.texts = texts
            self.bg = None
            self.background = background
            self.borderwidth = borderwidth
            self.relief = relief
            self.textwidth = textwidth
            self.debug = debug
            self.buttonframes = {}
            self.buttons = {}
    
            #Color code borders
            if self.debug:
                if platform.system() == 'Linux':
                    print('Linux')
                    bg = []
                    with open('/etc/X11/rgb.txt') as f:
                        lines = f.readlines()[30::10]
                        for line in lines:
                            color = line.replace('\t\t',' ').splitlines()[0]\
                                    .split()[3]
                            #print('color = ', color)
                            invalid = ['ghost','floral','old','antique','papaya',
                                      'blanched','peach','navajo','lemon','alice',
                                      'cornflower','slate','light','royal', 'dark',
                                      'mint','misty','dim','midnight','medium','dodger',
                                      'deep','sky','steel','pale','rosy','indian',
                                       'saddle','sandy','DebianRed', 'spring','forest',
                                       'sea','lawn','cadet']
                            if color not in invalid:
                                bg.append( color )
                    self.bg = bg
                else:
                    print('non-Linux')
                    self.bg = [ 'yellow', 'red','blue', 'grey','cyan','orange',
                                'black','gold','magenta','silver','maroon', 'salmon',
                                'honeydew','hotpink','indigo','ivory','khaki',
                                'lavender', 'lawn green', 'light blue','lime',
                                'midnight blue', 'olive']
            else:
                #no debug
                self.bg = [ str(x).replace( str(x), background )
                            for x in range( len(texts) ) ]
            print(self.bg)
    
            self._setStyle()
            self._createWidgets()
            self._setBindings()
    
    
        def _setStyle( self ):
            self.style.configure( 'main.TFrame', background=self.background, 
                                                 borderwidth=self.borderwidth,
                                                 relief=self.relief )
            self.style.configure( 'buttons.TFrame', background=self.background )
            self.style.configure( 'b.TButton', justify=tk.CENTER,
                                               width=self.textwidth ) 
    
    
        def _createButtonFrame( self, r ):
            self.buttonframes[r] = tk.Frame( self, background=self.bg[r],
                                                   borderwidth=self.borderwidth,
                                                   relief=self.relief )
            self.buttonframes[r].pack( anchor='w' )
    
    
        def _createButton( self, r, b):
            self.buttons[b] = ttk.Button( self, text=b, style='b.TButton' )
            self.buttons[b].pack( in_=self.buttonframes[r], anchor='w', side='left')
            self.buttons[b].update_idletasks()
    
        def _updateButtonFrame( self, r):
            return self.buttonframes[r].winfo_reqwidth()
    
    
        def _createWidgets( self ):
            wlimit = self.cget('width')
            print('wlimit = ', wlimit)
            self._createWidgets2( wlimit)
    
    
        def _createWidgets2( self, wlimit ):
            t_width=0; r=0; i=0 
            self._createButtonFrame( r )
            r +=1
    
            for b in self.texts:
    
                if t_width <= wlimit:
                    self._createButton( r-1, b )
                    i += 1
    
                    t_width = self._updateButtonFrame( r-1 )
                    if self.debug: print( 'r={}, i={}, t_width={}'
                                          .format( r-1, i-1, t_width ) )
    
                    # if buttons row width exceeded wlimit
                    if t_width > wlimit:
                        if self.debug: print('t_width > wlimit ({})'.format(wlimit) )
                        #remove button
                        self.buttons[b].pack_forget()
                        i -= 1
    
                        self._createButtonFrame( r )
                        r += 1
                        # create button
                        self._createButton(r-1, b)
                        i += 1
                        # update t_width
                        t_width = self._updateButtonFrame( r-1 )
                        if self.debug: print( 'r={}, i={}, t_width={}'
                                              .format( r-1, i-1, t_width ) )
    
    
        def _setBindings(self):
            self.bind( '<Configure>', self._configButtonFrame )
    
    
        def _configButtonFrame (self, event):
            self.parent.update_idletasks()
            wlimit = self.parent.winfo_width() - self.borderwidth*2
            #print('wlimit = ', wlimit)
    
            #remove old ButtonFrame widgets
            self._cleanup()
            self._createWidgets2( wlimit )
    
    
        def _cleanup(self):
            for k in self.buttons.keys():
                self.buttons[k].destroy()
            self.buttons.clear()
            for k in self.buttonframes.keys():
                self.buttonframes[k].destroy()
            self.buttonframes.clear()
    
    
    if __name__ == "__main__":
        root = tk.Tk()
        root.geometry( '102x500+10+0' )
        borderwidth = 10
        width = 100
        minwidth = width+borderwidth*2; print('minwidth =', minwidth)
        root.minsize( minwidth, 300)
    
        texts = [ str(x) for x in range(20) ]
    
        app = FrameButtons( root, background='pink', borderwidth=borderwidth,
                            relief=tk.RAISED, texts=texts, textwidth=2,
                            debug=True )
        root.mainloop() # Start Dynamic part of program to handle Tk events
    
    1 回复  |  直到 8 年前
        1
  •  1
  •   Bryan Oakley    8 年前

    你能做的最好的事情就是停止在 <Configure> 事件。创建它们一次,然后仅当您计算出它们需要移动时才移动它们。当我调整主窗口的大小以创建一行时,您的代码将创建200到2000个按钮或更多的按钮,这取决于我调整大小的速度。

    另一种解决方案

    你可能想考虑使用 grid 而不是 pack ,因为 网格 不需要为每行创建内部框架。

    下面是一个快速而肮脏的例子来说明这个概念。虽然测试得不多,但似乎有效:

    import tkinter as tk
    import tkinter.ttk as ttk
    
    class FrameButtons(ttk.Frame):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.buttons = []
            self.bind("<Configure>", self._redraw)
    
        def _redraw(self, event=None):
            maxwidth = self.winfo_width()
            row = column = rowwidth = 0
            for button in self.buttons:
                # will it fit? If not, move to the next row
                if rowwidth + button.winfo_width() > maxwidth:
                    row += 1
                    column = 0
                    rowwidth = 0
                rowwidth += button.winfo_width()
                button.grid(row=row, column=column)
                column += 1
    
        def add_button(self, *args, **kwargs):
            '''Add one button to the frame'''
            button = ttk.Button(self, *args, **kwargs)
            self.buttons.append(button)
            self._redraw()
    
    
    if __name__ == "__main__":
        root = tk.Tk()
        button_frame = FrameButtons(root)
        button_frame.pack(side="top", fill="x", expand=False)
        for i in range(20):
            button_frame.add_button(text=str(i))
        root.mainloop()