在这篇博客中,我们将介绍如何使用 Tkinter 创建一个可以动态添加子组件的应用程序,并实现窗口大小变化时自动调整布局。在这个过程中,我们将展示如何添加滚动条、动态添加和删除组件,以及根据窗口大小自动调整组件布局。
需求分析
- 动态添加子组件:在主窗口中,可以动态添加包含多个输入框的子组件。
- 删除子组件:每个子组件都有一个删除按钮,可以移除该组件。
- 自动布局调整:根据窗口大小变化,子组件的布局自动调整, 固定子组件的宽高。
- 滚动条:当子组件过多时,窗口自动添加纵向滚动条。
实现步骤
第一步:创建主窗口和主框架
首先,我们需要创建主窗口和主框架。在主框架中,我们将添加一个 Canvas 来容纳所有动态添加的子组件,并在 Canvas 中添加一个可滚动的 Frame。
import tkinter as tk
from tkinter import ttk
class Main:
def __init__(self):
self.root = tk.Tk()
self.root.geometry("500x500")
self.init()
def init(self):
self.root.title("Dynamic Frames")
# 创建样式
style = ttk.Style()
style.configure("TScrollbar", background="lightgray", troughcolor="gray", bordercolor="gray", arrowcolor="black")
# 主框架
self.main_frame = tk.Frame(self.root)
self.main_frame.pack(fill=tk.BOTH, expand=True)
# 创建 Canvas
self.canvas = tk.Canvas(self.main_frame)
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# 添加滚动条
self.scrollbar = ttk.Scrollbar(self.main_frame, orient=tk.VERTICAL, command=self.canvas.yview, style="TScrollbar")
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 配置 Canvas
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.canvas.bind('<Configure>', lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")))
# 创建可滚动的 Frame
self.scrollable_frame = tk.Frame(self.canvas)
self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
# 添加按钮
add_frame_button = tk.Button(self.root, text="Add Frame", command=self.add_frame)
add_frame_button.pack(pady=10)
print_button = tk.Button(self.root, text="Print Entries", command=self.print_entries)
print_button.pack(pady=10)
# 存储动态添加的子组件
self.frames = []
# 绑定窗口大小变化事件
self.root.bind('<Configure>', self.repack_frames)
第二步:动态添加子组件
我们需要一个方法来动态添加子组件,每个子组件包含多个输入框和一个删除按钮。以下是添加子组件的方法:
def add_frame(self):
# 创建新子组件
frame0 = tk.Frame(self.scrollable_frame, bd=2, relief=tk.SUNKEN, padx=5, pady=5)
# 创建输入框
entry1 = ttk.Entry(frame0, width=20)
entry1.pack(pady=2)
entry2 = ttk.Entry(frame0, width=20)
entry2.pack(pady=2)
entry3 = ttk.Entry(frame0, width=20)
entry3.pack(pady=2)
# 创建删除按钮
delete_button = tk.Button(frame0, text="Delete", command=lambda f=frame0: self.delete_frame(f))
delete_button.pack(pady=2)
# 存储子组件
self.frames.append((frame0, entry1, entry2, entry3, delete_button))
# 重新排列子组件
self.repack_frames()
第三步:删除子组件
我们需要一个方法来删除动态添加的子组件:
def delete_frame(self, frame0):
for frame_tuple in self.frames:
if frame_tuple[0] == frame0:
self.frames.remove(frame_tuple)
frame0.destroy()
self.repack_frames()
break
第四步:自动调整布局
我们需要根据窗口的大小自动调整子组件的布局。以下是实现布局调整的方法, 以及固定子组件的宽高:
def repack_frames(self, event=None):
for frame_tuple in self.frames:
frame_tuple[0].grid_forget()
# 获取窗口宽度
window_width = self.main_frame.winfo_width()
# 假设每个子组件的宽度为 160
frame_width = 160
max_columns = max(1, window_width // frame_width)
row, col = 0, 0
for frame_tuple in self.frames:
frame_tuple[0].grid(row=row, column=col, padx=5, pady=5)
# 取消子组件自适应宽高, 并固定宽: 160 高: 120
frame_tuple[0].grid_propagate(False) # Prevent frame0 from resizing based on its content
frame_tuple[0].configure(width=frame_width, height=120) # Set a fixed size for frame0
col += 1
if col >= max_columns:
col = 0
row += 1
第五步:打印所有输入框内容
我们还需要一个方法来打印所有子组件中的输入框内容:
def print_entries(self):
for frame_tuple in self.frames:
frame0, entry1, entry2, entry3, _ = frame_tuple
print(f"Entry 1: {entry1.get()}, Entry 2: {entry2.get()}, Entry 3: {entry3.get()}")
完整代码
以下是完整的代码实现:
import tkinter as tk
from tkinter import ttk
class Main:
def __init__(self):
self.root = tk.Tk()
self.root.geometry("500x500")
self.init()
def init(self):
self.root.title("Dynamic Frames")
style = ttk.Style()
style.configure("TScrollbar", background="lightgray", troughcolor="gray", bordercolor="gray", arrowcolor="black")
self.main_frame = tk.Frame(self.root)
self.main_frame.pack(fill=tk.BOTH, expand=True)
self.canvas = tk.Canvas(self.main_frame)
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.scrollbar = ttk.Scrollbar(self.main_frame, orient=tk.VERTICAL, command=self.canvas.yview, style="TScrollbar")
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.canvas.bind('<Configure>', lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")))
self.scrollable_frame = tk.Frame(self.canvas)
self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
add_frame_button = tk.Button(self.root, text="Add Frame", command=self.add_frame)
add_frame_button.pack(pady=10)
print_button = tk.Button(self.root, text="Print Entries", command=self.print_entries)
print_button.pack(pady=10)
self.frames = []
self.root.bind('<Configure>', self.repack_frames)
def add_frame(self):
frame0 = tk.Frame(self.scrollable_frame, bd=2, relief=tk.SUNKEN, padx=5, pady=5)
entry1 = ttk.Entry(frame0, width=20)
entry1.pack(pady=2)
entry2 = ttk.Entry(frame0, width=20)
entry2.pack(pady=2)
entry3 = ttk.Entry(frame0, width=20)
entry3.pack(pady=2)
delete_button = tk.Button(frame0, text="Delete", command=lambda f=frame0: self.delete_frame(f))
delete_button.pack(pady=2)
self.frames.append((frame0, entry1, entry2, entry3, delete_button))
self.repack_frames()
def delete_frame(self, frame0):
for frame_tuple in self.frames:
if frame_tuple[0] == frame0:
self.frames.remove(frame_tuple)
frame0.destroy()
self.repack_frames()
break
def repack_frames(self, event=None):
for frame_tuple in self.frames:
frame_tuple[0].grid_forget()
window_width = self.main_frame.winfo_width()
frame_width = 160
max_columns = max(1, window_width // frame_width)
row, col = 0, 0
for frame_tuple in self.frames:
frame_tuple[0].grid(row=row, column=col, padx=5, pady=5)
# 取消子组件自适应宽高, 并固定宽: 160 高: 120
frame_tuple[0].grid_propagate(False) # Prevent frame0 from resizing based on its content
frame_tuple[0].configure(width=frame_width, height=120) # Set a fixed size for frame0
col += 1
if col >= max_columns:
col = 0
row += 1
def print_entries(self):
for frame_tuple in self.frames:
frame0, entry1, entry2, entry3, _ = frame_tuple
print(f"Entry 1: {entry1.get()}, Entry 2: {entry2.get()}, Entry 3: {entry3.get()}")
if __name__ == "__main__":
app = Main()
app.root.mainloop()
总结
通过这篇博客,我们展示了如何使用 Tkinter 创建一个动态布局的应用程序,实现了动态添加和删除子组件,并在窗口大小变化时自动调整布局。此外,还介绍了如何添加滚动条以便在子组件过多时能够滚动查看。这些技巧和方法在创建 Tkinter GUI 应用程序时非常实用,可以帮助我们创建更加灵活和用户友好的界