I would really appreciate some help with figuring out grid geometry manager.
Here is what I want to build.
I was thinking of using grid but I cannot find any good tutorials that would clearly
explain how to work with it.
There are lots of tutorials but mostly all are either very simple or really outdated.
I am not sure how to build what is shown in the picture using only grid because all
elements are nested inside each other and each element is supposed to hold more elements inside it.
It's not so hard to arrange outermost widgets using grid. I just place Toolbar into 0th row,
then outermost PanedWidow (green) into 1st row, and then Status Bar into 2nd row.
After that I need to arrange things inside green PanedWindow.
I place another PanedWindow (pink) into the right pane of the green PanedWindow and then
stick a Notebook into it's top pane.
Now, I need to add more widgets to these inner panes. For instance. I am going to add
some buttons to the bottom pane of the pink PanedWindow. And that's where I run into problems.
If I try to use pack() to arrange things inside these innermost panes, Python screams at me for
using more than one geometry manager.
But when I think about how to accomplish this with grid, I just can't find a way to subdivide
innermost panes into smaller grids.
Can there be grids inside Widgets which have been acted upon by an outer grid?
When I see widgets that take up the full width or full height of an area I usually use pack since since it's specifically designed to lay objects along a side of an empty cavity. You can use grid but it requires extra code since you have to both add the widget and configure the rows and columns. With pack all you have to do is add the widgets.
For example, it's clear you want a statusbar along the bottom, and a toolbar along the time, and a paned widget in-between. So, start with that, as in the following example:
import tkinter as tk
root = tk.Tk()
root.geometry("600x400")
toolbar = tk.Frame(root, background="#d5e8d4", height=40)
statusbar = tk.Frame(root, background="#e3e3e3", height=20)
main = tk.PanedWindow(root, background="#99fb99")
toolbar.pack(side="top", fill="x")
statusbar.pack(side="bottom", fill="x")
main.pack(side="top", fill="both", expand=True)
root.mainloop()
Note: widths, heights, and colors are added to the frame for illustrative purposes since otherwise, an empty frame would have a size of 1x1. Once you add widgets inside a frame you can remove the width and height options.
You say the right will have a paned window, so add that on the right. We'll use a normal frame on the left.
left_pane = tk.Frame(main, background="#99fb99", width=100)
right_pane = tk.PanedWindow(main, background="#99fb99", width=200)
main.add(left_pane)
main.add(right_pane)
Next, add the two panes to the right. So that I can show colors with as little code as possible I'll use a frame on the top instead of a notebook:
notebook = tk.Frame(right_pane, background="#99ceff", height=70)
bottom_right = tk.Frame(right_pane, background="#ffe6cd", height=50)
right_pane.add(notebook)
right_pane.add(bottom_right)
With all that being said, you can use grid if you want. The trick is to use intermediate frames, since the layout in any widget is independent of the layout in parent or child widgets.
All you need to do is remove the first three calls to pack and replace it with these five lines:
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
toolbar.grid(row=0, column=0, sticky="ew")
main.grid(row=1, column=0, sticky="nsew")
statusbar.grid(row=2, column=0, sticky="ew")
Since the other widgets are children of paned widgets, there's nothing else to do. Any widgets you add to each pane have their own independent layout area, so you can use grid, pack, or place inside each frame.
To illustrate that point, I'll use grid to add several rows and columns of squares:
for row in range(6):
for column in range(30):
f = tk.Frame(bottom_right, background="white",
bd=2, relief="raised", width=10, height=10)
f.grid(row=row, column=column)
I found a post that's around two years old, which might be a little too old for your uses, but it has some information on nesting grids in Tkinter. It recommends using frames to nest the grids, essentially having children within children of a frame. Within these frames, you can place objects.
Related
I know this isn't the first time a question like this is asked, but even after like 2 hours of browsing the Internet I can't get it to work:
So I'm trying to create a Tkinter-Frame, that contains several Buttons (As Example I took 30). But Because I don't have enough space in my program, I need to add an Scrollbar next to it, so that one can scroll through the Buttons.
The Problems I had where, that the inner "moving part" of the bar was as big as the whole scrollbar and couldn't be moved, which I kinda solved by using scollregion=(0,0,1000,1000), but even then the moving of the bar had no effect on the canvas whatsoever.
Here Is the corresponding code that I extracted out of my program:
import Tkinter as tk
root = tk.Tk()
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=50)
root.columnconfigure(1, weight=1)
root.minsize(300,400)
root.maxsize(300,400)
#Buttons
buttonFrame = tk.Canvas(root, bg='#bbb')
buttonFrame.grid(row=0, column=0, sticky=tk.N+tk.E+tk.S+tk.W)
buttonFrame.columnconfigure(0, weight=1)
scroll = tk.Scrollbar(root, command=buttonFrame.yview)
scroll.grid(row=0, column=1, sticky=tk.N+tk.E+tk.S+tk.W)
buttonFrame.configure(yscrollcommand=scroll.set)
for i in range(30):
tk.Button(buttonFrame, text=str(i+1)).grid(row=i, column=0, sticky=tk.N+tk.E+tk.S+tk.W)
root.mainloop()
As you (hopefully) see, the slider can't even be moved nor does it change anything on the canvas, even if I squeeze a scrollregion=(bla) somewhere in there.
2 Questions:
a.) What do I need to add (or remove), so that I can scroll through the list of Buttons
b.) Does the fix from a. still work when I make the Scrollbar a child of the buttonFrame instead of the root?
To add widgets to a Canvas you have to use the create_window method, not grid(). Then you have to update the canvas before setting the scrollregion.
for i in range(30):
btn = tk.Button(buttonFrame, text=str(i+1))
buttonFrame.create_window((100,i*50), window=btn)
root.update()
buttonFrame.config(scrollregion=buttonFrame.bbox("all"))
If you try that I suspect it's not what you were looking for, since the create_window method requires absolute positioning (you can't use grid or pack). That's why most people put a Frame in the Canvas, and add their widgets to that instead. Many people have abstracted this faux Frame that is actually a Frame in a Canvas in another Frame, including me.
I am having an issue with a TkInter interface whereby I cannot get a scrollbar to visually attach itself to a Listbox element - see this image: Problem interface
Here is the code which creates and positions the Listbox & Scrollbar:
lblpd3 = ttk.Label(mainframe,text='',font=("Helvetica", 5))
lblpd3.grid(column=0, row=12, sticky=NW)
scltrn = Scrollbar(mainframe, orient=VERTICAL)
lbltrn = ttk.Label(mainframe,text='Select Transformation',font=("Helvetica", 11, "bold"))
lbltrn.grid(column=0, row=13, sticky=NW)
self.lsttrn = Listbox(mainframe,selectmode=SINGLE,exportselection=0,width=62,height=4,yscrollcommand=scltrn.set,activestyle='none',selectbackground='#4A6984',selectborderwidth=3,highlightcolor='#4A6984',highlightthickness=1)
scltrn.config(command=self.lsttrn.yview)
scltrn.grid(column=0, row=14, sticky=(N,S,E))
for item in self.coord:
self.lsttrn.insert(END, item)
self.lsttrn.grid(column=0, row=14, padx=0, sticky=NW)
self.lsttrn.select_set(0)
Is there a simple hack I could use to push the scrollbar a few pixels to the left - documentation seems to suggest there's no padding element?
You could add your Scrollbar widget and your Listbox widget into their own seperate Frame.
Then add that in as a single 'unit'.
While I dont know your whole program, here's what the logic behind the fix should roughly look like:
# Declare a new Frame to hold your Listbox and Scroll wheel
myFrame = Frame.__init__(self, parent)
# Make sure your Scrollbar is a part of our newly created Frame "myFrame"
scltrn = Scrollbar(myframe, orient=VERTICAL)
# Make sure your Listbox is a part of our newly created Frame "myFrame"
self.lsttrn = Listbox(myFrame,selectmode=SINGLE,exportselection=0,width=62,height=4,yscrollcommand=scltrn.set,activestyle='none',selectbackground='#4A6984',selectborderwidth=3,highlightcolor='#4A6984',highlightthickness=1)
...
#pack the Scrollbar and Listbox together in our Frame IN THIS ORDER
self.lsttrn.pack()
scltrn.pack()
....
# Now grid our Frame containing both the Scrollwheel and Listbox to your GUI
myFrame.grid(column=xxx,row=xxx,padx=xxx,sticky=xxx)
This shouldnt be a copy+paste fix, but hopefully you understand the logic behind creating a Frame to hold your Scrollbar and Listbox, and then gridding that in, rather than both seperately.
Hope this helps! ~Gunner
At least part of the problem stems from the fact that you are putting the scrollbar and listbox in the same column. They need to be in different columns.
One really simple solution is to use a frame that contains only the listbox and scrollbar. Because you don't have a horizontal scrollbar you can use pack to put the listbox and scrollbar in the frame with just a couple lines of code. You can then place that frame in a single column in its parent.
I know there have been many questions on grid and pack in the past but I just don't understand how to combine the two as I'm having difficulties expanding my 'table' in both directions (row/column).
Buttons I wish to keep the same size but always stay at the bottom of the window.
The 'table' however I wish to expand automatically with resizing the window but can't seem to make it work. Changing 'win1' to pack worked in the sense that it stays central but that's it.
How can I achieve the same effects such as sticky etc with pack as I know I'll need to change the terminology.
Code is as follows (showing basic frames and several widgets, not complete code):
root = Tk()
win1 = Frame(root)
win1.pack()
win1.grid_columnconfigure(0, weight=1)
win1.grid_rowconfigure(1, weight=1)
frame_table = ttk.Frame(win1, style="Black.TLabel", relief='sunken', borderwidth=1)
frame_table.pack(row=2, column=0, padx=1, pady=1, sticky= "nsew")
frame_table.grid_columnconfigure(0, weight=1)
frame_table.grid_rowconfigure(1, weight=1)
text_table1 = Label(frame_table, text='Number1', bg='white', borderwidth=0)
text_table1.grid(row=1, column=0, sticky="nsew", padx=1, pady=1)
empty1 = Label(frame_table, bg='white', borderwidth=0)
empty1.grid(row=2, column=0, sticky="nsew", padx=1, pady=1)
text_table2 = Label(frame_table, text='Number2', bg='white', borderwidth=0, width=12)
text_table2.grid(row=1, column=1, sticky="nsew", padx=1, pady=1)
empty2 = Label(frame_table, bg='white', borderwidth=0)
empty2.grid(row=2, column=1, sticky="nsew", padx=1, pady=1)
frame_but = ttk.Frame(win1)
frame_but.grid(sticky=S, padx=1, pady=1)
frame_but.grid_columnconfigure(0, weight=1)
frame_but.grid_rowconfigure(1, weight=1)
but1 = ttk.Button(frame_but, text='Start', command=Start)
but1.grid(row=3, column=0, padx=2, pady=1, sticky="S")
Your first problem is that the main frame, win1 is packed with no options. The default is for it to not fill the part of its container that it is in. Thus, no matter what you do to the inner widgets, the whole thing will stack anchored to the top portion of the window. The first thing you should do, then, is tell win1 to fill the whole window (assuming that's actually what you want it to do):
win1.pack(side="top", fill="both", expand=True)
That will cause this frame to properly expand and shrink when you resize the window.
The second problem is that you're giving row 0 in win a weight of 1, but you are putting frame_table in row 3 which has a default weight of 0. I don't know if that's intentional or not, but that is what keeps the labels and entry widgets stuck to the bottom of the screen, because the empty row 0 of win1 is expanding and shrinking to take up the extra space.
How to learn to lay out your widgets
Proper resize behavior is pretty easy to get right, but it's fairly hard to learn how to get it right. My recommendation is, get some paper and a pencil. Draw out the main regions of your application -- the areas that each have different properties. For example, a row along the bottom that should stay at the bottom (status bar, or row of buttons perhaps). Maybe something at the top (toolbar, for example) that should stay at the top, etc. Typically there will be only one region that is expandable, though that expandable region may itself be divided into two or more regions.
In this case I'm guessing you have two regions: a table, and a row of buttons. Drawing this out is easy. Next, create a frame for each region, and only a frame for each region. Give them separate background colors, and place them in the window using grid or pack, whichever one gives you the resize behavior you want. pack is great if you have a simple layout (every region is sticked either top-to-bottom or left-to-right), grid is great if you truly have a grid. Work with just this, tweaking options until you get the behavior you want for the main regions. The different colors will help you see which areas are resizing and which are not.
Once you have the main regions working exactly right, you can then start to focus on the inner portions. Get out that pencil and paper again, and do the same with each of these sub-regions. Draw out the inner regions, and figure out which ones will grow within their container and which ones will not. Maybe there's only one main sub-region so you can skip this part. Finally, create frames if you have sub-regions, again giving them different colors so you can see what is resizing. Tweak the settings until everything resizes just the way you want. Lather, rinse, repeat.
Finally, you will not be able to sub-divide your window any more. Usually there are only a couple of regions so this process is quick. Once you have the different regions of your program all resizing how you want, it's time to add the actual widgets. Once you've done that you can go back and remove the color from the frames.
It's simple, but it requires a methodical approach. Just throwing a bunch of widgets into a frame and trying random things to get it to work is not the right approach. Be methodical, lay out your design on paper, transfer to frames with distinct colors, and then add your real widgets and add the final polish.
I have a frame that I fixed the size of using the grid_propagate() method. I'd like to center a frame within this frame. How do I go about this?
pack it to fill in all directions. Add padding as needed.
Or, use place which lets you use relative or absolute positioning. You can use a relative x/y of .5/.5 and an anchor of "c" (center).
import Tkinter as tk
root=tk.Tk()
f1 = tk.Frame(width=200, height=200, background="red")
f2 = tk.Frame(width=100, height=100, background="blue")
f1.pack(fill="both", expand=True, padx=20, pady=20)
f2.place(in_=f1, anchor="c", relx=.5, rely=.5)
root.mainloop()
If you want to center a widget (like a frame) inside a frame, the easiest way to do it is via .grid(). If you use .pack(), you end up stuck along one of the edges, since pack() has the side keyword.
If you use .place(), then you're stuck forcing the size of the outer frame (which normally you don't have to do, but you've already done it, so it's not an issue), because placed widgets aren't detected when the frame autosizes like with packed or gridded widgets. I'm not sure why, but that's the way it is.
So, in general, the best way to center a widget inside a frame is to grid the widget into the frame. (The sticky option defaults to CENTER.) And then, if you want to be able to resize the outer frame and have the widget stay centered, you need to allow the outer frame's cell to expand/grow. You would do this via the .grid_rowconfigure() etc commands. So, an example might be:
widget = Widget(frame, ...)
widget.grid(row=0, column=0, sticky="")
frame.grid_rowconfigure(0, weight=1)
frame.grid_columnconfigure(0, weight=1)
I'm trying to embed a plot in my Tkinter GUI coded in Python. I believe the code below succeeds in simply putting a graph into a canvas, but I don't have any control of the canvas location within the GUI grid. I want to be able to have a subsection of my GUI be the plot...not the entirety of it. How can I position this canvas widget?
#!/usr/apps/Python/bin/python
import matplotlib, sys
matplotlib.use('TkAgg')
from numpy import arange, sin, pi
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
from Tkinter import *
master = Tk()
master.title("Hello World!")
#-------------------------------------------------------------------------------
f = Figure(figsize=(5,4), dpi=100)
a = f.add_subplot(111)
t = arange(0.0,3.0,0.01)
s = sin(2*pi*t)
a.plot(t,s)
dataPlot = FigureCanvasTkAgg(f, master=master)
dataPlot.show()
dataPlot.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1)
#-------------------------------------------------------------------------------
master.mainloop()
You don't have any other widgets so it's hard to know where you want other widgets. Here's what I can tell you though: by doing dataPlot.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1) you are asking Tkinter to fill the screen with the plot. This, because you ask it to fill in all directions (fill=BOTH) and expand to fill any extra space (expand=1).
However, you can still add other widgets. pack works by putting widgets on one side of a container. Your container, master, always has four sides. So, for example, if you wanted to create a toolbar you would do something like:
toolbar = tk.Frame(master)
button = tk.Button(toolbar, text="Push me")
button.pack(side="left") # left side of parent, the toolbar frame
toolbar.pack(side=TOP, fill="x") # top of parent, the master window
Notice that if you put this code after the code where you pack the plot, the toolbar shows up on the bottom! That's because TOP, BOTTOM, etc refer to space left over by any other widgets that have already been packed. The plot takes up the top, the space left over is at the bottom. So when you specify TOP again it means "at the top of the area below whatever is already at the top".
So, you have some choices. The best choice is to make your widgets in the order you wish them to appear. If you pack the toolbar at the top before you pack the plot, it will be the toolbar that shows up at the very top. Further, you can place the plot at the bottom rather than the top and that will solve the problem, too.
By the way, I typically create my widgets in one block, then lay them all out in a separate block. I find it makes the code easier to maintain.
Another choice which may fit your mental model better is to grid instead of pack. With grid you can choose the row(s) and column(s) that the widget occupies. This makes it easy to lay things out in a grid, but at the expense of having to use a little more code.
For example, to put the toolbar at the top and the plot down below you might do:
toolbar.grid(row=1, column=1, sticky="ew")
dataPlot.get_tk_widget().grid(row=1, column=1, sticky="nsew")
master.grid_rowconfigure(0, weight=0)
master.grid_rowconfigure(1, weight=1)
master.grid_columnconfigure(0, weight=1)
Notice that rows and columns start at zero. Also, "weight" refers to how much this widget expands relative to other widgets. With two rows of equal weight, they will expand equally when the window is resized. A weight of zero means no expansion. A weight of 2 for one row, and 1 for another means that the former will expand twice as much as the latter.
For more information see this page on grid, and this page on pack.