Home > Software design >  some kind of virtual split between the textbox and button in the bottom
some kind of virtual split between the textbox and button in the bottom

Time:01-29

I have a textbox and I want that its size will be fit to window size. Possible ?

The problem is that the button(in toplevel window) disappears. Is it possible to get the small window and still show the button with pack ? I told about "show_changes" function

It's supposed to be some kind of virtual split between the textbox and button in the bottom.

import tkinter as tk
from tkinter import *


def show_changes(root):
    newWindow = Toplevel(root)
    newWindow.title("Changes")
    newWindow.geometry("1000x100 200 200")
    frame = Frame(newWindow)
    frame.pack(side=tk.TOP, fill=BOTH, expand=True)
    verscrlbar_frame1 = Scrollbar(frame, orient="vertical")
    horscrlbar_frame1 = Scrollbar(frame, orient="horizontal")
    verscrlbar_frame1.pack(side='right', fill='y')
    horscrlbar_frame1.pack(side='bottom', fill='x')

    changes_txt = Text(frame, wrap=NONE)
    changes_txt.pack(side=tk.TOP, fill=BOTH, expand=True)
    changes_txt.config(xscrollcommand=horscrlbar_frame1.set)
    changes_txt.config(yscrollcommand=verscrlbar_frame1.set)
    changes_txt.config(state=DISABLED)

    verscrlbar_frame1.config(command=changes_txt.yview)
    horscrlbar_frame1.config(command=changes_txt.xview)


    frame_btn = Frame(newWindow)
    frame_btn.pack(side=tk.BOTTOM, expand=False)
    ok_btn = Button(frame_btn, text="Ok")
    ok_btn.pack(side=tk.LEFT, pady=10)

    newWindow.mainloop()

def main():
    root = tk.Tk()
    root.geometry("200x200")  # Size of the window
    root.title("Main Window")
    btn = Button(root, text="show_changes", command=lambda: show_changes(root))
    btn.pack()

    root.mainloop()


# ----------------------------------------------------------------------
if __name__ == "__main__":
    main()

Thanks, Alex

CodePudding user response:

When using pack, the order that widgets are packed matters. When the window isn't large enough to show all of the widgets at their requested size (such is the case in your code), pack will start to shrink widget starting with the widgets that were added last.

There are at least three solutions, each of which will solve the problem independently, but all three are best practices.

First, you can choose to not give the window an explicit size. Instead, make the widgets the minimum size necessary and let tkinter compute the window size. This is the best solution. For example, if the text widget only needs to be 2 lines tall, set its height to 2, and then remove the call to newWindow.geometry (or, leave it in but only use it to set the position). Tkinter will calculate a window size that is just big enough to hold the button and the 2 line text widget.

The second solution involves setting the text widget height to something tiny, and then using pack to have it fill the available space. The reason this works is that when widgets don't fit in a window that has a fixed size, the packer has to start shrinking or removing widgets. It will shrink all widgets (one at a time...) until they are all at their minimum requested size. Once that happens it has to actually start chopping off part of one or more widgets. Your text widget minimum requested size is the default 20 lines tall, and since that won't fit, the packer starts removing widgets with the last widget added, the button.

The third solution is leveraging the fact that tkinter will shrink or remove widgets in their stacking order, which is the reverse order to how they were added. In other words, it will shrink and remove the last widget to be packed, and then the next-to-last, and so on. If you pack the frame that the button is in first and then the text widget, the packer will shrink the text widget rather than the button when it has to start reducing widgets to make them fit.

I recommend combining all of the solutions because they all represent best practices that you should be in the habit of doing.

  • make the text widget only as tall as it needs to be
  • pack the frame with the button before packing the text widget
  • don't give the window a fixed size; let tkinter compute that for you.

Also, you don't need to call mainloop a second time, and you shouldn't import tkinter twice. Finally, I find that grouping calls to pack, place, or grid together for all children in a widget makes it easier to visualize the layout in the code, and makes maintenance easier.

Combining all of that advice, here's how I would rewrite your function:

import tkinter as tk


def show_changes(root):
    newWindow = tk.Toplevel(root)
    newWindow.title("Changes")
    newWindow.geometry(" 200 200")

    frame_btn = tk.Frame(newWindow)
    frame = tk.Frame(newWindow)

    # pack the widgets in the order that they should be clipped
    # if the window is too small. That usually means toolbars and
    # menubars first, "hero" widgets like a Text or Canvas last.
    frame_btn.pack(side=tk.BOTTOM, expand=False)
    frame.pack(side=tk.TOP, fill="both", expand=True)

    ok_btn = tk.Button(frame_btn, text="Ok")
    ok_btn.pack(side=tk.LEFT, pady=10)

    verscrlbar_frame1 = tk.Scrollbar(frame, orient="vertical")
    horscrlbar_frame1 = tk.Scrollbar(frame, orient="horizontal")
    changes_txt = tk.Text(frame, height=2, width=80, wrap="none")

    changes_txt.config(xscrollcommand=horscrlbar_frame1.set)
    changes_txt.config(yscrollcommand=verscrlbar_frame1.set)
    changes_txt.config(state="disabled")
    verscrlbar_frame1.config(command=changes_txt.yview)
    horscrlbar_frame1.config(command=changes_txt.xview)

    verscrlbar_frame1.pack(side='right', fill='y')
    horscrlbar_frame1.pack(side='bottom', fill='x')
    changes_txt.pack(side=tk.TOP, fill="both", expand=True)

    newWindow.mainloop()

def main():
    root = tk.Tk()
    root.geometry("200x200")  # Size of the window
    root.title("Main Window")
    btn = tk.Button(root, text="show_changes", command=lambda: show_changes(root))
    btn.pack()

    root.mainloop()


# ----------------------------------------------------------------------
if __name__ == "__main__":
    main()

  • Related