Letting a user supply the name of a function via tkinter entry - python

I wrote this program in which I want the user to supply the name of a function which resides in a module (in a folder in his computer for example). For instance I have the function, 'my_sum' which resides in the module 'sum_functions.py' which in turn sits in the 'sum_package' folder. The idea is to use a ttk.Entry to get the function string and supply to 'import_func' which uses the importlib module and getattr to retrieve 'my_sum' . Finally I intend to pass my_sum() to calculate.
I get an error although the program prints 'my_sum' in the end. I would like to know What I am doing wrong or if there is a better more pythonic way?
sum_package # resides in my computer
sum_function.py # resides in sum_package and contains my_sum.py
def my_sum(x, y): # actual function to be called
return x * y
import importlib
import tkinter as tk
from tkinter import ttk
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.grid()
self.createWidgets()
def createWidgets(self):
self.entry = ttk.Entry(self , width=12)
self.entry.grid(row=3, column=1)
load_func = ttk.Button(self, text="Load function", command = self.import_func).grid(row=3, column=2)
ttk.Button(self, text="Calculate",command=self.calculate).grid(column=3, row=3)
self.quitButton = ttk.Button(self, text="Quit", command=self.quit )
self.quitButton.grid()
def import_func(self, *args):
global f # I made this global so the function could be accessed by other functions in the program
try:
value = str(self.entry.get())
print(value)
module_str = "sum_package." + 'sum_function' # get module str
module = importlib.import_module(module_str) # import module from str
f = getattr(module, value) # get function "function" in module
return f
except:
pass
def calculate(self, *args):
global x, y
try:
f(x, y)
except ValueError:
pass
app = Application()
app.master.title("Sample application")
x, y = 5, 6
app.mainloop()
#Error
# Exception in Tkinter callback
# Traceback (most recent call last):
# File "/home/richard/anaconda3/lib/python3.9/tkinter/__init__.py", line 1892, in __call__
# return self.func(*args)
# File "/home/richard/Dropbox/impedance_fitting_program/gui/test4.py", line 55, in calculate
# f(x, y)
# NameError: name 'f' is not defined
# my_sum

Related

How do I correctly access a variable from a Class in another file?

I'm currently trying to make a UI for a future project using tkinter. However, I'm having a lot of trouble setting up some basic OOP concepts.
I have two files, main.py and pressed.py, I'm trying to write a function for a button press in pressed.py but I'm not sure how I'm supposed to get access to variables in main.py
Here's the error I'm currently running into:
Class 'UI' has no 'py_entry' member
I've tried using a lot of other Stack Overflow posts for reference, but none of them has worked.
main.py
import tkinter as tk
import os
import geocoder
from tkinter import messagebox
from PIL import Image
from PIL import ImageTk
import pressed
# Paths
assets_path = "./assets/"
class UI(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
# Set window title
parent.title("geo-location-data")
# Set window geometry
parent.geometry("800x600")
# Address entry label
py_label = tk.Label(parent, text="Emter an addres:", font=("Helvetica", 12))
py_label.place(x=0, y=0)
# Address entry field
py_entry = tk.Entry(parent, font=("Helvetica", 12))
py_entry.place(x=130, y=2)
# Open button_normal.png
button_normal_img = Image.open(assets_path + "button_normal.png")
# Resize button_normal.png
button_normal_img_resize = button_normal_img.resize((16, 16), Image.BILINEAR)
button_normal_img_resize_format = "resized_button_normal" + ".png"
# Set path
path_for_button_normal = os.path.join(assets_path, button_normal_img_resize_format)
# Save to path
button_normal_img_resize.save(path_for_button_normal)
# Open saved image
button_normal_img_r_open = Image.open(assets_path + "resized_button_normal.png")
button_normal_img_r_open = ImageTk.PhotoImage(button_normal_img_r_open)
#def button_pressed(self):
# If address entry field is blank
#if(py_entry.index("end") == 0):
#messagebox.showerror("Error", "Entry field was left blank.")
#return self
#else:
#print("test")
#return self
# Pressed
# ADD COMMAND #
py_button = tk.Button(parent, font=("Helvetica", 12), width=16, height=16, image=button_normal_img_r_open)
py_button.place(x=320, y=2)
if __name__ == "__main__":
root = tk.Tk()
UI(root).pack(side="top", fill="both", expand=True)
root.mainloop()
pressed.py
from sample import main
from tkinter import messagebox
class Pressed(main.UI):
def __init__(self):
def button_press(self):
# This is where I'm getting the error
if (main.UI.):
messagebox.showerror("Error", "Address entry field was left blank.")
return self
else:
print("Button pressed!")
return self
Your problem seems to be that you are declaring local variables inside the class which are only accessible from within the __init__() function. Python has a way to create class variables by either declaring them inside the class (outside of a method) or by calling self.variable_name. You then can access that variable by calling self.variable_name every place inside the class.
self is a reference to the current instance of your class.
For your example the way you probably want to could declare the variable is with the self keyword:
class UI(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.py_entry = tk.Entry(parent, font=("Helvetica", 12))
self.py_entry.place(x=130, y=2)
You should then be able to access the py_entry field after creating an instance of the UI class (calling UI()).

How can I access the particular tkinter StringVar that triggers a trace callback?

I'm trying to use a trace callback to validate inputs to a set of combo boxes but the arguments I get in the callback are just a string representation of the internal name. Is there a way to get a reference to the actual variable triggering the trace callback or a way to get at the variable using the internal name like PY_VAR1?
from Tkinter import *
import ttk
def validate(self, *args):
print(self)
print(self.get())
master = Tk()
cb1_var = StringVar()
cb2_var = StringVar()
cb1_var.trace('w', validate)
cb2_var.trace('w', validate)
cb1 = ttk.Combobox(master, textvariable=cb1_var)
cb2 = ttk.Combobox(master, textvariable=cb2_var)
cb1.grid(row=0, column=0, sticky='NW')
cb2.grid(row=1, column=0, sticky='NW')
mainloop()
Fails on trying to call self.get() since self is just that string representation, not the actual StringVar. I don't want to have a specific callback for each StringVar since the actual interface has around 30 boxes I all want validated by the same criteria.
You could simply pass the arguments you want by making use of lambda statement for anonymous functions. Replace:
def validate(self, *args):
print(self)
print(self.get())
...
cb1_var.trace('w', validate)
cb2_var.trace('w', validate)
with:
def validate(var):
print(var)
print(var.get())
...
cb1_var.trace('w', lambda *_, var=cb1_var: validate(var))
cb2_var.trace('w', lambda *_, var=cb2_var: validate(var))
If you use multiple objects that are related, simply use collection types. For the example in the question, I see a list should be a good fit.
See the example below:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
import tkinter.ttk as ttk
except ImportError:
import Tkinter as tk
import ttk
def upon_var_change(var):
print(var.get())
if __name__ == '__main__':
root = tk.Tk()
cbs = list()
for i in range(3):
cbs.append(ttk.Combobox(root))
cbs[i].var = tk.StringVar()
cbs[i].var.trace_add('write', lambda *_,
var=cbs[i].var:upon_var_change(var))
cbs[i]['textvariable'] = cbs[i].var
cbs[i].grid(row=i // 2, column=i % 2, sticky='nw')
tk.mainloop()
If such is required you could identify the Variable class from its internal reference as well:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
import tkinter.ttk as ttk
except ImportError:
import Tkinter as tk
import ttk
def upon_var_change(var_name):
value = root.tk.globalgetvar(var_name)
print(var_name, value)
if __name__ == '__main__':
root = tk.Tk()
cbs = list()
for i in range(3):
cbs.append(ttk.Combobox(root))
cbs[i].var = tk.StringVar()
cbs[i].var.trace_add('write', lambda var_name,
*_: upon_var_change(var_name))
cbs[i]['textvariable'] = cbs[i].var
cbs[i].grid(row=i // 2, column=i % 2, sticky='nw')
tk.mainloop()
I know this is an old post, gut I found a way to use the internal variable name. Modify your validate function like this:
def validate(self, *args):
var_name = args[0]
var = IntVar(name=var_name)
print(var.get())
master = Tk()
cb1_var = StringVar()
cb2_var = StringVar()
cb1_var.trace('w', validate)
cb2_var.trace('w', validate)

Submit button not showing in second module in Tkinter

I have two modules file1.py and file2.py. In file1.py i have created a class and a function with label and entry widgets. In file2.py, i inherit the class of file1.py and create a submit button in a function. So, when i click the submit button, the value entered in entry widget in file1.py should be displayed. But what i observe is, submit button is not dislayed and when i close the window, the entered value is displayed. I'm unable to understand this behavior, can anyone correct my mistake.
file1.py
from Tkinter import *
top = Tk()
class TestClass(object):
def __init__(self, master = None):
self.frame = Frame(master)
self.frame.pack()
self.func()
def func(self):
self.label = Label(self.frame, text = "LabelName")
self.label.pack()
self.x = StringVar()
self.entry = Entry(self.frame, textvariable=self.x)
self.entry.pack()
app = TestClass(master = top)
top.minsize(400, 400)
top.mainloop()
file2.py
from file1 import *
class ImportClass(TestClass):
def __init__(self):
super(ImportClass,self).__init__(master=None)
self.imp_func()
def imp_func(self):
def get_func():
print app.x.get()
self.s = Button(self.frame, text="Submit", command=get_func())
self.s.pack()
Im = ImportClass()
I see your problem, to get this to work you have to fix a few things:
First, you need to use your imported class as app, which has the submit button, to be still able to run just file1 you can check in file1.py the __name__ whether it's '__main__' like:
if __name__ == '__main__':
app = TestClass(master = top)
top.minsize(400, 400)
top.mainloop()
Secondly, your function is not called because you call the function and give the result to Button, here you should just pass the function without calling it:
self.s = Button(self.frame, text="Submit", command=get_func())
in the function itself you should not use a global variable like app, because for example if you have multiple instances of the same class they would all depend on one instance and in the TestClass you have set self.x which is also accessible in ImportClass so you should replace the print statement with print self.x.get() instead of print app.x.get() to set the master from ImportClass to top. I also added *args and **kwargs to be passed on in the __init__ method so all in all you get:
file1.py
from Tkinter import *
class TestClass(object):
def __init__(self, master = None):
self.frame = Frame(master)
self.frame.pack()
self.func()
def func(self):
self.label = Label(self.frame, text = "LabelName")
self.label.pack()
self.x = StringVar()
self.entry = Entry(self.frame, textvariable=self.x)
self.entry.pack()
if __name__ == '__main__':
#just run if the file is called as main
top = Tk()
app = TestClass(master = top)
top.minsize(400, 400)
top.mainloop()
and file2.py
from file1 import *
from Tkinter import *
class ImportClass(TestClass):
def __init__(self, *args, **kwargs):
#passing all args and kwargs to super
super(ImportClass,self).__init__(*args, **kwargs)
self.imp_func()
def imp_func(self):
def get_func():
print self.x.get()
#using class property instead of global
self.s = Button(self.frame, text="Submit", command=get_func)
#pass function not it's return value which has been None
self.s.pack()
if __name__ == '__main__':
top = Tk()
app = ImportClass(master = top)
#using the ImportClass to display the window
top.minsize(400, 400)
top.mainloop()
so this should work. Hopefully, this helps you to prevent further problems like this.
The main reason you're having trouble is that no lines will run after mainloop until the Tk instance is closed or an event happened. When you import file1, mainloop is eventually run and then the GUI is waited to be closed in order to first define the ImportClass and then later to initialize an object for it.
Simply remove:
top.mainloop()
from file1 and add:
top.mainloop()
to file2 as the last line.
After which there's another issue, command option of a button expects a reference to a callable object, as opposed to an actual call. Replace:
self.s = Button(..., command=get_func())
with:
self.s = Button(..., command=get_func)
Also, note that I think your imports are in reverse order, obtain GUI objects by the module that has the Tk instance as opposed to vice-versa.

python AttributeError: NoneType has no attribute

In my program snippet I create a python window with 2 fields and 3 buttons. The left two buttons should perform some action but instead an error is thrown:
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib64/python3.4/tkinter/__init__.py", line 1538, in __call__
return self.func(*args)
File ".../GuiFile.py", line 11, in <lambda>
self.F[2] = ButtonClass().make_button(stacked="left",buttontext= "Action button", buttoncommand = lambda: cf.mainButtons.doButtonAction1(self))
File ".../ClassFile.py", line 11, in doButtonAction1
print(gf.StartGui.F[0].textField.get("1.0","end-1c"))
AttributeError: 'NoneType' object has no attribute 'textField'
Why is dict item F[0] (created in line 9 of GuiFile.py) not recognized as Text() class with the attribute textField (defined in line 43 of GuiFile.py)?
MainProgramFile.py
#!/usr/bin/env python3
import sys
import ClassFile
import GuiFile as gf
if __name__== '__main__':
gf.StartGui().mainloop()
GuiFile.py
import sys
from tkinter import *
import ClassFile as cf
class StartGui(Frame):
F = {}
def __init__(self,parent=None):
Frame.__init__(self, parent)
self.F[0] = FieldTextClass().make_field(labeltext="Label of field 1", fieldtext="veld 1", fieldheight=90)
self.F[1] = FieldTextClass().make_field(labeltext="Label of field 2", fieldtext="veld 2")
self.F[2] = ButtonClass().make_button(stacked="left",buttontext= "Action button", buttoncommand = lambda: cf.mainButtons.doButtonAction1(self))
self.F[3] = ButtonClass().make_button(stacked="left", buttontext= "Exchange button", buttoncommand = lambda: cf.mainButtons.doButtonSwitchValues(self))
self.F[4] = ButtonClass().make_button(stacked="right",buttontext= "Quit button",buttoncommand = lambda: cf.mainButtons.doButtonQuit(self))
self.pack(expand=True, fill=BOTH, anchor="nw", side=LEFT)
#for i in range(self.F.__len__()): print(self.F[i].__class__,self.F[i].objectType)
class ButtonClass (Frame, Button):
objectType = "button"
def make_button(self, parent=None, stacked="horizontal", buttontext="Button", buttonwidth=120, buttonheight=32, buttoncommand=""):
self.buttonwidth=buttonwidth
self.buttonheight=buttonheight
self.buttontext=buttontext
self.buttoncommand=buttoncommand
if stacked=="vertical":
BUTTONSTACK = TOP
elif stacked=="right":
BUTTONSTACK = RIGHT
elif stacked=="horizontal" or stacked=="left":
BUTTONSTACK = LEFT
else:
BUTTONSTACK = LEFT
self.top = Frame(parent, height=self.buttonheight, width=self.buttonwidth)
self.top.pack_propagate(False)
self.top.pack(side=BUTTONSTACK)
button = Button(self.top, text=self.buttontext, command=self.buttoncommand,height=self.buttonheight, width=self.buttonwidth)
button.pack(fill=BOTH)
class FieldTextClass(Frame,Text,Label):
textField = None
objectType = "inputField"
def make_field(self, parent=None, labeltext="Empty", fieldtext="Empty", fieldwidth=600, fieldheight=20, labelwidth=120, labelheight=20):
self.fieldheight=fieldheight
self.fieldwidth=fieldwidth
self.fieldtext=fieldtext
self.labeltext=labeltext
self.labelheight=labelheight
self.labelwidth=labelwidth
self.top = Frame(parent)
#create the label, whith the text shifted left/top in a separate Frame
labelFrame = Frame(self.top, height = self.labelheight,width=self.labelwidth)
label = Label(labelFrame, text=self.labeltext, fg="black", anchor="nw")
label.pack(expand=True, fill=BOTH, anchor="nw", side=LEFT)
labelFrame.pack_propagate(False)
labelFrame.pack(side=LEFT, anchor="nw")
#create the text field, packed in a separate Frame
fieldFrame = Frame(self.top, height = self.fieldheight,width=self.fieldwidth)
self.textField = Text(fieldFrame, fg="black",bg="white")
self.textField.insert(INSERT,self.fieldtext)
self.textField.pack(expand=True, fill=BOTH, side=LEFT)
fieldFrame.pack_propagate(False)
fieldFrame.pack(side=LEFT)
self.top.pack(side=TOP)
ClassFile.py
import sys
from tkinter import *
import GuiFile as gf
class mainButtons():
def doButtonQuit(self):
print("Quitting test via ClassFile")
self.quit()
def doButtonAction1(self):
print(gf.StartGui.F[0].textField.get("1.0","end-1c"))
print(gf.StartGui.F[1].textField.get("1.0","end-1c"))
gf.StartGui.F[0].textField.delete("1.0","end")
gf.StartGui.F[0].textField.insert(INSERT, "New text")
def doButtonSwitchValues(self):
tmp0=gf.StartGui.F[0].textField.get("1.0","end-1c")
tmp1=gf.StartGui.F[1].textField.get("1.0","end-1c")
gf.StartGui.F[0].textField.delete("1.0","end")
gf.StartGui.F[0].textField.insert(INSERT, tmp1)
gf.StartGui.F[1].textField.delete("1.0","end")
gf.StartGui.F[1].textField.insert(INSERT, tmp0)
When you do ButtonClass().make_button() (or FieldTextClass.make_field()) , python will return the value of the function, not the instance of the class. The function returns None, so the dictionary elements are None.
The way you're using the custom classes is very strange. Instead of creating special functions, put that code in an __init__, and use the class like you would any other class.
For example:
class ButtonClass (Frame):
def __init__(self, parent=None, stacked="horizontal",
buttontext="Button", buttonwidth=120, buttonheight=32,
buttoncommand=""):
Frame.__init__(self, parent)
self.buttonwidth=buttonwidth
...
...
self.F[2] = ButtonClass(stacked="left",buttontext= "Action button", buttoncommand = lambda: cf.mainButtons.doButtonAction1(self))
Note: when doing it this way, you don't have to create a separate frame inside __init__ (ie: self.top), since self is itself already a Frame.

I keep getting TypeError: count_function() missing 1 required positional argument: 'self'

I'm trying to create a little counter app, but I seem to get stuck when trying to create an actual button which makes the counter go up.
Code:
from tkinter import *
class CounterClass(Frame):
buttonFrame = Frame(height=200, width=200)
counterStatus = Label(buttonFrame, text="0")
def count_function(self):
i = int(self.counterStatus.cget("text"))
i += 1
self.counterStatus.config(text=str(i))
counter = Button(buttonFrame, text="+1", command=count_function)
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.buttonFrame.pack()
self.counterStatus.pack()
self.counter.pack()
if __name__ == "__main__":
root = Tk()
c = CounterClass(master=root)
c.mainloop()
root.destroy()
When I click the button, it gives me this error:
Exception in Tkinter callback
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/tkinter/__init__.py", line 1533, in __call__
return self.func(*args)
TypeError: count_function() missing 1 required positional argument: 'self'
However, when I create a module that should do exactly the same, but I don't use a class it works fine:
from tkinter import *
root = Tk()
def count_function():
i = int(counterStatus.cget("text"))
i += 1
counterStatus.config(text=str(i))
buttonFrame = Frame(height=200, width=200)
counterStatus = Label(buttonFrame, text="0")
counterButton = Button(buttonFrame, text="+1", command=count_function)
buttonFrame.pack()
counterStatus.pack()
counterButton.pack()
root.mainloop()
counter = Button(buttonFrame, text="+1", command=count_function)
When you click the button, this will try to call the count_function without any argument. But since it’s an instance method, it requires the (implicit) self parameter.
To fix this, you should move the creation of your elements inside of the __init__ method. This not only prevents them from being stored as (shared) class members, but will also allow you to specify bound methods:
class CounterClass(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.buttonFrame = Frame(height=200, width=200)
self.counter = Button(self.buttonFrame, text="+1", command=self.count_function)
self.counterStatus = Label(self.buttonFrame, text="0")
self.pack()

Categories

Resources