I'm trying to update a label based on a button press that does some calculations first, but I'm getting an error I don't understand.
Background: This method was working fine in previous iterations. I have not changed the code. But I did make some changes to the class by adding a controller to the initial instantiation of the ShelfDownloader class, which these methods belong to. To clarify ShelfDownloader is only called once from a different module.
Description: On the initial call of this class, it displays the self.total_books_label correctly, but when I switch shelves it chokes. UPDATE: It seems I am passing a string, but I in the comments I posted what I tried and I still don't understand the error. See the second error:
Question: Since that's probably not the problem, I'm thinking one of my calls is missing something. Any suggestions please?
I removed the widget formatting for this question. I included these bits of code to help with the Error message.
class ShelfDownloader(ctk.CTkFrame):
def __init__(self, parent, controller):
ctk.CTkFrame.__init__(self, parent)
self.parent = parent
self.controller = controller
...
def shelf_option_callback(self, parent, *args):
if not self.downloader:
sys.exit()
shelf_choice = [s for s in SHELF_METADATA if self.shelf_choice_var.get() in s['shelf_name']][0]
for k, v in shelf_choice.items():
self.downloader_dict[k] = v
updated_dict = self.downloader.update_shelf(**self.downloader_dict)
for k, v in updated_dict.items():
self.downloader_dict[k] = v
self.display_total_books_and_pages(parent)
def draw_top_panel(self):
parent = self.top_frame
LL1 = ctk.CTkLabel(parent, text="Choose shelf to download:")
# LL1.pack()
shelf_opt_menu = ctk.CTkOptionMenu(master=parent, width=170, variable=self.shelf_choice_var,
values=self.shelf_list,
command= lambda: self.shelf_option_callback(parent)
## ^^^^^^^^^^^^^^^^^^^^^^^^^^^
## This is my problem spot. Tried adding
## command=self.shelf_option_callback(parent)
## but different error.
shelf_opt_menu.pack()
def display_total_books_and_pages(self, parent):
if hasattr(self, 'total_books_label'):
self.total_books_label.destroy()
## GUI feedback info, partially a debugging tool
_books, _pages = (self.downloader_dict['total_book_count'], self.downloader_dict['total_page_count'])
self.display_text.set(f"Shelf has {_books} books, retrieving {_pages} pages.")
logger.debug(f"{self.display_text.get()}")
logger.debug(f"display_total_books_and_pages: {type(self.display_text)}")
self.total_books_label = ctk.CTkLabel(master=parent, width=180,
text=f"{self.display_text.get()}")
self.total_books_label.pack()
My debugging returns correct and expected output after I switch the shelf, ie shelf_opt_menu:
2022-12-12 12:05:22,612, gui_download, 183: Shelf has 1624 books, retrieving 17 pages.
2022-12-12 12:05:22,612, gui_download, 184: display_total_books_and_pages: <class 'tkinter.StringVar'>
I get an error message that I'm passing a string in the code above. UPDATE: I trimmed the full error out since I confirmed that's what is happening with logger.debug(f"{type(parent)}")
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\megha\AppData\Local\Programs\Python\Python311\Lib\tkinter\__init__.py", line 1948, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
...
File "c:\MyProjects\gr_shelf_tools\src\gui_downloader.py", line 124, in shelf_option_callback
self.display_total_books_and_pages(parent)
File "c:\MyProjects\gr_shelf_tools\src\gui_downloader.py", line 184, in display_total_books_and_pages
self.total_books_label = ctk.CTkLabel(master=parent, width=180, text=f"{self.display_text.get()}")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
File "C:\Users\megha\AppData\Local\Programs\Python\Python311\Lib\tkinter\__init__.py", line 2591, in _setup
self.tk = master.tk
^^^^^^^^^
AttributeError: 'str' object has no attribute 'tk'
Error 2 (when I change it to a lambda call):
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\megha\AppData\Local\Programs\Python\Python311\Lib\tkinter\__init__.py", line 1948, in __call__
return self.func(*args)
^^^^^^^^^^^^^^^^
File "C:\Users\megha\AppData\Local\Programs\Python\Python311\Lib\site-packages\customtkinter\windows\widgets\core_widget_classes\dropdown_menu.py", line 101, in <lambda>
command=lambda v=value: self._button_callback(v),
^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\megha\AppData\Local\Programs\Python\Python311\Lib\site-packages\customtkinter\windows\widgets\core_widget_classes\dropdown_menu.py", line 106, in _button_callback
self._command(value)
File "C:\Users\megha\AppData\Local\Programs\Python\Python311\Lib\site-packages\customtkinter\windows\widgets\ctk_optionmenu.py", line 381, in _dropdown_callback
self._command(self._current_value)
TypeError: ShelfDownloader.draw_top_panel.<locals>.<lambda>() takes 0 positional arguments but 1 was given
I wasn't utilizing the StringVar, self.display_text, properly. I fixed it. Here's how: I set a trace in the callback shelf_option_callback. I call self.set_display_text from the trace to change the value of self.display_text. I moved the Label creation to draw_top_frame(self) so that it always resides in the desired position and added the option textvariable=self.display_text to the Label. This did the trick.
The Label rewrites new values in the same spot when the shelf_opt_menu OptionMenu changes.
def shelf_option_callback(self, *args):
parent = self.top_frame
if not self.downloader:
sys.exit()
shelf_choice = self.get_shelf_metadata() # a dictionary of values
self.update_dictionary(**shelf_choice) # calculates new page counts based on the shelf
self.display_text.trace('w', self.set_display_text())
def draw_top_panel(self):
## Dropbox: shelf menu
parent = self.top_frame
self.initialize_downloader() # sets up a dictionary of defaults
LL1 = ctk.CTkLabel(parent, text="Choose shelf to download:")
LL1.pack(anchor=tk.NW, ipadx=10, padx=(20, 10))
shelf_opt_menu = ctk.CTkOptionMenu(master=parent, width=170,
variable=self.shelf_choice_var,
values=self.shelf_list,
command=self.shelf_option_callback)
shelf_opt_menu.pack()
## Next option menu: download increment choices
## code here ...
self.set_display_text()
self.total_books_label = ctk.CTkLabel(master=self.top_frame, width=180,
textvariable= self.display_text,
text=f"{self.display_text.get()}")
self.total_books_label.pack()
## A couple of RadioButtons
## code here
def set_display_text(self, *args):
_books, _pages = (self.downloader_dict['total_book_count'], self.downloader_dict['total_page_count'])
self.display_text.set(f"Shelf has {_books} books, retrieving {_pages} pages.")
To simplify, although in this example the choices aren't really linked to anything.
def reset_text_var(*args):
new_text = "String from somewhere"
text_var.set(new_text)
def callback(*args):
print("made it?")
text_var.trace('w', reset_text_var())
root = tk.Tk()
text_var = tk.StringVar()
parent = tk.Frame(root)
parent.pack()
choices = ['Choice 1', 'Choice 2', 'Choice 3']
text_var.set("You haven't picked anything yet")
opt_menu = tk.OptionMenu(parent, text_var, *choices, command=callback)
a_label = tk.Label(parent, textvariable=text_var, text=f"Your new string: {text_var.get()}")
opt_menu.pack()
a_label.pack()
root.mainloop()
Related
I'm having a problem with this code:
from tkinter import *
class app:
def create(arrSettings):
proot = Toplevel()
proot.title("Settings")
m = Frame(proot).pack() #Some Frames so I can arrange them how I'd like to
mcan = Canvas(proot)
mcan.pack(fill="both", side="left")
x = Frame(proot).pack()
xcan = Canvas(proot)
xcan.pack(fill="both", expand="yes", side="left")
win_0 = Frame(xcan)
lbl_0 = Label(win_0, text="Option0").pack()
txt_0 = Text(win_0).pack()
win_0.pack()
win_1 = Frame(xcan)
lbl_1 = Label(win_1, text="Option1").pack()
txt_1 = Text(win_1).pack()
win_1.pack()
btn_menu0 = Button(mcan, text="Menu0", command=app.func_btn_menu0).pack()
btn_menu1 = Button(mcan, text="Menu1", command=app.func_btn_menu1).pack()
def func_btn_menu0():
lbl_0.config(text="foo") # <-- Problem
txt_0.insert("end", "bar") # <-- Problem
def func_btn_menu1():
pass
(I left the code for the design(bg, border, ...) out)
This is another window which will be started by the main one.
It shows some buttons on the left and some labels and textboxes on the right.
Whenever a button on the left has been pushed the text of the labels should be changed.
That's the problem: When I push a button I get this error and the text won't be changed:
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python3.4/tkinter/__init__.py", line 1536, in __call__
return self.func(*args)
File "/[...]/program.py", line 27, in colormain
lbl_0.config(text="Background")
NameError: name 'lbl_0' is not defined
I don't really understand why this gives me an error so I'd like to ask you.
This code is being started from the main window with the code:
program.app.create(arrSettings) #arrSettings is an array in which some colors for the design are
Thanks in advance.
Do not declare and pack in the same line
Return of this peice of code is None
Label(win_0, text="Option0").pack()
whereas, this returns an object of Label class
Label(win_0, text="Option0")
so use:-
lbl_0 = Label(win_0, text="Option0")
lbl_0.pack()
instead of
lbl_0 = Label(win_0, text="Option0").pack()
Also use self object as argument to functions. Check that the variables are in scope wherever you are using it.
This should help you get through this error...
I want to add data to Access by writing it in form of tkinter but I have a mistake. What's wrong? I tried to change the place of con.close () but it doesn't help, I even have another mistake if I put it before def
from tkinter import *
import pypyodbc
import ctypes
form=Tk ()
form.title ("Add data")
form.geometry ('400x200')
#Create connection
con = pypyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};UID=admin;UserCommitSync=Yes;Threads=3;SafeTransactions=0;PageTimeout=5;MaxScanRows=8;MaxBufferSize=2048;FIL={MS Access};DriverId=25;DefaultDir=C:/Users/HP/Desktop/PITL;DBQ=C:/Users/HP/Desktop/PITL/PITL.mdb;')
cursor = con.cursor ()
a = Entry (form, width=20, font="Arial 16")
a.pack ()
b = Entry (form, width=20, font="Arial 16")
b.pack ()
def Add (event):
cursor.execute ("INSERT INTO Crime (`Number_of_article`, `ID_of_criminal`) VALUES (?, ?)", (a, b))
Button=Button(form, text = 'PUSH ME')
Button.pack ()
Button.bind ('<Button-1>', Add)
form.mainloop ()
con.commit ()
cursor.close ()
con.close ()
The mistake is:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\HP\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 1699, in __call__
return self.func(*args)
File "C:\Users\HP\Desktop\PITL\ADD DATA.py", line 19, in Add
cursor.execute ("INSERT INTO Crime (`Number_of_article`, `ID_of_criminal`) VALUES (?, ?)", (a, b))
File "C:\Users\HP\AppData\Local\Programs\Python\Python36-32\lib\site-packages\pypyodbc-1.3.4-py3.6.egg\pypyodbc.py", line 1491, in execute
self._BindParams(param_types)
File "C:\Users\HP\AppData\Local\Programs\Python\Python36-32\lib\site-packages\pypyodbc-1.3.4-py3.6.egg\pypyodbc.py", line 1296, in _BindParams
if param_types[col_num][0] == 'u':
TypeError: 'type' object is not subscriptable
Assuming that your SQL input is working then the tkinter side of this isn't too tricky.
For a start, it's probably easier to do this inside of a class to make variable passing easier.
You also don't need to bind to the button you can add a command attribute to it instead.
See my code example below:
from tkinter import *
class App:
def __init__(self, root):
self.root = root
#create our entry boxes
self.entry1 = Entry(self.root)
self.entry2 = Entry(self.root)
#create out button, instead of binding we associate a command to it
self.button = Button(self.root, text="Insert", command=self.command)
#pack our objects
self.entry1.pack()
self.entry2.pack()
self.button.pack()
def command(self):
#this command outputs the results of the two entry boxes
#so this is where you'd implement your database entry
print("Insert "+self.entry1.get()+" & "+self.entry2.get())
#above, *object*.get() is used to return the value of the object rather than the object itself
root = Tk()
App(root)
root.mainloop()
I'm trying to print the value xf_in which is entered in the GUI.
However, I get the following error message when i press the run button:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\My_Name\Anaconda3\lib\tkinter\__init__.py", line 1699, in __call__
return self.func(*args)
File "C:/Users/My_Name/Python Scripts/test/gui.py", line 6, in EP
xf_In = tk.get(e_xf)
AttributeError: module 'tkinter' has no attribute 'get'
I've tried to find the source of the error online but to no avail.
Thanks in advance for any help
My code is as follows:
import tkinter as tk
from PIL import ImageTk as imtk
from PIL import Image as im
def EP(): # Enter inputs from values typed in
xf_In = tk.get(e_xf)
print(xf_In)
root = tk.Tk()
l_xf = tk.Label(root, text="xA of Feed").grid(row=0)
e_xf = tk.Entry(root).grid(row=0, column=1)
run = tk.Button(root, text="Run", command=EP).grid(row=8, column=0, columnspan = 2)
img = imtk.PhotoImage(im.open("x.png"))
panel = tk.Label(root, image = img).grid(row = 0, column = 2, rowspan = 7)
root.mainloop()
As the error message indicates, the tk module does not have a function named get. It might have plenty of classes whose instances have a get method, but you can't access them the way you're doing.
If you're trying to get the contents of the Entry, you should assign it to a name, and call get on that instead:
def EP(): # Enter inputs from values typed in
xf_In = e_xf.get(e_xf)
print(xf_In)
#...
e_xf = tk.Entry(root)
e_xf.grid(row=0, column=1)
Note that this assignment is different from doing e_xf = tk.Entry(root).grid(row=0, column=1). If you do that, then e_xf will be bound to the return value of grid, rather than the Entry instance. grid returns None, so trying to call get on that would only give you an AttributeError. Related reading: Why do my Tkinter widgets get stored as None?
I have only done a little work with Tkinter and I enjoy using it but as with any type programing it takes time to learn. I am trying to create a simple To do list that will eventually be saved on a file. But i can't get the button in line 17 to be removed and the on the next line be replace in a different position.
from tkinter import *
import time
root = Tk()
root.geometry("300x300")
root.title("Programs")
global TDrow
TDrow = 2
def tdTaskAdd():
global TDrow
global tdEnter
TDrow = int(TDrow+1)
s = tdEntry.get()
label = Label(ToDoFrame,text=s).grid(row=TDrow,column=1)
tdEntry.grid(row=TDrow+1,column=1)
tdEnter.grid_remove()
tdEnter = Button(ToDoFrame,text="AddTask",command=tdTaskAdd).grid(row=TDrow+2,column=1)
ToDoFrame = Frame()
ToDoFrame.place(x=0,y=10)
tdTitle = Label(ToDoFrame,text="To Do List:").grid(row=TDrow-1,column=1)
tdEntry= Entry(ToDoFrame)
tdEntry.grid(row=TDrow+1,column=1)
tdEntry.insert(0, "Enter a new task")
global tdEnter
tdEnter = Button(ToDoFrame,text="Add Task",command=tdTaskAdd).grid(row=TDrow+2,column=1)
mainloop()
I keep getting an error when running this saying that:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python33\lib\tkinter\__init__.py", line 1475, in __call__
return self.func(*args)
File "C:\Users\Eddy\Desktop\pythonStartup.py", line 17, in tdTaskAdd
tdEnter.grid_remove()
AttributeError: 'NoneType' object has no attribute 'grid_remove'
The problem is this line:
tdEnter = Button(ToDoFrame,text="Add Task",command=tdTaskAdd).grid(row=TDrow+2,column=1)
This way, tdEnter is not the Button, but the return value of grid, i.e. None.
Try this instead:
tdEnter = Button(ToDoFrame,text="Add Task",command=tdTaskAdd)
tdEnter.grid(row=TDrow+2,column=1)
Same for label and when you create a new button in your tdAddTask function.
BTW, no need to add a new button each time, just call it's grid method to repositon it.
Okay so here is my problem. I am trying to create a very open ended user friendly Gui out of Tkinter. In short I made a button a function STAGE that creates a Listbox that has choose-able indexes. Then I can press the button SUBMIT that will print the selected keys.
BUT if I press ADD STAGE to make another listbox of the same value I CANNOT go back and edit or retrieve the selected values of the old listbox.
I understand that this is because of the listbox will have the same name so.....
from Tkinter import *
import tkMessageBox
class Insert_page(Frame):
global i0
global listbox
global Tech_option
Tech_option=['Rolled Plate','extrude bar','as-cast','wire','Other','USER Details']
i0=-1
def __init__(self,parent):
Frame.__init__(self,parent,background="white")
self.parent=parent
self.initUI()
def initUI(self):
self.parent.title("material Gui v2")
self.grid(row=1,column=1)
mButton=Button(self,text='Start Stages',command=self.stage).grid(row=2,column=5,sticky=W)
mButton3=Button(self,text='submit',command=self.submit).grid(row=9,column=1,sticky=W)
def stage(self): ######################HERE IS THE PROBLEM#########
global i0
i0+=1
stageFrame=Frame(self,bd=1,bg='red',relief=SUNKEN)
stageFrame.grid(row = 1+5*i0, column = 1, rowspan = 5, columnspan = 7, sticky = W+E+N+S)
stageVar = StringVar()
OPTIONS = [""]+range(0,10)
w = apply(OptionMenu, (stageFrame, stageVar) + tuple(OPTIONS))
w.grid(row=1,column=1)
stageLabel=Label(stageFrame,text='Stage')
stageLabel.grid(row=1+5*i0,column=0,sticky=W)
mButton=Button(stageFrame,text='add Stage',command=self.stage).grid(row=9,column=1,sticky=W)
listbox = Listbox(stageFrame,selectmode= MULTIPLE,exportselection=False) ######REPLACED CODE######
listbox.grid(row=3+5*i0,column=2)
for item in Tech_option :
listbox.insert(END, item)
mButton3=Button(self,text='submit',command=lambda: self.submit(listbox,Tech_option)).grid(row=9+5*i0,column=1,sticky=W)
######REPLACED CODE######
def submit(self,lb,option_list):
Tech_select=[]
for i in list(lb.curselection()):
Tech_select.append(Tech_option[int(i)])
print Tech_select
def main():
mGui= Tk()
mGui.geometry('800x600+200+200')
menubar=Menu(mGui)
filemenu=Menu(menubar,tearoff=0)
filemenu.add_command(label="New")
filemenu.add_command(label="Open")
filemenu.add_command(label="SaveAs...")
filemenu.add_command(label="Close")
menubar.add_cascade(label='File',menu=filemenu)
mGui.config(menu=menubar)
app=Insert_page(mGui)
mGui.mainloop()
main()
I replaced it with this...
exec ('listbox_%s = Listbox(stageFrame,selectmode= MULTIPLE,exportselection=False)' % (i0)) in globals(), locals()
exec ('listbox_%s.grid(row=3+5*i0,column=2)' % (i0)) in globals(), locals()
for item in Tech_option :
exec ('listbox_%s.insert(END, item)' % (i0)) in globals(), locals()
mButton3=Button(self,text='submit',command=lambda: self.submit(eval('listbox_%s'%(i0)),Tech_option)).grid(row=9+5*i0,column=1,sticky=W)
What it should do is create NEW listbox variables but all I get back when pressing submit is.
Traceback (most recent call last):
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1413, in __call__
return self.func(*args)
File "/home/xxxxxx/pythons/xxxxxxxx.py", line 78, in <lambda>
mButton3=Button(self,text='submit',command=lambda: self.submit(eval('listbox_%s'%(i0)),Tech_option)).grid(row=9+5*i0,column=1,sticky=W)
File "<string>", line 1, in <module>
NameError: name 'listbox_1' is not defined
If someone could help me with this that would be great.
You need list of listbox to keep all lists from all stages.
Now in listbox you have only list from last created stage.
You will have to use lines like this (in different places)
self.all_listboxes = []
#---
self.all_listboxes.append( listbox )
#---
for one_list in self.all_listboxes:
for x in one_list.curselection():
BTW: use self.listbox (in __init__) in place of global listbox.
The same with i0 and Tech_option.
We use class and self to not use global