Tkinter variable definition, which is more desirable? - python

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).

Related

Calling textvariable from function within a class Python 3 tkinter

Thankful for any assistance in my question.
I am using Python 3 with tkinter where I have setup multiple pages, however I am trying to call a specific textvariable which is in a class, within a definition and wondering if it's possible to do with the way I built my script.
It's quite a big script so I don't want to paste all the code here, I hope this will be enough.
Basically the class is called POIIN(tk.frame) and I am trying to call
class POPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.entryboxes()
def entryboxes(self):
# Entry boxes + variables
global voucher
voucher = IntVar()
entry_box3 = tk.Entry(self, textvariable=voucher, width=32, bg="lightgreen")
entry_box3.place(x=435, y=151)
I am trying to call the textvariable by doing the following outside of the class:
def do_mssql():
try:
if int(POPage.entryboxes(voucher).get()) > int(maxN):
voucherchktext()
elif int(voucher.get()) < int(minN):
voucherchktext()
else:
sql()
except:
vouchermissing()
However it seems to be calling the IntVar() and I don't get any error message, (quite new with Python)
Basically I can pull the voucher from another class (page) with just doing the int(voucher.get()).
I could of course rename the textvariable and call it that way (which I don't mind), but it would be great if there is any way around this.
Greatful for any help!
BR,
Thanks for posting your entire code on pastebin. By looking at your code I can see that none of your classes actually take ownership of the tkinter widgets they create. In otherwords, they don't have any instance variables. I would suggest a radical redesign and getting rid of all global variables and objects - in this case it's a major code smell and suggests that there is a flaw in your design - and that's fine, you did say you were a beginner after all. I see a lot of good things in your code, too! Not to be one-sided...
Basically, the change I'm recommending is this: Each frame class should take ownership of the widgets they create. That includes things like tk.Labels, tk.Buttons, tk.Entrys and tk.IntVars among others.
The major benefit this provides, is that any instance of your frame class will have its own tk.IntVar (or whatever entry widget variable, like tk.StringVar) which it does not need to share with any of the other classes (you called it a textvariable). This makes sense if you think about it: Every tk.Entry widget is coupled with one variable object that keeps track of the user's entered data for that entry widget - if you create just one global entry variable and share it with all entry widgets in all frames, you could easily lose a user's entered data - there's really no reason to do that.
Try running/playing around with the following code snippet:
import tkinter as tk
class MyFrame(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.label = tk.Label(self, text="Some Text")
self.label.pack()
self.entry_var = tk.IntVar()
self.entry = tk.Entry(self, textvariable=self.entry_var, width=32)
self.entry.pack()
self.button = tk.Button(self, text="Print this frame's 'entry_var'", command=lambda: print(self.entry_var.get()))
self.button.pack()
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("Title")
self.geometry("256x256")
self.resizable(width=False, height=False)
self.frames = []
for frame in MyFrame(), MyFrame():
self.frames.append(frame)
frame.pack()
def main():
application = Application()
application.mainloop()
return 0
if __name__ == "__main__":
import sys
sys.exit(main())
You have a main Application (you call it Ghost in your example), which has multiple frames (my example only has one frame class, but the Application has two instances of that frame class). Each frame instance has an entry widget and a unique, completely separate and distinct corresponding entry variable object. There is no reason for frames to share the same entry variable object. Making the widgets and entry variable objects instance variables of their classes should eliminate your use of global variables entirely.
In my example, if I needed to get access to the values in the entry widgets from outside the classes - like in the main function for example, you would say application.frames[0].entry_var.get() to get the value in the first frame's entry widget.

Python Tkinter Avoid using "root" name in lower level function

Inspired by this 300+ vote closed Q&A: Best way to structure a tkinter application?, I'm looking to avoid explicitly using root in a function within a class. I think it should be implicitly declared through self or parent or something like that. Here is the code in question:
I have this code...
self.label_this = tk.StringVar()
self.label_last = tk.StringVar()
self.label_total = tk.StringVar()
tk.Label(count_frame, textvariable=self.label_this, \
font=(None, MON_FONTSIZE)).pack(anchor=tk.W)
tk.Label(count_frame, textvariable=self.label_last, \
font=(None, MON_FONTSIZE)).pack(anchor=tk.W)
tk.Label(count_frame, textvariable=self.label_total, \
font=(None, MON_FONTSIZE)).pack(anchor=tk.W)
self.update_cnt_labels()
Then later on...
''' Get list of Window ID's on monitor now '''
new_windows = self.windows_on_monitor(new_windows)
new_windows_cnt = len(new_windows) / WIN_CNT
if self.old_windows_cnt == new_windows_cnt :
FlashMessage (self.label_this, "No new windows to remove...", \
3, 750, 250)
self.update_cnt_labels()
return
Then later on...
class FlashMessage:
def __init__(self, widget, message, count=5, on=500, off=300):
self.delay_show (1, widget, message)
for i in range(count):
self.delay_show (on, widget, "")
self.delay_show (off, widget, message)
def delay_show(self, ms, widget, message):
root.after(ms, widget.set(message))
root.update_idletasks()
I want to avoid using root in the last two lines and use self or something similar.
My program call chain is something like:
the traditional: root = tk.Tk()
bunch of mainline initialization stuff.
the class: ResizingCanvas(mycanvas)
mainline function: popup(event) which is bound to <ButtonPress-1>
Dynamically formatted menu.tk_popup(event.x_root, event.y_root)
the class: RemoveNewWindows()
the function: remove()
the class: FlashMessage() (show above)
the function: self.delay_show() (shown above)
Each class and function has haphazard self, positional parameters, *args and **kwargs which mostly serve no purpose. Indeed even the __init__ above might be unnecessary. This is a result of copying code all over stack overflow.
Every second word in the program seems to be self but the word parent is only used in the class ResizingCanvas(). Do I have to propagate parent down the call list and use it somehow?
You can call after and update_idletasks on any widget. There are many such functions that can be called on any widget but which have a global effect.
In your case, you'll need to pass some widget into the FlashMessage constructor and save the reference. You can then use the reference to call the functions.
You're passing something called widget that doesn't actually contain a widget. You need to rename it to something more appropriate (eg: var), and then pass in an actual widget.
(Note: you also are calling after incorrectly, which I've fixed in the following example)
For example:
class FlashMessage:
def __init__(self, widget, var, message, count=5, on=500, off=300):
self.widget = widget
...
def delay_show(self, ...):
self.widget.after(ms, var.set, message)
self.widget.update_idletasks()
Then, whenever you create an instance of FlashMessage you need to add a widget as the first parameter.
For example, assuming that count_frame is defined in the context where you create an instance of FlashMessage and it is an actual widget, it might look something like this:
if self.old_windows_cnt == new_windows_cnt :
FlashMessage (count_frame, self.label_this, "No new windows to remove...", \
3, 750, 250)
self.update_cnt_labels()
return

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.

Questions on using ttk.Style()?

To make available an instance of the ttk.Style() class, it was illustrated in this tkinter guide that the syntax is:
import ttk
s=ttk.Style()
When typing these command in IDLE, I noticed that ttk.Style() actually has a predefined argument, i.e.
s=ttk.Style(master=None)
I have written the following test script:
import tkinter as tk
import tkinter.ttk as ttk
class App(ttk.Frame):
def __init__(self, parent):
ttk.Frame.__init__(self, parent, style='App.TFrame', relief=tk.SUNKEN,
border=10)
self.parent = parent
self.__createStyle()
self.__createWidgets()
def __createStyle(self):
self.s = ttk.Style()
self.s.configure('.', background='orange', border=100)
self.s.configure('App.TFrame', background='yellow')
self.s.configure('Btn.TButton', background='light blue', border=10)
def __createWidgets(self):
self._label = ttk.Label(self.parent, text='Label packed in root.')
self._label.pack()
self._btn = ttk.Button(self, style='Btn.TButton', command=self.__click,
text='Button packed inside self or class App, which is a ttk.Frame')
self._btn.pack()
def __click(self):
return print('Left Button Clicked!')
class myWidget(ttk.Frame):
def __init__(self, parent):
ttk.Frame.__init__(self, parent, style='my.TFrame', relief=tk.GROOVE,
border=10)
self.parent = parent
self.__createStyle()
self.__createWidgets()
def __createStyle(self):
self.s = ttk.Style()
self.s.configure('my.TFrame', background='purple')
self.s.configure('my.TLabel', background='pink', border=10)
self.s.configure('my.TEntry', foreground='red', border=10)
def __createWidgets(self):
self._label = ttk.Label(self, style='my.TLabel',
text='myWidget Label packed in self or class myWidget, which is a ttk.Frame.')
self._label.pack()
self._entry = ttk.Entry(self, style='my.TEntry')
self._entry.pack()
if __name__ == "__main__":
root = tk.Tk()
root.title('Test Style')
root.geometry('500x150')
a = App(root)
a.pack(fill='both', expand=1)
b = myWidget(a)
b.pack()
root.mainloop()
Question 1: When do I need to declare the master arguement in ttk.Style()? E.g. in the above script, if I write self.s = ttk.Style() and self.s = ttk.Style(master=self.parent) in class myWidget, I get the same result (see Fig1).
Question 2: Is there a need to prefix s=ttk.Style() with self? I get the same result as shown in Fig1 with and without the self prefix.
Question 3: If I rename 'my.TFrame' in class myWidget as 'App.TFrame'(this name was used in class App), the background colour of the class App changed to purple color too (same color as class myWidget. Why did this happened given that variable name in different classes are unique?
Question 4: The names 'App.TFrame' and 'my.TFrame' were called before it was declared. Why did python or tkinter not complain or give an error but allowed the script to execute?
Figure 1
Figure 2
When do I need to declare the master arguement in ttk.Style()?
Probably never, except the case when tkinter doesnt support the default root. When you pass None as the master, the master becomes the current root instance of Tk class.
The main purpose of the master (root or any tk-widget) is to delegate instance of tk to the Style, so that the Style could be able to execute Tcl-related commands.
No more, no less.
Is there a need to prefix s=ttk.Style() with self?
It depends on your requirements. In context of your code - self is meaningless, because you're setting up styles in a scope of the __createStyle function.
Otherwise, if you wish to keep the reference, it makes sense to prefix with self.
If I rename my.TFrame in class myWidget as App.TFrame(this name was used in class App), the background colour of the class App changed to purple color too (same color as class myWidget. Why did this happened given that variable name in different classes are unique?
Because both of classes share the same frame style, hence the same color. Created style is a global thing, it can be chaged at runtime, and all the relevant widgets will react to these chages.
The names App.TFrame and my.TFrame were called before it was declared. Why did python or tkinter not complain or give an error but allowed the script to execute?
Why you think they should? When you pass something like <any_sensible_name>.<any_relevant_and_existing_basestyle>, ttk knows that you want a variation of a base style, so it's implicitly creates one, which inherits all base properties.
Try that trick with something more meaningless, like your current style name without dot (ttk.Frame.__init__(..., style='AppTFrame', ...)), which gives you the desired error:
_tkinter.TclError: Layout AppTFrame not found
Only a partial Answer, but I suppose #Bryan Oakley will entlighten us sooner or later.
Question 3:
If you use "App.TFrame" instead of "my.TFrame" inside your MyWidget Class, you override the predefined style properties.
Short example:
If you style "TFrame", all "TFrame"( == Tkinter.Frame/ttk.Frame ) instances will be affected.This is also sometimes referred to as "root-Style".
If you define another "somename.TFrame" and set it for one Object of type frame, it will be styles according "somename.TFrame".
Question 4:
The lookup names only override default styles. As long as they have no properties, they do not override a thing.
This "assignment" results in a tcl call and has no specific error handling inside the Tkinter / ttk Sources (used in BaseWidget class).
I can only tell that tcl does not throw an error here but I am not a tcl expert myself.
I hope this at least helps a bit.

Python 3 and Tkinter. Too many global variables

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

Categories