I'm new to tkinter. I wanted to use pack() to align the components at the center of the window, but I need some rows to have 2 components, because of which I thought of using grid. Now the issue is that the components are aligned at the left side of the window. What I want is to have the items at the center of the window. I don't wanna make the components shift when I resize, so just one time fix is enough. This is how it looks now. This is how I want it to look.
So what I'm doing here is that after selecting the first OptionMenu, depending on the selection, I wanna show a Entry that has label with text "Pincode" or "District". And I want that Label and Entry to be on the same row.... My current code does show it the way I want, but all of it is pushed to the right. I want it all in the middle.
BTW, Indentation here is messed up, IDK how to correct it...
EDIT:
I've removed some bits of code that isn't necessary for the placement of the components so that you can read easily. And I've included "self.root.grid_columnconfigure(0, weight=1)", but then in the fetchData(), the Label() and Entry() is messed up. Entry() is on the far right side and looks like its on the 3rd column (column=2 in programmer count).
EDIT:
"self.root.grid_columnconfigure(1, weight=1)" using this, I got the output that I desired... SO this one is answered....
from tkinter import *
class TrackerGUI:
def __init__(self):
self.root = Tk()
self.root.geometry("500x300")
self.root.grid_columnconfigure(0, weight=1)
OptionMenu(self.root, clicked, *options, command = self.fetchData).grid(row = 1, column=0, columnspan=2, sticky=N)
self.root.mainloop()
def fetchData(self, urlType):
# Date Option Menu
self.date = OptionMenu(self.root, datesVar, *dates).grid(row = 2, column=0, columnspan=2)
if urlType.lower() == "pincode":
Label(self.root, text='Pincode').grid(row=3,column=0)
elif urlType.lower() == "district":
Label(self.root, text='District').grid(row=3,column=0)
e1 = Entry(self.root).grid(row=3,column=1)
Related
so i'm currently working on a piece of code that'll be integrating into something else later to act as a settings configurator. For the time being, i want to have a window that is laid out like you see below:
where each coloured box is a frame. This window is not resizable and will always be 480x720 pixels. As such, i want the 3 frames im using, sideBar(yellow), container (blue) and static(red) to always remain the same size and fill the window as pictured above with roughly the same ratios (doesn't need to be exact).
The code for this window is below
self.window = tk.Tk()
self.windowHeight = 480
self.windowLength = 720
self.windowDimensions = str(self.windowLength)+"x"+str(self.windowHeight) #make diemnsions string; dimensions are set as a single string
self.window.geometry(self.windowDimensions)
self.window.resizable(width=False, height=False)
self.container = tk.Frame(self.window, relief="sunken", borderwidth=2) #instantiate new window
self.sideBar = tk.Frame(self.window, relief="sunken", borderwidth=2)
self.static = tk.Frame(self.window, relief="sunken", borderwidth=2)
self.sideBar.grid_propagate(False)
self.sideBar.grid(row=0, column=0)
self.container.grid(row=0,column=1)
self.static.grid(row=5, column=1)
self.configuratorObject = configuratorObject
audioButton = tk.Button(self.sideBar, text="Audio Page", command=lambda: self.raisePage("audioPage"))
colourButton = tk.Button(self.sideBar, text="Colours", command=lambda: self.raisePage("coloursPage"))
saveButton = tk.Button(self.static, text = "Save", state="disabled")
applyButton = tk.Button(self.static, text = "Apply", state="disabled")
audioButton.pack()
colourButton.pack()
saveButton.pack()
applyButton.pack()
I've attempted to change the height and width parameters of the grids, but they really don't seem to be doing anything. So how could i go about explicitly defining the layout and sizes of the frames?
Any help is appreciated
In the comments you wrote
If theres a way of getting tkinter to do it then that'd be great
That is definitely the preferred way, over forcing widgets to be a particular size.
We'll start by using pack instead of grid for the three frames. For such a basic layout it requires fewer lines of code than grid.
self.sideBar.pack(side="left", fill="y")
self.static.pack(side="bottom", fill="x")
self.container.pack(side="top", fill="both", expand=True)
Next, add the buttons on the left. This will cause the left frame to shrink in width to fit the buttons. Because we used fill="y", the height will be forced to remain the full height of the window.
audioButton.pack(side="top", fill="x")
colourButton.pack(side="top", fill="x")
Finally, add the buttons on the bottom. Your original code shows them stacked top-to-bottom but your illustration shows them in a single horizontal row. This example adheres to the illustration.
applyButton.pack(side="right", padx=10)
saveButton.pack(side="right", padx=10)
With that we end up with a window that looks like the following, and the proportions and orientation stays exactly the same when you resize the window:
Note: you can do this with grid too, but it requires a few more lines of code to apply weights to the rows and columns. I personally prefer pack when the layout doesn't naturally fit in a grid since it requires fewer lines of code.
Dears,
Although I want to make it simple, I'm still failing in creating a class of "windows" which by default will have a basic menu (not in code yet), a frame of 10 rows and 10 cls, and in the last cell of this frame (row = 9, col=9) a "Close" button.
Then, I could create several classes that will inherit from that one, and adding more widgets, commands, .. Well very basic
Yes but, although I gave weight to cells,..,... the button is still on the top left corner, and not the bottom right one. What did I miss when managing widgets with .grid()
Thks a lot
import tkinter as tk
from tkinter import ttk
class myWindows(tk.Tk):
def __init__(self,
pWtitle='',
pParent = '',
pIsOnTop = False,
pWidth=800,
pHeight=600,
pIsResizable=False,
pNbRows = 10,
pNbCols = 10):
tk.Tk.__init__(self)
tk.Tk.wm_title(self, pWtitle)
self.geometry('%sx%s' % (pWidth, pHeight))
if pIsResizable :
self.minsize(pWidth, pWidth)
rFrame =tk.Frame(self, borderwidth=1, relief="ridge")
#to make it simple by default, there will be a grid of 10rows and 10columns
for r in range(pNbRows) :
rFrame.grid_rowconfigure(r,weight=1)
for c in range(pNbCols) :
rFrame.grid_columnconfigure(c,weight=1)
rFrame.grid(row=0, column=0, sticky='ns')
#all windows will have a quit button on the bottom right corner (last cells)
#Need deduct 1 in the parameter as indexes start from 0
bt=ttk.Button(self, text='Close',command=self.quit)
bt.grid(row=pNbRows -1, column=pNbCols -1, sticky='se')
app = myWindows( pWtitle='MAIN')
app.mainloop()
You are configuring the weights inside of rFrame, but you are putting rFrame and the button directly in the root window. You have not configured weights for any rows or columns in the root window.
grid doesn't display rows and columns that don't contain anythng. Try, for example adding one placeholder label with empty picture (Label = (self, image = PhotoImage())) in every row and column of the grid until you populate it with real stuff. Source
http://effbot.org/tkinterbook/grid.htm
minsize= Defines the minimum size for the row. Note that if a row is
completely empty, it will not be displayed, even if this option is
set.
Finally, I came up with a solution :
import tkinter as tk
from tkinter import ttk
class myWindows(tk.Tk):
def __init__(self,
pWtitle='',
pParent = '',
pIsOnTop = False,
pWidth=800,
pHeight=600,
pIsResizable=False
):
tk.Tk.__init__(self)
tk.Tk.wm_title(self, pWtitle)
self.geometry('%sx%s' % (pWidth, pHeight))
if pIsResizable :
self.minsize(pWidth, pHeight)
#all windows will have a quit button on the bottom right corner (last cells)
#Need deduct 1 in the parameter as indexes start from 0
bt=ttk.Button(self, text='Close',command=self.quit)
bt.grid(row=1, column=0, sticky='se')
#to make it simple by default, there will be a container on top of button
rFrame =tk.Frame(self, borderwidth=1, bg = 'blue', relief="ridge")
rFrame.grid(row=0, column=0)
#give max weight to the first cell to
#make sure the container is filling up the empty space
#on top of the button(s)
self.grid_rowconfigure(0, weight = 1)
self.grid_rowconfigure(1, weight =0)
self.grid_columnconfigure(0, weight =1)
app = myWindows( pWtitle='MAIN')
app.mainloop()
I want to have an entry and I want to have a listbox of fixed size under it which is fixed. and I want to have another listbox of dynamic height. That will appear and disappear in time and also change in size. I want the second listbox (which is actually a dropdown) to be shown over the other listbox which I want it to be fixed. My code for changing the size etc is correct and works perfectly with pack() but then it will move the other listbox up and down as it's size changes. And when I change pack() to place(...) it's not shown at all anymore.
Here is my code:
from tkinter import *
root = Tk()
entry = Entry(
root,
width=50
)
frame = Frame(
root,
height=10,
width=50,
background="#caeaa9"
)
dropdown = Listbox(
frame,
background="#11FF11",
height=5,
width=50
)
listbox = Listbox(
frame,
background="#FF1111",
height=10,
width=50
)
entry.pack()
listbox.pack()
dropdown.place()
frame.pack()
mainloop()
But the dropdown does not appear when I run it. What am I missing?
By the way, I want the top border of the dropdown to be exactly on the top border of the listbox and both of them right under the entry.
I highly recommend you don't use the place geometry manager. Ever. If you want to create larger or more complex interfaces, having to place widgets is terrible. I suggest using grid instead:
import tkinter as tk
# Avoid wildcard imports!
root = tk.Tk()
entry = tk.Entry(
root,
width=50
)
frame = tk.Frame(
root,
background="#caeaa9"
)
listbox = tk.Listbox(
frame,
background="#FF1111",
height=10,
width=50
)
dropdown = tk.Listbox(
frame,
background="#11FF11",
height=5,
width=50
)
entry.pack()
listbox.grid(column=0, row=0, sticky=N)
dropdown.grid(column=0, row=0, sticky=N)
frame.pack()
root.mainloop()
You have two problems:
you aren't telling place where to place the widget
the stacking order (z-index) of the dropdown is behind (lower) than the other listbox, so it will appear under the listbox.
give explicit coordinates to place
I'm not entirely clear where you want the dropdown to appear. Since you say "over" the other listbox, I suggest you use the in_ parameter to make the coordinates relative to the other listbox, and then use other place arguments to place it exactly where you want.
Example:
In the following example I make the dropdown exactly the same width, but half the height of the other listbox.
dropdown.place(in_=listbox, x=0, y=0, anchor="nw", relwidth=1.0, relheight=.5)
Fix the stacking order
All widgets have a stacking order. Some people call this a z-index. By default the order is the order in which the widgets are created. Since you create the dropdown before the other listbox, the other listbox has a higher stacking order. That means that it will appear above the dropdown.
A simple solution is to create the dropdown last. If you don't want to do that, you can call the lift method of the widget to raise its stacking order. The argument for lift is the name of a widget you want to be above.
Example:
dropdown.lift(listbox)
Tkinter place is one of the three geometry manager in tkinter. it allows us to specify the position of tkinter widgets in terms of x and y coordinate.
Here is the code example :
from tkinter import *
window = Tk()
Button(window,text = "Click Me").place(x = 50,y = 50)
window.mainloop()
For more info kindly refer this tutorial on tkinter place
I'm trying to make a Tkinter widget that contains a number of tables, which are currently frames with entries filled using the .grid method, which can be switched between by pressing buttons. My current attempt at a solution uses the following code:
from tkinter import *
def dot(root, num):
root.subframe.destroy()
root.subframe = TFrame(root, num)
root = Tk()
vscrollbar = Scrollbar(root,orient='vertical')
vscrollbar.grid(row=1,column=2,sticky=N+E+W+S)
root.defaultframe = MainFrame(root)
root.canvas = Canvas(root, yscrollcommand=vscrollbar.set)
root.subframe = Frame(root.canvas)
vscrollbar.config(command=root.canvas.yview)
root.canvas.grid(row=1,column=0)
root.subframe.grid(row=0,column=0)
where MainFrame has the following structure:
class MainFrame(Frame):
def __init__(self, root):
Frame.__init__(self, root)
self.grid(row=0,column=0)
b1 = Button(self, text='table 1', command=lambda: dot(root, 0))
b2 = Button(self, text='table 2', command=lambda: dot(root, 1))
b1.grid(row=0, column=0, sticky=N+E+W+S)
b2.grid(row=0, column=1, sticky=N+E+W+S)
and TFrame:
class TFrame(Frame):
def __init__(self, foor, num):
Frame.__init__(self, root.canvas)
for i in range(12):
self.grid_columnconfigure(i, minsize=50)
for x in range(12):
for y in range(20):
label = Label(self, text=num)
label.grid(row=y,column=x,sticky=N+E+W+S)
root.canvas.create_window((0,0),window=self,anchor='nw')
root.canvas.configure(scrollregion=root.canvas.bbox('all'))
When I run the code, pressing the buttons loads the tables, which scroll in the vertical as expected. But only the first 8 columns or so are visible, no matter how the window is resized. Changing the width of the MainFrame by adding empty labels and the like does not affect the size of the TFrame created, even if it is several times wider than the 8 columns the TFrame ends up being. While I could have a somewhat tolerable solution by adding a horizontal scroll bar as well as the vertical, my experiences so far with scrolling in tkinter in general have been negative enough that I hope to avoid using it by any possible means.
Okay, found a solution. It turns out there weren't columns being cut off, the whole canvas was being cut off, and all my test cases just happened to have exactly the right number of columns vs column width that it looked like the columns after the first 8 were being cut off.
Changing:
root.canvas.grid(row=1,column=0)
to
root.canvas.grid(row=1,column=0,sticky=N+E+W+S)
fixed the problem.
When I run the following code the created labels appear over top of the Entry boxes as if they are not being added to the same grid.
class Application(Frame):
def __init__(self,master):
super(Application,self).__init__(master)
self.grid()
self.new_intervals()
def new_intervals(self):
self.int_label = Label(text="Interval Name")
self.int_label.grid(row=0, column=0,sticky=W)
self.int_time_label = Label(text="Time (HH:MM:SS)")
self.int_time_label.grid(row=0, column=1,sticky=W)
self.box1 = Entry(self)
self.box1.grid(row=1,column=0,sticky=W)
self.box2 = Entry(self)
self.box2.grid(row=1,column=1,sticky=W)
self.box3 = Entry(self)
self.box3.grid(row=2,column=0,sticky=W)
self.box4 = Entry(self)
self.box4.grid(row=2,column=1,sticky=W)
root = Tk()
root.title("Interval Timer")
root.geometry("400x500")
app=Application(root)
root.mainloop()
I know that i can add these boxes in a loop, however, I can't get it to work without the loop at the moment
The application frame is in row 0, column 0 of the main window. That is the default when you don't specify anything. Also as a default, they appear in the middle
This frame has four entry widgets spread across two rows, making the frame grow to fit around those entry widgets
The "Interval Name" label is also being placed in row 0, column 0 of the main window, because that's what you explicitly tell it to do, and because its parent is the main window.
The "Time" label is also in row 0 of the main window because, again, it's parent is the main window
both of these labels are appearing in the vertical center of the row because that is the default behavior which you haven't overridden, which is why they appear on top of the entry widgets.
So, because the labels and the application frame are in the same row of the main window, and because the labels default to being in the vertical center, they appear to be in the middle of the entry widgets.
I assume you intended for the labels to be children of the frame, so you need to specify "self" as the first parameter when creating them:
self.int_label = Label(self, text="Interval Name")
...
self.int_time_label = Label(self, text="Time (HH:MM:SS)")
I also recommend grouping all of your grid statements for a particular master window together, so it's easier to see the organization of your widgets. In my experience this makes the code easier to read and easier to maintain.
For example:
self.int_label = Label(...)
self.int_time_label = Label(...)
self.box1 = Entry(...)
...
self.int_label.grid(...)
self.int_time_label.grid(...)
self.box1.grid(...)
...