pythontkinterlayoutpixelscreen-size

Tkinter Text widget width doesn't match combined width of two Button widgets despite proportional calculations


I'm creating a Tkinter GUI where I want a black Text widget (message_entry) to be exactly the same width as the combined width of two horizontally placed buttons. I’m using proportions of the screen size to set widget widths, but on different screens (with different resolutions or DPI), the Text widget ends up being either wider or narrower than the two buttons combined.

Important: I do not want to use expand=True or fill in my layouts, because I don’t want the Text widget to stretch and fill all available space—just match the two buttons.

Here's the exact code I have:

import tkinter as tk
from tkinter import ttk

root = tk.Tk(className="Chat")

root.title("Chat")

# Maximize the window
root.wm_state('zoomed')

# Get the screen width and height
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()


def proportion_height(p):
    return int(screen_height * p)

def proportion_box_width(p):
    box_width = int(screen_width * p)
    print("Box width is ", box_width)

    box_width = int(box_width / 2)
    print("Box width after devide is ", box_width)
    return box_width
# Proportional calculations
def proportion_width(p):
    return int(screen_width * p)


frame1 = tk.Frame(root, bg='white')
frame1.pack(side='bottom', padx=(proportion_width(0.22),0), pady=(proportion_height(0.1), 1), anchor='w')

frame2 = tk.Frame(root, bg='white')
frame2.pack(

            before=frame1,
            side='bottom',
            padx=(proportion_width(0.22),0),
            pady=(proportion_height(0), 5),
            anchor='w'
        )

# Frame to contain message_entry and buttons
frame_for_widgets = tk.Frame(root, bg='white')
frame_for_widgets.pack(
            padx=(proportion_width(0.22),0),
            anchor='w',
            before=frame2,
            pady=(0, proportion_height(0.02)),
            side='bottom',
        )

style = ttk.Style()
style.configure("def2.TButton", font=('Helvetica', 14,), background='white', fg='white', highlightthickness=1, highlightbackground="gray", borderwidth=1)


chat_with_pdf_button = ttk.Button(frame1, width=proportion_box_width(0.04), text="Chat with PDF", style="def2.TButton",)

chat_with_pdf_button.pack(side='left', expand=False, ipady=proportion_height(0.01))


# Create a button to trigger the summarization process
summarize_button = ttk.Button(frame1, width=proportion_box_width(0.04), text="Chat with a YouTube video", style="def2.TButton")
summarize_button.pack(side='right', expand=False, padx=(proportion_height(0.01), 0), ipady=proportion_height(0.01))

letter_button = ttk.Button(frame2, width=proportion_box_width(0.04), text="Write a letter", style="def2.TButton")
letter_button.pack(side='left',expand=False, ipady=proportion_height(0.01))

blog_button = ttk.Button(frame2, width=proportion_box_width(0.04), text="Chat with a Blog Post",  style="def2.TButton")
blog_button.pack(side='right',expand=False, padx=(proportion_height(0.01), 0), ipady=proportion_height(0.01))

message_entry = tk.Text(
    frame_for_widgets,
    width=proportion_width(0.04),
    bg='black',
    insertbackground='black',
    fg='black',
    borderwidth=0,
    # Removing fixed width and height, so it expands with the frame
    height=proportion_width(0.000001),
      # Set a fixed height if needed
    spacing1=proportion_height(0.02),  # Adjust spacing1 proportionally
    spacing3=proportion_height(0.02),  # Adjust spacing3 proportionally
    font=('Helvetica', 14)
)

# Pack message_entry into frame2
message_entry.pack(side='left', expand=False)

# Send button
send_button = ttk.Button(frame_for_widgets)
send_button.pack(side='left', 
        expand=False,                       
        padx=(proportion_width(0.001),0),
        pady=(proportion_height(0.02), proportion_height(0.02)),)

send_button.pack_configure(expand=False)


style = ttk.Style()
style.configure("TMenubutton", background="white", borderwidth=0, font=('Helvetica', 14),)  # Set background color, reduce border thickness, set font, and set text color
menu_button = ttk.Menubutton(frame_for_widgets, text="Select", direction='above',)  # Set "Option 1" as the initial text
menu_button.pack(side=tk.LEFT, pady=(proportion_height(0.02), proportion_height(0.02)), expand=False)


root.mainloop()

Here's how it looks on my screen

My Goal:

Make message_entry have exactly the same width as the sum of the widths of chat_with_pdf_button and summarize_button, regardless of the user’s screen resolution or DPI.

Avoid using expand=True or fill because I don’t want the widget to fill the entire window, just to match the button widths.

I’d really appreciate any help on how to get consistent widths across different screen resolutions without using expand or fill.


Solution

  • It is hard to align widgets using pack(). I would suggest to put those button and text widgets in a frame and use grid() on those widgets instead, and pack the frame to the position you want. Then it is easier to expand the width of the text widget to the sum of the first two buttons.

    Below is the modified code based on yours:

    import tkinter as tk
    from tkinter import ttk
    
    def proportion_width(p):
        return int(screen_width * p)
    
    def proportion_height(p):
        return int(screen_height * p)
    
    def proportion_box_width(p):
        box_width = proportion_width(p)
        print('Box width is', box_width)
        box_width //= 2
        print('Box width after divided is', box_width)
        return box_width
    
    root = tk.Tk(className='Chat')
    root.title('Chat')
    root.state('zoomed')
    
    style = ttk.Style()
    style.configure('def2.TButton', font=('Helvetica',14), background='white', fg='white',
                                    highlightthickness=1, highlightbackground='gray', borderwidth=1)
    style.configure('TMenubutton', font=('Helvetica',14), background='white', borderwidth=0)
    
    screen_width = root.winfo_screenwidth()
    screen_height = root.winfo_screenheight()
    
    # frame to hold those buttons and text widget
    frame = tk.Frame(root, bg='white')
    frame.pack(side='bottom', padx=(proportion_width(0.22),0), pady=proportion_height(0.02))
    
    # use grid manager on those buttons and text box
    
    chat_with_pdf_button = ttk.Button(frame, width=proportion_box_width(0.04), text='Chat with PDF', style='def2.TButton')
    chat_with_pdf_button.grid(row=0, column=0, ipady=proportion_height(0.01), pady=(0,5))
    
    summarize_button = ttk.Button(frame, width=proportion_box_width(0.04), text='Chat with a YouTube video', style='def2.TButton')
    summarize_button.grid(row=0, column=1, padx=(proportion_height(0.01),0), ipady=proportion_height(0.01), pady=(0,5))
    
    letter_button = ttk.Button(frame, width=proportion_box_width(0.04), text='Write a letter', style='def2.TButton')
    letter_button.grid(row=1, column=0, ipady=proportion_height(0.01), pady=(0,5))
    
    blog_button = ttk.Button(frame, width=proportion_box_width(0.04), text='Chat with a Blog Post', style='def2.TButton')
    blog_button.grid(row=1, column=1, padx=(proportion_height(0.01),0), ipady=proportion_height(0.01), pady=(0,5))
    
    message_entry = tk.Text(
        frame,
        bg='black',
        insertbackground='black',
        fg='black',
        borderwidth=0,
        width=1,  # let sticky option of grid() to expand the width
        height=proportion_width(0.000001),
        spacing1=proportion_height(0.02),
        spacing3=proportion_height(0.02),
        font=('Helvetica', 14)
    )
    message_entry.grid(row=2, column=0, columnspan=2, sticky='ew')
    
    send_button = ttk.Button(frame)
    send_button.grid(row=2, column=2,
                     padx=(proportion_width(0.001),0),
                     pady=proportion_height(0.02))
    
    menu_button = ttk.Menubutton(frame, text='Select', direction='above')
    menu_button.grid(row=2, column=3, pady=proportion_height(0.02))
    
    root.mainloop()
    

    Result:

    enter image description here