Изменение размера кадров Tkinter с фиксированным соотношением сторон

Я ищу способ заставить Tkinter Frames вести себя примерно так при изменении размера:

from Tkinter import *
w = Tk()
w.aspect(1,1,1,1)
w.mainloop()

Итак, я бы хотел, чтобы этот код здесь:

import Tkinter as tk
from Tkconstants import *
r=tk.Tk()
content_frame=tk.Frame(r,borderwidth=5,relief=GROOVE)
content_frame.grid(row=0,column=0,sticky=(N,S,E,W))
tk.Label(content_frame,text='content').pack()
pad_frame=tk.Frame(r,borderwidth=5,relief=GROOVE)
pad_frame.grid(row=1,column=0,sticky=(N,S,E,W))
tk.Label(pad_frame,text='-pad-').pack()
r.rowconfigure(0, weight=1)
r.rowconfigure(1, weight=1)
r.columnconfigure(0, weight=1)
r.mainloop()

который в основном создает 2 фрейма, один из которых должен содержать некоторый контент (в моем приложении этот фрейм содержит граф mathplotlib, размер которого можно изменить), а другой просто для заполнения.

Чтобы вести себя при изменении размера, следуйте этим правилам:

  • размер фрейма содержимого изменяется с фиксированным соотношением сторон (скажем, он всегда должен быть квадратным)
  • рамка пэда занимает оставшееся (вертикальное) пространство

Любые идеи? Давно читаю мануалы и не могу найти что-то подходящее.

-Дэниел


person Dani Gehtdichnixan    schedule 13.05.2013    source источник
comment
Почему бы вам не удалить заполнение и просто использовать aspect?   -  person Gary Kerr    schedule 13.05.2013
comment
Это упрощенный случай того, чем я на самом деле занимаюсь. Все это встроено в более крупный интерфейс, поэтому я не могу использовать windows/toplevel/... И у Frame нет метода .aspect().   -  person Dani Gehtdichnixan    schedule 14.05.2013


Ответы (2)


Есть по крайней мере несколько способов решить эту проблему. Самый простой, IMO, состоит в том, чтобы ваш виджет заполнения был контейнером для вашего виджета контента, а затем вы явно устанавливаете ширину и высоту виджета контента, используя place. Это один из крайних случаев, когда place предпочтительнее grid или pack.

В следующем примере я создал функцию, которая позволяет передавать фрейм содержимого, фрейм заполнения и соотношение сторон. Затем он ограничивает размер фрейма содержимого соотношением сторон и размером контейнера. Это заставит окно содержимого заполнить контейнер в измерении X, а затем установить соответствующую высоту. Если результирующее окно слишком высокое, чтобы быть видимым, оно устанавливает максимальную высоту на высоту контейнера и вместо этого регулирует ширину.

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

import Tkinter as tk
from Tkconstants import *
r=tk.Tk()

def set_aspect(content_frame, pad_frame, aspect_ratio):
    # a function which places a frame within a containing frame, and
    # then forces the inner frame to keep a specific aspect ratio

    def enforce_aspect_ratio(event):
        # when the pad window resizes, fit the content into it,
        # either by fixing the width or the height and then
        # adjusting the height or width based on the aspect ratio.

        # start by using the width as the controlling dimension
        desired_width = event.width
        desired_height = int(event.width / aspect_ratio)

        # if the window is too tall to fit, use the height as
        # the controlling dimension
        if desired_height > event.height:
            desired_height = event.height
            desired_width = int(event.height * aspect_ratio)

        # place the window, giving it an explicit size
        content_frame.place(in_=pad_frame, x=0, y=0, 
            width=desired_width, height=desired_height)

    pad_frame.bind("<Configure>", enforce_aspect_ratio)

pad_frame = tk.Frame(borderwidth=0, background="bisque", width=200, height=200)
pad_frame.grid(row=0, column=0, sticky="nsew", padx=10, pady=20)
content_frame=tk.Frame(r,borderwidth=5,relief=GROOVE, background="blue")
tk.Label(content_frame,text='content').pack()
set_aspect(content_frame, pad_frame, aspect_ratio=2.0/1.0) 
r.rowconfigure(0, weight=1)
r.columnconfigure(0, weight=1)

r.mainloop()

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

person Bryan Oakley    schedule 14.05.2013
comment
Работает как шарм. Очень полезно, спасибо за это. Если бы у меня было больше опыта в программировании графического интерфейса в Tkinter, я бы, вероятно, тоже сделал это по-другому, но я довольно новичок во всем этом. Еще раз спасибо за отличный код. - person Dani Gehtdichnixan; 17.05.2013

Вы можете привязать функцию к событию <Configure> для Frame, которое содержит фреймы содержимого и заполнения. Событие <Configure> будет запущено при изменении размера окна. Используйте атрибуты ширины и высоты события, чтобы зафиксировать размер фрейма содержимого, обновив веса строк и столбцов с помощью rowconfigure и columnconfigure.

Вам понадобятся две строки и два столбца во фрейме контейнера, чтобы иметь квадратный фрейм содержимого. При высоком окне вам понадобится набивка во втором ряду. А при широком окне нужен отступ во втором столбце.

Рабочий пример:

import Tkinter as tk
from Tkconstants import *


class Application(tk.Frame):
    def __init__(self, master, width, height):
        tk.Frame.__init__(self, master)
        self.grid(sticky=N + S + E + W)
        master.rowconfigure(0, weight=1)
        master.columnconfigure(0, weight=1)
        self._create_widgets()
        self.bind('<Configure>', self._resize)
        self.winfo_toplevel().minsize(150, 150)

    def _create_widgets(self):
        self.content = tk.Frame(self, bg='blue')
        self.content.grid(row=0, column=0, sticky=N + S + E + W)

        self.rowconfigure(0, weight=1)
        self.rowconfigure(1, weight=1)
        self.columnconfigure(0, weight=1)
        self.columnconfigure(1, weight=1)

    def _resize(self, event):
        '''Modify padding when window is resized.'''
        w, h = event.width, event.height
        w1, h1 = self.content.winfo_width(), self.content.winfo_height()
        print w1, h1  # should be equal
        if w > h:
            self.rowconfigure(0, weight=1)
            self.rowconfigure(1, weight=0)
            self.columnconfigure(0, weight=h)
            self.columnconfigure(1, weight=w - h)
        elif w < h:
            self.rowconfigure(0, weight=w)
            self.rowconfigure(1, weight=h - w)
            self.columnconfigure(0, weight=1)
            self.columnconfigure(1, weight=0)
        else:
            # width = height
            self.rowconfigure(0, weight=1)
            self.rowconfigure(1, weight=0)
            self.rowconfigure(0, weight=1)
            self.columnconfigure(1, weight=0)

root = tk.Tk()
app = Application(master=root, width=100, height=100)
app.mainloop()
person Gary Kerr    schedule 14.05.2013
comment
Тоже отлично работает. Принял бы и ваш ответ, но я могу принять только один... извините за это. Тем не менее, большое спасибо. - person Dani Gehtdichnixan; 17.05.2013
comment
Спасибо, ответ Брайана намного лучше. - person Gary Kerr; 17.05.2013