Преодоление медленного отображения кнопок и фреймов и пятнистых фонов для пользовательского ButtonFrame во время изменения размера фрейма. - PullRequest
0 голосов
/ 18 сентября 2018

Я написал алгоритм, который позволяет 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 голосов
/ 18 сентября 2018

Лучшее, что вы можете сделать, это перестать создавать новые виджеты на каждом <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()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...