I am trying to create a GUI for an application I am making and for some reason that I cannot figure out, the text widget that is inside the message_space frame is increasing the size of the message_space frame and reducing the size of the friends_space frame. I want the friends_space frame to take up 1/4th of the window size and the message_space frame to take up the remaining 3/4ts of the window size.
The red is the friends_space frame, the blue is the message_space frame.
This is how I would like the sizing of the frames to be.
This is what is happening when I add the text box.
Code
from tkinter import *
class app:
def __init__(self, master):
self.master = master
master.title("PyChat")
master.geometry("800x500")
master.configure(bg="grey")
master.resizable(0, 0)
master.grid_columnconfigure(0, weight=1)
master.grid_columnconfigure(1, weight=3)
master.grid_rowconfigure(0, weight=1)
self.friends_space = Frame(master, bg="red")
self.friends_space.grid(row=0, column=0, sticky=NSEW)
self.chat_space = Frame(master, bg="blue")
self.chat_space.grid(row=0, column=1, columnspan=3, sticky=NSEW)
self.message_area = Text(self.chat_space)
self.message_area.grid(row=0, column=0)
root = Tk()
my_gui = app(root)
root.mainloop()
If you're using grid, you divide your UI into four uniform-width columns (using the uniform option), then have the text widget span three.
You should also start with a small text widget that can grow into the space. Otherwise tkinter will try to preserve the large size and start removing space from the other widgets in order to try to make everything fit.
Here's an example based on your original code. However, I'm using pack for the text widget instead of grid because it requires fewer lines of code. I've also reorganized the code a bit. I find that grouping calls to grid together makes layout easier to grok.
I've also removed the restriction on resizing. There's rarely a good idea to limit the user's ability to resize the window. Plus, it allows you to see that the resulting UI is responsive.
from tkinter import *
class app:
def __init__(self, master):
self.master = master
master.title("PyChat")
master.geometry("800x500")
master.configure(bg="grey")
master.grid_columnconfigure((0,1,2,3), uniform="uniform", weight=1)
master.grid_rowconfigure(0, weight=1)
self.friends_space = Frame(master, bg="red")
self.chat_space = Frame(master, bg="blue")
self.friends_space.grid(row=0, column=0, sticky=NSEW)
self.chat_space.grid(row=0, column=1, columnspan=3, sticky=NSEW)
self.message_area = Text(self.chat_space, width=1, height=1)
self.message_area.pack(fill="both", expand=True)
root = Tk()
my_gui = app(root)
root.mainloop()
Related
The canvas Widget in Tkinter is very slow at drawing, causing a lot of distortion to the applications visuals when scrolling even when using limited widgets.
I have had a search around but only seem to have answers from people drawing multiple things to a canvas rather than the scrollbar effects.
Is there any issues with my code that would cause this issue or are there any methods to fix the draw times to be more visually smooth. In the application this is meant for each row is a different colour which can make it extremely ugly to look at and hard to find the data the user is looking for.
MVCE:
#python 3.8.6
from tkinter import *
import random
class test:
def __init__(self):
self.words = ["troop","relieve","exact","appeal","shortage","familiar","comfortable","sniff","mold","clay","rack","square","color","book","velvet","address","elaborate","grip","neutral","pupil"]
def scrollable_area2(self, holder):
base_frame = Frame(holder, padx=5, pady=5)
base_frame.pack(fill=BOTH, expand=1)
base_frame.rowconfigure(0, weight=0)
base_frame.columnconfigure(0, weight=1)
can = Canvas(base_frame, bg="white")
can.pack(side=LEFT, expand=1, fill=BOTH)
scrollArea = Frame(base_frame, bg="white", )
scrollArea.pack(side=LEFT, expand=1, fill=BOTH)
can.create_window(0, 0, window=scrollArea, anchor='nw')
Scroll = Scrollbar(base_frame, orient=VERTICAL)
Scroll.config(command=can.yview)
Scroll.pack(side=RIGHT, fill=Y)
can.config(yscrollcommand=Scroll.set)
scrollArea.bind("<Configure>", lambda e=Event(), c=can: self.update_scrollregion(e, c))
return scrollArea, can
def update_scrollregion(self, event, can):
if can.winfo_exists():
can.configure(scrollregion=can.bbox("all"))
def generate(self, count): #generates the rows
for i in range(int(count.get())):
row = Frame(self.holder)
row.pack(side=TOP)
for i in range(9):
a = Label(row, text=self.words[random.randint(0, len(self.words)-1)])
a.pack(side=LEFT)
b = Button(row, text=self.words[random.randint(0, len(self.words)-1)])
b.pack(side=LEFT)
def main(self):
opts = Frame(self.root)
opts.pack(side=TOP)
v= StringVar()
e = Entry(opts, textvariable=v)
e.pack(side=LEFT)
b=Button(opts, text="Run", command=lambda e=Event(), v=v:self.generate(v))
b.pack(side=LEFT)
main_frame=Frame(self.root)
main_frame.pack(side=TOP, fill=BOTH, expand=1)
self.holder, can = self.scrollable_area2(main_frame)
def run(self):
self.root = Tk()
self.main()
self.root.mainloop()
if __name__ == "__main__":
app = test()
app.run()
I have left a box where you can type the number of rows. I have tried from 30 rows to over 300 rows and although the initial render time changes the scroll issue is always the same.
NOTE: sorry about the weird way I am creating a scroll region, its from a more complex piece of code which I have modified to fit here if that ends up being a factor.
Since you are just creating a vertical stack of frames, it will likely be more efficient to use a text widget as the container rather than a canvas and embedded frame.
Here's a simple example that creates 1000 rows similar to how you're doing it with the canvas. On my OSX machine it performs much better than the canvas.
def scrollable_area2(self, parent):
base_frame = Frame(parent, padx=5, pady=5)
base_frame.pack(fill="both", expand=True)
holder = Text(base_frame)
vsb = Scrollbar(base_frame, orient="vertical", command=holder.yview)
holder.configure(yscrollcommand=vsb.set)
holder.pack(side="left", fill="both", expand=True)
vsb.pack(side="right", fill="y")
return holder
...
def generate(self, count): #generates the rows
for i in range(int(count.get())):
row = Frame(self.holder)
self.holder.window_create("end", window=row)
self.holder.insert("end", "\n")
...
def main(self):
...
self.holder = self.scrollable_area2(main_frame)
The above example keeps the inner frames, but you don't really need it. You can insert the text directly in the text widget, making the code even more efficient.
In a comment you said you aren't actually creating a stack of frames but rather a table of values. You can create a table in the text widget by using tabstops to create columns. By inserting text directly in the widget you're creating far fewer widgets which will definitely improve performance.
Here's an example using hard-coded the tabstops, but you could easily compute them based on the longest word in the list.
def scrollable_area2(self, parent):
base_frame = Frame(parent, padx=5, pady=5)
base_frame.pack(fill="both", expand=True)
self.holder = Text(base_frame, wrap="none", tabs=100)
vsb = Scrollbar(base_frame, orient="vertical", command=self.holder.yview)
self.holder.configure(yscrollcommand=vsb.set)
self.holder.pack(side="left", fill="both", expand=True)
vsb.pack(side="right", fill="y")
Your generate function then might look something like this:
def generate(self, count): #generates the rows
for i in range(int(count.get())):
for i in range(9):
text = "\t".join([random.choice(self.words) for x in range(9)])
self.holder.insert("end", text + "\t")
button = Button(self.holder, text=random.choice(self.words))
self.holder.window_create("end", window=button)
self.holder.insert("end", "\n")
I am trying to code a tkinter application that has three frames - a top frame, where the user inputs some text, a dynamically constructed middle section where some pre-analysis is conducted on the text, and a bottom frame where, once the user has selected which option they want in the middle section, the output will be produced.
The problem is that, depending upon the input, there could be around 10-20 (and in the worst case 30) lines displayed and on a small monitor the output will disappear off the screen.
What I would like is for the top (input) and bottom (output) frames to be visible no matter how the screen is re-sized, and for the middle section to scroll (if required) and still allow the user to select their choice.
I am confused as to how to get the middle section to resize when the screen is resized, show a scrollbar if required, and still allow all of the content to be accessed.
I have created a cut-down version here (for simplicity, I have removed the processing methods and have instead created some fake output in a loop that resembles what the actual middle section would look like).
Please ignore the hideous colour-scheme - I was just trying to understand which frame went where (I will remove the colours as soon as I can!)
Thank you for any suggestions...
import tkinter as tk
from tkinter import scrolledtext
class MyApp(tk.Tk):
def __init__(self, title="Sample App", *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title(title)
self.configure(background="Gray")
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
# Create the overall frame:
master_frame = tk.Frame(self, bg="Light Blue", bd=3, relief=tk.RIDGE)
master_frame.grid(sticky=tk.NSEW)
master_frame.rowconfigure([0, 2], minsize=90) # Set min size for top and bottom
master_frame.rowconfigure(1, weight=1) # Row 1 should adjust to window size
master_frame.columnconfigure(0, weight=1) # Column 0 should adjust to window size
# Create the frame to hold the input field and action button:
input_frame = tk.LabelFrame(master_frame, text="Input Section", bg="Green", bd=2, relief=tk.GROOVE)
input_frame.grid(row=0, column=0, padx = 5, pady = 5, sticky=tk.NSEW)
input_frame.columnconfigure(0, weight=1)
input_frame.rowconfigure(0, weight=1)
# Create a frame for the middle (processing) section.
middle_frame = tk.LabelFrame(master_frame, text = "Processing Section")
middle_frame.grid(row=1, column=0, padx=5, pady=5, sticky=tk.NSEW)
# Create the frame to hold the output:
output_frame = tk.LabelFrame(master_frame, text="Output Section", bg="Blue", bd=2, relief=tk.GROOVE)
output_frame.grid(row=2, column=0, columnspan=3, padx=5, pady=5, sticky=tk.NSEW)
output_frame.columnconfigure(0, weight=1)
output_frame.rowconfigure(0, weight=1)
# Add a canvas in the middle frame.
self.canvas = tk.Canvas(middle_frame, bg="Yellow")
self.canvas.grid(row=0, column=0)
# Create a vertical scrollbar linked to the canvas.
vsbar = tk.Scrollbar(middle_frame, orient=tk.VERTICAL, command=self.canvas.yview)
vsbar.grid(row=0, column=1, sticky=tk.NS)
self.canvas.configure(yscrollcommand=vsbar.set)
# Content for the input frame, (one label, one input box and one button).
tk.Label(input_frame,
text="Please type, or paste, the text to be analysed into this box:").grid(row=0, columnspan = 3, sticky=tk.NSEW)
self.input_box = scrolledtext.ScrolledText(input_frame, height=5, wrap=tk.WORD)
self.input_box.columnconfigure(0, weight=1)
self.input_box.grid(row=1, column=0, columnspan = 3, sticky=tk.NSEW)
tk.Button(input_frame,
text="Do it!",
command=self.draw_choices).grid(row=2, column=2, sticky=tk.E)
# Content for the output frame, (one text box only).
self.output_box = scrolledtext.ScrolledText(output_frame, width=40, height=5, wrap=tk.WORD)
self.output_box.grid(row=0, column=0, columnspan=3, sticky=tk.NSEW)
def draw_choices(self):
""" This method will dynamically create the content for the middle frame"""
self.option = tk.IntVar() # Variable used to hold user's choice
self.get_input_text()
for i in range(30):
tk.Radiobutton(self.canvas,
text=f"Option {i + 1}: ", variable=self.option,
value=i,
command=self.do_analysis
).grid(row=i, column=0, sticky=tk.W)
tk.Label(self.canvas,
text=f"If you pick Option {i + 1}, the output will look like this: {self.shortText}.",
anchor=tk.W
).grid(row=i, column=1, sticky=tk.W)
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def get_input_text(self):
""" Will get the text from the input box and also create a shortened version to display on one line"""
screenWidth = 78
self.input_text = self.input_box.get(0.0, tk.END)
if len(self.input_text) > screenWidth:
self.shortText = self.input_text[:screenWidth]
else:
self.shortText = self.input_text[:]
self.shortText = self.shortText.replace('\n', ' ') # strip out carriage returns just in case
def do_analysis(self):
"""This will ultimately process and display the results"""
option = self.option.get() # Get option from radio button press
output_txt = f"You picked option {option + 1} and here is the output: \n{self.input_text}"
self.output_box.delete(0.0, tk.END)
self.output_box.insert(0.0, output_txt)
if __name__ == "__main__":
app = MyApp("My Simple Text Analysis Program")
app.mainloop()
I understand that you can't mix grid and pack geometries in the same container, and that a scrollbar must be attached to a canvas, and objects to be placed on that canvas must therefore be in yet another container so, attempting to follow Bryan's example, I created a minimal version of what I want - window with three sections - top, middle and bottom. The Top and bottom sections will contain a simple text field, the middle section will contain dynamic content and must be able to scroll as required.
Imports:
ScrollbarFrame
Extends class tk.Frame to support a scrollable Frame]
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("A simple GUI")
# Top frame
self.top_frame = tk.Frame(self, bg="LIGHT GREEN")
self.top_frame.pack(fill=tk.X)
tk.Label(self.top_frame, bg=self.top_frame.cget('bg'),
text="This is a label on the top frame")\
.grid(row=0, columnspan=3, sticky=tk.NSEW)
# Middle Frame
# Import from https://stackoverflow.com/a/62446457/7414759
# and don't change anything
sbf = ScrollbarFrame(self, bg="LIGHT BLUE")
sbf.pack(fill=tk.X, expand=True)
# self.middle_frame = tk.Frame(self, bg="LIGHT BLUE")
self.middle_frame = sbf.scrolled_frame
# Force scrolling by adding multiple Label
for _ in range(25):
tk.Label(self.middle_frame, bg=self.middle_frame.cget('bg'),
text="This is a label on the dynamic (middle) section")\
.grid()
# Bottom Frame
self.bottom_frame = tk.Frame(self, bg="WHITE")
self.bottom_frame.pack(fill=tk.X)
tk.Label(self.bottom_frame, bg=self.bottom_frame.cget('bg'),
text="This is a label on the bottom section")\
.grid(row=0, columnspan=3, sticky=tk.NSEW)
if __name__ == '__main__':
App().mainloop()
I am using Python to parse entries from a log file, and display the entry contents using Tkinter and so far it's been excellent. The output is a grid of label widgets, but sometimes there are more rows than can be displayed on the screen. I'd like to add a scrollbar, which looks like it should be very easy, but I can't figure it out.
The documentation implies that only the List, Textbox, Canvas and Entry widgets support the scrollbar interface. None of these appear to be suitable for displaying a grid of widgets. It's possible to put arbitrary widgets in a Canvas widget, but you appear to have to use absolute co-ordinates, so I wouldn't be able to use the grid layout manager?
I've tried putting the widget grid into a Frame, but that doesn't seem to support the scrollbar interface, so this doesn't work:
mainframe = Frame(root, yscrollcommand=scrollbar.set)
Can anyone suggest a way round this limitation? I'd hate to have to rewrite in PyQt and increase my executable image size by so much, just to add a scrollbar!
Overview
You can only associate scrollbars with a few widgets, and the root widget and Frame aren't part of that group of widgets.
There are at least a couple of ways to do this. If you need a simple vertical or horizontal group of widgets, you can use a text widget and the window_create method to add widgets. This method is simple, but doesn't allow for a complex layout of the widgets.
A more common general-purpose solution is to create a canvas widget and associate the scrollbars with that widget. Then, into that canvas embed the frame that contains your label widgets. Determine the width/height of the frame and feed that into the canvas scrollregion option so that the scrollregion exactly matches the size of the frame.
Why put the widgets in a frame rather than directly in the canvas? A scrollbar attached to a canvas can only scroll items created with one of the create_ methods. You cannot scroll items added to a canvas with pack, place, or grid. By using a frame, you can use those methods inside the frame, and then call create_window once for the frame.
Drawing the text items directly on the canvas isn't very hard, so you might want to reconsider that approach if the frame-embedded-in-a-canvas solution seems too complex. Since you're creating a grid, the coordinates of each text item is going to be very easy to compute, especially if each row is the same height (which it probably is if you're using a single font).
For drawing directly on the canvas, just figure out the line height of the font you're using (and there are commands for that). Then, each y coordinate is row*(lineheight+spacing). The x coordinate will be a fixed number based on the widest item in each column. If you give everything a tag for the column it is in, you can adjust the x coordinate and width of all items in a column with a single command.
Object-oriented solution
Here's an example of the frame-embedded-in-canvas solution, using an object-oriented approach:
import tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.canvas = tk.Canvas(self, borderwidth=0, background="#ffffff")
self.frame = tk.Frame(self.canvas, background="#ffffff")
self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.vsb.set)
self.vsb.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
self.canvas.create_window((4,4), window=self.frame, anchor="nw",
tags="self.frame")
self.frame.bind("<Configure>", self.onFrameConfigure)
self.populate()
def populate(self):
'''Put in some fake data'''
for row in range(100):
tk.Label(self.frame, text="%s" % row, width=3, borderwidth="1",
relief="solid").grid(row=row, column=0)
t="this is the second column for row %s" %row
tk.Label(self.frame, text=t).grid(row=row, column=1)
def onFrameConfigure(self, event):
'''Reset the scroll region to encompass the inner frame'''
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
if __name__ == "__main__":
root=tk.Tk()
example = Example(root)
example.pack(side="top", fill="both", expand=True)
root.mainloop()
Procedural solution
Here is a solution that doesn't use a class:
import tkinter as tk
def populate(frame):
'''Put in some fake data'''
for row in range(100):
tk.Label(frame, text="%s" % row, width=3, borderwidth="1",
relief="solid").grid(row=row, column=0)
t="this is the second column for row %s" %row
tk.Label(frame, text=t).grid(row=row, column=1)
def onFrameConfigure(canvas):
'''Reset the scroll region to encompass the inner frame'''
canvas.configure(scrollregion=canvas.bbox("all"))
root = tk.Tk()
canvas = tk.Canvas(root, borderwidth=0, background="#ffffff")
frame = tk.Frame(canvas, background="#ffffff")
vsb = tk.Scrollbar(root, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=vsb.set)
vsb.pack(side="right", fill="y")
canvas.pack(side="left", fill="both", expand=True)
canvas.create_window((4,4), window=frame, anchor="nw")
frame.bind("<Configure>", lambda event, canvas=canvas: onFrameConfigure(canvas))
populate(frame)
root.mainloop()
Make it scrollable
Use this handy class to make the frame containing your widgets scrollable. Follow these steps:
create the frame
display it (pack, grid, etc)
make it scrollable
add widgets inside it
call the update() method
import tkinter as tk
from tkinter import ttk
class Scrollable(tk.Frame):
"""
Make a frame scrollable with scrollbar on the right.
After adding or removing widgets to the scrollable frame,
call the update() method to refresh the scrollable area.
"""
def __init__(self, frame, width=16):
scrollbar = tk.Scrollbar(frame, width=width)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y, expand=False)
self.canvas = tk.Canvas(frame, yscrollcommand=scrollbar.set)
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.config(command=self.canvas.yview)
self.canvas.bind('<Configure>', self.__fill_canvas)
# base class initialization
tk.Frame.__init__(self, frame)
# assign this obj (the inner frame) to the windows item of the canvas
self.windows_item = self.canvas.create_window(0,0, window=self, anchor=tk.NW)
def __fill_canvas(self, event):
"Enlarge the windows item to the canvas width"
canvas_width = event.width
self.canvas.itemconfig(self.windows_item, width = canvas_width)
def update(self):
"Update the canvas and the scrollregion"
self.update_idletasks()
self.canvas.config(scrollregion=self.canvas.bbox(self.windows_item))
Usage example
root = tk.Tk()
header = ttk.Frame(root)
body = ttk.Frame(root)
footer = ttk.Frame(root)
header.pack()
body.pack()
footer.pack()
ttk.Label(header, text="The header").pack()
ttk.Label(footer, text="The Footer").pack()
scrollable_body = Scrollable(body, width=32)
for i in range(30):
ttk.Button(scrollable_body, text="I'm a button in the scrollable frame").grid()
scrollable_body.update()
root.mainloop()
Extends class tk.Frame to support a scrollable Frame
This class is independent from the widgets to be scrolled and can be used to replace a standard tk.Frame.
import tkinter as tk
class ScrollbarFrame(tk.Frame):
"""
Extends class tk.Frame to support a scrollable Frame
This class is independent from the widgets to be scrolled and
can be used to replace a standard tk.Frame
"""
def __init__(self, parent, **kwargs):
tk.Frame.__init__(self, parent, **kwargs)
# The Scrollbar, layout to the right
vsb = tk.Scrollbar(self, orient="vertical")
vsb.pack(side="right", fill="y")
# The Canvas which supports the Scrollbar Interface, layout to the left
self.canvas = tk.Canvas(self, borderwidth=0, background="#ffffff")
self.canvas.pack(side="left", fill="both", expand=True)
# Bind the Scrollbar to the self.canvas Scrollbar Interface
self.canvas.configure(yscrollcommand=vsb.set)
vsb.configure(command=self.canvas.yview)
# The Frame to be scrolled, layout into the canvas
# All widgets to be scrolled have to use this Frame as parent
self.scrolled_frame = tk.Frame(self.canvas, background=self.canvas.cget('bg'))
self.canvas.create_window((4, 4), window=self.scrolled_frame, anchor="nw")
# Configures the scrollregion of the Canvas dynamically
self.scrolled_frame.bind("<Configure>", self.on_configure)
def on_configure(self, event):
"""Set the scroll region to encompass the scrolled frame"""
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
Usage:
class App(tk.Tk):
def __init__(self):
super().__init__()
sbf = ScrollbarFrame(self)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
sbf.grid(row=0, column=0, sticky='nsew')
# sbf.pack(side="top", fill="both", expand=True)
# Some data, layout into the sbf.scrolled_frame
frame = sbf.scrolled_frame
for row in range(50):
text = "%s" % row
tk.Label(frame, text=text,
width=3, borderwidth="1", relief="solid") \
.grid(row=row, column=0)
text = "this is the second column for row %s" % row
tk.Label(frame, text=text,
background=sbf.scrolled_frame.cget('bg')) \
.grid(row=row, column=1)
if __name__ == "__main__":
App().mainloop()
I'm trying to set up a grid inside of a frame which is in a larger grid structure. I've tried to distill it to the simplest version of the problem.
from tkinter import Tk, Frame, Label, Entry
root = Tk()
root.geometry('800x800')
frame1 = Frame(root, width=400, height=400, background="Blue")
frame2 = Frame(root, width=400, height=400, background="Red")
frame1.grid(row=0, column=0)
frame2.grid(row=1, column=1)
label1 = Label(frame1,text='Label1')
label1.grid()
Instead of placing the label inside of frame1, the label replaces the frame in the overall grid:
I've looked at other examples, but I haven't been able to identify why they work and mine does not.
Using jasonharper's observation that the Frame was automatically resizing itself and a similar question posted here, I was able to update the code.
from tkinter import Tk, Frame, Label
root = Tk()
root.geometry('800x800')
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(1, weight=1)
root.grid_columnconfigure(1, weight=1)
frame1 = Frame(root, background="Blue")
frame2 = Frame(root, background="Red")
frame1.grid(row=0, column=0, sticky="nsew")
frame2.grid(row=1, column=1, sticky="nsew")
label1 = Label(frame1,text='Label1')
label1.grid()
The changes that I made to the code are:
I removed the height and width parameters from the Frame instances because they weren't doing anything anyway.
Added weighting to the rows and columns of root, the container for the frames, to equally space the frames in the available space
Add the 'sticky' parameter to the grid placements of the frames so that the frames would take up all of the space that they were allotted.
The result:
The width= and height= of a Frame normally only apply when it has no children. Once child widgets are added, it resizes itself to fit its contents. So frame1 is still there, it's just now exactly the same size as, and entirely covered by, the label.
To turn off this auto-resizing behavior, call .grid_propagate(0) on the Frame (or .pack_propagate(0), depending on the geometry manager being used for the Frame's children).
I have a frame in PanedWindow which i need on every tkinter GUI (in this case it's topFrame). Below it are many frames and I want to switch between those frame on button click (just like in any software where the top portion of screen is fixed and clicking on buttons the lower portion of GUI changes).
I know i need grid layout for it. But, it is not happening and i am not getting a solution anywhere.I have researched a lot on this topic everywhere but this solution is nowhere. Here is my code... i have written in comments those code which i feel are not working fine.
#python 3.5
from tkinter import *
#function to raise the frame on button click
def raiseFrame(frame):
frame.tkraise()
m = PanedWindow(height=500, width=1000, orient=VERTICAL)
m.pack(fill=BOTH, expand=1)
#to expand the column and row to fill the extra space
m.grid_columnconfigure(index=0, weight=1) #this code is not working as it should
m.grid_rowconfigure(index=0, weight=1) #this code is not working as it should
#top frame has two buttons which switches the bottom frames
topFrame = Frame(m, bg="blue")
m.add(topFrame)
button1 = Button(topFrame, text="Raise Frame 2", command=lambda: raiseFrame(frame2)) #raises frame 2 on clicking it
button1.pack(side=LEFT)
button2 = Button(topFrame, text="Raise Frame 1", command=lambda: raiseFrame(frame1)) #raises frame 1 on clicking it
button2.pack(side=LEFT)
#bottomframe acts as container for two other frames which i need to switch
bottomframe = Frame(m, bg="orange")
m.add(bottomframe)
frame1 = Frame(bottomframe, bg="yellow")
frame1.grid(row=0, column=0, sticky="news") ## sticky is not working ##
frame2 = Frame(bottomframe, bg="green")
frame2.grid(row=0, column=0, sticky="news") ## sticky is not working ##
label1 = Label(frame1, text="i should change")
label1.pack(padx=10, pady=10)
label2 = Label(frame2, text="i am changed !!")
label2.pack(padx=10, pady=10)
mainloop()
1)Please correct my code.
2)Explain me why in the "topFrame" even though i have not written
topFrame.pack(fill=BOTH, expand=True)
my code is showing the above property and it's expanding as well as filling both X and Y.
Same goes for the bottomFrame, it's orange colour is filling the entire space which does not happen in normal frames. So, is it some special feature of PanedWindow ?
You don't want to call topFrame.pack() and m.add(topFrame). You either pack/place/grid the window, or you add it to a paned window, you don't do both.
Also, if the frame is going to be in the paned window it needs to be a child of the paned window rather than a child of the root window.