Grid and placing widgets : once again - python

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()

Related

tkinter in python align the grid at the center of the window

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)

Scrollable page in Tkinter Notebook

For a project I need to specify a certain value for N subfiles (sets of data), and this value can either be evenly spaced (omitted here), requiring only a starting value and an increment, or unevenly spaced, which means each subfile has its own value. I've decided to use a Notebook to separate the two methods of entry.
As the number of subfiles can get into hundreds, I would need a scrollbar, and after consulting Google I've found out that to use a scrollbar in such manner I would need to use a canvas and place a frame in it with everything I would want to scroll through.
The number can vary each time, so I decided to use a dictionary, that would be iteratively filled, to contain all 'entry frames' that each contain a label, an entry field and a variable, rolled up into one custom class IterEntryField. After a class instance is created, it's packed inside one container frame. After the for loop is over, the container frame is placed on a canvas and the scrollbar is given a new scrollregion.
from tkinter import *
from tkinter.ttk import Notebook
N = 25
class IterEntryField:
def __init__(self, frame, label):
self.frame = frame
self.label = label
def pack(self):
self.valLabel = Label(self.frame, text = self.label, anchor = 'w')
self.valLabel.pack(fill = X, side = LEFT)
self.variable = StringVar()
self.variable.set('0')
self.valEntry = Entry(self.frame, textvariable = self.variable)
self.valEntry.pack(fill = X, side = RIGHT)
def notebookpopup():
zSetupWindow = Toplevel(root)
zSetupWindow.geometry('{}x{}'.format(800, 300))
notebook = Notebook(zSetupWindow)
evspace = Frame(notebook)
notebook.add(evspace, text = "Evenly spaced values")
sOverflow = Label(evspace, text = 'Ignore this')
sOverflow.pack()
uevspace = Frame(notebook)
notebook.add(uevspace, text = "Individual values")
canvas = Canvas(uevspace, width = 800, height = 400)
vsb = Scrollbar(canvas, command=canvas.yview)
canvas.config(yscrollcommand = vsb.set)
canvas.pack(side = LEFT, fill = BOTH, expand = True)
vsb.pack(side = RIGHT, fill = Y)
entryContainer = Frame(canvas)
entryContainer.pack(fill = BOTH)
frameDict = {}
for i in range(0, N):
frameDict[i] = Frame(entryContainer)
frameDict[i].pack(fill = X)
entry = IterEntryField(frameDict[i], 'Z value for subfile {}'.format(i+1))
entry.pack()
canvas.create_window(200, 0, window = entryContainer)
canvas.config(scrollregion = (0,0,100,1000))
notebook.pack(fill = X)
root = Tk()
button = Button(root, text = 'new window', command = notebookpopup)
button.pack()
root.mainloop()
I'm having three problems with this code:
The pages are incredibly short, only showing a couple lines.
I can't figure out the "proper" offset in create_window. I thought 0, 0 would place it in upper left corner of the canvas, but apparently the upper left corner of the window is taken instead. This could probably fixed by some reverse of the canvasx and canvasy methods, but I haven't been able to find any.
The entry fields and labels are cramped together instead of taking up the entire width of the canvas. This wasn't a problem when I only used the notebook page frame as the container.
Your first problem goes back to how you pack your notebook. Simply change notebook.pack(...) to below:
notebook.pack(fill="both", expand=True)
The second one can be solved by specifying the anchor position in your create_window method:
canvas.create_window(0, 0, window = entryContainer, anchor="nw")
I don't understand what the 3rd problem is - it looks exactly as expected.

Python Tkinter: adding a scrollbar to frame within a canvas

I am quite new to Tkinter, but, nevertheless, I was asked to "create" a simple form where user could provide info about the status of their work (this is sort of a side project to my usual work).
Since I need to have quite a big number of text widget (where users are required to provide comments about status of documentation, or open issues and so far), I would like to have something "scrollable" (along the y-axis).
I browsed around looking for solutions and after some trial and error I found something that works quite fine.
Basically I create a canvas, and inside a canvas a have a scrollbar and a frame. Within the frame I have all the widgets that I need.
This is a snipet of the code (with just some of the actual widgets, in particular the text ones):
from Tkinter import *
## My frame for form
class simpleform_ap(Tk):
# constructor
def __init__(self,parent):
Tk.__init__(self,parent)
self.parent = parent
self.initialize()
#
def initialize(self):
#
self.grid_columnconfigure(0,weight=1)
self.grid_rowconfigure(0,weight=1)
#
self.canvas=Canvas(self.parent)
self.canvas.grid(row=0,column=0,sticky='nsew')
#
self.yscrollbar = Scrollbar(self,orient=VERTICAL)
self.yscrollbar.grid(column =4, sticky="ns")
#
self.yscrollbar.config(command=self.canvas.yview)
self.yscrollbar.pack(size=RIGTH,expand=FALSE)
#
self.canvas.config(yscrollcommand=self.yscrollbar.set)
self.canvas.pack(side=LEFT,expand=TRUE,fill=BOTH)
#
self.frame1 = Frame(self.canvas)
self.canvas.create_window(0,0,window=self.frame1,anchor='nw')
# Various Widget
# Block Part
# Label
self.labelVariableIP = StringVar() # Label variable
labelIP=Label(self.frame1,textvariable=self.labelVariableIP,
anchor="w",
fg="Black")
labelIP.grid(column=0,row=0,columnspan=1,sticky='EW')
self.labelVariableIP.set(u"IP: ")
# Entry: Single line of text!!!!
self.entryVariableIP =StringVar() # variable for entry field
self.entryIP =Entry(self.frame1,
textvariable=self.entryVariableIP,bg="White")
self.entryIP.grid(column = 1, row= 0, sticky='EW')
self.entryVariableIP.set(u"IP")
# Update Button or Enter
button1=Button(self.frame1, text=u"Update",
command=self.OnButtonClickIP)
button1.grid(column=2, row=0)
self.entryIP.bind("<Return>", self.OnPressEnterIP)
#...
# Other widget here
#
# Some Text
# Label
self.labelVariableText = StringVar() # Label variable
labelText=Label(self.frame1,textvariable=
self.labelVariableText,
anchor="nw",
fg="Black")
labelText.grid(column=0,row=curr_row,columnspan=1,sticky='EW')
self.labelVariableTexta.set(u"Insert some texts: ")
# Text
textState = TRUE
self.TextVar=StringVar()
self.mytext=Text(self.frame1,state=textState,
height = text_height, width = 10,
fg="black",bg="white")
#
self.mytext.grid(column=1, row=curr_row+4, columnspan=2, sticky='EW')
self.mytext.insert('1.0',"Insert your text")
#
# other text widget here
#
self.update()
self.geometry(self.geometry() )
self.frame1.update_idletasks()
self.canvas.config(scrollregion=(0,0,
self.frame1.winfo_width(),
self.frame1.winfo_height()))
#
def release_block(argv):
# Create Form
form = simpleform_ap(None)
form.title('Release Information')
#
form.mainloop()
#
if __name__ == "__main__":
release_block(sys.argv)
As I mentioned before, this scripts quite does the work, even if, it has a couple of small issue that are not "fundamental" but a little annoying.
When I launch it I got this (sorry for the bad screen-capture):
enter image description here
As it can be seen, it only shows up the first "column" of the grid, while I would like to have all them (in my case they should be 4)
To see all of the fields, I have to resize manually (with the mouse) the window.
What I would like to have is something like this (all 4 columns are there):
enter image description here
Moreover, the scrollbar does not extend all over the form, but it is just on the low, right corner of the windows.
While the latter issue (scrollbar) I can leave with it, the first one is a little more important, since I would like to have the final user to have a "picture" of what they should do without needing to resize the windows.
Does any have any idea on how I should proceed with this?
What am I missing?
Thanks in advance for your help
In the __init__ method of your class, you do not appear to have set the size of your main window. You should do that, or it will just set the window to a default size, which will only show whatever it can, and in your case, only 1 column. Therefore, in the __init__ method, try putting self.geometry(str(your_width) + "x" + str(your_height)) where your_width and your_height are whatever integers you choose that allow you to see what you need to in the window.
As for your scrollbar issue, all I had to do was change the way your scrollbar was added to the canvas to a .pack() and added the attributes fill = 'y' and side = RIGHT to it, like so:
self.yscrollbar.pack(side = 'right', fill = 'y')
Also, you don't need:
self.yscrollbar.config(command=self.canvas.yview)
self.yscrollbar.pack(size=RIGHT,expand=FALSE)
Just add the command option to the creation of the scrollbar, like so:
self.scrollbar = Scrollbar(self,orient=VERTICAL,command=self.canvas.yview)
In all, the following changes should make your code work as expected:
Add:
def __init__(self,parent):
Tk.__init__(self,parent)
self.parent = parent
self.initialize()
# Resize the window from the default size to make your widgets fit. Experiment to see what is best for you.
your_width = # An integer of your choosing
your_height = # An integer of your choosing
self.geometry(str(your_width) + "x" + str(your_height))
Add and Edit:
# Add `command=self.canvas.yview`
self.yscrollbar = Scrollbar(self,orient=VERTICAL,command=self.canvas.yview)
# Use `.pack` instead of `.grid`
self.yscrollbar.pack(side = 'right', fill = 'y')
Remove:
self.yscrollbar.config(command=self.canvas.yview)
self.yscrollbar.pack(size=RIGHT,expand=FALSE)

When using a scrolled canvas in Tkinter, columns of contained frame are cut off

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.

Labels appearing over Entry's in Tkinter

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(...)
...

Categories