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.
Related
As the question states, I can't seem to fully grasp the point of using classes with tkinter.
I have read through a decent number of different sites but I keep getting search results on how to create and use classes, but none so far have been able to get through to me. I've even scoured through the suggested questions while asking this one. The closest I've come to understanding is Bryan's explanation on this answer to the question Why use classes when programming a tkinter gui?
But still, I feel like I'm almost there, just not quite over the edge of understanding.
In his example in the link, he creates an unconventional program, and then a better, more conventional program that does the same thing. I know that it represents a much smaller scale than the thousand-line programs that could really benefit from an object oriented approach.
Does every widget need to be in its own separate frame that's maybe part of an even bigger frame?
Can classes have methods that create and place a frame? In addition, can those same classes have methods than can create, modify, and place a widget within the previously made frame?
I also have some code that allows me to create, modify, and place a widget. Although I know it's not conventional, so I would greatly appreciate some input on this as well. Any suggestions on what you would do with this code to make it better?
import tkinter as tk
def layout(self, row=0, column=0, columnspan=None, row_weight=None, column_weight=None, color=None, sticky=None, ipadx=None, padx=None, ipady=None, pady=None):
self.grid(row=row, column=column, columnspan=columnspan, sticky=sticky, ipadx=ipadx, padx=padx, ipady=ipady, pady=pady)
self.grid_rowconfigure(row, weight=row_weight)
self.grid_columnconfigure(column, weight=column_weight)
self.config(bg=color)
class MyLabels(tk.Label):
def __init__(self, parent, text, **kwargs):
tk.Label.__init__(self, parent, text=text)
layout(self, **kwargs)
class MyButtons(tk.Button):
def __init__(self, parent, text, command, **kwargs):
tk.Button.__init__(self, parent, text=text, command=command)
layout(self, **kwargs)
window = tk.Tk()
test_button = MyButtons(window, "hi", None, color="pink")
window.mainloop()
Edited after comments:
So I've been working many hours since yesterday trying to incorporate the ideas you've had for me. This is what I came up with:
import tkinter as tk
window = tk.Tk()
class MyWidgets(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.layout()
def layout(self):
self.grid(row=0, column=0)
self.config(bg="blue")
class MyButtons(MyWidgets):
def __init__(self, parent, text):
MyWidgets.__init__(self, parent)
tk.Button(parent, text=text)
self.layout()
frme = MyWidgets(window)
btn = MyButtons(frme, text="Test")
window.mainloop()
I've tried moving things around and rewriting lots of areas on this little side program, and even though I was able to prove that btn is infact accessing the self.config(bg="blue") attribute, the button doesn't appear to change. As a matter of fact I can't find a way to make the button appear without needing to put self.grid() in the child class just after the button is created.
Still, even if I did add the self.grid() the button still won't turn blue. Is it something with self?
Why won't the button appear when the child class creates it, and the parent class places it?
Note: I've purposefully omitted the entire layout function and replaced it with just a simple config method. I figure if I can understand this, I can then find a way to incorporate the whole function back into the code.
Does every widget need to be in its own separate frame that's maybe part of an even bigger frame?
That's a bit like asking if every part of a mathematical expression needs parenthesis. Strictly speaking, the answer is "no". However, using frames to organize groups of widgets is a tool designed to make writing and understanding the code easier, much like parenthesis in a complex math equation makes writing and understanding the equation easier.
Can classes have methods that create and place a frame? In addition, can those same classes have methods than can create, modify, and place a widget within the previously made frame?
Yes, and yes. Methods in a class don't have any limitations on what they can and cannot do. Methods of a class can do anything that a normal function can do.
I also have some code that allows me to create, modify, and place a widget. Although I know it's not conventional, so I would greatly appreciate some input on this as well. Any suggestions on what you would do with this code to make it better?
"Better" is highly subjective. What is better for 20 lines of code might not be better for 200, 2,000, or 20,000. What is better for a function used exactly twice might not be better for a function used hundreds or thousands of times (or visa versa).
That being said, you're doing one thing that is very unconventional and which leads to making your code harder to understand: you're using self as a parameter for a function that is not a method of a class. self means something very specific to python programmers; using it outside of the context of a method is very confusing.
You should do one of two things for the method layout:
Rename self to be widget or any other term other than self
Create a base class that defines layout, and then have your classes inherit from the base class. In that case, self is the proper first argument.
This part of the answer refers to code which was added after I wrote my original answer.
The base class I was referring to needs to be a separate class. For example:
class Base():
def layout(self):
self.grid(row=0, column=0)
self.config(bg="blue")
class MyLabels(Base, tk.Label):
def __init__(self, parent, text, **kwargs):
tk.Label.__init__(self, parent, text=text)
self.layout(self, **kwargs)
class MyButtons(Base, tk.Button):
def __init__(self, parent, text, command, **kwargs):
tk.Button.__init__(self, parent, text=text, command=command)
self.layout(self, **kwargs)
This type of class is sometimes called a mixin because it's not designed to be instantiated as a standalone object. Rather, it "mixes in" some additional behavior to other classes. A mixin will typically have methods, but won't have its own __init__.
This code is part of the main calculator application mentioned here: https://pastebin.com/ECA2AQzY
I am new to python and from my understanding, "self" is the first argument being passed automatically for each instance of a class. Here, I am confused whether "app" would be self? and Frame is some superclass?
I do not understand why init is called on Frame within a main init method?
Also, why is lambda needed? Can it just not be command = self.appendToDisplay("7"))
What was the need of calling grid method on "app" object when the grid is called in the class itself?
calculator = Tk()
calculator.title("Calculator")
calculator.resizable(0, 0)
#================================================
class Application(Frame):
def __init__(self, master, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
self.createWidgets()
#================================================
def createWidgets(self):
self.sevenButton = Button(self, font=("Helvetica", 11), text="7",
borderwidth=0, command=lambda: self.appendToDisplay("7"))
self.sevenButton.grid(row=1, column=0, sticky="NWNESWSE")
#================================================
app = Application(calculator).grid()
calculator.mainloop()
Let's try to answer your questions in order:
Application intherrits from Frame, that is all methods implemented in Frame are also avaliable in Application. In addition Application can implement its own methods as well as override implementations in Frame. In the latter case, the implementations in Application replace the implementations in Frame. This is the case for the __init__() method. The method in Application replaces the one in Frame. Thus, the Frame() __init__() method is never called and that is typically not good, since there may be a lot of the functionallity in Frame() that needs explicit initialization. Therefore, Application.__init__() must explicitly call Frame.__init__() to get it to initialize.
The difference between self.appendToDisplay("7") and lambda: self.appendToDisplay("7") is that in the first case the method appendToDisplay() is directly called and the result is returned. When using lambda instead of calling the function, a new function is defined. Thus, when using the lambda construct the result will not be directly available. Instead the result of the calculation will be delayed to a later time, when the command is to be executed. Thus, we want to display 7 on the display when the button is pressed and not now.
The grid call in Application.createWidgets() refers to the button, to place that in the calculator Application. The Application.grid() call on the other hand, is placing the whole calculator itself. Thus, it is a hierachical thing, where the parts of the application are first placed and then the application itself.
I hope the above answers help you understand the concepts a bit better. It may be quite a bit to digest and you should try to find a few tutorials that explains the concepts in a bit more detail.
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.
I have a simple graphical user interface using tkinter which gives a blank canvas on which the cursor places a green square wherever it is clicked.
I am trying to code in a "Clear all" button at the top of the interface which clears the entire canvas (whilst retaining the cursor's ability to draw squares when it clicks on the canvas again):
import tkinter as tk
class Interface(tk.Frame):
def __init__(self, master):
super().__init__(master)
tk.Button(self, text="Clear all", command=self.clearall).pack(side = tk.LEFT)
#What the heck do I do here?
def clearall(self):
#self._canvas.delete(tk.ALL)
pass
class App(object):
def __init__(self, master):
master.title("Doodle test")
master.geometry("600x600")
self._interface = Interface(master)
self._interface.pack()
self._canvas = tk.Canvas(master, bg='white')
self._canvas.pack(fill=tk.BOTH, expand=1)
self._canvas.bind("<Button-1>", self.leftclick)
# And would I need to put something here?
def plz_run_me_when_clearall_method_is_called(self):
self._canvas.delete(tk.ALL)
def leftclick(self, arg):
d = 10
self._canvas.create_rectangle([(arg.x, arg.y), (arg.x+d, arg.y+d)], fill="green")
root = tk.Tk()
app = App(root)
root.mainloop()
Ordinarily, this would be a simple task if the entire program was constructed within a single class, e.g.:
def clearall(self):
self._canvas.delete(tk.ALL)
Since there are two classes, I cannot call the .delete(tk.ALL) method for _canvas since it is declared in the App class. So how could I convey a button to execute function X which affects item Y from another class?
EDIT: Still trying to think of an appropriate title for this post.
A simple rule of python (not just tkinter) is that, if an object needs a resource in another object, that first object needs a reference to the second object.
That means either passing in the instance of App to the instance of Interface, or passing in the canvas itself.
Using tight coupling
For example, you could pass in the canvas this way:
class Interface(tk.Frame):
def __init__(self, master, canvas):
...
self._canvas = canvas
...
def clearall(self):
self._canvas.delete(tk.ALL)
class App(object):
def __init__(self, master):
...
self._canvas = tk.Canvas(master, bg='white')
self._interface = Interface(master, self._canvas)
...
There are many ways to accomplish a similar thing. For instance, instead of passing in the canvas you can pass in the reference to the app itself (eg: self._interface = Interface(master, self). You could then either makeself._canvas` "public", or you could provide a getter that the returns the canvas.
The potential downside to the above approach is that it tightly couples the two classes together. That is, if App some day is remade to draw on an image object rather than a canvas object, Interface will also have to be modified since it depends on a specific implementation in App (namely, that it does all its work on a canvas).
Using loose coupling
Loose coupling means that Interface doesn't know or care how App is implemented, it only knows that App promises to provide an API that Interface can use. In this way, App can completely change its implementation without breaking Interface, as long as it continues to provide the same API.
To keep these classes loosely coupled, you need to move add a clearall method into App. With this, Interface doesn't need to know that App uses a canvas, it just needs to know that App provides a way to clear the drawing surface, no matter what that drawing surface is.
class Interface(tk.Frame):
def __init__(self, master, app):
...
self._app = app
...
def clearall(self):
self._app.clearall()
class App(object):
def __init__(self, master):
...
self._canvas = tk.Canvas(master, bg='white')
self._interface = Interface(master, self)
...
def clearall(self):
self._calvas.delete(tk.ALL)
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).