使用 Tkinter 动态布局组件并实现窗口大小变化

在这篇博客中,我们将介绍如何使用 Tkinter 创建一个可以动态添加子组件的应用程序,并实现窗口大小变化时自动调整布局。在这个过程中,我们将展示如何添加滚动条、动态添加和删除组件,以及根据窗口大小自动调整组件布局。

需求分析

  1. 动态添加子组件:在主窗口中,可以动态添加包含多个输入框的子组件。
  2. 删除子组件:每个子组件都有一个删除按钮,可以移除该组件。
  3. 自动布局调整:根据窗口大小变化,子组件的布局自动调整, 固定子组件的宽高。
  4. 滚动条:当子组件过多时,窗口自动添加纵向滚动条。

实现步骤

第一步:创建主窗口和主框架

首先,我们需要创建主窗口和主框架。在主框架中,我们将添加一个 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 应用程序时非常实用,可以帮助我们创建更加灵活和用户友好的界

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值