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.
Related
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.
I just started learning Python and I ran into this problem. I want to set a variable from inside a method, but the variable is outside the method.
The method gets activated by a button. Then I want to get the value from that variable that I set when I press another button. The problem is that the value that I put inside a variable from inside the method doesn't stay. How would I solve this?
The code is underneath. currentMovie is the variable I try to change. When I press the button with the method UpdateText(), it prints out a random number like it is supposed to. But when I press the button that activates UpdateWatched() it prints out 0. So I am assuming the variable never gets set.
import random
from tkinter import *
currentMovie = 0
def UpdateText():
currentMovie = random.randint(0, 100)
print(currentMovie)
def UpdateWatched():
print(currentMovie)
root = Tk()
root.title("MovieSelector9000")
root.geometry("900x600")
app = Frame(root)
app.grid()
canvas = Canvas(app, width = 300, height = 75)
canvas.pack(side = "left")
button1 = Button(canvas, text = "SetRandomMovie", command = UpdateText)
button2 = Button(canvas, text = "GetRandomMovie", command = UpdateWatched)
button1.pack(anchor = NW, side = "left")
button2.pack(anchor = NW, side = "left")
root.mainloop()
Here's a simple (python 2.x) example of how to 1 not use globals and 2 use a (simplistic) domain model class.
The point is: you should first design your domain model independently from your user interface, then write the user interface code calling on your domain model. In this case your UI is a Tkinter GUI, but the same domain model should be able to work with a command line UI, a web UI or whatever.
NB : for python 3.x, replace Tkinter with tkinter (lowercase) and you can get rid of the object base class for Model.
import random
from Tkinter import *
class Model(object):
def __init__(self):
self.currentMovie = 0
def UpdateCurrentMovie(self):
self.currentMovie = random.randint(0, 100)
print(self.currentMovie)
def UpdateWatched(self):
print(self.currentMovie)
def ExampleWithArgs(self, arg):
print("ExampleWithArg({})".format(arg))
def main():
model = Model()
root = Tk()
root.title("MovieSelector9000")
root.geometry("900x600")
app = Frame(root)
app.grid()
canvas = Canvas(app, width = 300, height = 75)
canvas.pack(side = "left")
button1 = Button(canvas, text = "SetRandomMovie", command=model.UpdateCurrentMovie)
button2 = Button(canvas, text = "GetRandomMovie", command=model.UpdateWatched)
button3 = Button(canvas, text = "ExampleWithArg", command=lambda: model.ExampleWithArgs("foo"))
button1.pack(anchor = NW, side = "left")
button2.pack(anchor = NW, side = "left")
button3.pack(anchor = NW, side = "left")
root.mainloop()
if __name__ == "__main__":
main()
Use global to modify a variable outside of the function:
def UpdateText():
global currentMovie
currentMovie = random.randint(0, 100)
print(currentMovie)
However, don't use global. It's generally a code smell.
Here's a simple but dirty solution: use a mutable variable.
Instead of
currentMovie = 0
def UpdateText():
currentMovie = random.randint(0, 100)
print(currentMovie)
you can use a single-cell list for currentMovie and pass it as a (default) argument to UpdateText():
currentMovie = [0]
def UpdateText(cM=currentMovie): # The default value will 'bind' currentMovie to this argument
cM[0] = random.randint(0, 100) # This will change the *contents* of the variable
print(cM[0]) # I used a different name for the parameter to distinguish the two
UpdateText() # Calls UpdateText, updating the contents of currentMovie with a random number
Note that setting currentMovie itself (not its contents) with a new value—even with a new list—would cause UpdateText() to stop updating currentMovie unless the def block were run again.
currentMovie = [0]
def UpdateText(cM=currentMovie): # The default value will 'bind' currentMovie to this argument
cM[0] = random.randint(0, 100) # This will change the *contents* of the list
print(cM[0]) # I used a different name for the parameter to distinguish the two
currentMovie = 3 # UpdateText() will no longer affect this variable at all
# This will thus not throw an error, since it's modifying the 'old' currentMovie list:
UpdateText() # The contents of this list can also no longer be accessed
This is more of a handy trick if you're building something quick and dirty and don't want to build a class; I find that Python is great for such things, so I think that this is still worthwhile to share despite the other answers.
For more serious purposes, though, creating a class as in bruno's answer would almost certainly be better.
For me, the already mentioned answers did not work for two reasons.
In case of an error, I need the variable for a deeper analysis of the data which led to the error.
I'm using the function in a pandas.DataFrame.apply() to paste the usual output into a column of the existing DataFrame. Therefore the error information shall not be in the return statement.
Solution for me:
Since I did not find a direct solution I decided to write the variable on disk:
with open('var.pickle', 'wb') as f:
pickle.dump(var, f)
And then to import it where ever I need it:
with open('var.pickle', 'rb') as f:
var = pickle.load(f)
I tried making a simple integer sign calculator using tkinter. It has a class with two different functions. The second function is supposed to be initiated when the user presses the "Enter" button. When I run the code the window comes up just as it is supposed to. But when I type and hit "Enter" the second function fails to run and does not update the label. I want for it to update as either "This number is positive.", "This number is 0.", or "This number is negative." Instead it remains blank.
I doubt it is relevant, but I made this program in PyCharm Community Edition 5.0.4, and I am using Python 3.5 (32-bit).
import tkinter
class IntegerSign:
def __init__(self):
self.window = tkinter.Tk()
self.window.title("Integer Sign Calculator")
self.window.geometry("300x150")
self.number_frame = tkinter.Frame(self.window)
self.solution_frame = tkinter.Frame(self.window)
self.button_frame = tkinter.Frame(self.window)
self.number_label = tkinter.Label(self.number_frame, text="Enter an integer:")
self.number_entry = tkinter.Entry(self.number_frame, width=10)
self.number_label.pack(side='left')
self.number_entry.pack(side='left')
self.statement = tkinter.StringVar()
self.solution_label = tkinter.Label(self.solution_frame, textvariable=self.statement)
self.statement = tkinter.Label(self.solution_frame, textvariable=self.statement)
self.solution_label.pack(side='left')
self.calc_button = tkinter.Button(self.button_frame, text='Enter', command=self.calc_answer)
self.quit_button = tkinter.Button(self.button_frame, text='Quit', command=self.window.destroy)
self.calc_button.pack(side='left')
self.quit_button.pack(side='left')
self.number_frame.pack()
self.solution_frame.pack()
self.button_frame.pack()
tkinter.mainloop()
def calc_answer(self):
self.number = int(self.number_entry.get())
self.statement = tkinter.StringVar()
if self.number > 0:
self.statement = "This number is positive."
elif self.number == 0:
self.statement = "This number is 0."
else:
self.statement = "This number is negative."
IntegerSign()
The first problem: in your constructor you initialize a variable named self.statement to a StringVar and then initialize again to a Label. After that second initialization, you have no way of accessing the first object. You need to use two different names.
The second problem: in your event handler, calc_answer, you create a new object named self.statement, but instead you need to set a new value into the old one (see docs). Here is a modified version of your program that works as intended:
import tkinter
class IntegerSign:
def __init__(self):
self.window = tkinter.Tk()
self.window.title("Integer Sign Calculator")
self.window.geometry("300x150")
self.number_frame = tkinter.Frame(self.window)
self.solution_frame = tkinter.Frame(self.window)
self.button_frame = tkinter.Frame(self.window)
self.number_label = tkinter.Label(self.number_frame, text="Enter an integer:")
self.number_entry = tkinter.Entry(self.number_frame, width=10)
self.number_label.pack(side='left')
self.number_entry.pack(side='left')
self.solution_string = tkinter.StringVar()
self.solution_label = tkinter.Label(self.solution_frame, textvariable=self.solution_string)
self.statement = tkinter.Label(self.solution_frame, textvariable=self.solution_string)
self.solution_label.pack(side='left')
self.calc_button = tkinter.Button(self.button_frame, text='Enter', command=self.calc_answer)
self.quit_button = tkinter.Button(self.button_frame, text='Quit', command=self.window.destroy)
self.calc_button.pack(side='left')
self.quit_button.pack(side='left')
self.number_frame.pack()
self.solution_frame.pack()
self.button_frame.pack()
tkinter.mainloop()
def calc_answer(self):
self.number = int(self.number_entry.get())
if self.number > 0:
self.solution_string.set("This number is positive.")
elif self.number == 0:
self.solution_string.set("This number is 0.")
else:
self.solution_string.set("This number is negative.")
IntegerSign()
This code works but contains a bad practice that I recommend you fix. The function tkinter.mainloop() is essentially an infinite loop, and you have placed it inside the constructor. Thus the constructor won't return the way a constructor is normally supposed to. Take that statement out of the __init__ function and put it at the end, after the call to IntegerSign, and make this a pattern to be used in the future.
To set the value of a StringVar you need to use the set method.
Right now all you did was re-assign the variable. You can also set a stringvar's (default) value by giving it a value when you first initialize it. e.g. - var = tk.StringVar(value="some value")
Edit: Didn't see that you also set self.statement to be the label widget... This would work if you used the method all the way at the bottom of this answer, and disregarded (optionally) stringvar's entirely. But, when you do this you can think of it as sticky notes. You stuck a sticky note that says "this variable holds this value", then you re-assigned the variable put another sticky note over the previous one that says "it now holds this value" as a really loose visual analogy.
>>> import tkinter as tk
>>> root = tk.Tk()
>>> statement = tk.StringVar()
>>> type(statement)
>>> <class 'tkinter.StringVar'>
>>> statement = "This number is positive"
>>> type(statement)
>>> <class 'str'>
>>> statement = tk.StringVar()
>>> statement.set("This number is positive")
>>> statement.get()
'This number is positive'
>>> type(statement)
>>> <class 'tkinter.StringVar'>
Alternatively you could just change the labels text by doing label_widget['text'] = 'new_text'
I am having an issue getting this code to parse in tkinter. Two issues happen. I can't get the everb5 variable to pass into the generate function.
class App(Tk):
def __init__(self):
Tk.__init__(self)
self.everb5 = Entry(self).grid(row = 19, column = 2)
self.btn = Button(self, text = "Generate", command = self.generate).grid(row = 25, column = 3, columnspan = 3)
def generate(self):
self.everb5.config(text= "Hello")
Must I also pass the variable to the generate function like so...
def generate(self,everb5)
I also have about 23 variables that have to work in the generate function from the init function. Next steps? Variable containing an array of all variables? I am making a mad libs game and need to pass the answers from Tk() Entry() function to generate() and then i planned on using the .format function to replace the "blank spaces" used in mad libs with the answers provided from the user. I have the button working now, I just need to know how to pass the variables between functions.
I guess the problem is that self.everb5 and self.btn are none. The reason is that grid returns none. You should execute grid after you make an instance of Entry or Button:
class App(Tk):
def __init__(self):
Tk.__init__(self)
self.everb5 = Entry(self)
self.everb5.grid(row = 19, column = 2) #<-- here
self.btn = Button(self, text = "Generate", command = self.generate)
self.btn.grid(row = 25, column = 3, columnspan = 3) #<-- here
def generate(self):
self.everb5.config(text= "Hello")
I am using python 2.7 and trying to change the state of a tkinter entry box depending on the value of an OptionMenu widget. I found an example of how to do it online here, it's for python 3 but I don't think that's the issue (correct me if I am wrong). Some example code is below,
from Tkinter import *
class App:
def _disable_f2(self):
if self.filt.get() == 'bandpass':
self.filter_menu.configure(state='normal')
else:
self.filter_menu.configure(state='disabled')
def __init__(self, master):
self.f2var = Tkinter.StringVar()
self.f2var.set('5.0')
self.f2_entry = Tkinter.Entry(master, textvariable=self.f2var,
width=5)
self.f2_entry.pack()
self.filt = Tkinter.StringVar()
self.filt.set('bandpass')
self.filter_menu = Tkinter.OptionMenu(master, self.filt,
'bandpass', 'lowpass ',
'highpass',
command=self._disable_f2)
self.filter_menu.pack(ipadx=50)
root = Tk()
app = App(root)
root.mainloop()
however, I keep getting the following error even though I am not passing two arguments. Anyone know what the cause is?
TypeError: _disable_f2() takes exactly 1 argument (2 given)
If you just accept one more argument and print it, you can find out what the argument is that is passed by the OptionMenu:
def _disable_f2(self, arg):
print arg
You will see it prints the new value of the OptionMenu. Because this argument is passed you need the function to accept it, and you actually are using it (with self.filt.get()) so it's fine that it's passed.
You can rewrite your _disable_f2 function to:
def _disable_f2(self, option):
if option == 'bandpass':
self.f2_entry.configure(state='normal')
else:
self.f2_entry.configure(state='disabled')
In your original code you disabled the optionmenu when the option was not 'bandpass', but I assume you want to disable the entry right? That's what this code does.
Also, if you use from Tkinter import *, you don't have to use Tkinter.StringVar(), but you can just use StringVar(). Same goes for Entry(...), OptionMenu(...) and Tk().
Allthough I would advise to use import Tkinter as tk, and use tk.StringVar() etc.
If _disable_f2 is being given two arguments, let it have what it wants.. try below...
:)
from Tkinter import *
class App:
def _disable_f2(self, master):
if self.filt.get() == 'bandpass':
self.filter_menu.configure(state='normal')
else:
self.filter_menu.configure(state='disabled')
def __init__(self, master):
self.f2var = StringVar()
self.f2var.set('5.0')
self.f2_entry = Entry(master, textvariable=self.f2var,
width=5)
self.f2_entry.pack()
self.filt = StringVar()
self.filt.set('bandpass')
self.filter_menu = OptionMenu(master, self.filt,
'bandpass', 'lowpass ',
'highpass',
command=self._disable_f2)
self.filter_menu.pack(ipadx=50)
root = Tk()
app = App(root)
root.mainloop()