Жидкий дизайн в Ткинтере - PullRequest
0 голосов
/ 27 февраля 2020

Я пытаюсь создать несколько «адаптивный» дизайн с помощью ttk (tkinter). Основное размещение виджетов c не представляет никакой проблемы, но я не могу добиться его плавного изменения ширины программы. В CSS я знаю, что можно сказать что-то вроде «float: left» для всех контейнеров », и страница адаптируется к размеру экрана. Я не нашел чего-то подобного в Tkinter и frames.

Моя базовая c тестовая программа:

#!/usr/bin/python3

import tkinter
from tkinter import ttk
from ttkthemes import ThemedTk, THEMES

class quick_ui(ThemedTk):
    def __init__(self):
        ThemedTk.__init__(self, themebg=True)
        self.geometry('{}x{}'.format(900, 150))
        self.buttons = {}

        self.frame1 = ttk.Frame(self)
        self.frame1.pack(side="left")
        self.frame2 = ttk.Frame(self)
        self.frame2.pack(side="left")

        #------------------------------------------------------- BUTTONS
        i = 0
        while (i < 5):
            i += 1
            self.buttons[i]= ttk.Button(self.frame1,
                                            text='List 1 All ' + str(i),
                                            command=self.dump)
            self.buttons[i].pack(side="left")


        while (i < 10):
            i += 1
            self.buttons[i]= ttk.Button(self.frame2,
                                            text='List 2 All ' + str(i),
                                            command=self.dump)
            self.buttons[i].pack(side="left")

    def dump(self):
        print("dump called")

quick = quick_ui()
quick.mainloop()

Это создает окно с 10 кнопками, расположенными друг напротив друга. Когда я сжимаю окно до такой степени, что кнопки больше не помещаются на экране, я бы хотел, чтобы кнопки появлялись друг под другом

Так что я сделал, добавив прослушиватель изменения размера и настроив следующий метод:

    def resize(self, event):
        w=self.winfo_width()
        h=self.winfo_height()
        # print("width: " + str(w) + ", height: " + str(h))

        if(w < 830):
            self.frame1.config(side="top")
            self.frame2.config(side="top")

Но Frame не имеет свойства side, которое является параметром, заданным для метода pack. Так что это тоже не сработало.

И теперь я потерялся. Я потратил много времени на это, пробуя сетки и другие решения, но у меня есть ощущение, что я упускаю один простой, но очень важный параметр.

1 Ответ

0 голосов
/ 02 марта 2020

Я создал решение (hacki sh), и это хорошо, потому что это очень внутренняя программа. Поскольку пока нет ответа, я предоставлю свое решение здесь. Вероятно, в нем есть много возможностей для улучшений, но я надеюсь, что это может дать кому-то в будущем некоторые советы о том, как решить эту проблему сам (или она).

#!/usr/bin/python3

import re
import sys
import tkinter
from tkinter import filedialog
from tkinter import ttk
from ttkthemes import ThemedTk, THEMES

import subprocess
import os
from tkinter.constants import UNITS
import json
from functools import partial


class quick_ui(ThemedTk):

    def __init__(self):
        ThemedTk.__init__(self, themebg=True)
        self.minsize(600, 250)
        self.elems = {}
        self.resize_after_id = None


        #------------------------------------------------------- Window menu bar contents
        self.menubar = tkinter.Menu(self)
        self.menubar.add_command(label="Open", command = self.dump)
        self.menubar.add_command(label="Refresh", command = self.dump)
        self.config(menu=self.menubar)

        # Theme menu
        self.themeMenu = tkinter.Menu(self.menubar, tearoff=0)
        self.menubar.add_cascade(label="Theme", menu=self.themeMenu)
        self.themeMenu.add_command(label="DEFAULT", command=partial(self.dump, "default"))


        #---------------------------------------------------------------------- top_frame
        self.top_frame = ttk.Frame(self)
        self.top_frame.pack( side = tkinter.TOP, expand='YES', fill='both', padx=10)

        self.top_top_frame = ttk.Frame(self.top_frame)
        self.top_top_frame.pack(side=tkinter.TOP, expand='YES', fill='both')

        self.top_bottom_frame = ttk.Frame(self.top_frame)
        self.top_bottom_frame.pack(side=tkinter.BOTTOM)

        self.top_bottom_top_frame = ttk.Frame(self.top_frame)
        self.top_bottom_top_frame.pack(side=tkinter.TOP)


        self.top_bottom_bottom_frame = ttk.Frame(self.top_frame)
        self.top_bottom_bottom_frame.pack(side=tkinter.BOTTOM)

        #------------------------------------------------------------------- bottom_frame
        self.bottom_frame = ttk.Frame(self, relief="sunken")
        self.bottom_frame.pack( side = tkinter.BOTTOM, 
                                expand='YES', 
                                fill='both', 
                                padx=10, 
                                pady=10 )

        #------------------------------------------------------- BUTTONS
        i = 0
        while (i < 15):
            self.elems[i]=ttk.Button(self.top_bottom_top_frame,
                                                text='List All ' + str(i),
                                                command=self.dump)
            i += 1


        self.label_test_strings1 = ttk.Label(self.top_top_frame, text='Test strings1')
        self.label_test_strings2 = ttk.Label(self.top_bottom_frame, text='Test strings2')
        self.label_test_strings4 = ttk.Label(self.top_bottom_bottom_frame, text='Test strings4')

        self.label_test_strings1.pack(side = tkinter.TOP)
        self.label_test_strings2.pack(side = tkinter.TOP)
        self.label_test_strings4.pack(side = tkinter.TOP)


        self.placeElems()
        # Setup a hook triggered when the configuration (size of window) changes
        self.bind('<Configure>', self.resize)


    def placeElems(self):
        for index in self.elems:
            self.elems[index].grid(row=0, column=index, padx=5, pady=5)


    # ------------------------------------------------------ Resize event handler
    def resize(self, event):
        # Set a low "time-out" for resizing, to limit the change of "fighting" for growing and shrinking
        if self.resize_after_id is not None:
            self.after_cancel(self.resize_after_id)
        self.resize_after_id = self.after(200, self.resize_callback)


    # ------------------------------------------------------ Callback for the resize event handler
    def resize_callback(self):
        # The max right position of the program
        windowMaxRight = self.winfo_rootx() + self.winfo_width()

        # Some basic declarations
        found = False
        willAdd = False
        maxColumn = 0
        currIndex = 0
        currColumn = 0
        currRow = 0
        counter = 0
        last_rootx = 0
        last_maxRight = 0

        # Program is still starting up, so ignore this one
        if(windowMaxRight < 10):
            return

        # Loop through all the middle bar elements
        for child in self.top_bottom_frame.children.values():
            # Calculate the max right position of this element
            elemMaxRight = child.winfo_rootx() + child.winfo_width() + 10

            # If we already found the first 'changable' child, we need to remove the following child's also
            if(found == True):
                # Is the window growing?
                if(willAdd == True):
                    # Check to see if we have room for one more object
                    calcMaxRight = last_maxRight + child.winfo_width() + 20
                    if(calcMaxRight < windowMaxRight):
                        maxColumn = counter + 1
                # Remove this child from the view, to add it again later
                child.grid_forget()

            # If this child doesn't fit on the screen anymore
            elif(elemMaxRight >= windowMaxRight):
                # Remove this child from the view, to add it again later
                child.grid_forget()
                currIndex = counter
                maxColumn = counter
                currRow = 1
                found = True

            else:
                # If this child's x position is lower than the last child
                # we can asume it's on the next row
                if(child.winfo_rootx() < last_rootx):
                    # Check to see if we have room for one more object on the first row
                    calcMaxRight = last_maxRight + child.winfo_width() + 20
                    if(calcMaxRight < windowMaxRight):
                        child.grid_forget()
                        currIndex = counter
                        currColumn = counter
                        maxColumn = counter + 1
                        found = True
                        willAdd = True

            # Save some calculation data for the next run
            last_rootx = child.winfo_rootx()
            last_maxRight = elemMaxRight
            counter += 1

        # If we removed some elements from the UI
        if(found == True):
            counter = 0
            # Loop through all the middle bar elements (including removed ones)
            for child in self.top_bottom_frame.children.values():
                # Ignore the elements still in place
                if(counter < currIndex):
                    counter += 1
                    continue

                # If we hit our maxColumn count, move to the next row
                if(currColumn == maxColumn):
                    currColumn = 0
                    currRow += 1

                # Place this element on the UI again
                child.grid(row=currRow, column=currColumn, padx=5, pady=5)
                currColumn += 1
                counter += 1


    def dump(self):
        print("dump called")


quick = quick_ui()
quick.mainloop()
...