Changing a tkinter-label in a class - python

I am trying change a Label in a Class with a function.
class Fenster2(tk.Frame):
def __init__(self, parent,controller):
tk.Frame.__init__(self,parent)
label_fluss1 = tk.Label(self, width=9)
label_fluss1.grid(row=3,column=2)
label_fluss2 = tk.Label(self, width=9)
label_fluss2.grid(row=4,column=2)
GPIO.add_event_detect(27,GPIO.RISING,callback=hochzaehlen1)
GPIO.add_event_detect(22,GPIO.RISING,callback=hochzaehlen2)
GPIO.add_event_detect(23,GPIO.FALLING,callback=interrupt)
def hochzaehlen1(callback):
global ticks1
ticks1 +=1
return ticks1
def hochzaehlen2(callback):
global ticks2
ticks2 +=1
return ticks2
def interrupt(callback):
global ticks1
global ticks2
global fluss1
fluss1=ticks1/582.0 # liter/min
fluss2=ticks2/354.0 # liter/min
ticks1=0
ticks2=0
Fenster2.label_fluss1.config(text=str(fluss1))
pb_fluss1.config(value=fluss1)
label_fluss2.config(text=str(fluss2))
pb_fluss2.config(value=fluss2)
Now, when interrupt tries to configure label_fluss1 it says Fenster2 has no instance label_fluss1. Anyone knows how to make this work?
I know that I will get the same problem with label_fluss2 and both progressbars.
Any advice is much appreciated.
Greetings Sebastian

Believe you need to use self on those variables when creating a class. That could be the issue causing this. so in your init() you would have:
self.label_fluss1 = tk.Label()
You would do this for all the variables in init being defined and your methods.
As for the class itself goes, why are you making all your methods children of callback that doesnt exist?
I would read up on creating classes more on https://docs.python.org/3/tutorial/classes.html to get a better understanding of instance variables, global, and local. The scope in which these reside are very important for being used. If the class is created correctly, there is no reason that when you create the object that it should not have the fluss1 attribute. As it stands now though, you need to define self before these variables. Then go from there structuring your class properly to call these attributes as needed.

You are missing a basic understanding of how classes work. You need to make the variable you want to update an instance variable, and then call it the same way:
class Fenster2(tk.Frame):
def __init__(self, parent,controller):
tk.Frame.__init__(self,parent)
self.label_fluss1 = tk.Label(self, width=9)
self.label_fluss1.grid(row=3,column=2)
# etc...
def interrupt(self):
#etc ...
self.label_fluss1.config(text=str(fluss1))
I recommend you find a basic tutorial on classes before you continue, since GUIs rely heavily on them.

Related

How to access method of an object in the object?

I am trying to condense my code, so I want to create object instead of having to create labels each time I need one.
However, I can't figure out how to be able to change attributes of the object-labels using .config. I've tried using objectvariable.config(...), but that doesn't work. Neither does using a method like in the following:
class title_label():
def __init__(self):
self = tkinter.Label(root)
self.pack(side='left')
def update(self, text):
self.config(text=text)
Error-message is: objectvariable object has no attribute config.
How can I use .config on an object containing a label?
It should be
class title_label():
def __init__(self, root):
self.label = tkinter.Label(root) # <<< 'label' field here
self.label.pack(side='left')
def update(self, text):
self.label.config(text=text)
self hold the reference to the class itself. label is something that your class is supposed to hold not to be. Another approach would be to derive from the Label class, but for what it is worth storing the label in the field should be good enough for you.
If you made your class a subclass of tkinter.Label then it would have inherited a config() method from it.
Here's an example of how that might be done:
import tkinter as tk
class TitleLabel(tk.Label):
def update(self, text):
self.config(text=text)
if __name__ == '__main__':
root = tk.Tk()
title_lbl = TitleLabel(root, text='Initial Text')
title_lbl.pack(side='left')
root.after(1000, lambda: title_lbl.update('CHANGED!')) # Update after 1 sec.
root.mainloop()
But as you can see, there wouldn't really be much point of doing so, since the only thing update() does is forward to call on the base class.

How can I make an interface work with casting

So, I am programming my Python Program with the MVC-Architecture, and I want everything nice and seperated from each other. I don't want the View of my GUI having to work with the Controllers instance and so on. So I made an 'IController' abstract class which is the parent of 'Controller', which has all of the functions. In 'IController' I have the functions my Model and View need to access. The Controller looks somewhat like this:
class IController:
def method(self):
pass
class Controller(IController):
self.x = 'Hello'
def method(self):
print('self.x)
So where I previously had
class Frame(tk.Frame):
def __init__ (self, controller):
self.controller = controller
button = tk.Button(self, command=lambda: self.controller.method()
I now want to turn this into
class Frame(tk.Frame):
def __init__ (self, controller):
self._controller = type(controller)
button = tk.Button(self, command=lambda: self._controller.method()
The problem here is, that when I do this, I can't keep the instance of my 'Controller' Class. I need this, since the instance has values and methods I need to work with here. I also can't save the instance of 'Controlle'r in 'IController' since it is an abstract class, so I won't instance it and can't save anything in it.
I expected it to just work, but I am not sure if this is possible now. I read that casting is not possible in python, but I think there must be another way for me to fix this. When I ran it, it told me that I am lacking 'self'. I can't send the instance of the Controller with it, then it would not be capsulated. Is there a way around this?
For other people who might try the same: It is not possible to do this like you would do it in C# or others. I solved it by re-writing my program in and learning C#. That and some ugly workarounds are the only possibilities.

Tkinter: AttributeError: 'NoneType' object has no attribute '_root'

I'm learning Tkinter and I'm trying to use an OOP approach in designing a simple GUI.
I'm creating a class that is inheariting from tkinter.Frame and also implements my specific functionalities.
I'm trying to define a tkinter.StringVar() outside the init function of my class and I get the error described in the title. Now I know that I should have my variable inside the init method and I got it to work I just want to understand why is not working if I declare it as a class variable.
I understand that tk.StringVar() needs to have a tk.Tk() context to be able to run. So this should(and indeed does) compile with no errors:
root = tk.Tk()
str = tk.StringVar()
root.mainloop()
If I write like this however it will give the mentioned error:
class myClass(tk.Frame):
def __init__(self, master):
super(myClass, self).__init__(master)
pass
str=tk.StringVar()
root = tk.Tk()
inst = myClass(root)
So this is if I would try to write tk.StringVar() without creating the 'root=tk.Tk()' first. But to my understanding the tk.Tk() context is created before I create the 'inst' instance so by the time the interpreter goes into the class and sees that class variable 'str' it should have already ran tk.Tk(). Obviously I'm missing something. Please explain to me the sequence in which things take place and why is that root.Tk() not executed before I try creating a tk.StringVar() variable. Thank you.
Edit: The line str=tk.StringVar() is part of the class body, and therefore executes during the definition of the class - which happens before Tk() is instantiated.
Thank you #jasonharper for the answer.

Using variable from _init_

This is a section from my code. Basically, I want access to the value given in the entry box, which is a part of the def init ..Is there any way around this without using global variables? Also, it is set up this way so that this variable can be used throughout my program. Thanks.
import tkinter as tk
class MainApp(tk.Tk):
def test():
a = __init__.test_entry.get()
print(a)
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
test_entry = tk.Entry(self)
test_entry.pack()
submit_button = tk.Button(self, text="Submit", command=MyApp.test)
submit_button.pack()
if __name__ == "__main__":
app = MainApp()
app.mainloop()
No, there's no way for an external function to access the local variables defined in a function.
There are several ways to solve the problem you have without that though. You could make the variables defined in __init__ instance variables (attributes of self) and make sure your external function can access the right instance (perhaps in a global variable).
Or you could define a function within the __init__ function. Such a closure can access the variables defined in an outer namespace without issue. In your specific instance, you could even make the closure a really small one, using lambda to call the "real" outer function with an argument:
class MainApp(tk.Tk):
def test(test_entry):
a = test_entry.get()
print(a)
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
test_entry = tk.Entry(self)
test_entry.pack()
submit_button = tk.Button(self, text="Submit", command=lambda: MyApp.test(test_entry))
submit_button.pack()
The expression lambda: MyApp.test(test_entry) defines an unnamed function that keeps hold of the test_entry defined in the namespace of the __init__ method. When the lambda function is called, it passes that value on to the test function. You could also move the whole test definition into the __init__ method, and so call it directly, without a lambda, but that is often ugly as it involves lots of deep indentation.
I'm not sure if i understand you correctly tbh
Have you considered using this instead?
self.__test_entry = tk.Entry(self)

How to correctly create an instance of a class with exec() function?

I'm making a GUI using the TKinter library from Python. I want the user to select an option from a Combobox and then, to press a Button, which should create an instance of a class named as the selected option. In order to save code, I decided to use the exec() fuction in this way:
exec('instance = ' + comboExample.get() + '()').
This starts the __init__() method of the class, but when I try to call an other method (in this case from an inherited class) using instance.method() it displays the following error: NameError: name 'instance' is not defined. Here you have an example of the script:
from tkinter import *
from tkinter import ttk
master = Tk()
#Create classes
class Base():
def method(self):
self.label = Label(master, text = self.sentence)
self.label.pack()
class Example1(Base):
def __init__(self):
print('Example1 created')
self.sentence = 'This is example 1.'
class Example2(Base):
def __init__(self):
print('Example2 created')
self.sentence = 'This is example 2'
#Create Combobox and Button
combo = ttk.Combobox(master, state = 'readonly')
combo['values'] = ['Example1', 'Example2']
combo.pack()
def callback():
exec('instance = ' + combo.get() + '()')
#Here is the error
instance.method()
button = Button(master, command = callback, text = 'Button')
button.pack()
master.mainloop()
I don't now why but when I try with the following code it works properly:
class Example():
def __init__(self):
self.text = 'This is an example'
def add_text(self):
print(self.text)
exec('instance = Example()')
instance.add_text()
At the moment, I've only found one solution, which consists in not using exec(), but makes me waste more code than using it, especially if I want to create a lot of classes like Example1 and Example2. It's all like the previous big script, but changing the callback() function:
def callback():
if combo.get() == 'Example1':
instance = Example1()
if combo.get() == 'Example2':
instance = Example2()
instance.method()
That's all. I started programming in Python only 2 months ago and I'm also new in stackoverflow, so if I've made some mistake in the explanation or anything, please tell me and I'll fix it.
Thanks for your time. Any help would be appreciated.
The issue isn’t your syntax; it’s that you’re trying to do something illegal. You can’t create new local variables with exec. (The reason the same code outside a function works is that in general you can create a new global variable with exec, but it’s still a bad idea.)
But you also don’t need to do that. In Python, everything is an object, including classes. So, you just need the get the class from the name. Then you can create an instance of that class, and store it in a local variable, by just using the same normal syntax you’d use for instantiating a class statically and storing it in a local variable.
The right way to do this is to store a dictionary mapping names to class objects. If you want to get clever, you can write a decorator that registers classes with that dictionary, but if that sounds like Greek to you, just do it explicitly:
classes = {'Spam': Spam, 'Eggs': Eggs}
If you have dozens of these, you can avoid the repetition with a comprehension like this:
from your_module import Spam, Eggs
classes = {cls.__name__: cls for cls in (Spam, Eggs)}
… but at that point you’re probably better off learning how to write the decorator.
Either way, you can fill your combo box with the keys of that dictionary instead of repeating yourself in the combo['values'] line.
And then, to create an instance, you just do this:
cls = classes[comboExample.get()]
instance = cls()
(Obviously you can collapse that into a single line, but I thought it would be easier to understand if we keep the two parts separate.)
If you really want to do this in a hacky way, you can. Every class that you’ve created in this module is already stored in a dictionary by name—the module’s global namespace. That’s the same place you were trying to find it implicitly with exec, but you can find it explicitly by just looking it up in globals(). However, the global namespace also has the names of all of your functions, imported modules, top-level constants and variables, etc., so this is usually a bad idea. (Obviously, exec has the exact same problems.)
You should not be using exec for this purpose. exec is a powerful tool, but it's the wrong tool for this job.
A much simpler approach is to create a mapping from user inputs to classes. You can then use that mapping both for the combobox and for the callback.
Example:
...
mapping = {"Example1": Example1, "Example2": Example2}
#Create Combobox and Button
combo = ttk.Combobox(master, state = 'readonly')
combo['values'] = sorted(mapping.keys())
combo.pack()
def callback():
class_name = combo.get()
cls = mapping[class_name]
instance = cls()
instance.method()
...
You could even automatically generate the mapping by iterating over a list of classes, though for this example that seems like overkill.

Categories