pythonuser-interfacetkinterjupyter-notebooktkinter-layout

Changing Vertical Space between Tkinter Widgets in a TopLevel Window


I am building an Automated Data Preprocessing tool in Python where I am using Tkinter for the GUI, In the new_window, which is a TopLevel Window I want to shrink the vertical space that is unoccupied between inputs_frame and the text_widget3, and I want to place this inputs_frame right below the text_widget3. And thereafter I want to add another text_widget4 right below the inputs_frame.

Here is the code for this new window


    def open_new_window(self):
        new_window = tk.Toplevel()
        new_window.title("New Window")
        
        # Set the window size to full screen
        # Set the window size to full screen
        new_window.state('zoomed')

        # Create a text widget to display the info
        text_widget = tk.Text(new_window, bd=2, relief=tk.GROOVE, width=70, height=20)
        text_widget.grid(row=0, column=0, padx=15, pady=10, sticky="nw")

        # Redirect the output of df.info() to the text widget
        with io.StringIO() as stream:
            self.df.info(buf=stream)
            output = stream.getvalue()
            text_widget.insert(tk.END, output)
            
        # Make the text widget uneditable
        text_widget.config(state="disabled")
                
        # Create a text widget to display the info
        text_widget2 = tk.Text(new_window, bd=2, relief=tk.GROOVE, width=70, height=22)
        text_widget2.grid(row=1, column=0, padx=15, pady=5, sticky="sw")
             
        # Create another text widget to display additional info
        text_widget3 = tk.Text(new_window, bd=2, relief=tk.GROOVE, width=80, height=3)
        text_widget3.grid(row=0, column=1, padx=15, pady=10, rowspan=1, sticky="ne")
        
        # Add a frame to the window for inputs
        inputs_frame = tk.Frame(new_window, bd=2, relief=tk.GROOVE, width=text_widget3["width"])
        inputs_frame.grid(row=1, column=1, padx=15, pady=10, sticky="new")

        # Add labels and text inputs to the inputs frame
        label1 = tk.Label(inputs_frame, text="Label 1:")
        label1.grid(row=0, column=0, padx=5, pady=5, sticky="w")
        input1 = tk.Entry(inputs_frame)
        input1.grid(row=0, column=1, padx=5, pady=5, sticky="w")

        label2 = tk.Label(inputs_frame, text="Label 2:")
        label2.grid(row=1, column=0, padx=5, pady=5, sticky="w")
        input2 = tk.Entry(inputs_frame)
        input2.grid(row=1, column=1, padx=5, pady=5, sticky="w")

        # Add an "Add" button to the inputs frame
        add_button = tk.Button(inputs_frame, text="Add", width=10)
        add_button.grid(row=2, column=0, columnspan=2, pady=10)
        

Here is an output screenshot of the window after running the code -

enter image description here

I also have tried certain methods like changing the weights of the rows using.grid_rowconfigure() method, but even that didn't work!

And here is the full code for the whole program until now!

import tkinter as tk, pandas as pd, io
from tkinter import ttk, filedialog

class DataWizard:
    def __init__(self, root):
        self.root = root
        self.root.title("Data Wizard: Automated Data Preprocessing")
        self.root.geometry("800x600")

        # Top layout with title
        self.title_label = tk.Label(self.root, text="Data Wizard: Automated Data Preprocessing", font=("Arial", 18))
        self.title_label.pack(pady=20)

        # Middle layout with table frame
        self.table_frame = tk.Frame(self.root, bd=2, relief=tk.GROOVE)
        self.table_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)

        # Create a treeview widget to display table
        self.tree = ttk.Treeview(self.table_frame, selectmode='browse')
        self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # Add scrollbar to the treeview widget
        scrollbar = ttk.Scrollbar(self.table_frame, orient="vertical", command=self.tree.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.tree.configure(yscrollcommand=scrollbar.set)

        # Bottom layout with Upload CSV file label and button
        self.bottom_frame = tk.Frame(self.root, bd=2, relief=tk.GROOVE)
        self.bottom_frame.pack(fill=tk.X, padx=20, pady=10)

        self.upload_label = tk.Label(self.bottom_frame, text="Upload CSV file", font=("Arial", 12))
        self.upload_label.pack(side=tk.LEFT, padx=10, pady=10)

        self.upload_button = tk.Button(self.bottom_frame, text="Browse", command=self.upload_csv)
        self.upload_button.pack(side=tk.RIGHT, padx=10, pady=10)

        
        self.new_frame = tk.Frame(self.root, bd=0, relief=tk.GROOVE)
        self.new_frame.pack(fill=tk.X, padx=20, pady=10)
        
        # Add button to open a new window
        self.new_window_button = tk.Button(self.new_frame, text="Next", command=self.open_new_window)
        self.new_window_button.pack(side=tk.RIGHT, padx=10, pady=10)

    def upload_csv(self):
        # Open file dialog to select a CSV file
        file_path = filedialog.askopenfilename(filetypes=[("CSV files", "*.csv")])

        if file_path:
            # Read CSV file as pandas dataframe
            self.df = pd.read_csv(file_path)

            # Set the columns of the treeview widget based on the dataframe columns
            self.tree["columns"] = list(self.df.columns)

            # Set column headings and widths based on the dataframe
            for col in self.df.columns:
                self.tree.heading(col, text=col.title())
                self.tree.column(col, width=len(str(self.df[col].max())), anchor = tk.CENTER)

            # Insert data into treeview widget
            for i, row in self.df.iterrows():
                self.tree.insert("", "end", values=list(row))

            # Change the upload label to the filename
            self.upload_label.config(text=file_path)

            # Add horizontal scrollbar to the treeview widget
            horizontal_scrollbar = ttk.Scrollbar(self.table_frame, orient="horizontal", command=self.tree.xview)
            horizontal_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)
            self.tree.configure(xscrollcommand=horizontal_scrollbar.set)

    def open_new_window(self):
        new_window = tk.Toplevel()
        new_window.title("New Window")
        
        # Set the window size to full screen
        # Set the window size to full screen
        new_window.state('zoomed')

        # Create a text widget to display the info
        text_widget = tk.Text(new_window, bd=2, relief=tk.GROOVE, width=70, height=20)
        text_widget.grid(row=0, column=0, padx=15, pady=10, sticky="nw")

        # Redirect the output of df.info() to the text widget
        with io.StringIO() as stream:
            self.df.info(buf=stream)
            output = stream.getvalue()
            text_widget.insert(tk.END, output)
            
        # Make the text widget uneditable
        text_widget.config(state="disabled")
                
        # Create a text widget to display the info
        text_widget2 = tk.Text(new_window, bd=2, relief=tk.GROOVE, width=70, height=22)
        text_widget2.grid(row=1, column=0, padx=15, pady=5, sticky="sw")
             
        # Create another text widget to display additional info
        text_widget3 = tk.Text(new_window, bd=2, relief=tk.GROOVE, width=80, height=3)
        text_widget3.grid(row=0, column=1, padx=15, pady=10, rowspan=1, sticky="ne")
        
        # Add a frame to the window for inputs
        inputs_frame = tk.Frame(new_window, bd=2, relief=tk.GROOVE, width=text_widget3["width"])
        inputs_frame.grid(row=1, column=1, padx=15, pady=10, sticky="new")

        # Add labels and text inputs to the inputs frame
        label1 = tk.Label(inputs_frame, text="Label 1:")
        label1.grid(row=0, column=0, padx=5, pady=5, sticky="w")
        input1 = tk.Entry(inputs_frame)
        input1.grid(row=0, column=1, padx=5, pady=5, sticky="w")

        label2 = tk.Label(inputs_frame, text="Label 2:")
        label2.grid(row=1, column=0, padx=5, pady=5, sticky="w")
        input2 = tk.Entry(inputs_frame)
        input2.grid(row=1, column=1, padx=5, pady=5, sticky="w")

        # Add an "Add" button to the inputs frame
        add_button = tk.Button(inputs_frame, text="Add", width=10)
        add_button.grid(row=2, column=0, columnspan=2, pady=10)
        
              
                
if __name__ == "__main__":
    root = tk.Tk()
    app = DataWizard(root)
    root.mainloop()

Solution

  • I think the easiest solution is to put the widgets on the right into a frame, and then put the frame to the right of the other two windows.

    Since it appears you don't need a grid for the two main sections on the right, I would use pack for them.

    In the following example I create a frame named f. It is used as the first argument when creating text_widget3 and inputs_frame. Those two are then packed inside the frame.

    f = tk.Frame(new_window)
    ...
    f.grid(row=0, column=1, padx=15, pady=10, rowspan=2, sticky="nsew")
    
    text_widget3 = tk.Text(f, bd=2, relief=tk.GROOVE, width=80, height=3)
    inputs_frame = tk.Frame(f, bd=2, relief=tk.GROOVE, width=text_widget3["width"])
    
    text_widget3.pack(side="top", fill="x")
    inputs_frame.pack(side="top", fill="x")
    
    # Add labels and text inputs to the inputs frame
    label1 = tk.Label(inputs_frame, text="Label 1:")
    input1 = tk.Entry(inputs_frame)
    label2 = tk.Label(inputs_frame, text="Label 2:")
    input2 = tk.Entry(inputs_frame)
    add_button = tk.Button(inputs_frame, text="Add", width=10)
    
    label1.grid(row=0, column=0, padx=5, pady=5, sticky="w")
    input1.grid(row=0, column=1, padx=5, pady=5, sticky="w")
    label2.grid(row=1, column=0, padx=5, pady=5, sticky="w")
    input2.grid(row=1, column=1, padx=5, pady=5, sticky="w")
    add_button.grid(row=2, column=0, columnspan=2, pady=10)
    

    screenshot