Python 3 and Tkinter. Too many global variables - python

I started programming a simple GUI application in Python, using Tkinter library.
Everything works fine but, to dynamically change any widget (for example a button from "login" to "logout", etc), I have to get the variable containing the widget object itself in the function from outside.
I can do this in two ways:
passing the widget as a argument;
using global variables.
A simplified example:
1) Passing as argument:
def changeLabel(b):
b.config(text='Logout')
btn1 = Button(f0,text="Login",width=15)
btn1.grid(row=1,column=2,rowspan=1,padx=10,ipadx=10)
changeLabel(btn1)
2) Or using a global variable:
def changeLabel():
global btn1
btn1.config(text='Logout')
btn1 = Button(f0,text="Login",width=15)
btn1.grid(row=1,column=2,rowspan=1,padx=10,ipadx=10)
changeLabel(btn1)
Now, I know that global variables should be avoided but passing every widget as argument through many functions, even in a simple application, it's a mess.
So, what's the best way to manipulate the Tkinter widgets at runtime? Can you suggest me the right way?
Thank you

The best way to manipulate tkinter widgets at runtime is to store those widgets as attributes of a class.
For example:
class Example(...):
def create_widgets(self):
...
self.btn1 = Button(...)
...
def changeLabel(self):
self.bt1.config(...)
For more information see Best way to structure a tkinter application

Related

Typical way of creating multiple tkinter entries/buttons/

I have a question regarding Tkinter in Python.
I was thinking of the smartest/most commonly used way of creating GUIs with many/multiple Entries, Lables, Buttons, etc.
In my first sketches I just put them before the window.mainloop(), which at some point becomes confusing and unreadable. I then used methods for each Label, Button,... like this:
def someInput():
someInput = Entry(controlFrame)
someInput.grid(column = 1, row = 1)
...
Now I was thinking, if it makes sense to put them into a class like this:
class allInputs():
def inputOne(*args):
...
def inputTwo(*args):
...
What is your preferred way of doing this and why?
Thanks in advance.
My typical framework will look something like this
class App(tk.Frame):
def __init__(self,master=None,**kw):
tk.Frame.__init__(self,master=master,**kw)
self.btnOne = tk.Button(self,text="Hello")
self.btnOne.grid()
self.ow = OtherWidget(self)
class OtherWidget(tk.Frame):
....
if __name__ == '__main__':
root = tk.Tk()
App(root).grid()
root.mainloop()
The main application is a class (a sub-class of a tk Frame) and each "sub-component grouping" of widgets is its own class.
For example if I have a button which populates a text box, then they will be part of the same class. A textbox and it's scrollbar will be another class.
Basically I will group together widgets by their function and put them in the same class, all of which will be grouped in to the application class.
This makes sense to me as any data shared between widgets exist within the same class 'namespace' and they can access each others data without having to deal with globals.

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.

python / tk - affecting widget in other class

I am attempting to create a ttk.notebook in python where the selection in one tab affects the selection of a widget in a separate tab. Each tab is currently set up as a different class. Is there a way to pass or call a function in one class(tab) and have it change the widget/call a function in the other class(tab)?
in short, i have two functions: lb1 and lb2 (for tk lisboxes). Ideally, I would like the selection function on lb1 to call a function to populate a list in lb2. Each are in different classes.
a general sample of what I am trying to do follows.
class One(ttk.Frame):
...
lb1 = Listbox(listvariable = apps, height = 5)
def lb2_lookup(self, *args):
#this would somehow call a function to populate lb2
self.lb1.bind('<<ListboxSelect>>', self.lb2_lookup)
class Two(ttk.Frame):
lb2 = Listbox(listvariable = lb2apps, height = 5)
Thanks a bunch. I apologize if the code sample makes no sense, but I believe it gets the general point across.
The solution is that for one class to call a function in another, it simply needs to have a reference to that class, or a reference to some sort of controller class that has a reference to the other class.
class One(ttk.Frame):
def __init__(self, master, other_class):
self.other_class = other_class
...
self.lbl.bind('<<ListboxSelect>>`, self.other_class.lb2_lookup)
two = Two(...)
one = One(..., other_class = two)
Another way to accomplish the same thing is to have the class provide an interface, so that you can connect the classes after they are created:
class One(...):
...
def set_target(self, other_class):
self.other_class = other_class
class Two(...):
...
one = One(...)
two = Two(...)
one.set_target(two)
Finally, as written your code is tighly coupled. That means that even a small change in Two might mean you have to modify class One as well. That makes for code that is hard to maintain. You should create an interface that doesn't require one class to know much about the implementation of the other class.
Specifically, in your example you are setting up a binding to call lb2_lookup. But what if you change class Two and rename lb2 to lb3? Do you really want to have to also modify One? Better to create a function in Two that doesn't directly relate to a widget. For example:
class One(...):
...
self.lb1.bind('<<ListboxSelect>>', self.other_class.lookup)
With that, you are now free to reimplement Two however you want. The only requirement is that you keep a method named lookup. However, exactly what lookup does can change as long as it works the same way.
So, for example, right now lookup could return the value from a widget named lb2, but later it could look up data from a widget named foobar. No matter what lookup does, as long as it works in the same way (takes the same arguments, returns the same type of result), you won't have to modify One whenever you change Two.

Tkinter variable definition, which is more desirable?

I am new to Tkinter and I was wondering which of the following way to set variables is more desirable:
class App():
def __init__(self,master):
self.var1 = StringVar()
<filler>
def openFile(self,button_type):
name = tkFileDialog.askopenfilename()
if button_type == 1:
self.var1.set(name)
or
class App():
def __init__(self,master):
self.var1 = ""
<filler>
def openFile(self,button_type):
name = tkFileDialog.askopenfilename()
if button_type == 1:
self.var1 = name
The first option is what I found in the effbot documentation (http://effbot.org/tkinterbook/variable.htm) but the second option is what I would normally do. My biggest question is why would 1 be preferred over the other?
Tkinter variables like StringVar are commonly used to track the change of its values or to pass them as the variable or textvariable option for creating some widgets. From the section "When to use the Variable Classes" of the page you refer to:
Variables can be used with most entry widgets to track changes to the entered value. The Checkbutton and Radiobutton widgets require variables to work properly.
Variables can also be used to validate the contents of an entry widget, and to change the text in label widgets.
So in your case the natural solution would be the second one: it looks like you want to store the result of askopenfilename() like you would do with the result of another statement, but not use it to interact with the text of a widget or track if the value of the StringVar has changed (since you are calling that function, you already know when it is going to be updated).

which one of those python implementations is better

which one of the following is considered better a design and why ?.
i have 2 classes , one for the gui components and the other is for it's events.
please put in mind that the eventClass will be implemented so many times, (sometimes to get data from an oracle databases and sometimes mysql databases )
class MainWindow:
def __init__(self):
self.myEvents = eventClass() # the class that has all the events
self.button = button # consider it a button from any gui library
self.menu = menu # menu box
def bottonEvent(self):
data = self.myEvents.buttonEvent()
self.menu.populate(data)
class eventClass:
def __init__(self):
pass
def getData(self):
return data # return data to puplate in the list
OR
class MainWindow:
def __init__(self):
self.myEvents = eventClass(self) # the class that has all the events
self.button = button # consider it a button from any gui library
self.menu = menu # menu box
def bottonEvent(self):
self.myEvents.ButtonEvent()
class eventClass:
def __init__(self,window):
pass
def ButtonEvent(self):
window.menu.populateData()
please inform me if anything was unclear
please help ,
thanks in advance
The first choice is better "decoupled": the event class needs and has no knowledge whatsoever about the window object or its menu attribute -- an excellent approach that makes the event class especially easy to unit-test in isolation without any overhead. This is especially nice if many implementations of the same interface need to exist, as you mention they do in your case.
The second choice introduces a mutual dependency -- an event object can't work without a window object, and a window object builds an event object. That may be an acceptable complication in more abstruse cases where it buys you something, but for this specific use it sounds more like an arbitrary extra difficulty without any real plus.
So, I would recommend the first form.

Categories