initialising ttk.Entry in dictionary - python-3.x

Basically I have 8 entries I want to create in a loop and store in a dictionary, with a string as key:
class foo:
entries={}
keys=[#some keys are here, type string]
#do other stuff
def create_entries(self):
for key in keys:
entries[key]=ttk.Entry(self.frame,text='sometext')
#other stuff going on
def assign(self):
f=open('file.name','r').read()
#do some fancy slicing to get the strings for the entries
for key in keys:
entries[key].insert(0,string)
now here it fails, stating that 'NoneType' object has no attribute 'insert'.
I guess this is because I have declared entries as an empty dictionary.
But if I declare it like this: entries={'KEY':ttk.Entry} still states there is no insert for 'NoneType'. And if I declare it like entries={'KEY':ttk,Entry()} it initalises an empty toplayer on start, but if I come to load my entries, it tells me again, there is no insert for 'NoneType'.
So I am kind of lost right now.. is it even possible to initialise entries into a dictionary and later on insert some text in them? Or do I have to stick with each entry as a "individual variable"?
minimum working example:
If I delete the prints in the read-function and uncomment the inserts, it tells me:
self.entries[key].insert(0,s)
AttributeError: 'NoneType' object has no attribute 'insert'
import tkinter as tk
from tkinter import ttk
f = open('testfile.test', 'w')
f.write('A=non-relevant\nB=SomeFunnyStuff\nZ=MySecretCode:9128\n')
f.close()
class foo:
main = tk.Tk
frame = ttk.LabelFrame
keys = ['A','B','Z']
entries={}
labels={}
def read(self):
f = open('testfile.test','r').read()
for key in self.keys:
first=key+'='
if key != 'Z':
i = self.keys.index(key)+1
last='\n'+self.keys[i]+'='
s=f.split(first)[1].split(last)[0]
print(s)#self.entries[key].insert(0,s)
else:
s=f.split(first)[1]
print(s)#self.entries[key].insert(0,s)
def _quit(self):
self.main.quit()
self.main.destroy()
exit()
def run(self):
self.main.mainloop()
def __init__(self):
#init window
self.main = tk.Tk()
self.main.title('Test')
self.frame = ttk.LabelFrame(self.main, text='Try it').grid(column=0,row=0)
#init entries & labels
c,r=0,0
for key in self.keys:
s = key+'='
self.labels[key] = ttk.Label(self.frame, text=s).grid(column=c,row=r)
c+=1
self.entries[key] = ttk.Entry(self.frame,text=s).grid(column=c,row=r)
r+=1
c-=1
self.button = ttk.Button(self.frame, text='close',command=lambda:self._quit()).grid(column=0,row=r)
self.read()
print(self.entries.values())
t = foo()
t.run()

If anyone has a similar problem:
according to this post the problem is with how I initialized the entries in my original code & the minimum example posted (not in the original post unfortunately..):
self.entries[key] = ttk.Entry(self.frame,text=s).grid(column=c,row=r) returns a None to the dictionary, as .grid() returns None
Initializing labels and entries first and then using grid in a separate line however works fine:
self.entries[key] = ttk.Entry(self.frame,text=s)
self.entries[key].grid(column=c,row=r)
That fixed it for me.

Related

parameters inside f-string in a dictionary cannot update later

I encounter this issue, when I instantiate an Test object and call the main() method, it prints out
https://bunch/of/urls/with/None/
Although I am expecting to have
https://bunch/of/urls/with/1234/
It cannot update the self.id inside the dictionary f-string later inside the main function.
My expectation is when the dictionary value retrieves given the key, it will update the self.id as well while returning the value.
The reason I am expecting that because I call self.b_func(1234) before I call self.a_func() so it updates the self.id.
However, it is not updating the self.id inside the f-string. This behavior I don't understand why. What concept am I missing? How can I fix this?
class Test():
def __init__(self,id=None):
self.id = id
# Problem happens here
self.a_dict = {'a' : f'https://bunch/of/urls/with/{self.id}/'}
def a_func(self):
val = self.a_dict['a']
print(val)
# It prints
# https://bunch/of/urls/with/None/
# Not
# https://bunch/of/urls/with/1234/
def b_func(self, id):
self.id = id
return True
def main(self):
self.b_func(1234)
self.a_func()
if __name__ == '__main__':
a = Test()
a.main()
f-strings are evaluated right where they are defined. They do not magically update themselves when the values of the expressions inside change afterwards.
You can consider making a_dict a property instead so that its value would be dynamically generated:
class Test():
def __init__(self,id=None):
self.id = id
#property
def a_dict(self):
return {'a' : f'https://bunch/of/urls/with/{self.id}/'}
Demo: https://replit.com/#blhsing/UnnaturalElasticClasslibrary

Unable to bind function after pickling - tkinter

I have simple code which creates two fields by the press of a button. There are two other buttons to save and load back the entry fields created. I have used the bind function to bind field A and field B. Pressing the Enter button on field A after entering a number will print out its value multiplied by 5 in field B. At this point the bind function works perfectly.
When I create three entry fields and save the progress without entering any inputs and compile the program, then load the file, the bind function does not seem to work. It seems to work only for the last field created. My code is as follows. I tried my best to simplify the code.
from tkinter import *
from tkinter.filedialog import askopenfilename
from tkinter.filedialog import asksaveasfile
from tkinter import messagebox
import pickle
class Test(Frame):
def Widgets(self):
self.button_add = Button(self, text = "Add", command = self.add)
self.button_add.grid(row=0, column =2)
self.button_save = Button(self, text = "save", command = self.save)
self.button_save.grid(row=0, column =3)
self.button_load = Button(self, text = "load", command = self.load)
self.button_load.grid(row=0, column =4)
def add(self):
def test(event):
self.field_B[n].delete(0, END)
self.field_B[n].insert(0, (float(self.field_A[n].get()))*5)
self.field_A.append({})
n = len(self.field_A)-1
self.field_A[n] = Entry(self)
self.field_A[n].grid(row=n, column =0)
self.field_A[n].bind("<Return>", test)
self.field_B.append({})
n = len(self.field_B)-1
self.field_B[n] = Entry(self)
self.field_B[n].grid(row=n, column =1)
def save(self):
for n in range(len(self.field_A)):
self.entry_A.append(self.field_A[n].get())
self.entry_B.append(self.field_B[n].get())
fname = asksaveasfile(mode = "w", defaultextension = ".est")
data = {"fields": len(self.field_A), "entries_A": (self.entry_A),"entries_B": (self.entry_B)}
with open(fname.name, "wb") as file:
pickle.dump(data, file)
def load(self):
def test(event):
print("Why is the value of n always equal to", n, "?")
self.field_B[n].delete(0, END)
self.field_B[n].insert(0, (float(self.field_A[n].get()))*5)
fname = askopenfilename(filetypes = (("Estimation Files (est)", "*.est"),))
location = fname.replace("/", "\\")
if location:
with open(location, "rb") as file:
data = pickle.load(file)
for n in range(data["fields"]):
self.field_A.append({})
self.field_A[n] = Entry(self)
self.field_A[n].grid(row=n, column =0)
self.field_A[n].insert(0, data["entries_A"][n])
self.field_A[n].bind("<Return>", test)
self.field_B.append({})
self.field_B[n] = Entry(self)
self.field_B[n].grid(row=n, column =1)
self.field_B[n].insert(0, data["entries_B"][n])
def __init__(self,master = None):
Frame.__init__(self, master)
self.field_A = []
self.field_B = []
self.entry_A = []
self.entry_B = []
self.grid()
self.Widgets()
root = Tk()
app = Test(master = None)
app.mainloop()
You need a "closure". You can make a closure in python with the functools.partial function.
from functools import partial
def test(n, event=None):
self.field_B[n].delete(0, END)
self.field_B[n].insert(0, (float(self.field_A[n].get()))*5)
#other code ...
self.field_A[n].bind("<Return>", partial(test, n))
Both of your test() functions are accessing a variable n from the enclosing function. In the case of add(), there is no loop; n has a single value. Each Entry's test() gets its own n, because they were bound by a distinct call to add(). In load(), however, you are looping over n values; each test() is referring to the same n, which will have its final value by the time that any binding can possibly be invoked. The other answer gives a reasonable way to give each instance of test() its own personal n, so I'm not going to repeat that here.

Python3: Edit dictionary class instance variable

class myClass:
def __init__(self):
self.myClassDict = {}
def ADD_DictPair(self, Value, Key):
#Per ShadowRanger's Solution (Solved)
#self.myClassDict[Value] = Key #Wrong Code
self.myClassDict[Key] = Value #Correct Code
def get_myDict(self):
return self.myClassDict
InstanceOfmyClass = myClass()
InstanceOfmyClass.ADD_DictPair("Value1","Key1")
InstanceOfmyClass.ADD_DictPair("Value2","Key3")
InstanceOfmyClass.ADD_DictPair("Value3","Key3")
print(InstanceOfmyClass.get_myDict()["Key1"])
Desired Output: "Value1"
Error: print(InstanceOfmyClass.get_myDict()["Key1"])
KeyError: 'Key1'
Python3 in Windows // Sublime Text 3
My goal is to interact with the dictionary through it's class method to Add, Call, and Edit values.
def ADD_DictPair(self, Value, Key):
#Per ShadowRanger's Solution (Solved)
#self.myClassDict[Value] = Key #Wrong Code
self.myClassDict[Key] = Value #Correct Code

Binding method to dynamically created labels

I have a Tkinter frame that is essentially showing a group of thumbnails, displayed in Label widgets. I need the Labels to be created dynamically to accommodate differing numbers of thumbnails to generate. I have a generated list of file names, and can create the thumbnails as needed, but when I try to bind a function to each of the created labels, it seems to be over ridden by the last created Label/Binding. The result is that only the final label has the method bound to it.
import tkinter as tk
class test(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args)
self.shell = tk.Frame(self)
self.shell.pack()
self.create_widgets()
def create_widgets(self):
'''
Two rows of labels
'''
for row in range(2):
for i in range(5):
text = 'Thumbnail\nrow{}\ncolumn{}'.format(row,i)
self.thumb = tk.Label(self.shell,
text = text)
self.thumb.grid(row = row, column = i, sticky = 'news')
self.thumb.bind("<Button-1>",lambda x: self.click(self.thumb))
def click(self, *args):
#This should affect only the Label that was clicked
print('CLICK!')
app = test()
root = app
root.mainloop()
The method being called will always be the same, but how do I identify the Label to be effected?
There are at least three solutions.
The first is the simplest: the function is passed an event object that contains a reference to the widget:
label = tk.Label(...)
label.bind("<Button-1>", self.click)
...
def click(self, event):
print("you clicked:", event.widget)
If you prefer to use lambda, you can pass the label itself to the function:
label = tk.Label(...)
label.grid(...)
label.bind("<Button-1>",lambda event, label=label: self.click(label))
Another solution is to keep a reference to every label in a list, and pass the index to the function:
self.labels = []
for row in range(2):
for i in range(5):
label = tk.Label(...)
label.grid(...)
self.labels.append(label)
index = len(self.labels)
label.bind("<Button-1>",lambda event, i=index: self.click(i))
...
def click(self, index):
print("the label is ", self.labels[index])
When you click label then tk runs function with event object which you skip using lambda x.
You need
lambda event:self.click(event, ...)
and then in click you can use event.widget to get clicked widget.
def click(event, ...);
print('Widget text:', event.widget['text'])
You had problem with self.thumb in self.click(self.thumb) because you don't know how works lambda in for loop. (and it is very popular problem :) )
lambda is "lazy". It doesn't get value from self.thumb when you declare lambda x: self.click(self.thumb) but when you click button (and you execute lambda. But when you click label then for loop is finished and self.thumb keeps last value.
You have to use this method to get correct value when you declare lambda
labda event, t=self.thumb: self,clikc(t)

tkinter listbox insert error "invalid command name ".50054760.50055432"

I have a database of objects and you can view the items in the database in a listbox and there's a button to remove an item and to create an item. Creating an item opens a dialog window for the item class and then the item's data is stored in the database. I have reproduced the problem with a very simple duplicate of my set-up (see code below).
Every time I add a new item, the addition is successful (it's there the next time I open up the database dialog), but the listbox doesn't insert the item, and when I close the database dialog I get the following error:
Exception in Tkinter callback Traceback (most recent call last):
File "C:\Python33\lib\tkinter__init__.py", line 1442, in call
return self.func(*args) File "", line 21, in addRecord File "C:\Python33\lib\tkinter__init__.py", line 2604, in insert
self.tk.call((self._w, 'insert', index) + elements)
_tkinter.TclError: invalid command name ".50054760.50055432"
The same problem doesn't come up if I just try to create the object and populate its values without invoking its inputs GUI (which is necessary for the process of inserting things into my database). I've seen a similar error in another thread (sorry, but I can't seem to find it again), where the problem was with multithreading. I'm not aware of any threading that I'm doing and don't want to download yet another package to handle tkinter threading. Any ideas? Workarounds? I'm using Python v3.3 and 64-bit Windows 7, if that helps.
Here's my simplified database code:
import tkinter
import traceback
# Test =========================================================================
class Test:
def __init__(self):
"""A database of objects' IDs and values."""
self.data = {1: 'a', 2: 'b', 3: 'c'}
#---------------------------------------------------------------------------
def addRecord(self):
"""Opens up a new item for editing and saves that ability to the
database."""
print('hi0')
newItem = OtherObject()
newItem.create(self.root)
print('hi1')
self.data[newItem.ID] = newItem.value
print('hi2')
self.listbox.insert(tkinter.END, self.formatItem(newItem.ID))
print('hi3')
#---------------------------------------------------------------------------
def delRecord(self):
"""Removes selected item from the database."""
try:
index = self.listbox.curselection()[0]
selection = self.listbox.get(index)
except:
return
ID = int(selection.split(':')[0])
self.data.pop(ID)
self.listbox.delete(index)
#---------------------------------------------------------------------------
def dataframe(self, master):
"""Assembles a tkinter frame with a scrollbar to view database objects.
(Returns Frame, Scrollbar widget, Listbox widget)
master: (Tk or Toplevel) tkinter master widget."""
frame = tkinter.Frame(master)
# scrollbar
scrollbar = tkinter.Scrollbar(frame)
scrollbar.pack(side=tkinter.LEFT, fill=tkinter.Y)
# listbox
listbox = tkinter.Listbox(frame, yscrollcommand=scrollbar.set)
listbox.pack(side=tkinter.LEFT, fill=tkinter.BOTH)
# fill listbox
for ID in self.data:
listbox.insert(tkinter.END, self.formatItem(ID))
return (frame, listbox, scrollbar)
#---------------------------------------------------------------------------
def destroyLB(self, e):
for line in traceback.format_stack():
print(line.strip())
#---------------------------------------------------------------------------
def formatItem(self, ID):
"""Creates a nice string representation of an item in the database."""
return '{0}:{1}'.format(ID, self.data[ID])
#---------------------------------------------------------------------------
def listboxSelect(self, e):
"""Manages events when the selection changes in the database interface.
e: (Event) tkinter event."""
try:
selection = self.listbox.get(self.listbox.curselection()[0])
except:
return
# set description label
ID = int(selection.split(':')[0])
self.lblstr.set(self.data[ID])
#---------------------------------------------------------------------------
def view(self):
"""Displays database interface."""
self.root = tkinter.Tk()
# listbox frame
self.frame, self.listbox, self.scrollbar = self.dataframe(self.root)
self.frame.grid(column=0, row=0)
self.listbox.bind('<<ListboxSelect>>', self.listboxSelect)
self.listbox.bind('<Destroy>', self.destroyLB)
# record display frame
self.lblstr = tkinter.StringVar()
self.lbl = tkinter.Label(self.root, textvariable=self.lblstr)
self.lbl.grid(column=1, row=0, sticky=tkinter.N)
# buttons frame
self.frame_btn = tkinter.Frame(self.root)
self.frame_btn.grid(row=1, columnspan=2, sticky=tkinter.E+tkinter.W)
# 'create new' button
self.btn_new = tkinter.Button(
self.frame_btn, text='+', command=self.addRecord)
self.btn_new.grid(row=0, column=0)
# 'delete record' button
self.btn_del = tkinter.Button(
self.frame_btn, text='-', command=self.delRecord)
self.btn_del.grid(row=0, column=1)
# display
self.root.mainloop()
# Test =========================================================================
# OtherObject ==================================================================
class OtherObject:
"""An object with an ID and value."""
def __init__ (self):
self.ID = 0
self.value = ''
#---------------------------------------------------------------------------
def create(self, master=None):
"""open a dialog for the user to entry a new object ID and value.
master: (Tk or Toplevel) tkinter master widget."""
self.stuff = tkinter.Toplevel(master)
# ID
tkinter.Label(self.stuff, text='ID: ').grid(row=0, column=0)
self.IDvar = tkinter.StringVar(self.stuff)
self.IDvar.set(self.ID)
IDwidget = tkinter.Entry(self.stuff, textvariable=self.IDvar)
IDwidget.grid(row=0, column=1)
# value
tkinter.Label(self.stuff, text='Value: ').grid(row=1, column=0)
self.valueVar = tkinter.StringVar(self.stuff)
self.valueVar.set(self.value)
valueWidget = tkinter.Entry(self.stuff, textvariable=self.valueVar)
valueWidget.grid(row=1, column=1)
# OK button
tkinter.Button(self.stuff, text='OK', command=self.OK).grid(row=2)
self.stuff.mainloop()
#---------------------------------------------------------------------------
def OK(self):
try: self.ID = int(self.IDvar.get())
except: self.ID = 0
self.value = self.valueVar.get()
self.stuff.destroy()
# OtherObject ==================================================================
Thanks in advance
You are creating more than one instance of Tk. Tkinter is not designed to work like that and you will get unexpected behavior. You need to refactor your code so that you create an instance of Tk only once. If you need multiple windows, create instances of Toplevel.
... time passes ... the code in the question gets updated ...
In the updated version of your question you now are creating one instance of Tk, and then instances of Toplevel. This is good. However, you are also calling mainloop more than once which is a problem. Worse, you're redefining self.root which no doubt is part of the problem. You must call mainloop exactly once over the entirety of your program.
Bryan Oakley guided me to the exact problem that I was encountering:
(1) I created a second Tk object in my OtherObject class.
(2) I called mainloop in my OtherObject class.
Only one Tk object should exist and mainloop should only ever be called once, no matter how many windows are to be displayed.

Resources