Part of a GUI program I'm building needs to be able to convert times given into seconds. The frame class in my GUI that does this is giving me some trouble. I created an instance variable of type combobox to hold the options for types of time period to convert. I bound the combobox selection to convert the input time into seconds. I want to tie entering values into the entry box to doing the same thing. I tried to call my conversion function in my validation command function for the entry box, but it's telling me that my frame object "PERIODIC" doesn't have an attribute "period_type". I'm confused because I named the combobox as an instance variable, and it should be accessible to everything in the class. "self.period_type" is right there in my init. Why can't I access this variable? Am I missing something painfully obvious?
The Traceback I'm getting is:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\4D_User\AppData\Local\Programs\Python\Python38-32\lib\tkinter\__init__.py", line 1883, in __call__
return self.func(*args)
File "C:/Users/PycharmProjects/LoggerProject/Scripts/ProblemExample.py", line 37, in ValidateTime
self.convert_time(self.period_type.get())
AttributeError: 'PERIODIC' object has no attribute 'period_type'<
This is my code:
from tkinter import ttk
import re
root = tk.Tk()
class PERIODIC(tk.Frame):
def __init__(self, container, **kwargs):
super().__init__(container, **kwargs)
self.time_unconverted = tk.DoubleVar()
self.time_converted = tk.DoubleVar()
self.periodic_label = tk.Label(self, text="PERIODIC")
self.periodic_label.grid(row=0, columnspan=2, sticky="NSEW")
trigger_option_label = ttk.Label(self, text="Trigger Every: ")
trigger_option_label.grid(row=1, column=0)
vcmd = (self.register(self.ValidateTime), '%P')
self.num_period = tk.Entry(self,textvariable=self.time_unconverted, validate="key", validatecommand=vcmd)
self.num_period.grid(row=1, column=1)
self.period_type = ttk.Combobox(self, values=["seconds", "minutes", "hours", "days"])
self.period_type.bind("<<ComboboxSelected>>", lambda y: self.convert_time(self.period_type.get()))
self.period_type.grid(row=1, column=2)
def convert_time(self, type):
if type == 'seconds':
self.time_converted.set(self.time_unconverted.get())
if type == 'minutes':
self.time_converted.set(self.time_unconverted.get() * 60)
if type == 'hours':
self.time_converted.set(self.time_unconverted.get() * 3600)
if type == 'days':
self.time_converted.set(self.time_unconverted.get() * 86400)
def ValidateTime(self, P):
test = re.compile('^[0-9]{1,3}?\.?[0-9]?$')
if test.match(P):
self.convert_time(self.period_type.get())
return True
else:
return False
frame = PERIODIC(root)
frame.grid(row=0,column=0)
root.mainloop()
Your validation command is being triggered when you create the entry, and it's using self.period_type which hasn't been defined yet.
The simple solution is to move the creation of the combobox prior to creating the entry.
Related
I've made a tk.Toplevel class to get a date from the user. After the user clicked the date, the window is closing and the date should return to the mainprocess.
When the tk.Toplevel is closed I've got the date, but also an error:
\_tkinter.TclError: bad window path name ".!kalender.!dateentry.!toplevel"
What did I do wrong?
class Kalender(tk.Toplevel):
def __init__(self, parent, date=''):
Toplevel.__init__(self, parent)
# Fenster mittig zentrieren
x = (self.winfo_screenwidth() // 2) - (100 // 2)
y = (self.winfo_screenheight() // 2) - (50 // 2)
self.grab_set()
self.geometry('{}x{}+{}+{}'.format(180, 90, x, y))
self.attributes('-toolwindow', True)
self.title('Datum auswählen')
self.resizable(width=False, height=False)
self.date = None
self.sel = StringVar()
self.cal = DateEntry(self, font="Arial 14", selectmode='day', locale='de_DE', date_pattern="dd.mm.y ",
textvariable=self.sel)
self.cal.bind("<<DateEntrySelected>>", self.close_window)
self.cal.set_date(date)
self.cal.grid(row=0, column=0, padx=10, pady=10, sticky=W+E)
self.focus_set()
def close_window(self, e):
self.date = self.cal.get()
self.destroy()
def show(self):
self.deiconify()
self.wm_protocol("WM_DELETE_WINDOW", self.close_window)
self.wait_window()
return self.date
cal = Kalender(main_window, d).show()
I've got the following error:
Exception in Tkinter callback
Traceback (most recent call last):
File "B:\Python 310\lib\tkinter\__init__.py", line 1921, in __call__
return self.func(*args)
File "B:\Python 310\lib\site-packages\tkcalendar\dateentry.py", line 301, in _select
self._top_cal.withdraw()
File "B:\Python 310\lib\tkinter\__init__.py", line 2269, in wm_withdraw
return self.tk.call('wm', 'withdraw', self._w)
_tkinter.TclError: bad window path name ".!kalender.!dateentry.!toplevel"
It seems, that tkinter tries to call the kalender.dateentry after it has been destroyed.
It is because when the user has selected a date in the pop-up calendar, the bind function self.close_window() will be executed and the toplevel is destroyed (so is the DateEntry widget). Then DateEntry widget closes the pop-up calendar which raises the exception.
To fix this, you can delay the execution of self.close_window() a bit so that it is executed after the pop-up calendar is closed using after():
self.cal.bind("<<DateEntrySelected>>", lambda e: self.after(10, self.close_window, None))
I am not 100% sure about this, but it seems the that the tkcalendar module has some trouble forcing a destroy on the parent widget through a bind on the DateEntry class. You can try using the withdraw command instead, which hides the window rather than destroying it
def close_window(self, e):
self.date = self.cal.get()
self.withdraw()
I am using Tkinter in a basic Python 2.7 GUI application and I would like to retrieve the Checkbutton widget status (checked/unchecked) by using a IntVar but I am getting the following error.
TclError: can't read "PY_VAR": no such variable
I have followed the example on effbot about the Checkbutton Widget and I am using a different IntVar for each button and using a callback function to print the variable by calling the getvar function on the buttons.
My only goal is to view a status of the Checkbutton widget.
I am using the Tkinter Grid Geometry manger to place the widgets on the GUI. Here is a MCVE example that produces the error.
#!/usr/bin/env python
import Tkinter as tk
class Frame(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.buttons = dict() #number to button widget
self.createWidgets(master)
def printvar(self, button_number):
print self.buttons[button_number].getvar()
def createWidgets(self,master):
for n in range(0,4):
var = tk.IntVar()
button = tk.Checkbutton(
master,
variable=var,
command=lambda bn=n: self.printvar(bn)
)
button.grid(row=0, column=n)
self.buttons[n] = button
window = Frame(tk.Tk())
window.mainloop()
The code makes four numbered buttons passing the number to a lambda function which looks up the button in a dict and calls its corresponding getvar function.
If you run the example code it will produce the following error when you check any of the four buttons. It is in a file named tktest.py
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1540, in __call__
return self.func(*args)
File "./tktest.py", line 20, in <lambda>
command=lambda bn=n: self.printvar(bn)
File "./tktest.py", line 12, in printvar
print self.buttons[button_number].getvar()
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 513, in getvar
return self.tk.getvar(name)
TclError: can't read "PY_VAR": no such variable
There is no var variable for each button and I am therefore calling the getvar method.
Any alternative methodology for checking each individual Checkbutton widget status would also be acceptable.
EDIT: First create n number of var for use and than asign for each button and save in a list.
def createWidgets(self,master):
self.vars=[]
for n in range(0,4):
var = tk.IntVar()
self.vars.append(var)
for n in range(0,4):
button = tk.Checkbutton(
master,
variable=self.vars[n],
command=lambda bn=n: self.printvar(bn)
)
button.grid(row=0, column=n)
self.buttons[n] = button
and then you can change your method to this for call the var for the number
def printvar(self, button_number):
print "The button:{} is {}".format(button_number,self.vars[button_number].get())
The output for example is:
The button:2 is 0
The button:2 is 1
when 0 is unchecked and 1 checked
The goal of this program is to generate a sine wave using a 12 bit DAC. The code is as follows:
from Tkinter import *
import smbus, time, math, random
bus = smbus.SMBus(1)
address = 0x60
t=time.time()
class RPiRFSigGen:
# Build Graphical User Interface
def __init__(self, master):
self.start
frame = Frame(master, bd=10)
frame.pack(fill=BOTH,expand=1)
# set output frequency
frequencylabel = Label(frame, text='Frequency (Hz)', pady=10)
frequencylabel.grid(row=0, column=0)
self.frequency = StringVar()
frequencyentry = Entry(frame, textvariable=self.frequency, width=10)
frequencyentry.grid(row=0, column=1)
# Start button
startbutton = Button(frame, text='Enter', command=self.start)
startbutton.grid(row=1, column=0)
def start(self):
#self.low_freq=IntVar
low_freq = float(self.frequency.get())
out = 4095/2 + (math.sin(2*math.pi*low_freq*t))
#out = math.floor(out)
int(math.floor(out))
print (out)
bus.write_byte_data(address,0,out)
sendFrequency(low_freq)
# Assign TK to root
root = Tk()
# Set main window title
root.wm_title('DAC Controller')
root.geometry('250x150+650+250')
# Create instance of class RPiRFSigGen
app = RPiRFSigGen(root)
# Start main loop and wait for input from GUI
root.mainloop()
When I run the program I receive the following error after the value "out" is printed:
2046.18787764
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1437, in __call__
return self.func(*args)
File "/home/pi/DAC Controller.py", line 40, in start
bus.write_byte_data(address,0,out)
TypeError: integer argument expected, got float
It would appear that int(math.floor(out)) is not converting out to an integer because "out" is being printed as float still. Any suggestions?
int(math.floor(out))
This will create an integer version of out, but you aren't assigning it to anything, so it just gets discarded. If you want the changes to be reflected in the value of out, try:
out = int(math.floor(out))
I'm developing a package with GUI using tkinter. Now there is a problem when communicating classes via tkinter's bind method. A simple code which represents what I want to do is listed below:
import Tkinter as tk
lists = [1,2,3,4,5,6,7]
class selects():
def __init__(self,root):
self.root = root
self.selectwin()
def selectwin(self):
""" listbox and scrollbar for selection """
sb = tk.Scrollbar(self.root)
lb = tk.Listbox(self.root, relief ='sunken', cursor='hand2')
sb.config(command=lb.yview)
sb.pack(side=tk.RIGHT, fill=tk.Y)
lb.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
lb.config(yscrollcommand=sb.set, selectmode='single')
for value in lists: lb.insert(tk.END,value)
lb.bind('<Double-1>',lambda event: self.getvalue())
self.listbox = lb
def getvalue(self):
""" get the selected value """
value = self.listbox.curselection()
if value:
self.root.quit()
text = self.listbox.get(value)
self.selectvalue = int(text)
def returnvalue(self):
return self.selectvalue
class do():
def __init__(self):
root = tk.Tk()
sl = selects(root)
# do something... for example, get the value and print value+2, as coded below
value = sl.returnvalue()
print value+2
root.mainloop()
if __name__ == '__main__':
do()
class selects adopt Listbox widget to select a value in lists and return the selected value for use via attribute returnvalue. However, error is raised when running the above codes:
Traceback (most recent call last):
File "F:\Analysis\Python\fpgui\v2\test2.py", line 47, in <module>
do()
File "F:\Analysis\Python\fpgui\v2\test2.py", line 41, in __init__
value = sl.returnvalue()
File "F:\Analysis\Python\fpgui\v2\test2.py", line 32, in returnvalue
return self.selectvalue
AttributeError: selects instance has no attribute 'selectvalue'
I think this error can be solved by combining classes selects and do together as a single class. But in my package, class selects will be called by several classes, so it is better to make selects as a standalone class. Further, communications between classes like this will be frequently applied in my package. For example, do something after picking some information in matplotlib figure using pick_event, or update a list in one class after inputting texts in another class using Entry widget. So, any suggestion about this? Thanks in advance.
You're calling sl.returnvalue() right after having created sl. However, at this point sl.getvalue() has never been called, which means that sl.selectvalue does not yet exist.
If I understand what you want to do correctly, you should move the call to root.mainloop() to right after the creation of sl (sl = selects(root)). This way, Tk hits the mainloop, which runs until the window is destroyed, which is when the user double-clicks one of the values. Then, sl.getvalue() has been run and the program can continue with calling sl.returnvalue() without errors.
Since you are not actually calling the mainloop in that part of the code, I've altered your code to reflect that and still work as you want it to. A key method in this is wait_window, which halts execution in a local event loop until the window is destroyed. I've used this effbot page on Dialog Windows for reference:
import Tkinter as tk
lists = [1,2,3,4,5,6,7]
class selects():
def __init__(self,root):
self.root = root
self.selectwin()
def selectwin(self):
""" listbox and scrollbar for selection """
sb = tk.Scrollbar(self.root)
lb = tk.Listbox(self.root, relief ='sunken', cursor='hand2')
sb.config(command=lb.yview)
sb.pack(side=tk.RIGHT, fill=tk.Y)
lb.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
lb.config(yscrollcommand=sb.set, selectmode='single')
for value in lists: lb.insert(tk.END,value)
lb.bind('<Double-1>',lambda event: self.getvalue())
self.listbox = lb
def getvalue(self):
""" get the selected value """
value = self.listbox.curselection()
if value:
self.root.quit()
text = self.listbox.get(value)
self.selectvalue = int(text)
self.root.destroy() # destroy the Toplevel window without needing the Tk mainloop
def returnvalue(self):
return self.selectvalue
class do():
def __init__(self, master):
self.top = tk.Toplevel()
self.top.transient(master) # Make Toplevel a subwindow ow the root window
self.top.grab_set() # Make user only able to interacte with the Toplevel as long as its opened
self.sl = selects(self.top)
self.top.protocol("WM_DELETE_WINDOW", self.sl.getvalue) # use the if value: in getvalue to force selection
master.wait_window(self.top) # Wait until the Toplevel closes before continuing
# do something... for example, get the value and print value+2, as coded below
value = self.sl.returnvalue()
print value+2
if __name__ == '__main__':
root = tk.Tk()
d = do(root)
root.mainloop()
I am not familiar with Python scripting. I am simply trying to create a small GUI that will allow me to alter a parameter in an ArcGIS Model using the Tkinter Scale and then run the model by clicking a TKinter Button. (The code below is not finished and is being used for testing purposes, hence the button does not actually call a different script yet).
I get this TypeError in the Traceback:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python27\ArcGIS10.1\lib\lib-tk\Tkinter.py", line 1410, in __call__
return self.func(*args)
TypeError: GetScale() takes no arguments (2 given)
I have tried deleting many different things from my code to determine what these arguments might be but to no avail. It is incredibly frustrating as I am sure it is the most simple problem.
My code is as follows:
import Tkinter
from Tkinter import *
class createBuffer(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
def initialize(self):
self.grid()
#Define variable for text field
self.entryVariable = Tkinter.StringVar()
self.entry = Tkinter.Entry(self, textvariable=self.entryVariable)
self.entry.grid(column=0,row=0,sticky='EW')
self.entry.bind("<Return>", self.OnPressEnter)
#Set default text field text
self.entryVariable.set(u"Enter desired radius here.")
button = Tkinter.Button(self,text=u"Create Buffer",
command = self.OnButtonClick)
button.grid(column=1, row=0)
scale = Tkinter.Scale(self, variable = StringVar, orient = HORIZONTAL, from_ = 0, to = 100, resolution = 10, width = 30, length = 300, command = self.GetScale)
scale.grid(row = 4)
scale.set(50)
#Create a Tkinter variable and bind it to a widget
self.labelVariable = Tkinter.StringVar()
label = Tkinter.Label(self, textvariable = self.labelVariable,
anchor = "w", fg = "black", bg = "white")
label.grid(column=0, row=1, columnspan=2, sticky="EW")
self.labelVariable.set(u"Radius:")
self.grid_columnconfigure(0, weight=1)
self.resizable(True,False)
#Fix the size of the window as its own size (doesn't automatically resize with long text string input)
#self.update()
#self.geometry(self.geometry())
#set focus to text field
self.entry.focus_set()
self.entry.selection_range(0, Tkinter.END)
def OnButtonClick(self):
self.labelVariable.set("Radius: " +self.entryVariable.get())
def OnPressEnter(self,event):
self.labelVariable.set("Radius: " + self.entryVariable.get())
self.entry.focus_set()
self.entry.selection_range(0, Tkinter.END)
def GetScale():
sliderValue = scale.get()
print (sliderValue)
if __name__ == "__main__":
app = createBuffer(None)
app.title('Buffer Tool')
app.mainloop()
In this line:
return self.func(*args)
There are two arguments, one implicit and one explicit. The first one is implicit, and it's self. Whenever you call a function that's attached to an object, aka obj.func(), the obj object itself is the implicit first argument. The second argument is the *args array which is being passed in explicitly, and even if it contains zero arguments, it still counts as an argument in itself (though it just contains an empty tuple ()).
Now look at the following line in your code:
scale = Tkinter.Scale(self, variable = StringVar, orient = HORIZONTAL, from_ = 0, to = 100, resolution = 10, width = 30, length = 300, command = self.GetScale)
Here, you're passing in GetScale() as a function argument, and it's being called in the above way in the Tkinter module somewhere. The module is basically calling self.GetScale(*args). As ambi suggested, you'll need to change the definition of GetScale(), probably to this:
def GetScale(self, *args):
sliderValue = scale.get()
print (sliderValue)
On a less related note, if you want each of your objects to have a scale variable, it might be smarter to refer to it as self.scale. If it's a global scale, then ignore this.
Change definition of GetScale function to:
def GetScale(*args, **kwargs):
print(*args, **kwargs)
sliderValue = scale.get()
print (sliderValue)
This way your function will accept any positional or keyword arguments so you can check what these are.
Your function:
def GetScale():
sliderValue = scale.get()
print (sliderValue)
should instead be:
def GetScale(self):
sliderValue = scale.get()
print (sliderValue)
Since it is a class method you must list 'self' as an argument because it will be automatically passes to the function when it is called. As far as the second argument goes I'm not entirely sure about it. When you bind a method to a TK command such as
command = GetScale
you must omit the parenthesis to avoid the call happening prematurely. This means that you cannot pass additional arguments as far as I know. I would start with making the correct changes to your GetScale function and see if it positively changes your output/errors.