在调整帧大小期间,克服了自定义ButtonFrame的慢速按钮和框架显示以及不规则的背景。

时间:2018-09-18 18:31:09

标签: python tkinter

我编写了一种算法,该算法允许ttk.Frame将多个按钮包装在其中,以便当按钮水平占据过多空间时,受影响的按钮将自动重新定位到下一行。调整此ttk.Frame的大小时,还将显示这种按钮环绕行为。我将其称为FrameButton类。

此小部件仍存在问题:

  1. 这是按钮包装的行为,并且框架的大小调整非常滞后/延迟。需要更快的响应。
  2. 在调整大小期间,只要移除按钮以方便重新定位,框架的背景颜色就会显示主窗口小部件的背景。
  3. 在重新定位期间,当一行按钮超过框架宽度时,我必须移除该行上的最后一个按钮,以将其重新定位为新行。此过程会在框架的右侧造成灰显效果。
  4. 调整框架的高度时,按钮位置不会受到影响,但目前由于使用了绑定,按钮仍会将其自身重新放置在同一位置。这种包装行为是多余的,我想避免这种情况。

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

FrameButtons.py

#!/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 个答案:

答案 0 :(得分:1)

最好的办法是停止在每个<Configure>事件上创建新的窗口小部件。创建一次,然后仅在计算出需要移动它们时才移动它们。当我将主窗口的大小调整到足以创建一行的大小时,根据我执行调整大小的速度,您的代码将创建200至2000个按钮或更多的按钮。

替代解决方案

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

这是一个简单又肮脏的例子来说明这个概念。它没有经过太多测试,但似乎可以正常工作:

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()