Below is a simple illustration of the problem. It consists of a grid layout where I've placed a button widget in row 0 and a text widget in row 1.
What I want is for the text widget in row 1 to expand with the form while keeping the top of the text widget anchored NW (row 0 not expanding). The problem is that the horizontal(column expands correctly, but the text widget row does not. If I get rid of the button the text widget expands correctly. Also the original form I've put together is more involved and is best served using a grid. So basically using pack isn't a solution. Any help would be appreciated.
################################################
from tkinter import *
class Application(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initGui()
# ##################################################################
# Initialize GUI widgets
def initGui(self):
self.parent.title("Test Grid")
self.parent.resizable(width=TRUE, height=TRUE)
self.grid(sticky=W+E+N+S, padx=20, pady=20)
self.parent.columnconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
self.parent.rowconfigure(1, weight=1)
self.rowconfigure(1, weight=1)
# Add a button to row 0
self.btn = Button(self, text="Button", width=20)
self.btn.grid(row=0, column=0, padx=(0,10),pady=(0,10), sticky=N+W)
# Add a text box and v-scrollbar to row 1
self.txtOut = Text(self, width=80, height=20)
self.scrOut = Scrollbar(self)
self.txtOut.grid(row=1,column=0,padx=(0,18),sticky=N+E+S+W)
self.scrOut.grid(row=1,column=0,sticky=N+S+E)
self.scrOut.config(command=self.txtOut.yview)
self.txtOut.config(yscrollcommand=self.scrOut.set)
print(self.grid_size())
def main():
root = Tk()
app = Application(parent=root)
app.mainloop()
if __name__ == '__main__':
main()
There's a case where using pack probably is a solution. It won't cause any conflicts if you pack the instance of the Frame (Application) inside the root window, then grid widgets inside that frame. It'll cut down on the headache of all the rowconfigure and columnconfigure methods and just makes more sense to me.
def initGui(self):
self.parent.title("Test Grid")
self.parent.resizable(width=TRUE, height=TRUE)
self.pack(fill=BOTH, expand=1, padx=20, pady=20)
self.columnconfigure(0, weight=1)
self.rowconfigure(1, weight=1)
The core problem is that you are putting the application frame in row 0 (zero), but you are giving row 1 (one) a weight of 1 (one). That is why the text widget doesn't expand when you resize the window.
This is really hard to see because you mix grid commands of a widget in its parent, along with its own children widgets. It makes it hard to quickly scan the code to see what the layout options are, because you can't assume all of the grid, pack or place commands only affect child widgets.
Another minor problem with your code is that you've put the text widget and scrollbar in the same column.
Here's how I would change the code: remove the self.grid and self.parent.*configure commands from initGui. Then, use pack to add the application frame to the root window at the point where it's created. Also, move the scrollbar to column 1.
class Application(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initGui()
# ##################################################################
# Initialize GUI widgets
def initGui(self):
self.parent.title("Test Grid")
self.parent.resizable(width=TRUE, height=TRUE)
self.columnconfigure(0, weight=1)
self.rowconfigure(1, weight=1)
# Add a button to row 0
self.btn = Button(self, text="Button", width=20)
self.btn.grid(row=0, column=0, padx=(0,10),pady=(0,10), sticky=N+W)
# Add a text box and v-scrollbar to row 1
self.txtOut = Text(self, width=80, height=20)
self.scrOut = Scrollbar(self)
self.txtOut.grid(row=1,column=0,padx=(0,18),sticky=N+E+S+W)
self.scrOut.grid(row=1,column=1,sticky=N+S)
self.scrOut.config(command=self.txtOut.yview)
self.txtOut.config(yscrollcommand=self.scrOut.set)
print(self.grid_size())
def main():
root = Tk()
app = Application(parent=root)
app.pack(fill="both", expand=True)
app.mainloop()
if __name__ == '__main__':
main()
Related
I have a notebook that I played in grid (0;0). I want to the notebok to fill the entire screen even if its content (frames) would not fill the screen.
class App(Tk):
def __init__(self) -> None:
super().__init__()
# Widgets
self.notebook = Notebook(self) # <-- Widget I want to fill the window
self.notebook.grid(row=0, column=0)
self.control_frame = ControlFrame(self)
self.notebook.add(self.control_frame, text="Control")
class ControlFrame(Frame):
def __init__(self, master):
super().__init__(master)
self.control_bar = ControlBar(self)
self.control_bar.grid(row=0, column=0)
self.connect_btn = Button(self, text="Connect")
self.connect_btn.grid(row=1, column=0)
self.log = Text(self, width=100)
self.log.grid(row=2, column=0)
I think you need to use the sticky argument in you grid:
The width of a column (and height of a row) depends on all the widgets
contained in it. That means some widgets could be smaller than the
cells they are placed in. If so, where exactly should they be put
within their cells?
By default, if a cell is larger than the widget contained in it, the
widget will be centered within it, both horizontally and vertically.
The master's background color will display in the empty space around
the widget. In the figure below, the widget in the top right is
smaller than the cell allocated to it. The (white) background of the
master fills the rest of the cell.
The sticky option can change this default behavior. Its value is a
string of 0 or more of the compass directions nsew, specifying which
edges of the cell the widget should be "stuck" to. For example, a
value of n (north) will jam the widget up against the top side, with
any extra vertical space on the bottom; the widget will still be
centered horizontally. A value of nw (north-west) means the widget
will be stuck to the top left corner, with extra space on the bottom
and right.
So your code should look something like
self.notebook.grid(row=0, column=0, sticky='NSWE')
If you need more information check out this article
EDIT:
Thanks to #acw1668 for the hint!
I think the behaviour of the ttk.Notebook class is strange with the grid layout.
I manage to "solve" it using pack in the App() class.
Here is the code that worked for me:
class App(Tk):
def __init__(self) -> None:
super().__init__()
self.notebook = Notebook(self) # <-- Widget I want to fill the window
self.control_frame = ControlFrame(self.notebook)
self.notebook.add(self.control_frame, text="Control", sticky="NSWE")
self.notebook.pack(expand=True, fill=BOTH)
class ControlFrame(Frame):
def __init__(self, master):
super().__init__(master)
self.control_bar = ControlBar(self)
self.control_bar.grid(row=0, column=0)
self.grid(column=0, row=0, sticky="SNWE")
self.connect_btn = Button(self, text="Connect")
self.connect_btn.grid(row=1, column=0, sticky=N)
self.log = Text(self)
self.log.grid(row=2, column=0, sticky="NSWE")
self.grid_rowconfigure(0, weight=0)
self.grid_rowconfigure(1, weight=0)
self.grid_rowconfigure(2, weight=1)
self.grid_columnconfigure(0, weight=1)
Hope this solves your problem
I just started using python and I am trying to make a simple gui that consists of 3 frames. One on the left, one on the right and one in the middle. Later I'd like to add buttons and stuff to those frames but for now that is all. I want the left frame to disappear or appear again if I press the escape key. To do this I have written the following code:
from tkinter import Tk, Button, Label, Frame
class Main:
def __init__(self):
self.root = Tk()
self.init_gui()
def init_gui(self):
self.root.title("Gui Testing")
self.root.minsize(900,600)
self.root.bind("<Escape>", self.toggle_left_menu)
self.root.grid_columnconfigure(0, minsize=200)
self.root.grid_columnconfigure(1, weight=1)
self.root.grid_columnconfigure(2, minsize=250)
self.root.grid_rowconfigure(0, weight=1)
# main 3 panels
self.left_menu_active = True
self.left_menu = Frame(self.root, bg="#333")
self.left_menu.grid(row=0, column=0, sticky="nsew")
self.center = Frame(self.root, bg="white")
self.center.grid(row=0, column=1, sticky="nsew")
self.right_menu = Frame(self.root, bg="#888")
self.right_menu.grid(row=0, column=2, sticky="nsew")
self.toggle_left_menu()
def toggle_left_menu(self, event=None):
if self.left_menu_active == True:
self.left_menu_active = False
self.root.grid_columnconfigure(0, minsize=0)
self.left_menu.grid_forget()
else:
self.left_menu_active = True
self.root.grid_columnconfigure(0, minsize=200)
self.left_menu.grid(row=0, column=0, sticky="nsew")
def start(self):
self.root.mainloop()
Main().start()
The problem is that when I press escape, nothing happens. However, when I then move the window by clicking on it and dragging it, it updates all of a sudden and it shows the window the way I want it. So the code seems to be working but the window isn't updating for some reason.
I don't know what I can do about that. I found out that it does update the grid positions of the left and the center frame, but the grid_configure() doesn't seem to update without moving the window.
Is there a way to update the frame or to achieve the frame toggling in some other way?
Edit:
The problem has been solved by adding a button to each frame. Now the frames are not empty anymore it seems works. I also edited the toggle_left_menu() function a bit. This is what I changed:
Added Buttons:
self.test_button1 = Button(self.left_menu, text="left", padx=10, pady=5)
self.test_button1.grid(row=0, column=0)
self.test_button2 = Button(self.center, text="center", padx=10, pady=5)
self.test_button2.grid(row=0, column=0)
self.test_button3 = Button(self.right_menu, text="right", padx=10, pady=5)
self.test_button3.grid(row=0, column=0)
Edited toggle_left_menu():
def toggle_left_menu(self, event=None):
if self.left_menu.winfo_viewable():
self.root.grid_columnconfigure(0, minsize=0)
self.left_menu.grid_remove()
else:
self.root.grid_columnconfigure(0, minsize=200)
self.left_menu.grid()
This worked for me, thanks!
Extending Bryan Oakley's example ~ you have to toggle the minsize, as well. As an aside, I classed out all of your gui and made Main the root. All that self.root.this and self.root.that is unnecessary this way. Also, you would have had to do this anyway, unless you intended to dump your entire gui contents into your init_gui method. If your app is large that would be a nightmare to keep track of. As an added bonus, I made the whole toggle_menu method dynamic so it can toggle either menu. You can change the key-bindings, to whatever. I used Escape then l for left_menu and Escape then r for right_menu.
from tkinter import Tk, Button, Label, Frame
class LeftMenu(Frame):
#property
def minsize(self):
return 200
def __init__(self, master, row=0, column=0, **kwargs):
Frame.__init__(self, master, **kwargs)
self.grid(row=row, column=column, sticky='nswe')
class RightMenu(Frame):
#property
def minsize(self):
return 250
def __init__(self, master, row=0, column=0, **kwargs):
Frame.__init__(self, master, **kwargs)
self.grid(row=row, column=column, sticky='nswe')
class Center(Frame):
def __init__(self, master, row=0, column=0, **kwargs):
Frame.__init__(self, master, **kwargs)
self.grid(row=row, column=column, sticky='nswe')
class Main(Tk):
def __init__(self):
Tk.__init__(self)
self.bind("<Escape><l>", self.toggle_menu)
self.bind("<Escape><r>", self.toggle_menu)
self.grid_columnconfigure(0, minsize=200)
self.grid_columnconfigure(1, weight=1)
self.grid_columnconfigure(2, minsize=250)
self.grid_rowconfigure(0, weight=1)
# main 3 panels
self.left_menu = LeftMenu(self, 0, 0, bg="#333")
self.center = Center(self, 0, 1, bg="white")
self.right_menu = RightMenu(self, 0, 2, bg="#888")
self.toggle_menu(menu=self.left_menu)
def toggle_menu(self, event=None, menu=None):
if event and event.char in 'lr':
menu = self.left_menu if event.char == 'l' else self.right_menu
if menu:
if menu.winfo_viewable():
self.grid_columnconfigure(menu.grid_info()['column'], minsize=0)
menu.grid_remove()
else:
menu.grid()
self.grid_columnconfigure(menu.grid_info()['column'], minsize=menu.minsize)
self.after_idle(self.event_generate, '<Configure>')
if __name__ == "__main__":
root = Main()
root.title("Gui Testing")
root.minsize(900,600)
root.mainloop()
Part of the problem is that minsize only affects the minimum size. If the left frame is visible and is more than zero pixels wide, setting the minsize to zero isn't going to make the frame smaller. So, one step is to remove the minsize=200 option for column 0.
Since you are using grid, the best way to hide or show a frame is to use grid_remove to remove the widget, and then grid to restore it. grid_remove will remove the widget from the window but remember all of its settings. When you subsequently call .grid(), all of the previous settings will be used.
You can also just check if the window is visible or not without having to manage a boolean flag since your function is a toggle. That simplifies the code a bit.
Also, I think there's a bug on some versions of tk (the library upon which tkinter is built) that prevents the window from refreshing in this specific type of situation. What works for me is to synthetically generate a <Configure> event on the root window.
Rolling all of that together, this version of your toggle function works for me on OSX without any other modifications to your code.
def toggle_left_menu(self, event=None):
if self.left_menu.winfo_viewable():
self.left_menu.grid_remove()
self.root.after_idle(self.root.event_generate, '<Configure>')
else:
self.left_menu.grid()
self.root.after_idle(self.root.event_generate, '<Configure>')
This is related to a previous question:
Tkinter dynamically create widgets from button
At the time that I asked the previous question, I believed that it would be easy to add a scrollable frame around the dynamic GUI. Instead, I have had a single problem with the scrollbar not detecting the new frames and entry boxes after the button is pressed. How do I solve this without editing the ScrollFrame class much?
I know that the Scrollbarframe works with other widgets it is just that the dynamic component is causing issues. When I shrink the vertical size of the window past the original location of the createWidgets button, the scrollbar appears, but the scrollbar is not present for the rest of the dynamically created widgets. Does the canvas not detect that the vertical size of the frame increases with a button press?
Note: I am aware that wildcard imports are awful. I'm just using one for the example
from tkinter import *
class AutoScrollbar(Scrollbar):
# A scrollbar that hides itself if it's not needed.
# Only works if you use the grid geometry manager!
def set(self, lo, hi):
if float(lo) <= 0.0 and float(hi) >= 1.0:
# grid_remove is currently missing from Tkinter!
self.tk.call("grid", "remove", self)
else:
self.grid()
Scrollbar.set(self, lo, hi)
def pack(self, **kw):
raise TclError("cannot use pack with this widget")
def place(self, **kw):
raise TclError("cannot use place with this widget")
class ScrollFrame:
def __init__(self, master):
self.vscrollbar = AutoScrollbar(master)
self.vscrollbar.grid(row=0, column=1, sticky=N+S)
self.hscrollbar = AutoScrollbar(master, orient=HORIZONTAL)
self.hscrollbar.grid(row=1, column=0, sticky=E+W)
self.canvas = Canvas(master, yscrollcommand=self.vscrollbar.set,
xscrollcommand=self.hscrollbar.set)
self.canvas.grid(row=0, column=0, sticky=N+S+E+W)
self.vscrollbar.config(command=self.canvas.yview)
self.hscrollbar.config(command=self.canvas.xview)
# make the canvas expandable
master.grid_rowconfigure(0, weight=1)
master.grid_columnconfigure(0, weight=1)
# create frame inside canvas
self.frame = Frame(self.canvas)
self.frame.rowconfigure(1, weight=1)
self.frame.columnconfigure(1, weight=1)
def update(self):
self.canvas.create_window(0, 0, anchor=NW, window=self.frame)
self.frame.update_idletasks()
self.canvas.config(scrollregion=self.canvas.bbox("all"))
if self.frame.winfo_reqwidth() != self.canvas.winfo_width():
# update the canvas's width to fit the inner frame
self.canvas.config(width = self.frame.winfo_reqwidth())
if self.frame.winfo_reqheight() != self.canvas.winfo_height():
# update the canvas's width to fit the inner frame
self.canvas.config(height = self.frame.winfo_reqheight())
frames = []
widgets = []
def createwidgets():
global widgetNames
global frameNames
frame = Frame(o.frame, borderwidth=2, relief="groove")
frames.append(frame)
frame.pack(side="top", fill="x")
widget = Entry(frame)
widgets.append(widget)
widget.pack(side="left")
root = Tk()
o = ScrollFrame(root)
label = Label(o.frame, text = "test")
label1 = Label(o.frame, text = "test")
label2 = Label(o.frame, text = "test")
label3 = Label(o.frame, text = "test")
label.pack()
label1.pack()
label2.pack()
label3.pack()
createWidgetButton = Button(o.frame, text="createWidgets",
command=createwidgets)
createWidgetButton.pack(side="bottom", fill="x")
o.update()
root.mainloop()
This is what the window would look like if it was fully expanded
If I were to shrink the window, it should immediately create a vertical scrollbar because that would cover a widget. However, the scrollbar acts like the program was still in its initial state.
Incorrect Scrollbar(at the moment that the scrollbar appears)
You need to make sure that you update the canvas scrollregion whenever you add widgets to the inner frame. The most common solution is to bind to the frame's <Configure> event, which will fire whenever the frame changes size.
In ScrollFrame.__init__ add the following line after you create the frame:
self.frame.bind("<Configure>", self.reset_scrollregion)
Then, add this function to ScrollFrame:
def reset_scrollregion(self, event):
self.canvas.configure(scrollregion=self.canvas.bbox("all")
How do I place the QUIT button in below code to the extreme right of the Frame?
I tried several things like:
padx
and
self.pack(side="top", anchor="e")
but after trying some 15 times both buttons are coming close to each other. Maybe Some help from anyone would be really appreciated. I need one button on extreme right and other on extreme left
import tkinter as tk
from tkinter.ttk import *
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack()
self.createWidgets()
self.master.title("Log Parser")
def createWidgets(self):
self.Run_Main = tk.Button(self)
self.Run_Main["text"] = "Browse.."
# self.Run_Main["fg"] = "blue"
self.Run_Main["command"] = self.Sayhello
self.Run_Main.pack(side='left')
self.Label = tk.Label(self)
self.Label["text"] = 'Processing...'
self.progressbar = Progressbar(mode="indeterminate", maximum=20)
self.QUIT = tk.Button(self)
self.QUIT["text"] = "Quit!"
self.QUIT["command"] = self.quit
self.QUIT.pack(anchor='e')
self.pack(side="top", anchor="w")
def Sayhello(self):
print("Hello")
# scroll text inside application frame
class scrollTxtArea:
def __init__(self, root):
frame = tk.Frame(root)
frame.pack()
self.textPad(frame)
return
def textPad(self, frame):
# add a frame and put a text area into it
textPad = tk.Frame(frame)
self.text = tk.Text(textPad, height=18, width=60)
self.text.config()
# add a vertical scroll bar to the text area
scroll = tk.Scrollbar(textPad)
self.text.configure(yscrollcommand=scroll.set,background="black", foreground="green")
# pack everything
self.text.pack(side=tk.LEFT, pady=2)
scroll.pack(side=tk.RIGHT, fill=tk.Y)
textPad.pack(side=tk.TOP)
return
root = tk.Tk()
root.resizable(width=False, height=False)
root.option_add('*font', ('verdana', 9, 'bold'))
app = Application(master=root)
scrollFrame = scrollTxtArea(root)
app.mainloop()
You have several problems here.
First, you're using the wrong geometry manager. The pack geometry manager, as the name implies, packs the widgets as close together as possible. That's not what you want. The grid geometry manager lets you put the widgets into a table-like layout with rows and columns. If you put the Browse button into the first column and the Quit button into the last column, you'll be a step closer.
Second, your Application window contains three child widgets and you're only putting two of them into a geometry manager. How that is going to mess you up I don't even want to think about. So I put the label into column 1, the Quit button into column 2, and the Browse button into column 0. The Quit button I gave a "sticky" value of "e" so it will be attached to the east (right) side of its allocated space.
Third, all the geometry managers try to compact the widgets as much as possible unless you specifically tell it to do otherwise. I told the grid manager to expand column 2 so that the extra space gets assigned to the cell that holds the Quit button.
Fourth, you need to tell the pack manager to expand the top widget so that it spans the entire window. The directive for that is fill="x".
Fifth, you have a redundant call to the pack manager at the end of your createWidgets function.
import tkinter as tk
from tkinter.ttk import *
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack(fill="x")
self.createWidgets()
self.master.title("Log Parser")
def createWidgets(self):
self.Run_Main = tk.Button(self)
self.Run_Main["text"] = "Browse.."
# self.Run_Main["fg"] = "blue"
self.Run_Main["command"] = self.Sayhello
self.Label = tk.Label(self)
self.Label["text"] = 'Processing...'
self.progressbar = Progressbar(mode="indeterminate", maximum=20)
self.QUIT = tk.Button(self)
self.QUIT["text"] = "Quit!"
self.QUIT["command"] = self.quit
self.Label.grid(row=0, column=1)
self.Run_Main.grid(row=0, column=0, sticky="w")
self.QUIT.grid(row=0, column=2, sticky="e")
self.columnconfigure(2, weight=1)
def Sayhello(self):
print("Hello")
# scroll text inside application frame
class scrollTxtArea:
def __init__(self, root):
frame = tk.Frame(root)
frame.pack()
self.textPad(frame)
return
def textPad(self, frame):
# add a frame and put a text area into it
textPad = tk.Frame(frame)
self.text = tk.Text(textPad, height=18, width=60)
self.text.config()
# add a vertical scroll bar to the text area
scroll = tk.Scrollbar(textPad)
self.text.configure(yscrollcommand=scroll.set,background="black", foreground="green")
# pack everything
self.text.pack(side=tk.LEFT, pady=2)
scroll.pack(side=tk.RIGHT, fill=tk.Y)
textPad.pack(side=tk.TOP)
return
root = tk.Tk()
root.resizable(width=False, height=False)
root.option_add('*font', ('verdana', 9, 'bold'))
app = Application(master=root)
scrollFrame = scrollTxtArea(root)
app.mainloop()
These link, link helped. The other option would be to use tkinter's grid manager, it will be more intuitive and keep you more organized in the future.
import tkinter as tk
from tkinter.ttk import *
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack()
self.createWidgets()
self.master.title("Log Parser")
def createWidgets(self):
self.Run_Main = tk.Button(self)
self.Run_Main["text"] = "Browse.."
# self.Run_Main["fg"] = "blue"
self.Run_Main["command"] = self.Sayhello
self.Run_Main.pack(side='left')
self.Label = tk.Label(self)
self.Label["text"] = 'Processing...'
self.Label.pack(side='left')
self.progressbar = Progressbar(mode="indeterminate", maximum=20)
self.QUIT = tk.Button(self)
self.QUIT["text"] = "Quit!"
self.QUIT["command"] = self.quit
self.QUIT.pack(side='right')
self.pack(side="top", fill=tk.BOTH) # changes here
def Sayhello(self):
print("Hello")
# scroll text inside application frame
class scrollTxtArea:
def __init__(self, root):
frame = tk.Frame(root)
frame.pack()
self.textPad(frame)
return
def textPad(self, frame):
# add a frame and put a text area into it
textPad = tk.Frame(frame)
self.text = tk.Text(textPad, height=18, width=60)
self.text.config()
# add a vertical scroll bar to the text area
scroll = tk.Scrollbar(textPad)
self.text.configure(yscrollcommand=scroll.set,background="black", foreground="green")
# pack everything
self.text.pack(side=tk.LEFT, pady=2)
scroll.pack(side=tk.RIGHT, fill=tk.Y)
textPad.pack(side=tk.TOP)
return
root = tk.Tk()
root.resizable(width=False, height=False)
root.option_add('*font', ('verdana', 9, 'bold'))
app = Application(master=root)
scrollFrame = scrollTxtArea(root)
app.mainloop()
There are two simple fixes you can make in order to get the behavior you want.
First, you need to pack Application so that it fills the window:
class Application(...):
def __init__(...):
...
self.pack(fill="x")
Next, simply pack the quick button on the right side of the window:
self.QUIT.pack(side="right", anchor='e')
Even though the above is all you need to do in this specific example, there are additional things you can do to make your job much easier.
I would recommend creating a frame specifically for the buttons. You can pack it at the top. Then, put the buttons inside this frame, and pack them either on the left or right. You'll get the same results, but you'll find it easier to add additional buttons later.
I also find that it makes the code much easier to read, write, maintain, and visualize when you separate widget creation from widget layout.
class Application(...):
...
def createWidgets(self):
toolbar = tk.Frame(self)
toolbar.pack(side="top", fill="x")
self.Run_Main = tk.Button(toolbar)
self.Label = tk.Label(toolbar)
self.QUIT = tk.Button(toolbar)
...
self.Run_Main.pack(side="left")
self.Label.pack(side="left", fill="x")
self.QUIT.pack(side="right")
...
Using the folowing sample code I wrote I am having issues with some behavior.
I want to add/remove the scrollbar as needed. But when I do it shifts all other elements in the window as the window resizes. This is just a sample to demonstrate the issue, you will see the window resize when the scrollbar is added and removed. In the real application there are more widgets on the window.
Am I trying to do this the right way or if not how can I resolve the issue? I also plan to have a second widget with scrollbars as well in another separate frame.
from tkinter import *
from tkinter import ttk
class TopFrame(ttk.Frame):
def __init__(self, parent, col=0, row=0):
ttk.Frame.__init__(self, parent)
self.innerframe = ttk.Frame(parent)
self.list_scroll = ttk.Scrollbar(self.innerframe)
self.list_scroll.grid(column=1, row=0, sticky=NS)
self.list_scroll.grid_remove()
self.list = Listbox(self.innerframe, width=64, height=8,
yscrollcommand=self.list_scroll.set)
self.list_scroll.config(command=self.list.yview)
self.list.grid(column=0, row=0, sticky=NSEW)
self.innerframe.grid(column=col, row=row)
self.addbtn = ttk.Button(parent, text='add item',
command=self.additem)
self.addbtn.grid(column=col, row=row+1, padx=10, pady=2)
self.delbtn = ttk.Button(parent, text='del item',
command=self.delitem)
self.delbtn.grid(column=col, row=row+2, padx=10, pady=2)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
def additem(self):
count = str(len(self.list.get(0, END)))
self.list.insert(END, 'demo' + count)
if len(self.list.get(0, END)) > 8:
self.list_scroll.grid()
def delitem(self):
self.list.delete(END)
if len(self.list.get(0, END)) <= 8:
self.list_scroll.grid_remove()
class MasterFrame(Tk):
def __init__(self):
Tk.__init__(self)
topframe = TopFrame(self)
if __name__ == '__main__':
MasterFrame().mainloop()
Once the window has been displayed for the first time you can get the window size, and then use that to call the geometry method on the root window. When you set the size of the window with the geometry command it will stop resizing based on changes to its internal widgets.
The simplest thing is to write a function to do that, and schedule it to run with after_idle, which should fire after the window is first displayed.