Tkinter: Grid containing frame containing grid - python

This is slightly exposing the entire program I'm building but it's just the front end anyways. If you guys want to make the app yourself, go ahead. :P
So, as the title says, I'm trying to make a grid that contains a frame that contains a grid, and I have looked at a few similar questions and don't completely get the answers, likely because I'm new to guis and tkinter and the solutions are often suited to their specific program. So here's a similar problem suited to mine.
What I'm trying to do is build something with the overall frame structure that looks like this: GUI Frame structure, where each frame besides the ioFrame is a 2x2 grid. The ioFrame contains two things: an input row and an output row. In short, this is a calculator that computes logic inputs and your only "numbers" are true and false.
Here's my code atm:
from tkinter import *;
class calculator:
#def update(self,
def __init__(self, window):
"""
Constructor method.
"""
self.toCompute = []; self.In = StringVar(); self.Out = StringVar();
# Window title
window.title("Logic Calculator");
# The set of 5 frames.
ioFrame = Frame(window, relief=GROOVE, borderwidth=3); ioFrame.grid(row=0); ioFrame.pack();
nwFrame = Frame(window); nwFrame.grid(row=1,column=0); nwFrame.pack();
neFrame = Frame(window); neFrame.grid(row=1,column=1); neFrame.pack();
swFrame = Frame(window); swFrame.grid(row=2,column=0); swFrame.pack();
seFrame = Frame(window); seFrame.grid(row=2,column=1); seFrame.pack();
# Top 2 rows: the IO portion
Label(ioFrame, textvariable=self.In, relief=SUNKEN).grid(row=0, sticky=W);
Label(ioFrame, textvariable=self.Out).grid(row=1, sticky=E);
# Top left 2x2 Frame: [ ( | ) ][ T | F ]
brlButton = Button(nwFrame, text='(', height=2, width=10).grid(row=0,column=0);
brrButton = Button(nwFrame, text=')', height=2, width=10).grid(row=0,column=1);
truButton = Button(nwFrame, text='T', height=2, width=10).grid(row=1,column=0);
falButton = Button(nwFrame, text='F', height=2, width=10).grid(row=1,column=1);
# Top right 2x2 Frame: [ AND | OOR ][ NND | NOR ]
andButton = Button(neFrame, text='and', height=2, width=10).grid(row=0,column=0);
oorButton = Button(neFrame, text='oor', height=2, width=10).grid(row=0,column=1);
nndButton = Button(neFrame, text='nnd', height=2, width=10).grid(row=1,column=0);
norButton = Button(neFrame, text='nor', height=2, width=10).grid(row=1,column=1);
# Bottom left 2x2 Frame: [ SSO | IIF ][ NSO | NIF ]
andButton = Button(swFrame, text='sso', height=2, width=10).grid(row=0,column=0);
oorButton = Button(swFrame, text='iif', height=2, width=10).grid(row=0,column=1);
nndButton = Button(swFrame, text='nso', height=2, width=10).grid(row=1,column=0);
norButton = Button(swFrame, text='nif', height=2, width=10).grid(row=1,column=1);
# Bottom right 2x2 Frame:[ EEQ | NEG ][ NEQ | === ]
eeqButton = Button(seFrame, text='eeq', height=2, width=10).grid(row=0,column=0);
negButton = Button(seFrame, text='neg', height=2, width=10).grid(row=0,column=1);
neqButton = Button(seFrame, text='neq', height=2, width=10).grid(row=1,column=0);
comButton = Button(seFrame, text='=', height=2, width=10).grid(row=1,column=1);
if __name__ == "__main__": # Only runs program if this specfic file is opened.
window = Tk(); # The window
calculator(window);
window.mainloop();
I still have to add the commands to these (which I've written in a separate file already), but I was just trying to get the gui done separately for now.
My problem, is that my code outputs this result instead: Current GUI. This is clearly way off from what I was going for. And being new to this, I probably have a list of problems.
Any help would be appreciated.

I'm going to assume here that you came to python from another language because of all the semicolons. In python, YOU DO NOT NEED SEMICOLONS. I understand at the top if you really want to keep those variables all on one line, but each line in your script should not end in a semicolon.
Secondly, if you print out the value of any button you you have put in a variable, you will see that it is None instead of a tkinter.Button object. This is because of that .grid(...) call at the end of the line (grid returns None). Typically, I only put widgets into a variable if I intend on using it later in the app. If not, I do what you did with the labels, just called them without some_variable_name = in front of it. If you ever try to do anything with those button variables later, it will not work because they are all None.
As mentioned in the comments, I was unable to get your provided code to run because you are using .grid(...) and .pack(...) to place the Frames on the window. You only need one. That was actually what caused the errors for me. Think of the window or Frame as a container; if you use grid to put something in that container, then you cannot use pack to put something else in the container. The different methods of placing widgets conflict with each other. I believe the output you got was because it would place the Frames using grid, then re-place them using pack which is why they are vertically aligned.
So taking all of this into consideration, this is what I have:
########## Frames ##########
# this is what the frames should look like in your code
frame = Frame(window, ...)
frame.grid(row=, column=)
########## Buttons ##########
# if you want to keep the buttons in variables
btn = Button(parent, text='', width=, height=)
btn.grid(row=, column=)
# or
# if the buttons don't have to be in variables, like your Labels
Button(parent, text='', width=, height=).grid(row=, column=)
There is also a typo with your button variables. In the Bottom left 2x2 Frame section, the button variables names should be changed to match the section title (if you are keeping the variables names):
ssoButton = ...
iifButton = ...
nsoButton = ...
nifButton = ...
The button variables are just references to the Button objects, so reusing a name doesn't matter, unless you want to use the buttons later in your code. In that case, it will cause issues, like I said before.
I updated part of the code so you can see what it should look like:
EDIT: I almost forgot about the ioFrame. It should be allowed to expand to fill the space provided at the top, but it's not. To fix this:
ioFrame = Frame(window, relief=GROOVE, borderwidth=3, bg='green')
ioFrame.grid(row=0, columnspan=2, sticky=N+E+S+W)
I set the background to green just to make sure it filled the space, and set the StringVars to some dummy text for testing (done like this: self.In.set('text here') to change the text). After all changes were made, this is my output:

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)

Clearing objects from a frame, without deleting the Frame

I'm attempting to create a simple point of sale system where when a button is pressed, its quantity and price are added up and eventually a total is shown (haven't gotten to this bit yet)
I've decided to also incorporate a clear button which will clear the frame in which the clicked items and their prices + quantities are shown however I'm having some problems with clearing the frame and still being able to click buttons afterwards.
This is the code I have for the Item button:
def AddButton():
global item_num #calls global variable
item_num += 1
item_text = "Chips 2.00"+" "+str(item_num) #concatonates text & variable
item1.config(text=item_text) #updates label text - doesn't add multiple
item1.pack()
addButton = Button(itemFrame, text="Chips", width=10, height=10, command=AddButton)
addButton.grid(row=1, column=1)
item1 = Label(receiptFrame)
and I began by trying to use .destroy like this:
def clearClick(): #blank function for clear button
receiptFrame.destroy()
however, since this completely deletes the frame, I'm then not able to re-input more items after it's been cleared
I also tried re-creating the frame:
def clearClick(): #blank function for clear button
receiptFrame.destroy()
receiptFrame = Frame(root, width=600, height=500, bd=5, relief="ridge")
receiptFrame.grid(row=1, column=3, columnspan=2)
but this still doesn't work
Is there a way to clear the contents of a frame without deleting the frame itself or does .destroy have to be used?
fr.winfo_children() returns the list of widgets inside the frame:
root = tk.Tk()
fr = tk.Frame()
lb = tk.Label(fr)
lb.grid()
print(fr.winfo_children())
for child in fr.winfo_children():
child.destroy()
print(fr.winfo_children()) # Now empty

Tkinter: Adding and self-deleting buttons in list | Adding works, Deleting not

I am just in the middle of creating an entry form for a program, and it seems that I have got stuck with the logic on this one.
Basically I wanted to design a dropdwon-list, which adds words to an array and displays these words as little buttons beneath it. When you click the buttons they disappear again and remove themselfs from the array.
Simple enough, I thought. The adding worked fine so far. But removing not so much... There is a logic error with the button array and I can't seem to figure it out!
I extracted the code for reviewing,
any help is greatly appreciated!
Word adding Window
import tkinter as tk
from tkinter import ttk
def rel_add(*args):
rel_array.append(tkvar.get())
print(rel_array)
rel_buttons_update()
def del_button(i):
print(i)
del rel_array[i]
print(rel_array)
rel_buttons[i].grid_remove()
# del rel_buttons[i]
rel_buttons_update()
def rel_buttons_update():
for i, rel in enumerate(rel_array):
rel_buttons.append(tk.Button(rel_grid, text=rel, font="Helvetica 7", command=lambda c=i: del_button(c)))
rel_buttons[i].grid(column=i, row=0, sticky="nw")
rel_array = []
rel_buttons = []
win = tk.Tk()
tkvar = tk.StringVar(win) # Create a Tkinter variable
choices_words = ["oolbath", "pflanze", "haus", "wasser", "brimbambum"] # Create Variable List
tkvar.set('Related Words...') # set the default option
choices_words.sort() # Sort List
tk.Label(win, text="Related Words: ").grid(row=0,column=0, sticky="w")
rel = tk.OptionMenu(win, tkvar, *choices_words)
rel.grid(row=0,column=1, sticky="w")
# Callbuck Function for Dropdwon Menu
tkvar.trace("w", rel_add)
rel_grid = tk.Frame(win)
# Display the Buttons for the related Words
rel_grid.grid(row=1,column=1, sticky="w")
win.mainloop()
The main problem is that you keep recreating the same buttons over and over, so rel_buttons contains many more elements than you expect.
As a simple experiement, add a print statement to rel_buttons_update like this:
def rel_buttons_update():
for i, rel in enumerate(rel_array):
rel_buttons.append(ttk.Button(rel_grid, text=rel, command=lambda c=i: del_button(c)))
rel_buttons[i].grid(column=i, row=0, sticky="nw")
print('in update, rel_buttons is now', rel_buttons)
You'll notice that there is one button the first time you use the option menu, three buttons the second time, six buttons the third time, and so on.
You need to either only create new buttons, or delete all the old buttons before recreating them.

Python+Tkinter: Redrawing a rectangle in a canvas based on some % value

I have spent an embarassing amount of hours looking for a way to do this... Its for a project I'm working on that has over one hundred canvas items that need to update from a text file. Here is a simple version of that:
I would like to update a rectangle drawn in a canvas item when I push a button. I found a real bad hack way to do it that involves a crazy amount of code but I know there has to be some better way.
from tkinter import *
class MyGUI:
def __init__(self, root):
frame = Frame(root)
frame.pack()
self.BoxFillPercent = 0 # the canvas items get their % fill from this value
self.changeButton = Button(frame, text='SB', command=self.changeRange)
self.changeButton.grid(row=1, column=1)
self.hAA = Canvas(frame, width=35, height=35, bg='light blue')
self.hAA.grid(row=2, column=2)
self.hAA.create_rectangle(0,0,self.BoxFillPercent*35,35, fill="pink")
self.hAA.create_text(15, 15, anchor='center', text='AA')
def changeRange(self):
self.BoxFillPercent = 0.5
# When I push the button change the fill amount to 0.5
? What do I need to add here to make this work ?
root = Tk()
b = MyGUI(root)
root.mainloop()
I have tried to use update and update_idletasks among a bunch of other things but I must be missing something.
Every item on a canvas has an id. You can use the itemconfig method of the canvas to change the item.
rect = self.hAA.create_rectangle(...)
...
self.hAA.itemconfig(rect, ...)
If you need to apply the same change to multiple objects, you can give those objects a common tag and then use the tag in place of the id:
rect1 = self.hAA.create_rectangle(..., tags=("special",))
rect2 = self.hAA.create_rectangle(..., tags=("special",))
...
self.hAA.itemconfigure("special", ...)

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)

Categories