change label text in widgets created in loop dynamically from a dictionary - python

Im working on a tkinter GUI that creates labels from a dictionary.
on of the labels is a "Fails" counter (theres another function that updates this value in the dictionary) id like if so when the "fails" is updated in the dictionary, it updates the fails value in the label.
heres a snippet if createing the labels from the dictionary. i cant figure out how to make it so the fails_label text is changed when the dictionary is updated
Thank you
row = 2
widgets = {}
for key in obj2create:
strEngNumber = obj2create[key][0]
strLayer = obj2create[key][1]
strFile = obj2create[key][2]
intFails = obj2create[key][3]
strJobKey = key
job_label = tk.Label(self.frame, text=key, anchor="w",
justify="center", wraplength=701,background=bg)
job_label.grid(row=row, column=0, pady=10, padx=10, ipady=3)
eng_label = tk.Label(self.frame, text=strEngNumber, anchor="w",
justify="center", wraplength=701,background=bg)
eng_label.grid(row=row, column=1, pady=10, padx=10, sticky="w")
layer_label = tk.Label(self.frame, text=strLayer, anchor="w",
justify="center", wraplength=701,background=bg)
layer_label.grid(row=row, column=2, pady=10, padx=10, sticky="w")
file_label = tk.Label(self.frame, text=strFile, anchor="w",
justify="center", wraplength=701,background=bg)
file_label.grid(row=row, column=3, pady=10, padx=10, sticky="w")
fails_label = tk.Label(self.frame, text=intFails, anchor="w",
justify="center", wraplength=701,background=bg)
fails_label.grid(row=row, column=4, pady=10, padx=10, sticky="w")

You can use trace_add() method like in the example below:
from tkinter import *
def update(var, index, mode):
label.configure(text=intFails.get())
def functionOnClick():
intFails.set(intFails.get() + 1)
w = Tk()
w.geometry('400x400')
intFails = IntVar(w, 0)
intFails.trace_add('write', update)
label = Label(w, textvariable=intFails, anchor="w", justify="center", font=('Arial', 22))
label.place(x=0, y=0)
btn = Button(w, text='Click', command=functionOnClick, font=('Arial', 22))
btn.place(x=0, y=80, width = 100, height = 80)
w.mainloop()
This method helps you to automatically detect, when variable was changed. Though it works only with special IntVar() and StringVar(), but I think it's only possible way to do this at least with this type of vars.
On the other hand, you can try to create a class with dictionary as your field and works with it by set property. This link may be useful for you.

Related

Tkinter Button Callback Lambda Unexpected Result

I want to create multiple rows of tkinter widgets and I thought storing them in a dictionary would be a clean way to do this. I'm having trouble getting the callbacks for the buttons to work correctly though. With the code as is, the value passed to the button callback is always E and I can't see why that is. How do I pass the correct key to the callback please - i.e. A for the first row, B for the second etc?
import tkinter as tk
NUM_ROWS = 5
BOLD_FONT = ("calbri", 16, "bold")
NORMAL_FONT = ("calbri", 16, "normal")
def submit():
print("you win")
def clear(key):
print(key)
# widgets[key]["factor_field"].delete(0,"end")
# widgets[key]["factor_field"].insert(0, key)
data = {}
widgets = {}
root = tk.Tk()
# Build widgets
for i in range(NUM_ROWS):
key = chr(i + 65)
this_row = widgets[key] = {}
this_row["label"] = tk.Label(root, text=key, font=BOLD_FONT)
this_row["label"].grid(row=i, column=0, padx=5, pady=10)
this_row["factor_field"] = tk.Entry(root, width=60, font=NORMAL_FONT)
this_row["factor_field"] .grid(row=i, column=1, padx=5, pady=10)
this_row["target_node_field"] = tk.Entry(root, width=5, font=NORMAL_FONT)
this_row["target_node_field"].grid(row=i, column=2, padx=5, pady=10)
this_row["clear_button"] = tk.Button(root, text="Clear", command=lambda: clear(
key), font=BOLD_FONT).grid(row=i, column=3, padx=5, pady=10)
submit_button = tk.Button(root, text="Submit", command=submit,
font=BOLD_FONT).grid(row=NUM_ROWS + 1, column=0, padx=5, pady=10)
print(widgets)
root.mainloop()
Use this:
this_row["clear_button"] = tk.Button(root, text="Clear",
command=lambda x=key: clear(x),
font=BOLD_FONT)
this_row["clear_button"].grid(row=i, column=3, padx=5, pady=10)
In your current code, the value for the key variable is fixed at E after the for loop has completed. As a result, your existing lambda function will always call clear('E').

Set tkinter alignment/position

If Info is blank, then the Entry Field and Button are at the red vertical line. But if Info has text, then they shift to the right. How can I fix the positions of the Entry Field and Button? Thanks.
window = Toplevel()
window.geometry('400x400')
searchL = Label(window, text='Enter ID:')
searchL.grid(row=0, column=0, padx=10, pady=10)
searchE = Entry(window)
searchE.grid(row=0, column=1, padx=10, pady=10)
def searchEmp():
for e in listOfEmployees:
if e.i == searchE.get():
results.set(repr(e))
search = Button(window, text='Search', command=searchEmp)
search.grid(row=1, column=0, columnspan=2)
infoL = Label(window, text='Info:')
infoL.grid(row=2, column=0, padx=10, pady=10)
results = StringVar()
resultsL = Label(window, textvariable=results)
resultsL.grid(row=2, column=1, padx=10, pady=10)
Adding the sticky arg fixed it for this Entry Field.
searchE.grid(row=0, column=1, padx=10, pady=10, **sticky=W**)
For the Search button, columnspan was set to 2, so if I removed columnspan, set the column=2, and added sticky=W, it worked.
Thanks to stovfl for the link.

ttk.Entry not setting default based on textvariable

I have created following window with tkinter and ttk, however I simply can't force the ttk.Entry fields to follow textvariable for its default value, they just keep appearing as blank. Strangely sometimes when I actually cause an exception to occur elsewhere in the window they suddenly work.
class PopUpEdit:
def __init__(self, idx):
self.idx = idx
self.data = get_single(self.idx)
self.have = tk.StringVar(root)
self.site = tk.StringVar(root)
self.title = tk.StringVar(root)
self.tags = tk.StringVar(root)
self._setup_vars()
self._setup_window()
def _setup_window(self):
window = tk.Toplevel(root)
window.geometry('400x600')
pprint.pprint(self.data)
ttk.Label(window, text='Site').grid(row=2, column=1, pady=10, padx=10)
ttk.Entry(window, textvariable=self.site, width=40).grid(row=2, column=2, sticky='e', pady=10, padx=10)
ttk.Label(window, text='Title').grid(row=3, column=1, pady=10, padx=10)
ttk.Entry(window, textvariable=self.title, width=40).grid(row=3, column=2, sticky='e', pady=10, padx=10)
ttk.Label(window, text=self.data['id']).grid(row=4, column=1, pady=10, padx=10)
def _setup_vars(self):
self.title.set(self.data['title'])
self.site.set(self.data['site'])
self.tags.set(self.data['tags'])

im using .get() to return the value of the entry box named 'fName' and then print it on the click of the button... but its not working

from tkinter import *
root = Tk()
fN = StringVar()
sN = StringVar()
age = StringVar()
yG = StringVar()
Label(root, text="First Name").grid(row=0, sticky=W, padx=4)
fName = Entry(root, width=50, textvariable=fN ).grid(row=0, column=1, sticky=E, pady=4)
Label(root, text="Surname").grid(row=1, sticky=W, padx=4)
sName = Entry(root, width=50, textvariable=sN).grid(row=1, column=1, sticky=E, pady=4)
Label(root, text="Age").grid(row=2, sticky=W, padx=4)
age = Entry(root, width=50, textvariable=age).grid(row=2, column=1, sticky=E, pady=4)
Label(root, text="Year Group").grid(row=3, sticky=W, padx=4)
yearGruop = Entry(root, width=50, textvariable=yG).grid(row=3, column=1, sticky=E, pady=4)
fName_1 = fN.get()
returning the value of the StringVar 'fN' and storing it
def print_():
print (fName_1)
not printing the contents of the first name entry box ##
Button(root, text="Create account", command=print_).grid(row=4, column=1)
root.mainloop()
Right now you are getting the contents as soon as you started the program thus you are getting empty value.
You need to get the value after clicking the button which means you should put the code that gets value inside of your method.
def print_():
fName_1 = fN.get()
print (fName_1)
Also, in your code fName, sName etc. are all set to None since grid() returns None. If you want to use them later you need to use grid on separate line.
fName = Entry(root, width=50, textvariable=fN )
fName.grid(row=0, column=1, sticky=E, pady=4)
Another point is, you don't need those stringvar values in your code either. You can directly get contents of Entry using get.
fName = Entry(root, width=50, textvariable=fN )
fName.grid(row=0, column=1, sticky=E, pady=4)
def print_():
print (fName.get())

Issues with layout management in Tkinter Python application

I am having a real issue with a little Tkinter program I'm making in Python as a frontend to my own CMS, regarding layout of the controls on the window. I am new to Tkinter but not Python but am struggling to use the grid layout manager to arrange my controls as I want them.
Here is a (terrible) mockup of what I'm aiming for:
But my code (below) only renders this:
Here is my code:
'''
Configure main window controls
'''
postTtlFrame = Frame(tkRoot, bg="red")
postTtlLbl = Label(postTtlFrame, text="Page title:").grid(row=0, column=0)
postTtlEnt = Entry(postTtlFrame).grid(row=0, column=1)
postTtlFrame.grid(row=0, column=0)
postTxtFrame = Frame(tkRoot, bg="blue")
postTxtLbl = Label(postTxtFrame, text="Page body content:").grid(row=0, column=0)
postTxtArea = Text(postTxtFrame).grid(row=1, columnspan=1)
postTxtFrame.grid(row=1, column=0)
pageConfigFrame = Frame(tkRoot, bg="green")
headerDirecLbl = Label(tkRoot, text="Page header location:").grid(row=0, column=0)
headerDirecEnt = Entry(tkRoot).grid(row=0, column=1)
footerDirecLbl = Label(tkRoot, text="Page footer location:").grid(row=1, column=0)
footerDirecEnt = Entry(tkRoot).grid(row=1, column=1)
stylesDirecLbl = Label(tkRoot, text="Page stylesheet location:").grid(row=2, column=0)
stylesDirecEnt = Entry(tkRoot).grid(row=2, column=1)
outputDirecLbl = Label(tkRoot, text="Page output location:").grid(row=3, column=0)
outputDirecEnt = Entry(tkRoot).grid(row=3, column=1)
pageConfigFrame.grid(row=2, column=0)
buttonsFrame = Frame(tkRoot, bg="orange")
postBtn = Button(tkRoot, text="Post").grid(row=0, column=0)
exitBtn = Button(tkRoot, text="Exit", command=quitTk).grid(row=0, column=1)
buttonsFrame.grid(row=3, column=0)
Please can someone explain to me what on earth is going wrong!
Thanks in advance,
Ilmiont
Yeah, I have it working now #jeanrjc, my code was FULL of errors I realise now; here is what I used in the end:
'''
Configure main window controls
'''
postTtlFrame = Frame(tkRoot)
postTtlLbl = Label(postTtlFrame, text="Page title:").grid(row=0, column=0)
postTtlEnt = Entry(postTtlFrame).grid(row=0, column=1)
postTtlFrame.grid(row=0, column=0, sticky=W)
postTxtFrame = Frame(tkRoot)
postTxtLbl = Label(postTxtFrame, text="Page body content:").grid(row=0, column=0, sticky=W)
postTxtArea = Text(postTxtFrame).grid(row=1, columnspan=1)
postTxtFrame.grid(row=1, column=0, sticky=W)
pageConfigFrame = Frame(tkRoot)
headerDirecLbl = Label(pageConfigFrame, text="Page header location:").grid(row=0, column=0, sticky=W)
headerDirecEnt = Entry(pageConfigFrame).grid(row=0, column=1)
footerDirecLbl = Label(pageConfigFrame, text="Page footer location:").grid(row=1, column=0, sticky=W)
footerDirecEnt = Entry(pageConfigFrame).grid(row=1, column=1)
stylesDirecLbl = Label(pageConfigFrame, text="Page stylesheet location:").grid(row=2, column=0, sticky=W)
stylesDirecEnt = Entry(pageConfigFrame).grid(row=2, column=1)
outputDirecLbl = Label(pageConfigFrame, text="Page output location:").grid(row=3, column=0, sticky=W)
outputDirecEnt = Entry(pageConfigFrame).grid(row=3, column=1)
pageConfigFrame.grid(row=2, column=0, sticky=W)
buttonsFrame = Frame(tkRoot)
postBtn = Button(buttonsFrame, text="Post").grid(row=0, column=0)
exitBtn = Button(buttonsFrame, text="Exit", command=quitTk).grid(row=0, column=1)
buttonsFrame.grid(row=3, column=0, sticky=E)
Now I have the following result which is what I wanted:
Thanks anyway though!
First off, it's pointless to assign a widget to a variable if you're calling grid (or pack or place) at the same time. foo=Label(..).grid(...) will always return None because grid(...) always returns None. Also, I find that layout problems are much easier to solve when you separate your layout from widget creation.
So, let's start by doing that:
'''
Configure main window controls
'''
postTtlFrame = Frame(tkRoot, bg="red")
postTxtFrame = Frame(tkRoot, bg="blue")
pageConfigFrame = Frame(tkRoot, bg="green")
buttonsFrame = Frame(tkRoot, bg="orange")
postTtlFrame.grid(row=0, column=0)
postTxtFrame.grid(row=1, column=0)
pageConfigFrame.grid(row=2, column=0)
buttonsFrame.grid(row=3, column=0)
postTtlLbl = Label(postTtlFrame, text="Page title:")
postTtlEnt = Entry(postTtlFrame).grid(row=0, column=1)
postTtlLbl.grid(row=0, column=0)
postTxtLbl = Label(postTxtFrame, text="Page body content:")
postTxtArea = Text(postTxtFrame)
postTxtLbl.grid(row=0, column=0)
postTxtArea.grid(row=1, columnspan=1)
headerDirecLbl = Label(tkRoot, text="Page header location:")
headerDirecEnt = Entry(tkRoot)
footerDirecLbl = Label(tkRoot, text="Page footer location:")
footerDirecEnt = Entry(tkRoot)
stylesDirecLbl = Label(tkRoot, text="Page stylesheet location:")
stylesDirecEnt = Entry(tkRoot)
outputDirecLbl = Label(tkRoot, text="Page output location:")
outputDirecEnt = Entry(tkRoot)
postBtn = Button(tkRoot, text="Post")
exitBtn = Button(tkRoot, text="Exit")
headerDirecLbl.grid(row=0, column=0)
headerDirecEnt.grid(row=0, column=1)
footerDirecLbl.grid(row=1, column=0)
footerDirecEnt.grid(row=1, column=1)
stylesDirecLbl.grid(row=2, column=0)
stylesDirecEnt.grid(row=2, column=1)
outputDirecLbl.grid(row=3, column=0)
outputDirecEnt.grid(row=3, column=1)
postBtn.grid(row=0, column=0)
exitBtn.grid(row=0, column=1)
Now, I think you can see more clearly what is happening. The problems I see are:
You seem to want to organize things into four main areas, but your mock-up shows that everything in the first three areas should share the same grid structure, so I'm not sure why you're creating these frames
You don't assign any weights to rows or columns, so they won't grow and shrink how you expect them to
Most of the widgets all share a common parent of tkRoot rather than the organizational frames that you create, so the frames end up serving no purpose
Because many widgets share the same parent, you end up putting several widgets on top of each other in the same grid cell.
You don't use the sticky attribute, so widgets won't fill their columns.
The fix for all this depends on exactly what effect you're trying to achieve. If you want four independent areas you need to make sure each widget has the appropriate frame for its parent, rather than lumping most widgets in the tkRoot frame. This makes it likely that, for example, the postTtlEnt won't line up with the other entry widgets.
If you don't want four independent areas and do want the postTtlEnt widget to line up with everything else, get rid of the intermediate frames and put everything into a single grid.
Likely you want a mix -- the buttons don't necessarily need to share the same grid, but all of the entry widgets should share the same grid. Here's how I would do it. Notice that I only have one extra internal frame, for the buttons. Everything else shares a common parent. Also notice that I give a weight to one row and one column so that you get the right resize behavior:
Here's a complete, working example. It doesn't precisely match your mockup: the exit and post buttons don't have their own dedicated column, but if you really want that you can do that if you want. The space above the buttons seems wasted, so I elected to put the buttons directly below the input widgets.
'''
Configure main window controls
'''
postTtlLbl = Label(tkRoot, text="Page title:")
postTxtLbl = Label(tkRoot, text="Page body content:")
headerDirecLbl = Label(tkRoot, text="Page header location:")
footerDirecLbl = Label(tkRoot, text="Page footer location:")
stylesDirecLbl = Label(tkRoot, text="Page stylesheet location:")
outputDirecLbl = Label(tkRoot, text="Page output location:")
postTtlEnt = Entry(tkRoot)
postTxtArea = Text(tkRoot)
footerDirecEnt = Entry(tkRoot)
headerDirecEnt = Entry(tkRoot)
stylesDirecEnt = Entry(tkRoot)
outputDirecEnt = Entry(tkRoot)
buttonsFrame = Frame(tkRoot, bg="orange")
postBtn = Button(buttonsFrame, text="Post")
exitBtn = Button(buttonsFrame, text="Exit")
postBtn.pack(side="right")
exitBtn.pack(side="right")
postTtlLbl.grid(row=0, column=0, sticky="w")
postTxtLbl.grid(row=1, column=0, sticky="w")
headerDirecLbl.grid(row=3, column=0, sticky="w")
footerDirecLbl.grid(row=4, column=0, sticky="w")
stylesDirecLbl.grid(row=5, column=0, sticky="w")
outputDirecLbl.grid(row=6, column=0, sticky="w")
postTtlEnt.grid(row=0, column=1, sticky="ew")
postTxtArea.grid(row=2, column=0, columnspan=2, sticky="nsew")
headerDirecEnt.grid(row=3, column=1, sticky="ew")
footerDirecEnt.grid(row=4, column=1, sticky="ew")
stylesDirecEnt.grid(row=5, column=1, sticky="ew")
outputDirecEnt.grid(row=6, column=1, sticky="ew")
buttonsFrame.grid(row=7, column=0, sticky="ew", columnspan=2)
tkRoot.grid_rowconfigure(2, weight=1)
tkRoot.grid_columnconfigure(1, weight=1)
I manage to get something pretty close to what you want :
from Tkinter import *
root = Tk()
content = Label(root, padx=30,pady=30,background = "white")
content.grid(column=0, row=0, sticky=(N, S, E, W))
title_frame = Label(content, borderwidth=5, relief="sunken", padx=30,pady=30,background = "white")
title_frame.grid(column=0, row=0, sticky=(N))
body_frame = Label(content, borderwidth=5, relief="sunken", padx=30,pady=30,background = "white")
body_frame.grid(column=0, row=1, sticky=(N,W,E))
config_frame = Label(content, borderwidth=5, relief="sunken", padx=30,pady=30,background = "white")
config_frame.grid(column=0, row=2, sticky=(N, W))
button_frame = Label(content, borderwidth=5, relief="sunken", padx=30,pady=30,background = "orange")
button_frame.grid(column=1, row=2, sticky=(S, E))
title_entry = Entry(title_frame, background="lightblue")
title_entry.grid(column=1, row=0)
title_label = Label(title_frame,text = "Page title",background = "white")
title_label.grid(column=0,row=0)
body_text = Text(body_frame, background="lightblue")
body_text.grid(column=1,row=0)
body_label = Label(body_frame,text = "Page body content",background = "white")
body_label.grid(column=0, row=0)
header_entry = Entry(config_frame, background="lightblue")
header_entry.grid(column=1, row=0)
header_label = Label(config_frame,text = "header",background = "white")
header_label.grid(column=0,row=0)
footer_entry = Entry(config_frame, background="lightblue")
footer_entry.grid(column=1, row=1)
footer_label = Label(config_frame,text = "footer",background = "white")
footer_label.grid(column=0,row=1)
postBtn = Button(button_frame, text="Post")
postBtn.grid(row=0, column=0)
exitBtn = Button(button_frame, text="Exit")
exitBtn.grid(row=0, column=1)
And it displays that :
Concerning why your code doesn't work :
I guess it's because you defined your grid position with :
postTtlLbl = Label(postTtlFrame, text="Page title:").grid(row=0, column=0)
Instead of
postTtlLbl = Label(postTtlFrame, text="Page title:")
postTtlLbl.grid(row=0, column=0)
It's a problem when you expect to create an instance of your frame (by returning the instance into the postTtlLbl variable), because the grid method doesn't return anything so these variables handling the frame are Nonetype, and you can't do anything with them.
Hope it's clear.

Categories

Resources