小能豆

Expending tk.Frame and it's children for an Attribute Editor like GUI

py

ython 3.12 Windows 10

enter image description here

I’m trying to create a property editor using tkinter. Here is the code:

import tkinter as tk
from tkinter import colorchooser
from tkinter.filedialog import asksaveasfile 
from tkinter import ttk  

# Third party
from tkscrolledframe import ScrolledFrame
import os
if os.name == 'nt':
    from ctypes import windll
    windll.shcore.SetProcessDpiAwareness(1)

LABEL_WIDTH = 14   
PADX = 5
PADY = 3

class StringWidget(tk.Frame):
    ''''''
    def __init__(self, parent, label:str, default=""):
        ''''''
        tk.Frame.__init__(self, parent)

        self.label = tk.Label(self, text=label, anchor="e", width=LABEL_WIDTH)
        self.entry = tk.Entry(self)
        self.entry.insert(0, default)

        self.label.pack(side="left", padx=PADX)
        self.entry.pack(side="left", padx=PADX)

class FileWidget(tk.Frame):
    ''''''
    def __init__(self, parent, label:str, default=""):
        ''''''
        print(parent.winfo_width())
        tk.Frame.__init__(self, parent, width=parent.winfo_width())

        self.label = tk.Label(self, text=label, anchor="e", width=LABEL_WIDTH)
        self.entry = tk.Entry(self)
        self.entry.insert(0, default)
        self.btn = tk.Button(self, text="Browse")

        self.label.pack(side="left", padx=PADX)
        self.entry.pack(side="left", padx=PADX)
        self.btn.pack(side="left", padx=PADX)

class Point3DWidget(tk.Frame):
    ''''''
    def __init__(self, parent, label:str, minv:int=0, maxv:int=10, default:int=0, incr:int=1, format="%.0f"):
        ''''''
        tk.Frame.__init__(self, parent)
        self.strv = tk.StringVar()
        self.strv.set(default)

        self.label = tk.Label(self, text=label, anchor='e', width=LABEL_WIDTH)
        self.spinbox1 = ttk.Spinbox(self, from_=minv, to=maxv, increment=incr, format=format, textvariable=self.strv, wrap=True)
        self.spinbox2 = ttk.Spinbox(self, from_=minv, to=maxv, increment=incr, format=format, textvariable=self.strv, wrap=True)
        self.spinbox3 = ttk.Spinbox(self, from_=minv, to=maxv, increment=incr, format=format, textvariable=self.strv, wrap=True)

        self.label.pack(side="left", padx=PADX)
        self.spinbox1.pack(side="left", padx=PADX)
        self.spinbox2.pack(side="left", padx=PADX)
        self.spinbox3.pack(side="left", padx=PADX)


class AttributeEditor(tk.Frame):
    ''''''
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.label = tk.Label(self, text="Theme", font=('bold'))

        self.sw = StringWidget(self, "String Widget", "Inigo")
        self.fw = FileWidget(self, "File Widget", "Inigo")
        self.pw = Point3DWidget(self, "Point 3D Widget", 0, 10, 6)

        self.label.grid(row=0, column=0, sticky="w", padx=PADX, pady=PADY)

        self.sw.grid(row=1, column=0, sticky="ew", pady=PADY)
        self.fw.grid(row=2, column=0, sticky="ew", pady=PADY)
        self.pw.grid(row=3, column=0, sticky="ew", pady=PADY)

        separator = ttk.Separator(self, orient='horizontal')
        separator.grid(row=9, column=0, sticky="ew", pady=PADX, padx=PADY)

if __name__ == "__main__":
    root = tk.Tk()
    root.title("Attribute Editor")
    root.geometry("380x360")

    # Create a ScrolledFrame widget
    sf = ScrolledFrame(root)
    sf.pack(side="top", expand=1, fill="both")

    # Bind the arrow keys and scroll wheel
    sf.bind_arrow_keys(root)
    sf.bind_scroll_wheel(root)

    # Create a frame within the ScrolledFrame
    inner_frame = sf.display_widget(AttributeEditor)

    root.mainloop()

When ever I resize the main window, the desired expending behavior I’m looking for is:

  1. In StringWidget entry should stretch till end of the window.
  2. In Filewidget the entry should take the remaining space between label and button and stretched to cover the space.
  3. The 3 spinners should be stretched till the end of the window and should take equal space between label and window end.

For reference

enter image description here


阅读 104

收藏
2023-12-16

共1个答案

小能豆

To achieve what you want, you need to:

  • set fit_width=True when calling sf.display_widget(...) to expand the inner frame to the width of the canvas of ScrolledFrame
  • call self.columnconfigure(0, weight) inside AttributeEditor.__init__() so that all child frames created inside it in column 0 will use all the horizontal available space
  • add fill="x" and expand=1 in self.entry.pack(...) inside StringWidget.__init__() to fulfill item #1
  • add fill="x" and expand=1 in self.entry.pack(...) inside FileWidget.__init__() to fulfill item #2
  • add fill="x" and expand=1 in those self.spinboxX.pack(...) inside Point3DWidget.__init__() to fulfill item #3

Note also that in order to see the above result, you need to resize the window to a wider width.

Below is the required changes:

...

class StringWidget(tk.Frame):
    def __init__(self, parent, label:str, default=""):
        ...
        self.entry.pack(side="left", padx=PADX, fill="x", expand=1)

class FileWidget(tk.Frame):
    def __init__(self, parent, label:str, default=""):
        ...
        self.entry.pack(side="left", padx=PADX, fill="x", expand=1)
        ...

class Point3DWidget(tk.Frame):
    def __init__(self, parent, label:str, minv:int=0, maxv:int=10, default:int=0, incr:int=1, format="%.0f"):
        ...
        self.spinbox1.pack(side="left", padx=PADX, fill="x", expand=1)
        self.spinbox2.pack(side="left", padx=PADX, fill="x", expand=1)
        self.spinbox3.pack(side="left", padx=PADX, fill="x", expand=1)

class AttributeEditor(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.columnconfigure(0, weight=1)
        ...

if __name__ == "__main__":
    ...
    root.geometry("800x360") # create wider window
    ...
    inner_frame = sf.display_widget(AttributeEditor, fit_width=True)

    root.mainloop()

The result:

enter image description here

2023-12-16